workos 0.12.2 → 0.13.0
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 +16 -12
- package/dist/bin.js +5 -0
- package/dist/bin.js.map +1 -1
- package/dist/commands/auth-status.js +2 -1
- package/dist/commands/auth-status.js.map +1 -1
- package/dist/commands/claim.js +4 -3
- package/dist/commands/claim.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/install-skill.d.ts +64 -3
- package/dist/commands/install-skill.js +213 -27
- package/dist/commands/install-skill.js.map +1 -1
- package/dist/commands/install.js +5 -1
- package/dist/commands/install.js.map +1 -1
- package/dist/commands/login.d.ts +13 -0
- package/dist/commands/login.js +43 -2
- package/dist/commands/login.js.map +1 -1
- package/dist/doctor/checks/ai-analysis.js +4 -3
- package/dist/doctor/checks/ai-analysis.js.map +1 -1
- package/dist/doctor/checks/skills.d.ts +9 -0
- package/dist/doctor/checks/skills.js +84 -0
- package/dist/doctor/checks/skills.js.map +1 -0
- package/dist/doctor/index.d.ts +21 -1
- package/dist/doctor/index.js +52 -1
- package/dist/doctor/index.js.map +1 -1
- package/dist/doctor/issues.js +15 -0
- package/dist/doctor/issues.js.map +1 -1
- package/dist/doctor/output.js +16 -0
- package/dist/doctor/output.js.map +1 -1
- package/dist/doctor/types.d.ts +27 -0
- package/dist/doctor/types.js.map +1 -1
- package/dist/lib/adapters/cli-adapter.js +2 -1
- package/dist/lib/adapters/cli-adapter.js.map +1 -1
- package/dist/lib/agent-interface.js +38 -14
- package/dist/lib/agent-interface.js.map +1 -1
- package/dist/lib/credential-proxy.js +2 -1
- package/dist/lib/credential-proxy.js.map +1 -1
- package/dist/lib/device-auth.js +26 -10
- package/dist/lib/device-auth.js.map +1 -1
- package/dist/lib/ensure-auth.js +4 -3
- package/dist/lib/ensure-auth.js.map +1 -1
- package/dist/lib/installer-core.d.ts +3 -3
- package/dist/lib/resolve-install-credentials.js +4 -4
- package/dist/lib/resolve-install-credentials.js.map +1 -1
- package/dist/lib/run-with-core.js +3 -1
- package/dist/lib/run-with-core.js.map +1 -1
- package/dist/lib/token-refresh-client.js +2 -1
- package/dist/lib/token-refresh-client.js.map +1 -1
- package/dist/lib/token-refresh.d.ts +1 -1
- package/dist/lib/token-refresh.js +3 -2
- package/dist/lib/token-refresh.js.map +1 -1
- package/dist/utils/command-invocation.d.ts +8 -0
- package/dist/utils/command-invocation.js +17 -0
- package/dist/utils/command-invocation.js.map +1 -0
- package/dist/utils/exit-codes.js +3 -1
- package/dist/utils/exit-codes.js.map +1 -1
- package/dist/utils/help-json.js +8 -0
- package/dist/utils/help-json.js.map +1 -1
- package/package.json +2 -2
package/dist/commands/install.js
CHANGED
|
@@ -24,7 +24,11 @@ export async function handleInstall(argv) {
|
|
|
24
24
|
}
|
|
25
25
|
try {
|
|
26
26
|
await runInstaller(options);
|
|
27
|
-
await autoInstallSkills();
|
|
27
|
+
const skillResult = await autoInstallSkills();
|
|
28
|
+
if (skillResult && !isJsonMode()) {
|
|
29
|
+
const skillWord = skillResult.skills.length === 1 ? 'skill' : 'skills';
|
|
30
|
+
clack.log.info(`Installed ${skillResult.skills.length} WorkOS ${skillWord} for ${skillResult.agents.join(', ')}. Your coding agent now has up-to-date WorkOS guidance.`);
|
|
31
|
+
}
|
|
28
32
|
process.exit(0);
|
|
29
33
|
}
|
|
30
34
|
catch (err) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"install.js","sourceRoot":"","sources":["../../src/commands/install.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAEzC,OAAO,KAAK,MAAM,mBAAmB,CAAC;AACtC,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAE/D,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAEvD;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,IAAuC;IACzE,MAAM,OAAO,GAAG,EAAE,GAAG,IAAI,EAAE,CAAC;IAE5B,qBAAqB;IACrB,IAAI,OAAO,CAAC,EAAE,EAAE,CAAC;QACf,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;YACpB,aAAa,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,oDAAoD,EAAE,CAAC,CAAC;QACzG,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC;YACtB,aAAa,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,4DAA4D,EAAE,CAAC,CAAC;QACjH,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC;YACxB,aAAa,CAAC;gBACZ,IAAI,EAAE,cAAc;gBACpB,OAAO,EAAE,yEAAyE;aACnF,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,IAAI,CAAC;QACH,MAAM,YAAY,CAAC,OAAO,CAAC,CAAC;QAC5B,MAAM,iBAAiB,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"install.js","sourceRoot":"","sources":["../../src/commands/install.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAEzC,OAAO,KAAK,MAAM,mBAAmB,CAAC;AACtC,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAE/D,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAEvD;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,IAAuC;IACzE,MAAM,OAAO,GAAG,EAAE,GAAG,IAAI,EAAE,CAAC;IAE5B,qBAAqB;IACrB,IAAI,OAAO,CAAC,EAAE,EAAE,CAAC;QACf,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;YACpB,aAAa,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,oDAAoD,EAAE,CAAC,CAAC;QACzG,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC;YACtB,aAAa,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,4DAA4D,EAAE,CAAC,CAAC;QACjH,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC;YACxB,aAAa,CAAC;gBACZ,IAAI,EAAE,cAAc;gBACpB,OAAO,EAAE,yEAAyE;aACnF,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,IAAI,CAAC;QACH,MAAM,YAAY,CAAC,OAAO,CAAC,CAAC;QAC5B,MAAM,WAAW,GAAG,MAAM,iBAAiB,EAAE,CAAC;QAC9C,IAAI,WAAW,IAAI,CAAC,UAAU,EAAE,EAAE,CAAC;YACjC,MAAM,SAAS,GAAG,WAAW,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC;YACvE,KAAK,CAAC,GAAG,CAAC,IAAI,CACZ,aAAa,WAAW,CAAC,MAAM,CAAC,MAAM,WAAW,SAAS,QAAQ,WAAW,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,yDAAyD,CACzJ,CAAC;QACJ,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,EAAE,cAAc,EAAE,GAAG,MAAM,MAAM,CAAC,mBAAmB,CAAC,CAAC;QAC7D,MAAM,OAAO,GAAG,cAAc,EAAE,CAAC;QAEjC,IAAI,UAAU,EAAE,EAAE,CAAC;YACjB,aAAa,CAAC;gBACZ,IAAI,EAAE,iBAAiB;gBACvB,OAAO,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe;aAC9D,CAAC,CAAC;QACL,CAAC;QAED,IAAI,OAAO,CAAC,KAAK,IAAI,GAAG,YAAY,KAAK,IAAI,GAAG,CAAC,KAAK,EAAE,CAAC;YACvD,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QAC3B,CAAC;QACD,IAAI,OAAO,EAAE,CAAC;YACZ,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,eAAe,OAAO,EAAE,CAAC,CAAC;QAC3C,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC","sourcesContent":["import { runInstaller } from '../run.js';\nimport type { InstallerArgs } from '../run.js';\nimport clack from '../utils/clack.js';\nimport { exitWithError, isJsonMode } from '../utils/output.js';\nimport type { ArgumentsCamelCase } from 'yargs';\nimport { autoInstallSkills } from './install-skill.js';\n\n/**\n * Handle install command execution.\n */\nexport async function handleInstall(argv: ArgumentsCamelCase<InstallerArgs>): Promise<void> {\n const options = { ...argv };\n\n // CI mode validation\n if (options.ci) {\n if (!options.apiKey) {\n exitWithError({ code: 'missing_args', message: 'CI mode requires --api-key (WorkOS API key sk_xxx)' });\n }\n if (!options.clientId) {\n exitWithError({ code: 'missing_args', message: 'CI mode requires --client-id (WorkOS Client ID client_xxx)' });\n }\n if (!options.installDir) {\n exitWithError({\n code: 'missing_args',\n message: 'CI mode requires --install-dir (directory to install WorkOS AuthKit in)',\n });\n }\n }\n\n try {\n await runInstaller(options);\n const skillResult = await autoInstallSkills();\n if (skillResult && !isJsonMode()) {\n const skillWord = skillResult.skills.length === 1 ? 'skill' : 'skills';\n clack.log.info(\n `Installed ${skillResult.skills.length} WorkOS ${skillWord} for ${skillResult.agents.join(', ')}. Your coding agent now has up-to-date WorkOS guidance.`,\n );\n }\n process.exit(0);\n } catch (err) {\n const { getLogFilePath } = await import('../utils/debug.js');\n const logPath = getLogFilePath();\n\n if (isJsonMode()) {\n exitWithError({\n code: 'installer_error',\n message: err instanceof Error ? err.message : 'Unknown error',\n });\n }\n\n if (options.debug && err instanceof Error && err.stack) {\n console.error(err.stack);\n }\n if (logPath) {\n clack.log.info(`Debug logs: ${logPath}`);\n }\n process.exit(1);\n }\n}\n"]}
|
package/dist/commands/login.d.ts
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Best-effort skill install after a successful auth-login.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors the install.ts hook copy, but wraps `autoInstallSkills` in its own
|
|
5
|
+
* try/catch AND a 30s timeout so a skill install hang (e.g. blocked filesystem
|
|
6
|
+
* call) never blocks login completion. Login already succeeded by the time
|
|
7
|
+
* this runs — the user having a working session is the contract that must hold.
|
|
8
|
+
*
|
|
9
|
+
* Extracted from runLogin so it can be unit-tested without standing up the
|
|
10
|
+
* device-auth polling loop.
|
|
11
|
+
*/
|
|
12
|
+
export declare const SKILL_INSTALL_TIMEOUT_MS: number;
|
|
13
|
+
export declare function installSkillsAfterLogin(): Promise<void>;
|
|
1
14
|
/**
|
|
2
15
|
* Auto-provision a staging environment after login.
|
|
3
16
|
*
|
package/dist/commands/login.js
CHANGED
|
@@ -7,6 +7,9 @@ import { refreshAccessToken } from '../lib/token-refresh-client.js';
|
|
|
7
7
|
import { logInfo, logError } from '../utils/debug.js';
|
|
8
8
|
import { fetchStagingCredentials } from '../lib/staging-api.js';
|
|
9
9
|
import { getConfig, saveConfig } from '../lib/config-store.js';
|
|
10
|
+
import { formatWorkOSCommand } from '../utils/command-invocation.js';
|
|
11
|
+
import { autoInstallSkills } from './install-skill.js';
|
|
12
|
+
import { isJsonMode } from '../utils/output.js';
|
|
10
13
|
/**
|
|
11
14
|
* Parse JWT payload
|
|
12
15
|
*/
|
|
@@ -44,6 +47,41 @@ function getConnectEndpoints() {
|
|
|
44
47
|
function sleep(ms) {
|
|
45
48
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
46
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* Best-effort skill install after a successful auth-login.
|
|
52
|
+
*
|
|
53
|
+
* Mirrors the install.ts hook copy, but wraps `autoInstallSkills` in its own
|
|
54
|
+
* try/catch AND a 30s timeout so a skill install hang (e.g. blocked filesystem
|
|
55
|
+
* call) never blocks login completion. Login already succeeded by the time
|
|
56
|
+
* this runs — the user having a working session is the contract that must hold.
|
|
57
|
+
*
|
|
58
|
+
* Extracted from runLogin so it can be unit-tested without standing up the
|
|
59
|
+
* device-auth polling loop.
|
|
60
|
+
*/
|
|
61
|
+
export const SKILL_INSTALL_TIMEOUT_MS = 30 * 1000;
|
|
62
|
+
export async function installSkillsAfterLogin() {
|
|
63
|
+
let timeoutHandle;
|
|
64
|
+
try {
|
|
65
|
+
const timeout = new Promise((resolve) => {
|
|
66
|
+
timeoutHandle = setTimeout(() => resolve(null), SKILL_INSTALL_TIMEOUT_MS);
|
|
67
|
+
// Don't keep the event loop alive on this timer — process should exit
|
|
68
|
+
// immediately if everything else has resolved.
|
|
69
|
+
timeoutHandle.unref?.();
|
|
70
|
+
});
|
|
71
|
+
const result = await Promise.race([autoInstallSkills(), timeout]);
|
|
72
|
+
if (result && !isJsonMode()) {
|
|
73
|
+
const skillWord = result.skills.length === 1 ? 'skill' : 'skills';
|
|
74
|
+
clack.log.info(`Installed ${result.skills.length} WorkOS ${skillWord} for ${result.agents.join(', ')}.`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
// Skill install must never fail login.
|
|
79
|
+
}
|
|
80
|
+
finally {
|
|
81
|
+
if (timeoutHandle)
|
|
82
|
+
clearTimeout(timeoutHandle);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
47
85
|
/**
|
|
48
86
|
* Auto-provision a staging environment after login.
|
|
49
87
|
*
|
|
@@ -80,7 +118,7 @@ export async function runLogin() {
|
|
|
80
118
|
if (getAccessToken()) {
|
|
81
119
|
const creds = getCredentials();
|
|
82
120
|
console.log(chalk.green(`Already logged in as ${creds?.email ?? 'unknown'}`));
|
|
83
|
-
console.log(chalk.dim(
|
|
121
|
+
console.log(chalk.dim(`Run \`${formatWorkOSCommand('auth logout')}\` to log out`));
|
|
84
122
|
return;
|
|
85
123
|
}
|
|
86
124
|
// Try to refresh if we have expired credentials with a refresh token
|
|
@@ -93,7 +131,7 @@ export async function runLogin() {
|
|
|
93
131
|
updateTokens(result.accessToken, result.expiresAt, result.refreshToken);
|
|
94
132
|
logInfo('[login] Session refreshed via refresh token');
|
|
95
133
|
console.log(chalk.green(`Already logged in as ${existingCreds.email ?? 'unknown'}`));
|
|
96
|
-
console.log(chalk.dim(
|
|
134
|
+
console.log(chalk.dim(`Run \`${formatWorkOSCommand('auth logout')}\` to log out`));
|
|
97
135
|
return;
|
|
98
136
|
}
|
|
99
137
|
}
|
|
@@ -176,6 +214,9 @@ export async function runLogin() {
|
|
|
176
214
|
else {
|
|
177
215
|
clack.log.info(chalk.dim('Run `workos env add` to configure an environment manually'));
|
|
178
216
|
}
|
|
217
|
+
// Best-effort skill install. Wrapped helper guarantees login never
|
|
218
|
+
// fails on skill errors.
|
|
219
|
+
await installSkillsAfterLogin();
|
|
179
220
|
return;
|
|
180
221
|
}
|
|
181
222
|
const errorData = data;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"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","sourcesContent":["import open from 'opn';\nimport chalk from 'chalk';\nimport clack from '../utils/clack.js';\nimport { saveCredentials, getCredentials, getAccessToken, isTokenExpired, updateTokens } from '../lib/credentials.js';\nimport { getCliAuthClientId, getAuthkitDomain } from '../lib/settings.js';\nimport { refreshAccessToken } from '../lib/token-refresh-client.js';\nimport { logInfo, logError } from '../utils/debug.js';\nimport { fetchStagingCredentials } from '../lib/staging-api.js';\nimport { getConfig, saveConfig } from '../lib/config-store.js';\nimport type { CliConfig } from '../lib/config-store.js';\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\nconst POLL_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes\n\n/**\n * Get Connect OAuth endpoints from AuthKit domain\n */\nfunction getConnectEndpoints() {\n const domain = getAuthkitDomain();\n return {\n deviceAuthorization: `${domain}/oauth2/device_authorization`,\n token: `${domain}/oauth2/token`,\n };\n}\n\ninterface 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\ninterface ConnectTokenResponse {\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\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Auto-provision a staging environment after login.\n *\n * Fetches staging credentials using the access token, then saves them\n * as a \"staging\" environment in the config store. Non-fatal — logs a\n * hint on failure instead of throwing.\n */\nexport async function provisionStagingEnvironment(accessToken: string): Promise<boolean> {\n try {\n const staging = await fetchStagingCredentials(accessToken);\n\n const config: CliConfig = getConfig() ?? { environments: {} };\n const isFirst = Object.keys(config.environments).length === 0;\n\n config.environments['staging'] = {\n name: 'staging',\n type: 'sandbox',\n apiKey: staging.apiKey,\n clientId: staging.clientId,\n };\n\n if (isFirst || !config.activeEnvironment) {\n config.activeEnvironment = 'staging';\n }\n\n saveConfig(config);\n logInfo('[login] Staging environment auto-provisioned');\n return true;\n } catch (error) {\n logError('[login] Failed to auto-provision staging environment:', error instanceof Error ? error.message : error);\n return false;\n }\n}\n\nexport async function runLogin(): Promise<void> {\n const clientId = getCliAuthClientId();\n\n // Check if already logged in with valid token\n if (getAccessToken()) {\n const creds = getCredentials();\n console.log(chalk.green(`Already logged in as ${creds?.email ?? 'unknown'}`));\n console.log(chalk.dim('Run `workos auth logout` to log out'));\n return;\n }\n\n // Try to refresh if we have expired credentials with a refresh token\n const existingCreds = getCredentials();\n if (existingCreds?.refreshToken && isTokenExpired(existingCreds)) {\n try {\n const authkitDomain = getAuthkitDomain();\n const result = await refreshAccessToken(authkitDomain, clientId);\n if (result.accessToken && result.expiresAt) {\n updateTokens(result.accessToken, result.expiresAt, result.refreshToken);\n logInfo('[login] Session refreshed via refresh token');\n console.log(chalk.green(`Already logged in as ${existingCreds.email ?? 'unknown'}`));\n console.log(chalk.dim('Run `workos auth logout` to log out'));\n return;\n }\n } catch {\n // Refresh failed, proceed with fresh login\n }\n }\n\n clack.log.step('Starting authentication...');\n\n const endpoints = getConnectEndpoints();\n\n const authResponse = await fetch(endpoints.deviceAuthorization, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: new URLSearchParams({\n client_id: clientId,\n scope: 'openid email staging-environment:credentials:read offline_access',\n }),\n });\n\n if (!authResponse.ok) {\n clack.log.error(`Failed to start authentication: ${authResponse.status}`);\n process.exit(1);\n }\n\n const deviceAuth = (await authResponse.json()) as DeviceAuthResponse;\n const pollIntervalMs = (deviceAuth.interval || 5) * 1000;\n\n clack.log.info(`\\nOpen this URL in your browser:\\n`);\n console.log(` ${deviceAuth.verification_uri}`);\n console.log(`\\nEnter code: ${deviceAuth.user_code}\\n`);\n\n try {\n open(deviceAuth.verification_uri_complete);\n clack.log.info('Browser opened automatically');\n } catch {\n // User can open manually\n }\n\n const spinner = clack.spinner();\n spinner.start('Waiting for authentication...');\n\n const startTime = Date.now();\n let currentInterval = pollIntervalMs;\n\n while (Date.now() - startTime < POLL_TIMEOUT_MS) {\n await sleep(currentInterval);\n\n try {\n const tokenResponse = await fetch(endpoints.token, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: new URLSearchParams({\n grant_type: 'urn:ietf:params:oauth:grant-type:device_code',\n device_code: deviceAuth.device_code,\n client_id: clientId,\n }),\n });\n\n const data = await tokenResponse.json();\n\n if (tokenResponse.ok) {\n const result = data as ConnectTokenResponse;\n\n // Parse user info from id_token JWT\n const idTokenPayload = parseJwt(result.id_token);\n const userId = (idTokenPayload?.sub as string) || 'unknown';\n const email = (idTokenPayload?.email as string) || undefined;\n\n // Extract actual expiry from access token JWT, fallback to response or 15 min\n const jwtExpiry = getJwtExpiry(result.access_token);\n const expiresAt =\n jwtExpiry ?? (result.expires_in ? Date.now() + result.expires_in * 1000 : Date.now() + 15 * 60 * 1000);\n\n const expiresInSec = Math.round((expiresAt - Date.now()) / 1000);\n\n saveCredentials({\n accessToken: result.access_token,\n expiresAt,\n userId,\n email,\n refreshToken: result.refresh_token,\n });\n\n spinner.stop('Authentication successful!');\n clack.log.success(`Logged in as ${email || userId}`);\n clack.log.info(`Token expires in ${expiresInSec} seconds`);\n\n // Auto-provision staging environment\n const provisioned = await provisionStagingEnvironment(result.access_token);\n if (provisioned) {\n clack.log.success('Staging environment configured automatically');\n } else {\n clack.log.info(chalk.dim('Run `workos env add` to configure an environment manually'));\n }\n return;\n }\n\n const errorData = data as AuthErrorResponse;\n if (errorData.error === 'authorization_pending') continue;\n if (errorData.error === 'slow_down') {\n currentInterval += 5000;\n continue;\n }\n\n spinner.stop('Authentication failed');\n clack.log.error(`Authentication error: ${errorData.error}`);\n process.exit(1);\n } catch {\n continue;\n }\n }\n\n spinner.stop('Authentication timed out');\n clack.log.error('Authentication timed out. Please try again.');\n process.exit(1);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"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","sourcesContent":["import open from 'opn';\nimport chalk from 'chalk';\nimport clack from '../utils/clack.js';\nimport { saveCredentials, getCredentials, getAccessToken, isTokenExpired, updateTokens } from '../lib/credentials.js';\nimport { getCliAuthClientId, getAuthkitDomain } from '../lib/settings.js';\nimport { refreshAccessToken } from '../lib/token-refresh-client.js';\nimport { logInfo, logError } from '../utils/debug.js';\nimport { fetchStagingCredentials } from '../lib/staging-api.js';\nimport { getConfig, saveConfig } from '../lib/config-store.js';\nimport type { CliConfig } from '../lib/config-store.js';\nimport { formatWorkOSCommand } from '../utils/command-invocation.js';\nimport { autoInstallSkills } from './install-skill.js';\nimport { isJsonMode } from '../utils/output.js';\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\nconst POLL_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes\n\n/**\n * Get Connect OAuth endpoints from AuthKit domain\n */\nfunction getConnectEndpoints() {\n const domain = getAuthkitDomain();\n return {\n deviceAuthorization: `${domain}/oauth2/device_authorization`,\n token: `${domain}/oauth2/token`,\n };\n}\n\ninterface 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\ninterface ConnectTokenResponse {\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\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Best-effort skill install after a successful auth-login.\n *\n * Mirrors the install.ts hook copy, but wraps `autoInstallSkills` in its own\n * try/catch AND a 30s timeout so a skill install hang (e.g. blocked filesystem\n * call) never blocks login completion. Login already succeeded by the time\n * this runs — the user having a working session is the contract that must hold.\n *\n * Extracted from runLogin so it can be unit-tested without standing up the\n * device-auth polling loop.\n */\nexport const SKILL_INSTALL_TIMEOUT_MS = 30 * 1000;\n\nexport async function installSkillsAfterLogin(): Promise<void> {\n let timeoutHandle: ReturnType<typeof setTimeout> | undefined;\n try {\n const timeout = new Promise<null>((resolve) => {\n timeoutHandle = setTimeout(() => resolve(null), SKILL_INSTALL_TIMEOUT_MS);\n // Don't keep the event loop alive on this timer — process should exit\n // immediately if everything else has resolved.\n timeoutHandle.unref?.();\n });\n const result = await Promise.race([autoInstallSkills(), timeout]);\n if (result && !isJsonMode()) {\n const skillWord = result.skills.length === 1 ? 'skill' : 'skills';\n clack.log.info(`Installed ${result.skills.length} WorkOS ${skillWord} for ${result.agents.join(', ')}.`);\n }\n } catch {\n // Skill install must never fail login.\n } finally {\n if (timeoutHandle) clearTimeout(timeoutHandle);\n }\n}\n\n/**\n * Auto-provision a staging environment after login.\n *\n * Fetches staging credentials using the access token, then saves them\n * as a \"staging\" environment in the config store. Non-fatal — logs a\n * hint on failure instead of throwing.\n */\nexport async function provisionStagingEnvironment(accessToken: string): Promise<boolean> {\n try {\n const staging = await fetchStagingCredentials(accessToken);\n\n const config: CliConfig = getConfig() ?? { environments: {} };\n const isFirst = Object.keys(config.environments).length === 0;\n\n config.environments['staging'] = {\n name: 'staging',\n type: 'sandbox',\n apiKey: staging.apiKey,\n clientId: staging.clientId,\n };\n\n if (isFirst || !config.activeEnvironment) {\n config.activeEnvironment = 'staging';\n }\n\n saveConfig(config);\n logInfo('[login] Staging environment auto-provisioned');\n return true;\n } catch (error) {\n logError('[login] Failed to auto-provision staging environment:', error instanceof Error ? error.message : error);\n return false;\n }\n}\n\nexport async function runLogin(): Promise<void> {\n const clientId = getCliAuthClientId();\n\n // Check if already logged in with valid token\n if (getAccessToken()) {\n const creds = getCredentials();\n console.log(chalk.green(`Already logged in as ${creds?.email ?? 'unknown'}`));\n console.log(chalk.dim(`Run \\`${formatWorkOSCommand('auth logout')}\\` to log out`));\n return;\n }\n\n // Try to refresh if we have expired credentials with a refresh token\n const existingCreds = getCredentials();\n if (existingCreds?.refreshToken && isTokenExpired(existingCreds)) {\n try {\n const authkitDomain = getAuthkitDomain();\n const result = await refreshAccessToken(authkitDomain, clientId);\n if (result.accessToken && result.expiresAt) {\n updateTokens(result.accessToken, result.expiresAt, result.refreshToken);\n logInfo('[login] Session refreshed via refresh token');\n console.log(chalk.green(`Already logged in as ${existingCreds.email ?? 'unknown'}`));\n console.log(chalk.dim(`Run \\`${formatWorkOSCommand('auth logout')}\\` to log out`));\n return;\n }\n } catch {\n // Refresh failed, proceed with fresh login\n }\n }\n\n clack.log.step('Starting authentication...');\n\n const endpoints = getConnectEndpoints();\n\n const authResponse = await fetch(endpoints.deviceAuthorization, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: new URLSearchParams({\n client_id: clientId,\n scope: 'openid email staging-environment:credentials:read offline_access',\n }),\n });\n\n if (!authResponse.ok) {\n clack.log.error(`Failed to start authentication: ${authResponse.status}`);\n process.exit(1);\n }\n\n const deviceAuth = (await authResponse.json()) as DeviceAuthResponse;\n const pollIntervalMs = (deviceAuth.interval || 5) * 1000;\n\n clack.log.info(`\\nOpen this URL in your browser:\\n`);\n console.log(` ${deviceAuth.verification_uri}`);\n console.log(`\\nEnter code: ${deviceAuth.user_code}\\n`);\n\n try {\n open(deviceAuth.verification_uri_complete);\n clack.log.info('Browser opened automatically');\n } catch {\n // User can open manually\n }\n\n const spinner = clack.spinner();\n spinner.start('Waiting for authentication...');\n\n const startTime = Date.now();\n let currentInterval = pollIntervalMs;\n\n while (Date.now() - startTime < POLL_TIMEOUT_MS) {\n await sleep(currentInterval);\n\n try {\n const tokenResponse = await fetch(endpoints.token, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: new URLSearchParams({\n grant_type: 'urn:ietf:params:oauth:grant-type:device_code',\n device_code: deviceAuth.device_code,\n client_id: clientId,\n }),\n });\n\n const data = await tokenResponse.json();\n\n if (tokenResponse.ok) {\n const result = data as ConnectTokenResponse;\n\n // Parse user info from id_token JWT\n const idTokenPayload = parseJwt(result.id_token);\n const userId = (idTokenPayload?.sub as string) || 'unknown';\n const email = (idTokenPayload?.email as string) || undefined;\n\n // Extract actual expiry from access token JWT, fallback to response or 15 min\n const jwtExpiry = getJwtExpiry(result.access_token);\n const expiresAt =\n jwtExpiry ?? (result.expires_in ? Date.now() + result.expires_in * 1000 : Date.now() + 15 * 60 * 1000);\n\n const expiresInSec = Math.round((expiresAt - Date.now()) / 1000);\n\n saveCredentials({\n accessToken: result.access_token,\n expiresAt,\n userId,\n email,\n refreshToken: result.refresh_token,\n });\n\n spinner.stop('Authentication successful!');\n clack.log.success(`Logged in as ${email || userId}`);\n clack.log.info(`Token expires in ${expiresInSec} seconds`);\n\n // Auto-provision staging environment\n const provisioned = await provisionStagingEnvironment(result.access_token);\n if (provisioned) {\n clack.log.success('Staging environment configured automatically');\n } else {\n clack.log.info(chalk.dim('Run `workos env add` to configure an environment manually'));\n }\n\n // Best-effort skill install. Wrapped helper guarantees login never\n // fails on skill errors.\n await installSkillsAfterLogin();\n return;\n }\n\n const errorData = data as AuthErrorResponse;\n if (errorData.error === 'authorization_pending') continue;\n if (errorData.error === 'slow_down') {\n currentInterval += 5000;\n continue;\n }\n\n spinner.stop('Authentication failed');\n clack.log.error(`Authentication error: ${errorData.error}`);\n process.exit(1);\n } catch {\n continue;\n }\n }\n\n spinner.stop('Authentication timed out');\n clack.log.error('Authentication timed out. Please try again.');\n process.exit(1);\n}\n"]}
|
|
@@ -3,6 +3,7 @@ import { getLlmGatewayUrl, getAuthkitDomain, getCliAuthClientId, getConfig } fro
|
|
|
3
3
|
import { getCredentials, isTokenExpired, updateTokens, diagnoseCredentials } from '../../lib/credentials.js';
|
|
4
4
|
import { refreshAccessToken } from '../../lib/token-refresh-client.js';
|
|
5
5
|
import { buildDoctorPrompt } from '../agent-prompt.js';
|
|
6
|
+
import { formatWorkOSCommand } from '../../utils/command-invocation.js';
|
|
6
7
|
const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
7
8
|
function startSpinner(message) {
|
|
8
9
|
let i = 0;
|
|
@@ -55,10 +56,10 @@ async function callModel(prompt, model) {
|
|
|
55
56
|
throw new Error('Not authenticated');
|
|
56
57
|
if (isTokenExpired(creds)) {
|
|
57
58
|
if (!creds.refreshToken)
|
|
58
|
-
throw new Error(
|
|
59
|
+
throw new Error(`Session expired — run \`${formatWorkOSCommand('auth login')}\` to re-authenticate`);
|
|
59
60
|
const result = await refreshAccessToken(getAuthkitDomain(), getCliAuthClientId());
|
|
60
61
|
if (!result.success || !result.accessToken || !result.expiresAt) {
|
|
61
|
-
throw new Error(
|
|
62
|
+
throw new Error(`Session expired — run \`${formatWorkOSCommand('auth login')}\` to re-authenticate`);
|
|
62
63
|
}
|
|
63
64
|
updateTokens(result.accessToken, result.expiresAt, result.refreshToken);
|
|
64
65
|
creds = getCredentials();
|
|
@@ -99,7 +100,7 @@ export async function checkAiAnalysis(context, options) {
|
|
|
99
100
|
process.stderr.write(` ${line}\n`);
|
|
100
101
|
}
|
|
101
102
|
process.stderr.write('\n');
|
|
102
|
-
return skippedResult(
|
|
103
|
+
return skippedResult(`Not authenticated — run \`${formatWorkOSCommand('auth login')}\` for AI-powered analysis`);
|
|
103
104
|
}
|
|
104
105
|
const startTime = Date.now();
|
|
105
106
|
const spinner = startSpinner('Analyzing project with AI...');
|
|
@@ -1 +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 auth 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 auth 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 auth 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"]}
|
|
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';\nimport { formatWorkOSCommand } from '../../utils/command-invocation.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)\n throw new Error(`Session expired — run \\`${formatWorkOSCommand('auth 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 \\`${formatWorkOSCommand('auth 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 \\`${formatWorkOSCommand('auth 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"]}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { SkillsInfo } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Check the freshness of auto-installed WorkOS skills across detected coding
|
|
4
|
+
* agents. Compares each agent's version marker (written by autoInstallSkills)
|
|
5
|
+
* against the bundled @workos/skills version the CLI ships with. Returns null
|
|
6
|
+
* when no agents have a WorkOS skill installed at all — no noise for users who
|
|
7
|
+
* never installed through the CLI.
|
|
8
|
+
*/
|
|
9
|
+
export declare function checkSkills(home?: string): Promise<SkillsInfo | null>;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { homedir } from 'node:os';
|
|
2
|
+
import { access, readFile } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import semver from 'semver';
|
|
5
|
+
import { createAgents, getBundledSkillsVersion, SKILL_VERSION_MARKER_FILENAME } from '../../commands/install-skill.js';
|
|
6
|
+
async function pathExists(p) {
|
|
7
|
+
try {
|
|
8
|
+
await access(p);
|
|
9
|
+
return true;
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Stale = installed version is strictly older than the bundled version.
|
|
17
|
+
* String inequality would also fire when installed > bundled (user installed
|
|
18
|
+
* via a newer CLI then downgraded), and the SKILLS_OUTDATED remediation would
|
|
19
|
+
* silently downgrade their agent's skills. Use semver ordering so we only
|
|
20
|
+
* recommend an update when the bundled set is actually ahead.
|
|
21
|
+
*
|
|
22
|
+
* Falls back to string inequality when either version can't be parsed as
|
|
23
|
+
* semver — better to flag a possibly-stale skill than to ignore drift entirely.
|
|
24
|
+
*/
|
|
25
|
+
function isStale(installed, bundled) {
|
|
26
|
+
const installedValid = semver.valid(installed);
|
|
27
|
+
const bundledValid = semver.valid(bundled);
|
|
28
|
+
if (installedValid && bundledValid) {
|
|
29
|
+
return semver.lt(installedValid, bundledValid);
|
|
30
|
+
}
|
|
31
|
+
return installed !== bundled;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Check the freshness of auto-installed WorkOS skills across detected coding
|
|
35
|
+
* agents. Compares each agent's version marker (written by autoInstallSkills)
|
|
36
|
+
* against the bundled @workos/skills version the CLI ships with. Returns null
|
|
37
|
+
* when no agents have a WorkOS skill installed at all — no noise for users who
|
|
38
|
+
* never installed through the CLI.
|
|
39
|
+
*/
|
|
40
|
+
export async function checkSkills(home = homedir()) {
|
|
41
|
+
const bundledVersion = await getBundledSkillsVersion();
|
|
42
|
+
const agents = createAgents(home);
|
|
43
|
+
const statuses = [];
|
|
44
|
+
for (const [, agent] of Object.entries(agents)) {
|
|
45
|
+
// Only report on agents that actually have a WorkOS skill installed.
|
|
46
|
+
// An agent's `skills/` dir existing (e.g. for unrelated user-installed
|
|
47
|
+
// skills) doesn't mean WE installed — and `doctor --fix` would otherwise
|
|
48
|
+
// happily write `workos/` and `workos-widgets/` onto an agent that never
|
|
49
|
+
// opted in. The marker OR a workos/ / workos-widgets/ subdir is the signal
|
|
50
|
+
// (either is enough — older explicit installs of just `workos-widgets`
|
|
51
|
+
// shouldn't be invisible to doctor).
|
|
52
|
+
const markerPath = join(agent.globalSkillsDir, SKILL_VERSION_MARKER_FILENAME);
|
|
53
|
+
const workosSkillDir = join(agent.globalSkillsDir, 'workos');
|
|
54
|
+
const widgetsSkillDir = join(agent.globalSkillsDir, 'workos-widgets');
|
|
55
|
+
const [hasMarker, hasWorkos, hasWidgets] = await Promise.all([
|
|
56
|
+
pathExists(markerPath),
|
|
57
|
+
pathExists(workosSkillDir),
|
|
58
|
+
pathExists(widgetsSkillDir),
|
|
59
|
+
]);
|
|
60
|
+
if (!hasMarker && !hasWorkos && !hasWidgets)
|
|
61
|
+
continue;
|
|
62
|
+
let installedVersion = null;
|
|
63
|
+
if (hasMarker) {
|
|
64
|
+
try {
|
|
65
|
+
installedVersion = (await readFile(markerPath, 'utf8')).trim() || null;
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
installedVersion = null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
statuses.push({
|
|
72
|
+
agent: agent.displayName,
|
|
73
|
+
installedVersion,
|
|
74
|
+
stale: Boolean(bundledVersion && installedVersion && isStale(installedVersion, bundledVersion)),
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
if (statuses.length === 0)
|
|
78
|
+
return null;
|
|
79
|
+
return {
|
|
80
|
+
bundledVersion,
|
|
81
|
+
agents: statuses,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=skills.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skills.js","sourceRoot":"","sources":["../../../src/doctor/checks/skills.ts"],"names":[],"mappings":"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","sourcesContent":["import { homedir } from 'node:os';\nimport { access, readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport semver from 'semver';\nimport { createAgents, getBundledSkillsVersion, SKILL_VERSION_MARKER_FILENAME } from '../../commands/install-skill.js';\nimport type { SkillsInfo, SkillAgentStatus } from '../types.js';\n\nasync function pathExists(p: string): Promise<boolean> {\n try {\n await access(p);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Stale = installed version is strictly older than the bundled version.\n * String inequality would also fire when installed > bundled (user installed\n * via a newer CLI then downgraded), and the SKILLS_OUTDATED remediation would\n * silently downgrade their agent's skills. Use semver ordering so we only\n * recommend an update when the bundled set is actually ahead.\n *\n * Falls back to string inequality when either version can't be parsed as\n * semver — better to flag a possibly-stale skill than to ignore drift entirely.\n */\nfunction isStale(installed: string, bundled: string): boolean {\n const installedValid = semver.valid(installed);\n const bundledValid = semver.valid(bundled);\n if (installedValid && bundledValid) {\n return semver.lt(installedValid, bundledValid);\n }\n return installed !== bundled;\n}\n\n/**\n * Check the freshness of auto-installed WorkOS skills across detected coding\n * agents. Compares each agent's version marker (written by autoInstallSkills)\n * against the bundled @workos/skills version the CLI ships with. Returns null\n * when no agents have a WorkOS skill installed at all — no noise for users who\n * never installed through the CLI.\n */\nexport async function checkSkills(home: string = homedir()): Promise<SkillsInfo | null> {\n const bundledVersion = await getBundledSkillsVersion();\n const agents = createAgents(home);\n\n const statuses: SkillAgentStatus[] = [];\n\n for (const [, agent] of Object.entries(agents)) {\n // Only report on agents that actually have a WorkOS skill installed.\n // An agent's `skills/` dir existing (e.g. for unrelated user-installed\n // skills) doesn't mean WE installed — and `doctor --fix` would otherwise\n // happily write `workos/` and `workos-widgets/` onto an agent that never\n // opted in. The marker OR a workos/ / workos-widgets/ subdir is the signal\n // (either is enough — older explicit installs of just `workos-widgets`\n // shouldn't be invisible to doctor).\n const markerPath = join(agent.globalSkillsDir, SKILL_VERSION_MARKER_FILENAME);\n const workosSkillDir = join(agent.globalSkillsDir, 'workos');\n const widgetsSkillDir = join(agent.globalSkillsDir, 'workos-widgets');\n const [hasMarker, hasWorkos, hasWidgets] = await Promise.all([\n pathExists(markerPath),\n pathExists(workosSkillDir),\n pathExists(widgetsSkillDir),\n ]);\n if (!hasMarker && !hasWorkos && !hasWidgets) continue;\n\n let installedVersion: string | null = null;\n if (hasMarker) {\n try {\n installedVersion = (await readFile(markerPath, 'utf8')).trim() || null;\n } catch {\n installedVersion = null;\n }\n }\n\n statuses.push({\n agent: agent.displayName,\n installedVersion,\n stale: Boolean(bundledVersion && installedVersion && isStale(installedVersion, bundledVersion)),\n });\n }\n\n if (statuses.length === 0) return null;\n\n return {\n bundledVersion,\n agents: statuses,\n };\n}\n"]}
|
package/dist/doctor/index.d.ts
CHANGED
|
@@ -1,4 +1,24 @@
|
|
|
1
|
-
import type { DoctorOptions, DoctorReport } from './types.js';
|
|
1
|
+
import type { DoctorOptions, DoctorReport, SkillsRefreshResult } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Skills `--fix` is allowed to refresh. Hardcoded — NOT derived from
|
|
4
|
+
* discoverSkills — so future bundled skills require an explicit opt-in here
|
|
5
|
+
* before doctor will write to their target directory. This is the contract's
|
|
6
|
+
* promise that `--fix` only ever touches `workos/` and `workos-widgets/`.
|
|
7
|
+
*/
|
|
8
|
+
export declare const FIXABLE_SKILLS: readonly ["workos", "workos-widgets"];
|
|
9
|
+
/**
|
|
10
|
+
* Refresh stale WorkOS skills if `--fix` is set and at least one agent is
|
|
11
|
+
* stale or has no marker. Always re-reads `checkSkills()` after a successful
|
|
12
|
+
* refresh so detectIssues sees the post-refresh state and we don't ship a
|
|
13
|
+
* doctor report that simultaneously claims "fixed" and "still stale".
|
|
14
|
+
*
|
|
15
|
+
* Extracted from runDoctor for unit testability — runDoctor itself depends on
|
|
16
|
+
* eight upstream checks that are expensive to mock.
|
|
17
|
+
*/
|
|
18
|
+
export declare function maybeRefreshSkills(options: Pick<DoctorOptions, 'fix'>, skills: DoctorReport['skills']): Promise<{
|
|
19
|
+
skillsRefresh?: SkillsRefreshResult;
|
|
20
|
+
skills: DoctorReport['skills'];
|
|
21
|
+
}>;
|
|
2
22
|
export declare function runDoctor(options: DoctorOptions): Promise<DoctorReport>;
|
|
3
23
|
export declare function outputReport(report: DoctorReport, options: DoctorOptions): Promise<void>;
|
|
4
24
|
export { formatReport } from './output.js';
|
package/dist/doctor/index.js
CHANGED
|
@@ -7,12 +7,52 @@ import { checkConnectivity } from './checks/connectivity.js';
|
|
|
7
7
|
import { checkDashboardSettings, compareRedirectUris } from './checks/dashboard.js';
|
|
8
8
|
import { checkAuthPatterns } from './checks/auth-patterns.js';
|
|
9
9
|
import { checkAiAnalysis } from './checks/ai-analysis.js';
|
|
10
|
+
import { checkSkills } from './checks/skills.js';
|
|
11
|
+
import { refreshWorkOSSkills } from '../commands/install-skill.js';
|
|
10
12
|
import { detectIssues } from './issues.js';
|
|
11
13
|
import { formatReport } from './output.js';
|
|
12
14
|
import { formatReportAsJson } from './json-output.js';
|
|
13
15
|
import { copyToClipboard } from './clipboard.js';
|
|
14
16
|
import Chalk from 'chalk';
|
|
15
17
|
const DOCTOR_VERSION = '1.0.0';
|
|
18
|
+
/**
|
|
19
|
+
* Skills `--fix` is allowed to refresh. Hardcoded — NOT derived from
|
|
20
|
+
* discoverSkills — so future bundled skills require an explicit opt-in here
|
|
21
|
+
* before doctor will write to their target directory. This is the contract's
|
|
22
|
+
* promise that `--fix` only ever touches `workos/` and `workos-widgets/`.
|
|
23
|
+
*/
|
|
24
|
+
export const FIXABLE_SKILLS = ['workos', 'workos-widgets'];
|
|
25
|
+
/**
|
|
26
|
+
* Refresh stale WorkOS skills if `--fix` is set and at least one agent is
|
|
27
|
+
* stale or has no marker. Always re-reads `checkSkills()` after a successful
|
|
28
|
+
* refresh so detectIssues sees the post-refresh state and we don't ship a
|
|
29
|
+
* doctor report that simultaneously claims "fixed" and "still stale".
|
|
30
|
+
*
|
|
31
|
+
* Extracted from runDoctor for unit testability — runDoctor itself depends on
|
|
32
|
+
* eight upstream checks that are expensive to mock.
|
|
33
|
+
*/
|
|
34
|
+
export async function maybeRefreshSkills(options, skills) {
|
|
35
|
+
if (!options.fix || !skills)
|
|
36
|
+
return { skills };
|
|
37
|
+
const stalePresent = skills.agents.some((a) => a.stale || a.installedVersion === null);
|
|
38
|
+
if (!stalePresent)
|
|
39
|
+
return { skills };
|
|
40
|
+
const refresh = await refreshWorkOSSkills({
|
|
41
|
+
// Explicit allowlist — NOT discoverSkills — so the contract's
|
|
42
|
+
// workos/+workos-widgets-only constraint can't drift.
|
|
43
|
+
skills: [...FIXABLE_SKILLS],
|
|
44
|
+
});
|
|
45
|
+
if (!refresh)
|
|
46
|
+
return { skills };
|
|
47
|
+
return {
|
|
48
|
+
skillsRefresh: {
|
|
49
|
+
before: refresh.perAgentBefore,
|
|
50
|
+
after: refresh.perAgentAfter,
|
|
51
|
+
skillsInstalled: refresh.skills,
|
|
52
|
+
},
|
|
53
|
+
skills: (await checkSkills()) ?? undefined,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
16
56
|
export async function runDoctor(options) {
|
|
17
57
|
// Environment check first - loads project's .env/.env.local files
|
|
18
58
|
// Must run before connectivity so the resolved base URL is available
|
|
@@ -25,6 +65,14 @@ export async function runDoctor(options) {
|
|
|
25
65
|
checkConnectivity(options, environment.baseUrl ?? 'https://api.workos.com'),
|
|
26
66
|
checkLanguage(options.installDir),
|
|
27
67
|
]);
|
|
68
|
+
let skills = (await checkSkills()) ?? undefined;
|
|
69
|
+
// `--fix`: refresh stale WorkOS skills BEFORE earlyIssues + AI analysis so
|
|
70
|
+
// every downstream consumer (issue detection, AI prompt context) sees the
|
|
71
|
+
// post-refresh skill state and doesn't reference a SKILLS_OUTDATED warning
|
|
72
|
+
// that was just resolved.
|
|
73
|
+
const refreshOutcome = await maybeRefreshSkills(options, skills);
|
|
74
|
+
const skillsRefresh = refreshOutcome.skillsRefresh;
|
|
75
|
+
skills = refreshOutcome.skills;
|
|
28
76
|
// Dashboard settings + auth patterns + AI analysis (parallel, all need sdk/framework results)
|
|
29
77
|
// AI analysis also receives early issues as context to avoid duplication
|
|
30
78
|
const earlyIssues = detectIssues({
|
|
@@ -37,6 +85,7 @@ export async function runDoctor(options) {
|
|
|
37
85
|
framework,
|
|
38
86
|
environment,
|
|
39
87
|
connectivity,
|
|
88
|
+
skills,
|
|
40
89
|
});
|
|
41
90
|
const [dashboardResult, authPatterns, aiAnalysis] = await Promise.all([
|
|
42
91
|
checkDashboardSettings(options, environment.apiKeyType, envRaw),
|
|
@@ -73,8 +122,10 @@ export async function runDoctor(options) {
|
|
|
73
122
|
redirectUris,
|
|
74
123
|
authPatterns,
|
|
75
124
|
aiAnalysis,
|
|
125
|
+
skills,
|
|
126
|
+
skillsRefresh,
|
|
76
127
|
};
|
|
77
|
-
// Detect issues based on
|
|
128
|
+
// Detect issues based on (post-refresh) data.
|
|
78
129
|
const issues = detectIssues(partialReport);
|
|
79
130
|
// Calculate summary
|
|
80
131
|
const errors = issues.filter((i) => i.severity === 'error').length;
|
package/dist/doctor/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/doctor/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { checkSdk } from './checks/sdk.js';\nimport { checkFramework } from './checks/framework.js';\nimport { checkRuntime } from './checks/runtime.js';\nimport { checkLanguage } from './checks/language.js';\nimport { checkEnvironment } from './checks/environment.js';\nimport { checkConnectivity } from './checks/connectivity.js';\nimport { checkDashboardSettings, compareRedirectUris } from './checks/dashboard.js';\nimport { checkAuthPatterns } from './checks/auth-patterns.js';\nimport { checkAiAnalysis } from './checks/ai-analysis.js';\nimport { detectIssues } from './issues.js';\nimport { formatReport } from './output.js';\nimport { formatReportAsJson } from './json-output.js';\nimport { copyToClipboard } from './clipboard.js';\nimport Chalk from 'chalk';\nimport type { DoctorOptions, DoctorReport } from './types.js';\n\nconst DOCTOR_VERSION = '1.0.0';\n\nexport async function runDoctor(options: DoctorOptions): Promise<DoctorReport> {\n // Environment check first - loads project's .env/.env.local files\n // Must run before connectivity so the resolved base URL is available\n const { info: environment, raw: envRaw } = checkEnvironment(options);\n\n // Run remaining checks concurrently\n const [sdk, framework, runtime, connectivity, language] = await Promise.all([\n checkSdk(options),\n checkFramework(options),\n checkRuntime(options),\n checkConnectivity(options, environment.baseUrl ?? 'https://api.workos.com'),\n checkLanguage(options.installDir),\n ]);\n\n // Dashboard settings + auth patterns + AI analysis (parallel, all need sdk/framework results)\n // AI analysis also receives early issues as context to avoid duplication\n const earlyIssues = detectIssues({\n version: DOCTOR_VERSION,\n timestamp: '',\n project: { path: options.installDir, packageManager: runtime.packageManager },\n sdk,\n language,\n runtime,\n framework,\n environment,\n connectivity,\n });\n\n const [dashboardResult, authPatterns, aiAnalysis] = await Promise.all([\n checkDashboardSettings(options, environment.apiKeyType, envRaw),\n checkAuthPatterns(options, framework, environment, sdk),\n checkAiAnalysis(\n { installDir: options.installDir, language, framework, sdk, environment, existingIssues: earlyIssues },\n { skipAi: options.skipAi },\n ),\n ]);\n\n // Compute expected redirect URI from framework detection if not set in env\n const redirectUriSource: 'env' | 'inferred' = environment.redirectUri ? 'env' : 'inferred';\n const expectedRedirectUri =\n environment.redirectUri ??\n (framework.expectedCallbackPath && framework.detectedPort\n ? `http://localhost:${framework.detectedPort}${framework.expectedCallbackPath}`\n : null);\n\n // Compare redirect URIs if we have dashboard data\n const redirectUris = dashboardResult.settings\n ? compareRedirectUris(expectedRedirectUri, dashboardResult.settings.redirectUris, redirectUriSource)\n : undefined;\n\n // Build partial report\n const partialReport = {\n version: DOCTOR_VERSION,\n timestamp: new Date().toISOString(),\n project: {\n path: options.installDir,\n packageManager: runtime.packageManager,\n },\n sdk,\n language,\n runtime,\n framework,\n environment,\n connectivity,\n credentialValidation: dashboardResult.credentialValidation,\n dashboardSettings: dashboardResult.settings ?? undefined,\n dashboardError: dashboardResult.settings ? undefined : dashboardResult.error,\n redirectUris,\n authPatterns,\n aiAnalysis,\n };\n\n // Detect issues based on collected data\n const issues = detectIssues(partialReport);\n\n // Calculate summary\n const errors = issues.filter((i) => i.severity === 'error').length;\n const warnings = issues.filter((i) => i.severity === 'warning').length;\n\n const report: DoctorReport = {\n ...partialReport,\n issues,\n summary: {\n errors,\n warnings,\n healthy: errors === 0,\n },\n };\n\n return report;\n}\n\nexport async function outputReport(report: DoctorReport, options: DoctorOptions): Promise<void> {\n if (options.json) {\n const json = formatReportAsJson(report);\n console.log(json);\n\n if (options.copy) {\n const success = await copyToClipboard(json);\n if (success) {\n console.error('(Copied to clipboard)');\n }\n }\n } else {\n formatReport(report, { verbose: options.verbose });\n\n if (options.copy) {\n const json = formatReportAsJson(report);\n const success = await copyToClipboard(json);\n if (success) {\n console.log(Chalk.dim('Report copied to clipboard'));\n }\n }\n }\n}\n\nexport { formatReport } from './output.js';\nexport { formatReportAsJson } from './json-output.js';\nexport type { DoctorReport, DoctorOptions } from './types.js';\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/doctor/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { checkSdk } from './checks/sdk.js';\nimport { checkFramework } from './checks/framework.js';\nimport { checkRuntime } from './checks/runtime.js';\nimport { checkLanguage } from './checks/language.js';\nimport { checkEnvironment } from './checks/environment.js';\nimport { checkConnectivity } from './checks/connectivity.js';\nimport { checkDashboardSettings, compareRedirectUris } from './checks/dashboard.js';\nimport { checkAuthPatterns } from './checks/auth-patterns.js';\nimport { checkAiAnalysis } from './checks/ai-analysis.js';\nimport { checkSkills } from './checks/skills.js';\nimport { refreshWorkOSSkills } from '../commands/install-skill.js';\nimport { detectIssues } from './issues.js';\nimport { formatReport } from './output.js';\nimport { formatReportAsJson } from './json-output.js';\nimport { copyToClipboard } from './clipboard.js';\nimport Chalk from 'chalk';\nimport type { DoctorOptions, DoctorReport, SkillsRefreshResult } from './types.js';\n\nconst DOCTOR_VERSION = '1.0.0';\n\n/**\n * Skills `--fix` is allowed to refresh. Hardcoded — NOT derived from\n * discoverSkills — so future bundled skills require an explicit opt-in here\n * before doctor will write to their target directory. This is the contract's\n * promise that `--fix` only ever touches `workos/` and `workos-widgets/`.\n */\nexport const FIXABLE_SKILLS = ['workos', 'workos-widgets'] as const;\n\n/**\n * Refresh stale WorkOS skills if `--fix` is set and at least one agent is\n * stale or has no marker. Always re-reads `checkSkills()` after a successful\n * refresh so detectIssues sees the post-refresh state and we don't ship a\n * doctor report that simultaneously claims \"fixed\" and \"still stale\".\n *\n * Extracted from runDoctor for unit testability — runDoctor itself depends on\n * eight upstream checks that are expensive to mock.\n */\nexport async function maybeRefreshSkills(\n options: Pick<DoctorOptions, 'fix'>,\n skills: DoctorReport['skills'],\n): Promise<{\n skillsRefresh?: SkillsRefreshResult;\n skills: DoctorReport['skills'];\n}> {\n if (!options.fix || !skills) return { skills };\n\n const stalePresent = skills.agents.some((a) => a.stale || a.installedVersion === null);\n if (!stalePresent) return { skills };\n\n const refresh = await refreshWorkOSSkills({\n // Explicit allowlist — NOT discoverSkills — so the contract's\n // workos/+workos-widgets-only constraint can't drift.\n skills: [...FIXABLE_SKILLS],\n });\n if (!refresh) return { skills };\n\n return {\n skillsRefresh: {\n before: refresh.perAgentBefore,\n after: refresh.perAgentAfter,\n skillsInstalled: refresh.skills,\n },\n skills: (await checkSkills()) ?? undefined,\n };\n}\n\nexport async function runDoctor(options: DoctorOptions): Promise<DoctorReport> {\n // Environment check first - loads project's .env/.env.local files\n // Must run before connectivity so the resolved base URL is available\n const { info: environment, raw: envRaw } = checkEnvironment(options);\n\n // Run remaining checks concurrently\n const [sdk, framework, runtime, connectivity, language] = await Promise.all([\n checkSdk(options),\n checkFramework(options),\n checkRuntime(options),\n checkConnectivity(options, environment.baseUrl ?? 'https://api.workos.com'),\n checkLanguage(options.installDir),\n ]);\n\n let skills = (await checkSkills()) ?? undefined;\n\n // `--fix`: refresh stale WorkOS skills BEFORE earlyIssues + AI analysis so\n // every downstream consumer (issue detection, AI prompt context) sees the\n // post-refresh skill state and doesn't reference a SKILLS_OUTDATED warning\n // that was just resolved.\n const refreshOutcome = await maybeRefreshSkills(options, skills);\n const skillsRefresh = refreshOutcome.skillsRefresh;\n skills = refreshOutcome.skills;\n\n // Dashboard settings + auth patterns + AI analysis (parallel, all need sdk/framework results)\n // AI analysis also receives early issues as context to avoid duplication\n const earlyIssues = detectIssues({\n version: DOCTOR_VERSION,\n timestamp: '',\n project: { path: options.installDir, packageManager: runtime.packageManager },\n sdk,\n language,\n runtime,\n framework,\n environment,\n connectivity,\n skills,\n });\n\n const [dashboardResult, authPatterns, aiAnalysis] = await Promise.all([\n checkDashboardSettings(options, environment.apiKeyType, envRaw),\n checkAuthPatterns(options, framework, environment, sdk),\n checkAiAnalysis(\n { installDir: options.installDir, language, framework, sdk, environment, existingIssues: earlyIssues },\n { skipAi: options.skipAi },\n ),\n ]);\n\n // Compute expected redirect URI from framework detection if not set in env\n const redirectUriSource: 'env' | 'inferred' = environment.redirectUri ? 'env' : 'inferred';\n const expectedRedirectUri =\n environment.redirectUri ??\n (framework.expectedCallbackPath && framework.detectedPort\n ? `http://localhost:${framework.detectedPort}${framework.expectedCallbackPath}`\n : null);\n\n // Compare redirect URIs if we have dashboard data\n const redirectUris = dashboardResult.settings\n ? compareRedirectUris(expectedRedirectUri, dashboardResult.settings.redirectUris, redirectUriSource)\n : undefined;\n\n // Build partial report\n const partialReport = {\n version: DOCTOR_VERSION,\n timestamp: new Date().toISOString(),\n project: {\n path: options.installDir,\n packageManager: runtime.packageManager,\n },\n sdk,\n language,\n runtime,\n framework,\n environment,\n connectivity,\n credentialValidation: dashboardResult.credentialValidation,\n dashboardSettings: dashboardResult.settings ?? undefined,\n dashboardError: dashboardResult.settings ? undefined : dashboardResult.error,\n redirectUris,\n authPatterns,\n aiAnalysis,\n skills,\n skillsRefresh,\n };\n\n // Detect issues based on (post-refresh) data.\n const issues = detectIssues(partialReport);\n\n // Calculate summary\n const errors = issues.filter((i) => i.severity === 'error').length;\n const warnings = issues.filter((i) => i.severity === 'warning').length;\n\n const report: DoctorReport = {\n ...partialReport,\n issues,\n summary: {\n errors,\n warnings,\n healthy: errors === 0,\n },\n };\n\n return report;\n}\n\nexport async function outputReport(report: DoctorReport, options: DoctorOptions): Promise<void> {\n if (options.json) {\n const json = formatReportAsJson(report);\n console.log(json);\n\n if (options.copy) {\n const success = await copyToClipboard(json);\n if (success) {\n console.error('(Copied to clipboard)');\n }\n }\n } else {\n formatReport(report, { verbose: options.verbose });\n\n if (options.copy) {\n const json = formatReportAsJson(report);\n const success = await copyToClipboard(json);\n if (success) {\n console.log(Chalk.dim('Report copied to clipboard'));\n }\n }\n }\n}\n\nexport { formatReport } from './output.js';\nexport { formatReportAsJson } from './json-output.js';\nexport type { DoctorReport, DoctorOptions } from './types.js';\n"]}
|
package/dist/doctor/issues.js
CHANGED
|
@@ -137,6 +137,21 @@ export function detectIssues(report) {
|
|
|
137
137
|
});
|
|
138
138
|
}
|
|
139
139
|
}
|
|
140
|
+
// Skill freshness — warn when agent-installed skills trail the bundled version.
|
|
141
|
+
// Surfaced so users know their coding agent is using older WorkOS guidance.
|
|
142
|
+
if (report.skills?.bundledVersion) {
|
|
143
|
+
const stale = report.skills.agents.filter((a) => a.stale);
|
|
144
|
+
if (stale.length > 0) {
|
|
145
|
+
const agentList = stale.map((a) => `${a.agent} (${a.installedVersion ?? 'unknown'})`).join(', ');
|
|
146
|
+
issues.push({
|
|
147
|
+
code: 'SKILLS_OUTDATED',
|
|
148
|
+
severity: 'warning',
|
|
149
|
+
message: `WorkOS skills outdated for ${agentList} — bundled: ${report.skills.bundledVersion}`,
|
|
150
|
+
remediation: 'Run: workos skills install',
|
|
151
|
+
details: { bundledVersion: report.skills.bundledVersion, stale: stale.map((a) => a.agent) },
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
}
|
|
140
155
|
return issues;
|
|
141
156
|
}
|
|
142
157
|
function getUpdateCommand(packageManager, sdkName) {
|