workos 0.6.0 → 0.7.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/README.md +51 -15
- package/dist/bin.js +151 -0
- package/dist/bin.js.map +1 -1
- 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/commands/doctor.d.ts +1 -0
- package/dist/commands/doctor.js +1 -0
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/env.d.ts +9 -0
- package/dist/commands/env.js +188 -0
- package/dist/commands/env.js.map +1 -0
- package/dist/commands/organization.d.ts +18 -0
- package/dist/commands/organization.js +142 -0
- package/dist/commands/organization.js.map +1 -0
- package/dist/commands/user.d.ts +19 -0
- package/dist/commands/user.js +128 -0
- package/dist/commands/user.js.map +1 -0
- package/dist/dashboard/components/CompletionView.js +5 -1
- package/dist/dashboard/components/CompletionView.js.map +1 -1
- package/dist/doctor/agent-prompt.d.ts +10 -0
- package/dist/doctor/agent-prompt.js +190 -0
- package/dist/doctor/agent-prompt.js.map +1 -0
- package/dist/doctor/checks/ai-analysis.d.ts +9 -0
- package/dist/doctor/checks/ai-analysis.js +122 -0
- package/dist/doctor/checks/ai-analysis.js.map +1 -0
- package/dist/doctor/checks/auth-patterns.d.ts +2 -0
- package/dist/doctor/checks/auth-patterns.js +530 -0
- package/dist/doctor/checks/auth-patterns.js.map +1 -0
- package/dist/doctor/checks/environment.js +22 -4
- package/dist/doctor/checks/environment.js.map +1 -1
- package/dist/doctor/checks/framework.js +27 -8
- package/dist/doctor/checks/framework.js.map +1 -1
- package/dist/doctor/checks/language.d.ts +6 -0
- package/dist/doctor/checks/language.js +104 -0
- package/dist/doctor/checks/language.js.map +1 -0
- package/dist/doctor/checks/sdk.js +113 -22
- package/dist/doctor/checks/sdk.js.map +1 -1
- package/dist/doctor/index.js +26 -3
- package/dist/doctor/index.js.map +1 -1
- package/dist/doctor/issues.js +22 -1
- package/dist/doctor/issues.js.map +1 -1
- package/dist/doctor/output.js +85 -18
- package/dist/doctor/output.js.map +1 -1
- package/dist/doctor/types.d.ts +38 -0
- package/dist/doctor/types.js.map +1 -1
- package/dist/lib/adapters/cli-adapter.js +4 -14
- package/dist/lib/adapters/cli-adapter.js.map +1 -1
- package/dist/lib/adapters/dashboard-adapter.js +3 -16
- package/dist/lib/adapters/dashboard-adapter.js.map +1 -1
- package/dist/lib/api-key.d.ts +13 -0
- package/dist/lib/api-key.js +26 -0
- package/dist/lib/api-key.js.map +1 -0
- package/dist/lib/config-store.d.ts +27 -0
- package/dist/lib/config-store.js +142 -0
- package/dist/lib/config-store.js.map +1 -0
- package/dist/lib/credential-store.d.ts +4 -0
- package/dist/lib/credential-store.js +47 -11
- package/dist/lib/credential-store.js.map +1 -1
- package/dist/lib/credentials.d.ts +1 -1
- package/dist/lib/credentials.js +1 -1
- package/dist/lib/credentials.js.map +1 -1
- package/dist/lib/run-with-core.js +23 -2
- package/dist/lib/run-with-core.js.map +1 -1
- package/dist/lib/settings.d.ts +1 -0
- package/dist/lib/settings.js.map +1 -1
- package/dist/lib/validation/build-validator.js +0 -1
- package/dist/lib/validation/build-validator.js.map +1 -1
- package/dist/lib/validation/quick-checks.js +0 -1
- package/dist/lib/validation/quick-checks.js.map +1 -1
- package/dist/lib/workos-api.d.ts +30 -0
- package/dist/lib/workos-api.js +69 -0
- package/dist/lib/workos-api.js.map +1 -0
- package/dist/utils/cli-symbols.d.ts +1 -1
- package/dist/utils/lock-art.d.ts +4 -0
- package/dist/utils/lock-art.js +73 -0
- package/dist/utils/lock-art.js.map +1 -0
- package/dist/utils/package-json.d.ts +1 -0
- package/dist/utils/package-json.js +11 -0
- package/dist/utils/package-json.js.map +1 -1
- package/dist/utils/summary-box.d.ts +18 -0
- package/dist/utils/summary-box.js +148 -0
- package/dist/utils/summary-box.js.map +1 -0
- package/dist/utils/table.d.ts +5 -0
- package/dist/utils/table.js +18 -0
- package/dist/utils/table.js.map +1 -0
- package/package.json +1 -1
- package/skills/workos-authkit-nextjs/SKILL.md +5 -5
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
const MAX_FILE_SIZE = 2000;
|
|
4
|
+
const README_TIMEOUT_MS = 5000;
|
|
5
|
+
const MAX_README_SIZE = 15000;
|
|
6
|
+
// Map SDK package names to their GitHub repo README URLs
|
|
7
|
+
const SDK_README_URLS = {
|
|
8
|
+
'@workos/authkit-nextjs': 'https://raw.githubusercontent.com/workos/authkit-nextjs/main/README.md',
|
|
9
|
+
'@workos-inc/authkit-nextjs': 'https://raw.githubusercontent.com/workos/authkit-nextjs/main/README.md',
|
|
10
|
+
'@workos/authkit-react-router': 'https://raw.githubusercontent.com/workos/authkit-react-router/main/README.md',
|
|
11
|
+
'@workos-inc/authkit-react-router': 'https://raw.githubusercontent.com/workos/authkit-react-router/main/README.md',
|
|
12
|
+
'@workos/authkit-react': 'https://raw.githubusercontent.com/workos/authkit-react/main/README.md',
|
|
13
|
+
'@workos-inc/authkit-react': 'https://raw.githubusercontent.com/workos/authkit-react/main/README.md',
|
|
14
|
+
'@workos/authkit-tanstack-react-start': 'https://raw.githubusercontent.com/workos/authkit-tanstack-react-start/main/README.md',
|
|
15
|
+
'@workos/authkit-remix': 'https://raw.githubusercontent.com/workos/authkit-remix/main/README.md',
|
|
16
|
+
'@workos-inc/authkit-remix': 'https://raw.githubusercontent.com/workos/authkit-remix/main/README.md',
|
|
17
|
+
'@workos/authkit-sveltekit': 'https://raw.githubusercontent.com/workos/authkit-sveltekit/main/README.md',
|
|
18
|
+
'@workos/authkit-js': 'https://raw.githubusercontent.com/workos/authkit-js/main/README.md',
|
|
19
|
+
'@workos-inc/authkit-js': 'https://raw.githubusercontent.com/workos/authkit-js/main/README.md',
|
|
20
|
+
'@workos-inc/node': 'https://raw.githubusercontent.com/workos/workos-node/main/README.md',
|
|
21
|
+
};
|
|
22
|
+
async function fetchSdkReadme(sdkName) {
|
|
23
|
+
if (!sdkName)
|
|
24
|
+
return null;
|
|
25
|
+
const url = SDK_README_URLS[sdkName];
|
|
26
|
+
if (!url)
|
|
27
|
+
return null;
|
|
28
|
+
try {
|
|
29
|
+
const controller = new AbortController();
|
|
30
|
+
const timeoutId = setTimeout(() => controller.abort(), README_TIMEOUT_MS);
|
|
31
|
+
const response = await fetch(url, { signal: controller.signal });
|
|
32
|
+
clearTimeout(timeoutId);
|
|
33
|
+
if (!response.ok)
|
|
34
|
+
return null;
|
|
35
|
+
const text = await response.text();
|
|
36
|
+
return text.length > MAX_README_SIZE ? text.slice(0, MAX_README_SIZE) + '\n... (truncated)' : text;
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function readFileSafe(path) {
|
|
43
|
+
try {
|
|
44
|
+
const content = readFileSync(path, 'utf-8');
|
|
45
|
+
return content.length > MAX_FILE_SIZE ? content.slice(0, MAX_FILE_SIZE) + '\n... (truncated)' : content;
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function readEnvFileRedacted(path) {
|
|
52
|
+
const content = readFileSafe(path);
|
|
53
|
+
if (!content)
|
|
54
|
+
return null;
|
|
55
|
+
return content.replace(/^([A-Z_]+)=(.+)$/gm, (_match, key, value) => {
|
|
56
|
+
if (key.includes('SECRET') || key.includes('PASSWORD') || key.includes('API_KEY')) {
|
|
57
|
+
return `${key}=${value.slice(0, 3)}...(redacted)`;
|
|
58
|
+
}
|
|
59
|
+
return `${key}=${value}`;
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
function collectProjectFiles(installDir) {
|
|
63
|
+
const candidates = [
|
|
64
|
+
'middleware.ts',
|
|
65
|
+
'middleware.js',
|
|
66
|
+
'proxy.ts',
|
|
67
|
+
'proxy.js',
|
|
68
|
+
'src/middleware.ts',
|
|
69
|
+
'src/middleware.js',
|
|
70
|
+
'src/proxy.ts',
|
|
71
|
+
'src/proxy.js',
|
|
72
|
+
'app/layout.tsx',
|
|
73
|
+
'app/layout.jsx',
|
|
74
|
+
'src/app/layout.tsx',
|
|
75
|
+
'src/app/layout.jsx',
|
|
76
|
+
'app/page.tsx',
|
|
77
|
+
'app/page.jsx',
|
|
78
|
+
'src/app/page.tsx',
|
|
79
|
+
'src/app/page.jsx',
|
|
80
|
+
'src/start.ts',
|
|
81
|
+
'src/start.tsx',
|
|
82
|
+
'app/start.ts',
|
|
83
|
+
'app/root.tsx',
|
|
84
|
+
'app/root.jsx',
|
|
85
|
+
'config/initializers/workos.rb',
|
|
86
|
+
'config/routes.rb',
|
|
87
|
+
'app/settings.py',
|
|
88
|
+
'settings.py',
|
|
89
|
+
'main.go',
|
|
90
|
+
'cmd/main.go',
|
|
91
|
+
];
|
|
92
|
+
// Callback routes
|
|
93
|
+
for (const prefix of ['app', 'src/app']) {
|
|
94
|
+
for (const path of ['auth/callback', 'callback', 'api/auth/callback']) {
|
|
95
|
+
for (const ext of ['ts', 'tsx', 'js', 'jsx']) {
|
|
96
|
+
candidates.push(`${prefix}/${path}/route.${ext}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const files = [];
|
|
101
|
+
for (const candidate of candidates) {
|
|
102
|
+
const fullPath = join(installDir, candidate);
|
|
103
|
+
if (existsSync(fullPath)) {
|
|
104
|
+
const content = readFileSafe(fullPath);
|
|
105
|
+
if (content) {
|
|
106
|
+
files.push(`### ${candidate}\n\`\`\`\n${content}\n\`\`\``);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
// Include env files with redacted secrets
|
|
111
|
+
for (const envFile of ['.env.local', '.env']) {
|
|
112
|
+
const fullPath = join(installDir, envFile);
|
|
113
|
+
const content = readEnvFileRedacted(fullPath);
|
|
114
|
+
if (content) {
|
|
115
|
+
files.push(`### ${envFile} (secrets redacted)\n\`\`\`\n${content}\n\`\`\``);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return files.length > 0 ? files.join('\n\n') : 'No key files found.';
|
|
119
|
+
}
|
|
120
|
+
export async function buildDoctorPrompt(context) {
|
|
121
|
+
const { installDir, language, framework, sdk, environment, existingIssues } = context;
|
|
122
|
+
const projectContext = [
|
|
123
|
+
`- Language: ${language.name}`,
|
|
124
|
+
framework.name
|
|
125
|
+
? `- Framework: ${framework.name} ${framework.version ?? ''}${framework.variant ? ` (${framework.variant})` : ''}`
|
|
126
|
+
: null,
|
|
127
|
+
sdk.name ? `- SDK: ${sdk.name}${sdk.version ? ` v${sdk.version}` : ''}` : '- SDK: Not installed',
|
|
128
|
+
`- Environment: ${environment.apiKeyType ?? 'Unknown'}`,
|
|
129
|
+
environment.baseUrl ? `- Base URL: ${environment.baseUrl}` : null,
|
|
130
|
+
]
|
|
131
|
+
.filter(Boolean)
|
|
132
|
+
.join('\n');
|
|
133
|
+
const existingIssuesList = existingIssues.length > 0
|
|
134
|
+
? existingIssues.map((i) => `- [${i.severity}] ${i.code}: ${i.message}`).join('\n')
|
|
135
|
+
: 'None detected.';
|
|
136
|
+
const readme = await fetchSdkReadme(sdk.name);
|
|
137
|
+
const readmeSection = readme
|
|
138
|
+
? `## SDK Documentation (source of truth)\nThis is the official README for ${sdk.name}. Use it to determine correct patterns, imports, and configuration.\n\n${readme}`
|
|
139
|
+
: '## SDK Documentation\nUnable to fetch SDK README. Be conservative — only report issues you are certain about.';
|
|
140
|
+
return `You are a WorkOS integration analyst. Compare this project's code against the SDK documentation and identify real issues.
|
|
141
|
+
|
|
142
|
+
## Project Context
|
|
143
|
+
${projectContext}
|
|
144
|
+
|
|
145
|
+
## General SDK Knowledge
|
|
146
|
+
- \`@workos-inc/node\` works in ANY JavaScript runtime (Node.js, browsers, React Native, Expo, etc.). Despite the name, it has no Node.js-specific dependencies.
|
|
147
|
+
- Some packages are under \`@workos-inc/*\` and some under \`@workos/*\`. Both are official. Do NOT flag package scope as an issue.
|
|
148
|
+
|
|
149
|
+
${readmeSection}
|
|
150
|
+
|
|
151
|
+
## Project Files
|
|
152
|
+
${collectProjectFiles(installDir)}
|
|
153
|
+
|
|
154
|
+
## Already Detected Issues
|
|
155
|
+
${existingIssuesList}
|
|
156
|
+
|
|
157
|
+
## Your Task
|
|
158
|
+
Compare the project files against the SDK documentation above. Report issues where the code DEVIATES from what the documentation says to do. If the code follows the documented patterns, it is correct.
|
|
159
|
+
|
|
160
|
+
## Output Format
|
|
161
|
+
Return your analysis as a JSON object wrapped in a markdown code block:
|
|
162
|
+
\`\`\`json
|
|
163
|
+
{
|
|
164
|
+
"findings": [
|
|
165
|
+
{
|
|
166
|
+
"severity": "error | warning | info",
|
|
167
|
+
"title": "Short description",
|
|
168
|
+
"detail": "What's wrong and why it matters",
|
|
169
|
+
"docSays": "Direct quote or paraphrase from the SDK documentation above",
|
|
170
|
+
"codeDoes": "What the project code actually does that contradicts the docs",
|
|
171
|
+
"remediation": "How to fix it",
|
|
172
|
+
"filePath": "path/to/relevant/file"
|
|
173
|
+
}
|
|
174
|
+
],
|
|
175
|
+
"summary": "One paragraph summary of the integration health"
|
|
176
|
+
}
|
|
177
|
+
\`\`\`
|
|
178
|
+
|
|
179
|
+
## Rules
|
|
180
|
+
- Every finding MUST have both "docSays" and "codeDoes" filled in. If you cannot cite what the documentation requires AND how the code deviates, it is not a valid finding — drop it.
|
|
181
|
+
- "docSays" must reference something the documentation REQUIRES, not something optional or a suggestion.
|
|
182
|
+
- "codeDoes" must show an actual contradiction, not "the code doesn't use an optional feature."
|
|
183
|
+
- If the code matches the documented patterns, it is CORRECT. Do not suggest alternatives, improvements, or optional features.
|
|
184
|
+
- Do NOT report issues about optional configuration, missing optional callbacks, or "consider adding" suggestions.
|
|
185
|
+
- Do NOT repeat issues already detected (listed above).
|
|
186
|
+
- Do NOT invent SDK options, config properties, or API methods not in the documentation.
|
|
187
|
+
- Do NOT flag package scope (@workos-inc/* vs @workos/*) as an issue.
|
|
188
|
+
- A well-configured project should have ZERO findings — return an empty findings array and a positive summary.`;
|
|
189
|
+
}
|
|
190
|
+
//# sourceMappingURL=agent-prompt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-prompt.js","sourceRoot":"","sources":["../../src/doctor/agent-prompt.ts"],"names":[],"mappings":"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","sourcesContent":["import { readFileSync, existsSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type { LanguageInfo, FrameworkInfo, SdkInfo, EnvironmentInfo, Issue } from './types.js';\n\nexport interface AnalysisContext {\n installDir: string;\n language: LanguageInfo;\n framework: FrameworkInfo;\n sdk: SdkInfo;\n environment: EnvironmentInfo;\n existingIssues: Issue[];\n}\n\nconst MAX_FILE_SIZE = 2000;\nconst README_TIMEOUT_MS = 5000;\nconst MAX_README_SIZE = 15000;\n\n// Map SDK package names to their GitHub repo README URLs\nconst SDK_README_URLS: Record<string, string> = {\n '@workos/authkit-nextjs': 'https://raw.githubusercontent.com/workos/authkit-nextjs/main/README.md',\n '@workos-inc/authkit-nextjs': 'https://raw.githubusercontent.com/workos/authkit-nextjs/main/README.md',\n '@workos/authkit-react-router': 'https://raw.githubusercontent.com/workos/authkit-react-router/main/README.md',\n '@workos-inc/authkit-react-router': 'https://raw.githubusercontent.com/workos/authkit-react-router/main/README.md',\n '@workos/authkit-react': 'https://raw.githubusercontent.com/workos/authkit-react/main/README.md',\n '@workos-inc/authkit-react': 'https://raw.githubusercontent.com/workos/authkit-react/main/README.md',\n '@workos/authkit-tanstack-react-start':\n 'https://raw.githubusercontent.com/workos/authkit-tanstack-react-start/main/README.md',\n '@workos/authkit-remix': 'https://raw.githubusercontent.com/workos/authkit-remix/main/README.md',\n '@workos-inc/authkit-remix': 'https://raw.githubusercontent.com/workos/authkit-remix/main/README.md',\n '@workos/authkit-sveltekit': 'https://raw.githubusercontent.com/workos/authkit-sveltekit/main/README.md',\n '@workos/authkit-js': 'https://raw.githubusercontent.com/workos/authkit-js/main/README.md',\n '@workos-inc/authkit-js': 'https://raw.githubusercontent.com/workos/authkit-js/main/README.md',\n '@workos-inc/node': 'https://raw.githubusercontent.com/workos/workos-node/main/README.md',\n};\n\nasync function fetchSdkReadme(sdkName: string | null): Promise<string | null> {\n if (!sdkName) return null;\n const url = SDK_README_URLS[sdkName];\n if (!url) return null;\n\n try {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), README_TIMEOUT_MS);\n const response = await fetch(url, { signal: controller.signal });\n clearTimeout(timeoutId);\n\n if (!response.ok) return null;\n const text = await response.text();\n return text.length > MAX_README_SIZE ? text.slice(0, MAX_README_SIZE) + '\\n... (truncated)' : text;\n } catch {\n return null;\n }\n}\n\nfunction readFileSafe(path: string): string | null {\n try {\n const content = readFileSync(path, 'utf-8');\n return content.length > MAX_FILE_SIZE ? content.slice(0, MAX_FILE_SIZE) + '\\n... (truncated)' : content;\n } catch {\n return null;\n }\n}\n\nfunction readEnvFileRedacted(path: string): string | null {\n const content = readFileSafe(path);\n if (!content) return null;\n return content.replace(/^([A-Z_]+)=(.+)$/gm, (_match, key: string, value: string) => {\n if (key.includes('SECRET') || key.includes('PASSWORD') || key.includes('API_KEY')) {\n return `${key}=${value.slice(0, 3)}...(redacted)`;\n }\n return `${key}=${value}`;\n });\n}\n\nfunction collectProjectFiles(installDir: string): string {\n const candidates: string[] = [\n 'middleware.ts',\n 'middleware.js',\n 'proxy.ts',\n 'proxy.js',\n 'src/middleware.ts',\n 'src/middleware.js',\n 'src/proxy.ts',\n 'src/proxy.js',\n 'app/layout.tsx',\n 'app/layout.jsx',\n 'src/app/layout.tsx',\n 'src/app/layout.jsx',\n 'app/page.tsx',\n 'app/page.jsx',\n 'src/app/page.tsx',\n 'src/app/page.jsx',\n 'src/start.ts',\n 'src/start.tsx',\n 'app/start.ts',\n 'app/root.tsx',\n 'app/root.jsx',\n 'config/initializers/workos.rb',\n 'config/routes.rb',\n 'app/settings.py',\n 'settings.py',\n 'main.go',\n 'cmd/main.go',\n ];\n\n // Callback routes\n for (const prefix of ['app', 'src/app']) {\n for (const path of ['auth/callback', 'callback', 'api/auth/callback']) {\n for (const ext of ['ts', 'tsx', 'js', 'jsx']) {\n candidates.push(`${prefix}/${path}/route.${ext}`);\n }\n }\n }\n\n const files: string[] = [];\n for (const candidate of candidates) {\n const fullPath = join(installDir, candidate);\n if (existsSync(fullPath)) {\n const content = readFileSafe(fullPath);\n if (content) {\n files.push(`### ${candidate}\\n\\`\\`\\`\\n${content}\\n\\`\\`\\``);\n }\n }\n }\n\n // Include env files with redacted secrets\n for (const envFile of ['.env.local', '.env']) {\n const fullPath = join(installDir, envFile);\n const content = readEnvFileRedacted(fullPath);\n if (content) {\n files.push(`### ${envFile} (secrets redacted)\\n\\`\\`\\`\\n${content}\\n\\`\\`\\``);\n }\n }\n\n return files.length > 0 ? files.join('\\n\\n') : 'No key files found.';\n}\n\nexport async function buildDoctorPrompt(context: AnalysisContext): Promise<string> {\n const { installDir, language, framework, sdk, environment, existingIssues } = context;\n\n const projectContext = [\n `- Language: ${language.name}`,\n framework.name\n ? `- Framework: ${framework.name} ${framework.version ?? ''}${framework.variant ? ` (${framework.variant})` : ''}`\n : null,\n sdk.name ? `- SDK: ${sdk.name}${sdk.version ? ` v${sdk.version}` : ''}` : '- SDK: Not installed',\n `- Environment: ${environment.apiKeyType ?? 'Unknown'}`,\n environment.baseUrl ? `- Base URL: ${environment.baseUrl}` : null,\n ]\n .filter(Boolean)\n .join('\\n');\n\n const existingIssuesList =\n existingIssues.length > 0\n ? existingIssues.map((i) => `- [${i.severity}] ${i.code}: ${i.message}`).join('\\n')\n : 'None detected.';\n\n const readme = await fetchSdkReadme(sdk.name);\n const readmeSection = readme\n ? `## SDK Documentation (source of truth)\\nThis is the official README for ${sdk.name}. Use it to determine correct patterns, imports, and configuration.\\n\\n${readme}`\n : '## SDK Documentation\\nUnable to fetch SDK README. Be conservative — only report issues you are certain about.';\n\n return `You are a WorkOS integration analyst. Compare this project's code against the SDK documentation and identify real issues.\n\n## Project Context\n${projectContext}\n\n## General SDK Knowledge\n- \\`@workos-inc/node\\` works in ANY JavaScript runtime (Node.js, browsers, React Native, Expo, etc.). Despite the name, it has no Node.js-specific dependencies.\n- Some packages are under \\`@workos-inc/*\\` and some under \\`@workos/*\\`. Both are official. Do NOT flag package scope as an issue.\n\n${readmeSection}\n\n## Project Files\n${collectProjectFiles(installDir)}\n\n## Already Detected Issues\n${existingIssuesList}\n\n## Your Task\nCompare the project files against the SDK documentation above. Report issues where the code DEVIATES from what the documentation says to do. If the code follows the documented patterns, it is correct.\n\n## Output Format\nReturn your analysis as a JSON object wrapped in a markdown code block:\n\\`\\`\\`json\n{\n \"findings\": [\n {\n \"severity\": \"error | warning | info\",\n \"title\": \"Short description\",\n \"detail\": \"What's wrong and why it matters\",\n \"docSays\": \"Direct quote or paraphrase from the SDK documentation above\",\n \"codeDoes\": \"What the project code actually does that contradicts the docs\",\n \"remediation\": \"How to fix it\",\n \"filePath\": \"path/to/relevant/file\"\n }\n ],\n \"summary\": \"One paragraph summary of the integration health\"\n}\n\\`\\`\\`\n\n## Rules\n- Every finding MUST have both \"docSays\" and \"codeDoes\" filled in. If you cannot cite what the documentation requires AND how the code deviates, it is not a valid finding — drop it.\n- \"docSays\" must reference something the documentation REQUIRES, not something optional or a suggestion.\n- \"codeDoes\" must show an actual contradiction, not \"the code doesn't use an optional feature.\"\n- If the code matches the documented patterns, it is CORRECT. Do not suggest alternatives, improvements, or optional features.\n- Do NOT report issues about optional configuration, missing optional callbacks, or \"consider adding\" suggestions.\n- Do NOT repeat issues already detected (listed above).\n- Do NOT invent SDK options, config properties, or API methods not in the documentation.\n- Do NOT flag package scope (@workos-inc/* vs @workos/*) as an issue.\n- A well-configured project should have ZERO findings — return an empty findings array and a positive summary.`;\n}\n"]}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type AnalysisContext } from '../agent-prompt.js';
|
|
2
|
+
import type { AiAnalysis, AiFinding } from '../types.js';
|
|
3
|
+
export declare function parseAiResponse(text: string): {
|
|
4
|
+
findings: AiFinding[];
|
|
5
|
+
summary: string;
|
|
6
|
+
};
|
|
7
|
+
export declare function checkAiAnalysis(context: AnalysisContext, options: {
|
|
8
|
+
skipAi?: boolean;
|
|
9
|
+
}): Promise<AiAnalysis>;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import Anthropic from '@anthropic-ai/sdk';
|
|
2
|
+
import { getLlmGatewayUrl, getAuthkitDomain, getCliAuthClientId, getConfig } from '../../lib/settings.js';
|
|
3
|
+
import { getCredentials, isTokenExpired, updateTokens, diagnoseCredentials } from '../../lib/credentials.js';
|
|
4
|
+
import { refreshAccessToken } from '../../lib/token-refresh-client.js';
|
|
5
|
+
import { buildDoctorPrompt } from '../agent-prompt.js';
|
|
6
|
+
const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
7
|
+
function startSpinner(message) {
|
|
8
|
+
let i = 0;
|
|
9
|
+
const interval = setInterval(() => {
|
|
10
|
+
process.stderr.write(`\r ${SPINNER_FRAMES[i++ % SPINNER_FRAMES.length]} ${message}`);
|
|
11
|
+
}, 80);
|
|
12
|
+
return {
|
|
13
|
+
stop: () => {
|
|
14
|
+
clearInterval(interval);
|
|
15
|
+
process.stderr.write(`\r${' '.repeat(message.length + 6)}\r`);
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export function parseAiResponse(text) {
|
|
20
|
+
const codeBlockMatch = text.match(/```(?:json)?\s*\n?([\s\S]*?)\n?\s*```/);
|
|
21
|
+
const jsonStr = codeBlockMatch ? codeBlockMatch[1] : text;
|
|
22
|
+
try {
|
|
23
|
+
const parsed = JSON.parse(jsonStr.trim());
|
|
24
|
+
const findings = Array.isArray(parsed.findings)
|
|
25
|
+
? parsed.findings.map((f) => ({
|
|
26
|
+
severity: (['error', 'warning', 'info'].includes(f.severity) ? f.severity : 'info'),
|
|
27
|
+
title: String(f.title ?? ''),
|
|
28
|
+
detail: String(f.detail ?? ''),
|
|
29
|
+
remediation: String(f.remediation ?? ''),
|
|
30
|
+
filePath: f.filePath ? String(f.filePath) : undefined,
|
|
31
|
+
}))
|
|
32
|
+
: [];
|
|
33
|
+
return { findings, summary: String(parsed.summary ?? '') };
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
const jsonMatch = text.match(/\{[\s\S]*"findings"[\s\S]*\}/);
|
|
37
|
+
if (jsonMatch) {
|
|
38
|
+
try {
|
|
39
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
40
|
+
return {
|
|
41
|
+
findings: Array.isArray(parsed.findings) ? parsed.findings : [],
|
|
42
|
+
summary: String(parsed.summary ?? ''),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
// give up
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return { findings: [], summary: text.slice(0, 500) };
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
async function callModel(prompt, model) {
|
|
53
|
+
let creds = getCredentials();
|
|
54
|
+
if (!creds)
|
|
55
|
+
throw new Error('Not authenticated');
|
|
56
|
+
if (isTokenExpired(creds)) {
|
|
57
|
+
if (!creds.refreshToken)
|
|
58
|
+
throw new Error('Session expired — run `workos login` to re-authenticate');
|
|
59
|
+
const result = await refreshAccessToken(getAuthkitDomain(), getCliAuthClientId());
|
|
60
|
+
if (!result.success || !result.accessToken || !result.expiresAt) {
|
|
61
|
+
throw new Error('Session expired — run `workos login` to re-authenticate');
|
|
62
|
+
}
|
|
63
|
+
updateTokens(result.accessToken, result.expiresAt, result.refreshToken);
|
|
64
|
+
creds = getCredentials();
|
|
65
|
+
}
|
|
66
|
+
const client = new Anthropic({
|
|
67
|
+
baseURL: getLlmGatewayUrl(),
|
|
68
|
+
apiKey: 'gateway',
|
|
69
|
+
defaultHeaders: { Authorization: `Bearer ${creds.accessToken}` },
|
|
70
|
+
});
|
|
71
|
+
const response = await client.messages.create({
|
|
72
|
+
model,
|
|
73
|
+
max_tokens: 2048,
|
|
74
|
+
messages: [{ role: 'user', content: prompt }],
|
|
75
|
+
});
|
|
76
|
+
const text = response.content[0];
|
|
77
|
+
if (text.type === 'text')
|
|
78
|
+
return text.text;
|
|
79
|
+
throw new Error('Unexpected response format');
|
|
80
|
+
}
|
|
81
|
+
export async function checkAiAnalysis(context, options) {
|
|
82
|
+
const model = getConfig().doctorModel;
|
|
83
|
+
const skippedResult = (reason) => ({
|
|
84
|
+
findings: [],
|
|
85
|
+
summary: '',
|
|
86
|
+
model,
|
|
87
|
+
durationMs: 0,
|
|
88
|
+
skipped: true,
|
|
89
|
+
skipReason: reason,
|
|
90
|
+
});
|
|
91
|
+
if (options.skipAi) {
|
|
92
|
+
return skippedResult('Skipped (--skip-ai flag)');
|
|
93
|
+
}
|
|
94
|
+
const creds = getCredentials();
|
|
95
|
+
if (!creds) {
|
|
96
|
+
const diag = diagnoseCredentials();
|
|
97
|
+
process.stderr.write('\n [credential-debug]\n');
|
|
98
|
+
for (const line of diag) {
|
|
99
|
+
process.stderr.write(` ${line}\n`);
|
|
100
|
+
}
|
|
101
|
+
process.stderr.write('\n');
|
|
102
|
+
return skippedResult('Not authenticated — run `workos login` for AI-powered analysis');
|
|
103
|
+
}
|
|
104
|
+
const startTime = Date.now();
|
|
105
|
+
const spinner = startSpinner('Analyzing project with AI...');
|
|
106
|
+
try {
|
|
107
|
+
const prompt = await buildDoctorPrompt(context);
|
|
108
|
+
const responseText = await callModel(prompt, model);
|
|
109
|
+
const durationMs = Date.now() - startTime;
|
|
110
|
+
const { findings, summary } = parseAiResponse(responseText);
|
|
111
|
+
return { findings, summary, model, durationMs };
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
const durationMs = Date.now() - startTime;
|
|
115
|
+
const errMsg = error instanceof Error ? error.message : 'Unknown error';
|
|
116
|
+
return { ...skippedResult(`Analysis failed: ${errMsg}`), durationMs };
|
|
117
|
+
}
|
|
118
|
+
finally {
|
|
119
|
+
spinner.stop();
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=ai-analysis.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai-analysis.js","sourceRoot":"","sources":["../../../src/doctor/checks/ai-analysis.ts"],"names":[],"mappings":"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","sourcesContent":["import Anthropic from '@anthropic-ai/sdk';\nimport { getLlmGatewayUrl, getAuthkitDomain, getCliAuthClientId, getConfig } from '../../lib/settings.js';\nimport { getCredentials, isTokenExpired, updateTokens, diagnoseCredentials } from '../../lib/credentials.js';\nimport { refreshAccessToken } from '../../lib/token-refresh-client.js';\nimport { buildDoctorPrompt, type AnalysisContext } from '../agent-prompt.js';\nimport type { AiAnalysis, AiFinding } from '../types.js';\n\nconst SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];\n\nfunction startSpinner(message: string): { stop: () => void } {\n let i = 0;\n const interval = setInterval(() => {\n process.stderr.write(`\\r ${SPINNER_FRAMES[i++ % SPINNER_FRAMES.length]} ${message}`);\n }, 80);\n return {\n stop: () => {\n clearInterval(interval);\n process.stderr.write(`\\r${' '.repeat(message.length + 6)}\\r`);\n },\n };\n}\n\nexport function parseAiResponse(text: string): { findings: AiFinding[]; summary: string } {\n const codeBlockMatch = text.match(/```(?:json)?\\s*\\n?([\\s\\S]*?)\\n?\\s*```/);\n const jsonStr = codeBlockMatch ? codeBlockMatch[1] : text;\n\n try {\n const parsed = JSON.parse(jsonStr.trim());\n const findings = Array.isArray(parsed.findings)\n ? parsed.findings.map((f: Record<string, unknown>) => ({\n severity: (['error', 'warning', 'info'].includes(f.severity as string) ? f.severity : 'info') as\n | 'error'\n | 'warning'\n | 'info',\n title: String(f.title ?? ''),\n detail: String(f.detail ?? ''),\n remediation: String(f.remediation ?? ''),\n filePath: f.filePath ? String(f.filePath) : undefined,\n }))\n : [];\n return { findings, summary: String(parsed.summary ?? '') };\n } catch {\n const jsonMatch = text.match(/\\{[\\s\\S]*\"findings\"[\\s\\S]*\\}/);\n if (jsonMatch) {\n try {\n const parsed = JSON.parse(jsonMatch[0]);\n return {\n findings: Array.isArray(parsed.findings) ? parsed.findings : [],\n summary: String(parsed.summary ?? ''),\n };\n } catch {\n // give up\n }\n }\n return { findings: [], summary: text.slice(0, 500) };\n }\n}\n\nasync function callModel(prompt: string, model: string): Promise<string> {\n let creds = getCredentials();\n if (!creds) throw new Error('Not authenticated');\n\n if (isTokenExpired(creds)) {\n if (!creds.refreshToken) throw new Error('Session expired — run `workos login` to re-authenticate');\n const result = await refreshAccessToken(getAuthkitDomain(), getCliAuthClientId());\n if (!result.success || !result.accessToken || !result.expiresAt) {\n throw new Error('Session expired — run `workos login` to re-authenticate');\n }\n updateTokens(result.accessToken, result.expiresAt, result.refreshToken);\n creds = getCredentials()!;\n }\n\n const client = new Anthropic({\n baseURL: getLlmGatewayUrl(),\n apiKey: 'gateway',\n defaultHeaders: { Authorization: `Bearer ${creds.accessToken}` },\n });\n\n const response = await client.messages.create({\n model,\n max_tokens: 2048,\n messages: [{ role: 'user', content: prompt }],\n });\n\n const text = response.content[0];\n if (text.type === 'text') return text.text;\n throw new Error('Unexpected response format');\n}\n\nexport async function checkAiAnalysis(context: AnalysisContext, options: { skipAi?: boolean }): Promise<AiAnalysis> {\n const model = getConfig().doctorModel;\n\n const skippedResult = (reason: string): AiAnalysis => ({\n findings: [],\n summary: '',\n model,\n durationMs: 0,\n skipped: true,\n skipReason: reason,\n });\n\n if (options.skipAi) {\n return skippedResult('Skipped (--skip-ai flag)');\n }\n\n const creds = getCredentials();\n if (!creds) {\n const diag = diagnoseCredentials();\n process.stderr.write('\\n [credential-debug]\\n');\n for (const line of diag) {\n process.stderr.write(` ${line}\\n`);\n }\n process.stderr.write('\\n');\n return skippedResult('Not authenticated — run `workos login` for AI-powered analysis');\n }\n\n const startTime = Date.now();\n const spinner = startSpinner('Analyzing project with AI...');\n\n try {\n const prompt = await buildDoctorPrompt(context);\n const responseText = await callModel(prompt, model);\n const durationMs = Date.now() - startTime;\n const { findings, summary } = parseAiResponse(responseText);\n return { findings, summary, model, durationMs };\n } catch (error) {\n const durationMs = Date.now() - startTime;\n const errMsg = error instanceof Error ? error.message : 'Unknown error';\n return { ...skippedResult(`Analysis failed: ${errMsg}`), durationMs };\n } finally {\n spinner.stop();\n }\n}\n"]}
|