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.
Files changed (60) hide show
  1. package/README.md +16 -12
  2. package/dist/bin.js +5 -0
  3. package/dist/bin.js.map +1 -1
  4. package/dist/commands/auth-status.js +2 -1
  5. package/dist/commands/auth-status.js.map +1 -1
  6. package/dist/commands/claim.js +4 -3
  7. package/dist/commands/claim.js.map +1 -1
  8. package/dist/commands/doctor.d.ts +1 -0
  9. package/dist/commands/doctor.js +1 -0
  10. package/dist/commands/doctor.js.map +1 -1
  11. package/dist/commands/install-skill.d.ts +64 -3
  12. package/dist/commands/install-skill.js +213 -27
  13. package/dist/commands/install-skill.js.map +1 -1
  14. package/dist/commands/install.js +5 -1
  15. package/dist/commands/install.js.map +1 -1
  16. package/dist/commands/login.d.ts +13 -0
  17. package/dist/commands/login.js +43 -2
  18. package/dist/commands/login.js.map +1 -1
  19. package/dist/doctor/checks/ai-analysis.js +4 -3
  20. package/dist/doctor/checks/ai-analysis.js.map +1 -1
  21. package/dist/doctor/checks/skills.d.ts +9 -0
  22. package/dist/doctor/checks/skills.js +84 -0
  23. package/dist/doctor/checks/skills.js.map +1 -0
  24. package/dist/doctor/index.d.ts +21 -1
  25. package/dist/doctor/index.js +52 -1
  26. package/dist/doctor/index.js.map +1 -1
  27. package/dist/doctor/issues.js +15 -0
  28. package/dist/doctor/issues.js.map +1 -1
  29. package/dist/doctor/output.js +16 -0
  30. package/dist/doctor/output.js.map +1 -1
  31. package/dist/doctor/types.d.ts +27 -0
  32. package/dist/doctor/types.js.map +1 -1
  33. package/dist/lib/adapters/cli-adapter.js +2 -1
  34. package/dist/lib/adapters/cli-adapter.js.map +1 -1
  35. package/dist/lib/agent-interface.js +38 -14
  36. package/dist/lib/agent-interface.js.map +1 -1
  37. package/dist/lib/credential-proxy.js +2 -1
  38. package/dist/lib/credential-proxy.js.map +1 -1
  39. package/dist/lib/device-auth.js +26 -10
  40. package/dist/lib/device-auth.js.map +1 -1
  41. package/dist/lib/ensure-auth.js +4 -3
  42. package/dist/lib/ensure-auth.js.map +1 -1
  43. package/dist/lib/installer-core.d.ts +3 -3
  44. package/dist/lib/resolve-install-credentials.js +4 -4
  45. package/dist/lib/resolve-install-credentials.js.map +1 -1
  46. package/dist/lib/run-with-core.js +3 -1
  47. package/dist/lib/run-with-core.js.map +1 -1
  48. package/dist/lib/token-refresh-client.js +2 -1
  49. package/dist/lib/token-refresh-client.js.map +1 -1
  50. package/dist/lib/token-refresh.d.ts +1 -1
  51. package/dist/lib/token-refresh.js +3 -2
  52. package/dist/lib/token-refresh.js.map +1 -1
  53. package/dist/utils/command-invocation.d.ts +8 -0
  54. package/dist/utils/command-invocation.js +17 -0
  55. package/dist/utils/command-invocation.js.map +1 -0
  56. package/dist/utils/exit-codes.js +3 -1
  57. package/dist/utils/exit-codes.js.map +1 -1
  58. package/dist/utils/help-json.js +8 -0
  59. package/dist/utils/help-json.js.map +1 -1
  60. package/package.json +2 -2
@@ -2,6 +2,7 @@ import chalk from 'chalk';
2
2
  import { getCredentials, isTokenExpired } from '../lib/credentials.js';
3
3
  import { getActiveEnvironment } from '../lib/config-store.js';
4
4
  import { isJsonMode, outputJson } from '../utils/output.js';
5
+ import { formatWorkOSCommand } from '../utils/command-invocation.js';
5
6
  function formatTimeRemaining(ms) {
6
7
  if (ms <= 0)
7
8
  return 'expired';
@@ -23,7 +24,7 @@ export async function runAuthStatus() {
23
24
  return;
24
25
  }
25
26
  console.log(chalk.yellow('Not logged in'));
26
- console.log(chalk.dim('Run `workos auth login` to authenticate'));
27
+ console.log(chalk.dim(`Run \`${formatWorkOSCommand('auth login')}\` to authenticate`));
27
28
  return;
28
29
  }
29
30
  const expired = isTokenExpired(creds);
@@ -1 +1 @@
1
- {"version":3,"file":"auth-status.js","sourceRoot":"","sources":["../../src/commands/auth-status.ts"],"names":[],"mappings":"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","sourcesContent":["import chalk from 'chalk';\nimport { getCredentials, isTokenExpired } from '../lib/credentials.js';\nimport { getActiveEnvironment } from '../lib/config-store.js';\nimport { isJsonMode, outputJson } from '../utils/output.js';\n\nfunction formatTimeRemaining(ms: number): string {\n if (ms <= 0) return 'expired';\n const seconds = Math.floor(ms / 1000);\n const minutes = Math.floor(seconds / 60);\n const hours = Math.floor(minutes / 60);\n if (hours > 0) return `${hours}h ${minutes % 60}m`;\n if (minutes > 0) return `${minutes}m ${seconds % 60}s`;\n return `${seconds}s`;\n}\n\nexport async function runAuthStatus(): Promise<void> {\n const creds = getCredentials();\n const activeEnv = getActiveEnvironment();\n\n if (!creds) {\n if (isJsonMode()) {\n outputJson({ authenticated: false });\n return;\n }\n console.log(chalk.yellow('Not logged in'));\n console.log(chalk.dim('Run `workos auth login` to authenticate'));\n return;\n }\n\n const expired = isTokenExpired(creds);\n const timeRemaining = creds.expiresAt - Date.now();\n\n if (isJsonMode()) {\n outputJson({\n authenticated: true,\n email: creds.email ?? null,\n userId: creds.userId,\n tokenExpired: expired,\n tokenExpiresAt: new Date(creds.expiresAt).toISOString(),\n tokenExpiresIn: expired ? null : formatTimeRemaining(timeRemaining),\n hasRefreshToken: !!creds.refreshToken,\n activeEnvironment: activeEnv ? { name: activeEnv.name, type: activeEnv.type } : null,\n });\n return;\n }\n\n console.log(chalk.green(`Logged in as ${creds.email ?? creds.userId}`));\n\n if (expired) {\n console.log(chalk.yellow(`Token expired ${formatTimeRemaining(-timeRemaining)} ago`));\n } else {\n console.log(chalk.dim(`Token expires in ${formatTimeRemaining(timeRemaining)}`));\n }\n\n console.log(chalk.dim(`Refresh token: ${creds.refreshToken ? 'present' : 'absent'}`));\n\n if (activeEnv) {\n console.log(chalk.dim(`Environment: ${activeEnv.name} (${activeEnv.type})`));\n }\n}\n"]}
1
+ {"version":3,"file":"auth-status.js","sourceRoot":"","sources":["../../src/commands/auth-status.ts"],"names":[],"mappings":"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","sourcesContent":["import chalk from 'chalk';\nimport { getCredentials, isTokenExpired } from '../lib/credentials.js';\nimport { getActiveEnvironment } from '../lib/config-store.js';\nimport { isJsonMode, outputJson } from '../utils/output.js';\nimport { formatWorkOSCommand } from '../utils/command-invocation.js';\n\nfunction formatTimeRemaining(ms: number): string {\n if (ms <= 0) return 'expired';\n const seconds = Math.floor(ms / 1000);\n const minutes = Math.floor(seconds / 60);\n const hours = Math.floor(minutes / 60);\n if (hours > 0) return `${hours}h ${minutes % 60}m`;\n if (minutes > 0) return `${minutes}m ${seconds % 60}s`;\n return `${seconds}s`;\n}\n\nexport async function runAuthStatus(): Promise<void> {\n const creds = getCredentials();\n const activeEnv = getActiveEnvironment();\n\n if (!creds) {\n if (isJsonMode()) {\n outputJson({ authenticated: false });\n return;\n }\n console.log(chalk.yellow('Not logged in'));\n console.log(chalk.dim(`Run \\`${formatWorkOSCommand('auth login')}\\` to authenticate`));\n return;\n }\n\n const expired = isTokenExpired(creds);\n const timeRemaining = creds.expiresAt - Date.now();\n\n if (isJsonMode()) {\n outputJson({\n authenticated: true,\n email: creds.email ?? null,\n userId: creds.userId,\n tokenExpired: expired,\n tokenExpiresAt: new Date(creds.expiresAt).toISOString(),\n tokenExpiresIn: expired ? null : formatTimeRemaining(timeRemaining),\n hasRefreshToken: !!creds.refreshToken,\n activeEnvironment: activeEnv ? { name: activeEnv.name, type: activeEnv.type } : null,\n });\n return;\n }\n\n console.log(chalk.green(`Logged in as ${creds.email ?? creds.userId}`));\n\n if (expired) {\n console.log(chalk.yellow(`Token expired ${formatTimeRemaining(-timeRemaining)} ago`));\n } else {\n console.log(chalk.dim(`Token expires in ${formatTimeRemaining(timeRemaining)}`));\n }\n\n console.log(chalk.dim(`Refresh token: ${creds.refreshToken ? 'present' : 'absent'}`));\n\n if (activeEnv) {\n console.log(chalk.dim(`Environment: ${activeEnv.name} (${activeEnv.type})`));\n }\n}\n"]}
@@ -12,6 +12,7 @@ import { createClaimNonce, UnclaimedEnvApiError } from '../lib/unclaimed-env-api
12
12
  import { logInfo, logError } from '../utils/debug.js';
13
13
  import { isJsonMode, outputJson, exitWithError } from '../utils/output.js';
14
14
  import { sleep } from '../lib/helper-functions.js';
15
+ import { formatWorkOSCommand } from '../utils/command-invocation.js';
15
16
  const POLL_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes
16
17
  const POLL_INTERVAL_MS = 5_000; // 5 seconds
17
18
  const MAX_CONSECUTIVE_FAILURES = 10;
@@ -41,7 +42,7 @@ export async function runClaim() {
41
42
  }
42
43
  else {
43
44
  clack.log.success('Environment already claimed!');
44
- clack.log.info('Run `workos auth login` to connect your account.');
45
+ clack.log.info(`Run \`${formatWorkOSCommand('auth login')}\` to connect your account.`);
45
46
  }
46
47
  return;
47
48
  }
@@ -71,7 +72,7 @@ export async function runClaim() {
71
72
  if (check.alreadyClaimed) {
72
73
  spinner.stop('Environment claimed!');
73
74
  markEnvironmentClaimed();
74
- clack.log.info('Run `workos auth login` to connect your account.');
75
+ clack.log.info(`Run \`${formatWorkOSCommand('auth login')}\` to connect your account.`);
75
76
  return;
76
77
  }
77
78
  consecutiveFailures = 0;
@@ -83,7 +84,7 @@ export async function runClaim() {
83
84
  // when the environment is claimed. Safe to promote to sandbox.
84
85
  spinner.stop('Claim token is invalid or expired.');
85
86
  markEnvironmentClaimed();
86
- clack.log.warn('Run `workos auth login` to set up your environment.');
87
+ clack.log.warn(`Run \`${formatWorkOSCommand('auth login')}\` to set up your environment.`);
87
88
  return;
88
89
  }
89
90
  consecutiveFailures++;
@@ -1 +1 @@
1
- {"version":3,"file":"claim.js","sourceRoot":"","sources":["../../src/commands/claim.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `workos env claim` — claim an unclaimed environment.\n *\n * Reads claim token from active environment, generates a nonce via\n * createClaimNonce(), opens browser to dashboard claim URL, and polls\n * until the environment is claimed.\n */\n\nimport open from 'opn';\nimport clack from '../utils/clack.js';\nimport { getActiveEnvironment, isUnclaimedEnvironment, markEnvironmentClaimed } from '../lib/config-store.js';\nimport { createClaimNonce, UnclaimedEnvApiError } from '../lib/unclaimed-env-api.js';\nimport { logInfo, logError } from '../utils/debug.js';\nimport { isJsonMode, outputJson, exitWithError } from '../utils/output.js';\nimport { sleep } from '../lib/helper-functions.js';\n\nconst POLL_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes\nconst POLL_INTERVAL_MS = 5_000; // 5 seconds\nconst MAX_CONSECUTIVE_FAILURES = 10;\n\n/**\n * Run the claim flow.\n */\nexport async function runClaim(): Promise<void> {\n const activeEnv = getActiveEnvironment();\n\n if (!activeEnv || !isUnclaimedEnvironment(activeEnv)) {\n if (isJsonMode()) {\n outputJson({ status: 'no_unclaimed_environment', message: 'No unclaimed environment found. Nothing to claim.' });\n } else {\n clack.log.info('No unclaimed environment found. Nothing to claim.');\n }\n return;\n }\n\n // claimToken and clientId guaranteed present by UnclaimedEnvironmentConfig\n\n logInfo('[claim] Starting claim flow for environment:', activeEnv.name);\n\n try {\n clack.log.step('Generating claim link...');\n\n const result = await createClaimNonce(activeEnv.clientId, activeEnv.claimToken);\n\n if (result.alreadyClaimed) {\n markEnvironmentClaimed();\n if (isJsonMode()) {\n outputJson({ status: 'already_claimed', message: 'Environment already claimed!' });\n } else {\n clack.log.success('Environment already claimed!');\n clack.log.info('Run `workos auth login` to connect your account.');\n }\n return;\n }\n\n const claimUrl = `https://dashboard.workos.com/claim?nonce=${result.nonce}`;\n\n if (isJsonMode()) {\n outputJson({ status: 'claim_url', claimUrl, nonce: result.nonce });\n return;\n }\n\n clack.log.info(`Open this URL to claim your environment:\\n\\n ${claimUrl}`);\n\n try {\n open(claimUrl, { wait: false });\n clack.log.info('Browser opened automatically');\n } catch (openError) {\n logError('[claim] Failed to open browser:', openError instanceof Error ? openError.message : String(openError));\n clack.log.info('Could not open browser — open the URL above manually.');\n }\n\n // Poll for claim completion\n const spinner = clack.spinner();\n spinner.start('Waiting for claim...');\n\n const startTime = Date.now();\n let consecutiveFailures = 0;\n\n while (Date.now() - startTime < POLL_TIMEOUT_MS) {\n await sleep(POLL_INTERVAL_MS);\n try {\n const check = await createClaimNonce(activeEnv.clientId, activeEnv.claimToken);\n if (check.alreadyClaimed) {\n spinner.stop('Environment claimed!');\n markEnvironmentClaimed();\n clack.log.info('Run `workos auth login` to connect your account.');\n return;\n }\n consecutiveFailures = 0;\n } catch (pollError) {\n const statusCode = pollError instanceof UnclaimedEnvApiError ? pollError.statusCode : undefined;\n if (statusCode === 401) {\n // 401 means the server invalidated the claim token — this happens\n // when the environment is claimed. Safe to promote to sandbox.\n spinner.stop('Claim token is invalid or expired.');\n markEnvironmentClaimed();\n clack.log.warn('Run `workos auth login` to set up your environment.');\n return;\n }\n consecutiveFailures++;\n logError('[claim] Poll error:', pollError instanceof Error ? pollError.message : 'Unknown');\n if (consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) {\n spinner.stop('Too many connection failures');\n clack.log.error(\n `Polling failed ${consecutiveFailures} times in a row. Check your network and try again.\\n` +\n `You can also complete the claim at: ${claimUrl}`,\n );\n return;\n }\n if (consecutiveFailures >= 3) {\n spinner.message('Still waiting... (connection issues detected)');\n }\n }\n }\n\n spinner.stop('Claim timed out');\n clack.log.info('Complete the claim in your browser, then run `workos env list` to verify.');\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Unknown error';\n logError('[claim] Error:', message);\n exitWithError({ code: 'claim_failed', message: `Claim failed: ${message}` });\n }\n}\n"]}
1
+ {"version":3,"file":"claim.js","sourceRoot":"","sources":["../../src/commands/claim.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `workos env claim` — claim an unclaimed environment.\n *\n * Reads claim token from active environment, generates a nonce via\n * createClaimNonce(), opens browser to dashboard claim URL, and polls\n * until the environment is claimed.\n */\n\nimport open from 'opn';\nimport clack from '../utils/clack.js';\nimport { getActiveEnvironment, isUnclaimedEnvironment, markEnvironmentClaimed } from '../lib/config-store.js';\nimport { createClaimNonce, UnclaimedEnvApiError } from '../lib/unclaimed-env-api.js';\nimport { logInfo, logError } from '../utils/debug.js';\nimport { isJsonMode, outputJson, exitWithError } from '../utils/output.js';\nimport { sleep } from '../lib/helper-functions.js';\nimport { formatWorkOSCommand } from '../utils/command-invocation.js';\n\nconst POLL_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes\nconst POLL_INTERVAL_MS = 5_000; // 5 seconds\nconst MAX_CONSECUTIVE_FAILURES = 10;\n\n/**\n * Run the claim flow.\n */\nexport async function runClaim(): Promise<void> {\n const activeEnv = getActiveEnvironment();\n\n if (!activeEnv || !isUnclaimedEnvironment(activeEnv)) {\n if (isJsonMode()) {\n outputJson({ status: 'no_unclaimed_environment', message: 'No unclaimed environment found. Nothing to claim.' });\n } else {\n clack.log.info('No unclaimed environment found. Nothing to claim.');\n }\n return;\n }\n\n // claimToken and clientId guaranteed present by UnclaimedEnvironmentConfig\n\n logInfo('[claim] Starting claim flow for environment:', activeEnv.name);\n\n try {\n clack.log.step('Generating claim link...');\n\n const result = await createClaimNonce(activeEnv.clientId, activeEnv.claimToken);\n\n if (result.alreadyClaimed) {\n markEnvironmentClaimed();\n if (isJsonMode()) {\n outputJson({ status: 'already_claimed', message: 'Environment already claimed!' });\n } else {\n clack.log.success('Environment already claimed!');\n clack.log.info(`Run \\`${formatWorkOSCommand('auth login')}\\` to connect your account.`);\n }\n return;\n }\n\n const claimUrl = `https://dashboard.workos.com/claim?nonce=${result.nonce}`;\n\n if (isJsonMode()) {\n outputJson({ status: 'claim_url', claimUrl, nonce: result.nonce });\n return;\n }\n\n clack.log.info(`Open this URL to claim your environment:\\n\\n ${claimUrl}`);\n\n try {\n open(claimUrl, { wait: false });\n clack.log.info('Browser opened automatically');\n } catch (openError) {\n logError('[claim] Failed to open browser:', openError instanceof Error ? openError.message : String(openError));\n clack.log.info('Could not open browser — open the URL above manually.');\n }\n\n // Poll for claim completion\n const spinner = clack.spinner();\n spinner.start('Waiting for claim...');\n\n const startTime = Date.now();\n let consecutiveFailures = 0;\n\n while (Date.now() - startTime < POLL_TIMEOUT_MS) {\n await sleep(POLL_INTERVAL_MS);\n try {\n const check = await createClaimNonce(activeEnv.clientId, activeEnv.claimToken);\n if (check.alreadyClaimed) {\n spinner.stop('Environment claimed!');\n markEnvironmentClaimed();\n clack.log.info(`Run \\`${formatWorkOSCommand('auth login')}\\` to connect your account.`);\n return;\n }\n consecutiveFailures = 0;\n } catch (pollError) {\n const statusCode = pollError instanceof UnclaimedEnvApiError ? pollError.statusCode : undefined;\n if (statusCode === 401) {\n // 401 means the server invalidated the claim token — this happens\n // when the environment is claimed. Safe to promote to sandbox.\n spinner.stop('Claim token is invalid or expired.');\n markEnvironmentClaimed();\n clack.log.warn(`Run \\`${formatWorkOSCommand('auth login')}\\` to set up your environment.`);\n return;\n }\n consecutiveFailures++;\n logError('[claim] Poll error:', pollError instanceof Error ? pollError.message : 'Unknown');\n if (consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) {\n spinner.stop('Too many connection failures');\n clack.log.error(\n `Polling failed ${consecutiveFailures} times in a row. Check your network and try again.\\n` +\n `You can also complete the claim at: ${claimUrl}`,\n );\n return;\n }\n if (consecutiveFailures >= 3) {\n spinner.message('Still waiting... (connection issues detected)');\n }\n }\n }\n\n spinner.stop('Claim timed out');\n clack.log.info('Complete the claim in your browser, then run `workos env list` to verify.');\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Unknown error';\n logError('[claim] Error:', message);\n exitWithError({ code: 'claim_failed', message: `Claim failed: ${message}` });\n }\n}\n"]}
@@ -6,6 +6,7 @@ interface DoctorArgs {
6
6
  installDir?: string;
7
7
  json?: boolean;
8
8
  copy?: boolean;
9
+ fix?: boolean;
9
10
  }
10
11
  export declare function handleDoctor(argv: ArgumentsCamelCase<DoctorArgs>): Promise<void>;
11
12
  export {};
@@ -8,6 +8,7 @@ export async function handleDoctor(argv) {
8
8
  skipAi: argv.skipAi ?? false,
9
9
  json: argv.json ?? false,
10
10
  copy: argv.copy ?? false,
11
+ fix: argv.fix ?? false,
11
12
  };
12
13
  try {
13
14
  const report = await runDoctor(options);
@@ -1 +1 @@
1
- {"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAC7D,OAAO,KAAK,MAAM,mBAAmB,CAAC;AAWtC,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,IAAoC;IACrE,MAAM,OAAO,GAAG;QACd,UAAU,EAAE,IAAI,CAAC,UAAU,IAAI,OAAO,CAAC,GAAG,EAAE;QAC5C,OAAO,EAAE,IAAI,CAAC,OAAO,IAAI,KAAK;QAC9B,OAAO,EAAE,IAAI,CAAC,OAAO,IAAI,KAAK;QAC9B,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,KAAK;QAC5B,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,KAAK;QACxB,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,KAAK;KACzB,CAAC;IAEF,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,OAAO,CAAC,CAAC;QACxC,MAAM,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAEpC,gDAAgD;QAChD,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC9B,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;YAClB,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,kBAAkB,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,EAAE,CAAC,CAAC;QAChG,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,EAAE,CAAC,CAAC,CAAC;QACrG,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC","sourcesContent":["import type { ArgumentsCamelCase } from 'yargs';\nimport { runDoctor, outputReport } from '../doctor/index.js';\nimport clack from '../utils/clack.js';\n\ninterface DoctorArgs {\n verbose?: boolean;\n skipApi?: boolean;\n skipAi?: boolean;\n installDir?: string;\n json?: boolean;\n copy?: boolean;\n}\n\nexport async function handleDoctor(argv: ArgumentsCamelCase<DoctorArgs>): Promise<void> {\n const options = {\n installDir: argv.installDir ?? process.cwd(),\n verbose: argv.verbose ?? false,\n skipApi: argv.skipApi ?? false,\n skipAi: argv.skipAi ?? false,\n json: argv.json ?? false,\n copy: argv.copy ?? false,\n };\n\n try {\n const report = await runDoctor(options);\n await outputReport(report, options);\n\n // Exit with error code if critical issues found\n if (report.summary.errors > 0) {\n process.exit(1);\n }\n process.exit(0);\n } catch (error) {\n if (!options.json) {\n clack.log.error(`Doctor failed: ${error instanceof Error ? error.message : 'Unknown error'}`);\n } else {\n console.error(JSON.stringify({ error: error instanceof Error ? error.message : 'Unknown error' }));\n }\n process.exit(1);\n }\n}\n"]}
1
+ {"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAC7D,OAAO,KAAK,MAAM,mBAAmB,CAAC;AAYtC,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,IAAoC;IACrE,MAAM,OAAO,GAAG;QACd,UAAU,EAAE,IAAI,CAAC,UAAU,IAAI,OAAO,CAAC,GAAG,EAAE;QAC5C,OAAO,EAAE,IAAI,CAAC,OAAO,IAAI,KAAK;QAC9B,OAAO,EAAE,IAAI,CAAC,OAAO,IAAI,KAAK;QAC9B,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,KAAK;QAC5B,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,KAAK;QACxB,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,KAAK;QACxB,GAAG,EAAE,IAAI,CAAC,GAAG,IAAI,KAAK;KACvB,CAAC;IAEF,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,OAAO,CAAC,CAAC;QACxC,MAAM,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAEpC,gDAAgD;QAChD,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC9B,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;YAClB,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,kBAAkB,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,EAAE,CAAC,CAAC;QAChG,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,EAAE,CAAC,CAAC,CAAC;QACrG,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC","sourcesContent":["import type { ArgumentsCamelCase } from 'yargs';\nimport { runDoctor, outputReport } from '../doctor/index.js';\nimport clack from '../utils/clack.js';\n\ninterface DoctorArgs {\n verbose?: boolean;\n skipApi?: boolean;\n skipAi?: boolean;\n installDir?: string;\n json?: boolean;\n copy?: boolean;\n fix?: boolean;\n}\n\nexport async function handleDoctor(argv: ArgumentsCamelCase<DoctorArgs>): Promise<void> {\n const options = {\n installDir: argv.installDir ?? process.cwd(),\n verbose: argv.verbose ?? false,\n skipApi: argv.skipApi ?? false,\n skipAi: argv.skipAi ?? false,\n json: argv.json ?? false,\n copy: argv.copy ?? false,\n fix: argv.fix ?? false,\n };\n\n try {\n const report = await runDoctor(options);\n await outputReport(report, options);\n\n // Exit with error code if critical issues found\n if (report.summary.errors > 0) {\n process.exit(1);\n }\n process.exit(0);\n } catch (error) {\n if (!options.json) {\n clack.log.error(`Doctor failed: ${error instanceof Error ? error.message : 'Unknown error'}`);\n } else {\n console.error(JSON.stringify({ error: error instanceof Error ? error.message : 'Unknown error' }));\n }\n process.exit(1);\n }\n}\n"]}
@@ -1,3 +1,12 @@
1
+ export declare const SKILL_VERSION_MARKER_FILENAME = ".workos-skill-version";
2
+ /**
3
+ * Read the bundled @workos/skills version by walking up from the skills
4
+ * directory to the package.json. The package's `exports` map doesn't expose
5
+ * package.json, so we resolve it by filesystem convention.
6
+ * Returns null if the version can't be determined — callers treat that as
7
+ * "no marker written" rather than failing the install.
8
+ */
9
+ export declare function getBundledSkillsVersion(skillsDir?: string): Promise<string | null>;
1
10
  export interface AgentConfig {
2
11
  name: string;
3
12
  displayName: string;
@@ -12,13 +21,65 @@ export interface InstallSkillOptions {
12
21
  export declare function getSkillsDir(): string;
13
22
  export declare function discoverSkills(skillsDir: string): Promise<string[]>;
14
23
  export declare function detectAgents(agents: Record<string, AgentConfig>, filter?: string[]): AgentConfig[];
24
+ /**
25
+ * Recursively install a skill directory (SKILL.md + references/ + any other
26
+ * files) with prune-replace semantics. Uses a sibling temp dir + backup-rename
27
+ * pattern so the operation is effectively atomic per skill: the target either
28
+ * matches the source exactly, or (on rollback) is restored to its prior state.
29
+ *
30
+ * Returns `{ success, error }` rather than throwing — callers (autoInstallSkills,
31
+ * runInstallSkill) accumulate failures across the (skill × agent) matrix.
32
+ */
15
33
  export declare function installSkill(skillsDir: string, skillName: string, agent: AgentConfig): Promise<{
16
34
  success: boolean;
17
35
  error?: string;
18
36
  }>;
19
37
  export declare function runInstallSkill(options: InstallSkillOptions): Promise<void>;
38
+ export interface AutoInstallResult {
39
+ skills: string[];
40
+ agents: string[];
41
+ version: string | null;
42
+ }
43
+ export interface RefreshOptions {
44
+ /** Pre-detected agents. Default: detect from $HOME. */
45
+ agents?: AgentConfig[];
46
+ /** Skill names to install. Default: all bundled skills. */
47
+ skills?: string[];
48
+ /** Whether to write the version marker after a successful per-agent install. Default: true. */
49
+ writeMarker?: boolean;
50
+ }
51
+ export interface RefreshResult {
52
+ /** Agents where at least one skill installed successfully. */
53
+ agents: AgentConfig[];
54
+ /** Skills that were attempted (the resolved set after filtering). */
55
+ skills: string[];
56
+ /** Bundled skills package version, or null if it couldn't be resolved. */
57
+ version: string | null;
58
+ /** Marker version per agent.name BEFORE refresh (null = no marker / unreadable). */
59
+ perAgentBefore: Record<string, string | null>;
60
+ /** Marker version per agent.name AFTER refresh. */
61
+ perAgentAfter: Record<string, string | null>;
62
+ }
63
+ /**
64
+ * Reusable primitive: discover bundled skills, install each one to each agent,
65
+ * write per-agent version markers, and report before/after marker state.
66
+ *
67
+ * Both `autoInstallSkills` (best-effort hook called from install/login) and
68
+ * `doctor --fix` (Phase 3) call this — there is no duplicate copy logic.
69
+ *
70
+ * Returns null when nothing applied (no agents detected, no skills found, or
71
+ * every install attempt failed).
72
+ */
73
+ export declare function refreshWorkOSSkills(opts?: RefreshOptions): Promise<RefreshResult | null>;
20
74
  /**
21
- * Silently install all bundled skills to all detected coding agents.
22
- * Errors are swallowed this must never disrupt the calling flow.
75
+ * Install all bundled skills to all detected coding agents.
76
+ * Returns a summary when anything was installed, or null when nothing applied.
77
+ * Performs minimal IO: writes a version marker file alongside installed
78
+ * skills so `workos doctor` can detect staleness later. Errors are swallowed
79
+ * so skill install never disrupts the calling flow.
80
+ *
81
+ * Thin back-compat wrapper around `refreshWorkOSSkills` — the install/auth-login
82
+ * call sites use this; doctor `--fix` (Phase 3) calls `refreshWorkOSSkills`
83
+ * directly to surface the per-agent before/after marker state.
23
84
  */
24
- export declare function autoInstallSkills(): Promise<void>;
85
+ export declare function autoInstallSkills(): Promise<AutoInstallResult | null>;
@@ -1,9 +1,42 @@
1
1
  import { homedir } from 'os';
2
- import { join } from 'path';
2
+ import { dirname, join } from 'path';
3
3
  import { existsSync } from 'fs';
4
- import { mkdir, copyFile, readdir } from 'fs/promises';
4
+ import { mkdir, mkdtemp, cp, rename, rm, readdir, readFile, stat, access, writeFile } from 'fs/promises';
5
5
  import chalk from 'chalk';
6
6
  import { getSkillsDir as getSkillsPackageDir } from '@workos/skills';
7
+ export const SKILL_VERSION_MARKER_FILENAME = '.workos-skill-version';
8
+ // Stale-orphan cutoff for `.workos.tmp-*` / `.workos.bak-*` siblings left behind
9
+ // by a crashed prior run. Anything younger may belong to a concurrent install
10
+ // and must NOT be removed.
11
+ const ORPHAN_STALE_MS = 60 * 60 * 1000;
12
+ /** Async equivalent of `existsSync` — `access` rejects with ENOENT when missing. */
13
+ async function pathExists(p) {
14
+ try {
15
+ await access(p);
16
+ return true;
17
+ }
18
+ catch {
19
+ return false;
20
+ }
21
+ }
22
+ /**
23
+ * Read the bundled @workos/skills version by walking up from the skills
24
+ * directory to the package.json. The package's `exports` map doesn't expose
25
+ * package.json, so we resolve it by filesystem convention.
26
+ * Returns null if the version can't be determined — callers treat that as
27
+ * "no marker written" rather than failing the install.
28
+ */
29
+ export async function getBundledSkillsVersion(skillsDir = getSkillsPackageDir()) {
30
+ try {
31
+ // skillsDir = <packageRoot>/plugins/workos/skills
32
+ const packageRoot = dirname(dirname(dirname(skillsDir)));
33
+ const pkgJson = JSON.parse(await readFile(join(packageRoot, 'package.json'), 'utf8'));
34
+ return typeof pkgJson.version === 'string' ? pkgJson.version : null;
35
+ }
36
+ catch {
37
+ return null;
38
+ }
39
+ }
7
40
  export function createAgents(home) {
8
41
  return {
9
42
  'claude-code': {
@@ -37,7 +70,9 @@ export function getSkillsDir() {
37
70
  }
38
71
  export async function discoverSkills(skillsDir) {
39
72
  const entries = await readdir(skillsDir, { withFileTypes: true });
40
- return entries.filter((e) => e.isDirectory() && existsSync(join(skillsDir, e.name, 'SKILL.md'))).map((e) => e.name);
73
+ const dirs = entries.filter((e) => e.isDirectory());
74
+ const checks = await Promise.all(dirs.map((e) => pathExists(join(skillsDir, e.name, 'SKILL.md'))));
75
+ return dirs.filter((_, i) => checks[i]).map((e) => e.name);
41
76
  }
42
77
  export function detectAgents(agents, filter) {
43
78
  const detected = [];
@@ -50,22 +85,84 @@ export function detectAgents(agents, filter) {
50
85
  }
51
86
  return detected;
52
87
  }
88
+ /**
89
+ * Recursively install a skill directory (SKILL.md + references/ + any other
90
+ * files) with prune-replace semantics. Uses a sibling temp dir + backup-rename
91
+ * pattern so the operation is effectively atomic per skill: the target either
92
+ * matches the source exactly, or (on rollback) is restored to its prior state.
93
+ *
94
+ * Returns `{ success, error }` rather than throwing — callers (autoInstallSkills,
95
+ * runInstallSkill) accumulate failures across the (skill × agent) matrix.
96
+ */
53
97
  export async function installSkill(skillsDir, skillName, agent) {
54
- const sourceFile = join(skillsDir, skillName, 'SKILL.md');
98
+ const sourceDir = join(skillsDir, skillName);
55
99
  const targetDir = join(agent.globalSkillsDir, skillName);
56
- const targetFile = join(targetDir, 'SKILL.md');
100
+ const parent = dirname(targetDir);
101
+ // Setup (mkdir parent, mkdtemp) is inside the try so EACCES / ENOTDIR / etc.
102
+ // surface as `{ success: false }` rather than rejecting — runInstallSkill and
103
+ // refreshWorkOSSkills accumulate failures across the (skill × agent) matrix
104
+ // and would otherwise abort the whole batch on a single bad agent dir.
105
+ let tempDir;
57
106
  try {
58
- await mkdir(targetDir, { recursive: true });
59
- await copyFile(sourceFile, targetFile);
107
+ await mkdir(parent, { recursive: true });
108
+ // Best-effort cleanup of OLD orphans only — never current-run paths.
109
+ await cleanupStaleOrphans(parent, skillName).catch(() => { });
110
+ // mkdtemp gives us atomic creation + a random suffix that prevents
111
+ // collisions between concurrent installers.
112
+ tempDir = await mkdtemp(join(parent, `.workos.tmp-${skillName}-`));
113
+ const backupDir = tempDir.replace('.workos.tmp-', '.workos.bak-');
114
+ await cp(sourceDir, tempDir, { recursive: true, errorOnExist: false });
115
+ const targetExisted = await pathExists(targetDir);
116
+ if (targetExisted) {
117
+ await rename(targetDir, backupDir);
118
+ }
119
+ try {
120
+ await rename(tempDir, targetDir);
121
+ }
122
+ catch (renameErr) {
123
+ if (targetExisted) {
124
+ await rename(backupDir, targetDir).catch(() => { });
125
+ }
126
+ throw renameErr;
127
+ }
128
+ // Backup cleanup is best-effort: target is already in place, so failure
129
+ // here leaves a stale backup that the next run's cleanup handles after 1h.
130
+ if (targetExisted) {
131
+ await rm(backupDir, { recursive: true, force: true }).catch(() => { });
132
+ }
60
133
  return { success: true };
61
134
  }
62
135
  catch (error) {
136
+ if (tempDir) {
137
+ await rm(tempDir, { recursive: true, force: true }).catch(() => { });
138
+ }
63
139
  return {
64
140
  success: false,
65
141
  error: error instanceof Error ? error.message : 'Unknown error',
66
142
  };
67
143
  }
68
144
  }
145
+ /**
146
+ * Remove `.workos.tmp-{skillName}-*` and `.workos.bak-{skillName}-*` siblings
147
+ * older than ORPHAN_STALE_MS. Fresh siblings (from a concurrent install) are
148
+ * preserved — destroying them would race the other run's final rename.
149
+ */
150
+ async function cleanupStaleOrphans(parent, skillName) {
151
+ if (!(await pathExists(parent)))
152
+ return;
153
+ const entries = await readdir(parent).catch(() => []);
154
+ const cutoff = Date.now() - ORPHAN_STALE_MS;
155
+ for (const entry of entries) {
156
+ const isOrphan = entry.startsWith(`.workos.tmp-${skillName}-`) || entry.startsWith(`.workos.bak-${skillName}-`);
157
+ if (!isOrphan)
158
+ continue;
159
+ const path = join(parent, entry);
160
+ const st = await stat(path).catch(() => null);
161
+ if (st && st.mtimeMs < cutoff) {
162
+ await rm(path, { recursive: true, force: true }).catch(() => { });
163
+ }
164
+ }
165
+ }
69
166
  export async function runInstallSkill(options) {
70
167
  const home = homedir();
71
168
  const agents = createAgents(home);
@@ -93,11 +190,7 @@ export async function runInstallSkill(options) {
93
190
  for (const skill of targetSkills) {
94
191
  for (const agent of targetAgents) {
95
192
  const result = await installSkill(skillsDir, skill, agent);
96
- results.push({
97
- skill,
98
- agent: agent.displayName,
99
- ...result,
100
- });
193
+ results.push({ skill, agent, ...result });
101
194
  }
102
195
  }
103
196
  const successful = results.filter((r) => r.success);
@@ -105,39 +198,132 @@ export async function runInstallSkill(options) {
105
198
  if (successful.length > 0) {
106
199
  console.log(chalk.green(`✓ Installed ${successful.length} skill(s):\n`));
107
200
  for (const r of successful) {
108
- console.log(` ${chalk.cyan(r.skill)} → ${chalk.dim(r.agent)}`);
201
+ console.log(` ${chalk.cyan(r.skill)} → ${chalk.dim(r.agent.displayName)}`);
202
+ }
203
+ }
204
+ // Write per-agent version markers for any agent that had at least one
205
+ // successful install, so `workos doctor` doesn't immediately flag the
206
+ // freshly-installed skills as stale or missing. Same primitive as
207
+ // refreshWorkOSSkills — single source of truth for marker semantics.
208
+ const version = await getBundledSkillsVersion(skillsDir);
209
+ if (version) {
210
+ const succeededAgents = new Set();
211
+ for (const r of successful)
212
+ succeededAgents.add(r.agent);
213
+ for (const agent of succeededAgents) {
214
+ await writeAgentSkillMarker(agent, version);
109
215
  }
110
216
  }
111
217
  if (failed.length > 0) {
112
218
  console.log(chalk.red(`\n✗ Failed to install ${failed.length}:\n`));
113
219
  for (const r of failed) {
114
- console.log(` ${r.skill} → ${r.agent}: ${chalk.dim(r.error)}`);
220
+ console.log(` ${r.skill} → ${r.agent.displayName}: ${chalk.dim(r.error)}`);
115
221
  }
116
222
  process.exit(1);
117
223
  }
118
224
  console.log(chalk.green('\nDone!'));
119
225
  }
226
+ async function readSkillVersionMarker(agent) {
227
+ const path = join(agent.globalSkillsDir, SKILL_VERSION_MARKER_FILENAME);
228
+ try {
229
+ return (await readFile(path, 'utf8')).trim() || null;
230
+ }
231
+ catch {
232
+ return null;
233
+ }
234
+ }
120
235
  /**
121
- * Silently install all bundled skills to all detected coding agents.
122
- * Errors are swallowed this must never disrupt the calling flow.
236
+ * Best-effort marker write any failure is swallowed (filesystem permission
237
+ * errors shouldn't fail the install; doctor treats missing markers as "unknown").
238
+ * Single source of truth for the .workos-skill-version write semantics.
123
239
  */
124
- export async function autoInstallSkills() {
240
+ async function writeAgentSkillMarker(agent, version) {
125
241
  try {
126
- const home = homedir();
127
- const agents = createAgents(home);
128
- const skillsDir = getSkillsDir();
129
- const skills = await discoverSkills(skillsDir);
130
- const targetAgents = detectAgents(agents);
131
- if (skills.length === 0 || targetAgents.length === 0)
132
- return;
242
+ await writeFile(join(agent.globalSkillsDir, SKILL_VERSION_MARKER_FILENAME), version, 'utf8');
243
+ }
244
+ catch {
245
+ // Marker is best-effort; doctor treats missing marker as "unknown".
246
+ }
247
+ }
248
+ /**
249
+ * Reusable primitive: discover bundled skills, install each one to each agent,
250
+ * write per-agent version markers, and report before/after marker state.
251
+ *
252
+ * Both `autoInstallSkills` (best-effort hook called from install/login) and
253
+ * `doctor --fix` (Phase 3) call this — there is no duplicate copy logic.
254
+ *
255
+ * Returns null when nothing applied (no agents detected, no skills found, or
256
+ * every install attempt failed).
257
+ */
258
+ export async function refreshWorkOSSkills(opts = {}) {
259
+ const home = homedir();
260
+ const skillsDir = getSkillsDir();
261
+ const detected = opts.agents ?? detectAgents(createAgents(home));
262
+ const allSkills = await discoverSkills(skillsDir).catch(() => []);
263
+ const skills = opts.skills ? allSkills.filter((s) => opts.skills.includes(s)) : allSkills;
264
+ const writeMarker = opts.writeMarker ?? true;
265
+ if (skills.length === 0 || detected.length === 0)
266
+ return null;
267
+ const version = await getBundledSkillsVersion(skillsDir);
268
+ const perAgentBefore = {};
269
+ const perAgentAfter = {};
270
+ const succeededAgents = [];
271
+ // Union of skills that succeeded for at least one agent. Returning the full
272
+ // attempted list would inflate "Installed N skills" copy when some skills
273
+ // failed to copy; only count what actually landed somewhere.
274
+ const installedSkills = new Set();
275
+ for (const agent of detected) {
276
+ perAgentBefore[agent.name] = await readSkillVersionMarker(agent);
277
+ let agentSucceeded = false;
133
278
  for (const skill of skills) {
134
- for (const agent of targetAgents) {
135
- await installSkill(skillsDir, skill, agent);
279
+ const result = await installSkill(skillsDir, skill, agent);
280
+ if (result.success) {
281
+ agentSucceeded = true;
282
+ installedSkills.add(skill);
283
+ }
284
+ }
285
+ if (agentSucceeded) {
286
+ succeededAgents.push(agent);
287
+ if (writeMarker && version) {
288
+ await writeAgentSkillMarker(agent, version);
136
289
  }
137
290
  }
291
+ perAgentAfter[agent.name] = await readSkillVersionMarker(agent);
292
+ }
293
+ if (succeededAgents.length === 0)
294
+ return null;
295
+ return {
296
+ agents: succeededAgents,
297
+ skills: skills.filter((s) => installedSkills.has(s)),
298
+ version,
299
+ perAgentBefore,
300
+ perAgentAfter,
301
+ };
302
+ }
303
+ /**
304
+ * Install all bundled skills to all detected coding agents.
305
+ * Returns a summary when anything was installed, or null when nothing applied.
306
+ * Performs minimal IO: writes a version marker file alongside installed
307
+ * skills so `workos doctor` can detect staleness later. Errors are swallowed
308
+ * so skill install never disrupts the calling flow.
309
+ *
310
+ * Thin back-compat wrapper around `refreshWorkOSSkills` — the install/auth-login
311
+ * call sites use this; doctor `--fix` (Phase 3) calls `refreshWorkOSSkills`
312
+ * directly to surface the per-agent before/after marker state.
313
+ */
314
+ export async function autoInstallSkills() {
315
+ try {
316
+ const result = await refreshWorkOSSkills();
317
+ if (!result)
318
+ return null;
319
+ return {
320
+ skills: result.skills,
321
+ agents: result.agents.map((a) => a.displayName),
322
+ version: result.version,
323
+ };
138
324
  }
139
325
  catch {
140
- // Intentionally swallowed — skill install is best-effort
326
+ return null;
141
327
  }
142
328
  }
143
329
  //# sourceMappingURL=install-skill.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"install-skill.js","sourceRoot":"","sources":["../../src/commands/install-skill.ts"],"names":[],"mappings":"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","sourcesContent":["import { homedir } from 'os';\nimport { join } from 'path';\nimport { existsSync } from 'fs';\nimport { mkdir, copyFile, readdir } from 'fs/promises';\nimport chalk from 'chalk';\nimport { getSkillsDir as getSkillsPackageDir } from '@workos/skills';\n\nexport interface AgentConfig {\n name: string;\n displayName: string;\n globalSkillsDir: string;\n detect: () => boolean;\n}\n\nexport function createAgents(home: string): Record<string, AgentConfig> {\n return {\n 'claude-code': {\n name: 'claude-code',\n displayName: 'Claude Code',\n globalSkillsDir: join(home, '.claude/skills'),\n detect: () => existsSync(join(home, '.claude')),\n },\n codex: {\n name: 'codex',\n displayName: 'Codex',\n globalSkillsDir: join(home, '.codex/skills'),\n detect: () => existsSync(join(home, '.codex')),\n },\n cursor: {\n name: 'cursor',\n displayName: 'Cursor',\n globalSkillsDir: join(home, '.cursor/skills'),\n detect: () => existsSync(join(home, '.cursor')),\n },\n goose: {\n name: 'goose',\n displayName: 'Goose',\n globalSkillsDir: join(home, '.config/goose/skills'),\n detect: () => existsSync(join(home, '.config/goose')),\n },\n };\n}\n\nexport interface InstallSkillOptions {\n skill?: string[];\n agent?: string[];\n}\n\nexport function getSkillsDir(): string {\n return getSkillsPackageDir();\n}\n\nexport async function discoverSkills(skillsDir: string): Promise<string[]> {\n const entries = await readdir(skillsDir, { withFileTypes: true });\n\n return entries.filter((e) => e.isDirectory() && existsSync(join(skillsDir, e.name, 'SKILL.md'))).map((e) => e.name);\n}\n\nexport function detectAgents(agents: Record<string, AgentConfig>, filter?: string[]): AgentConfig[] {\n const detected: AgentConfig[] = [];\n\n for (const [key, config] of Object.entries(agents)) {\n if (filter && !filter.includes(key)) continue;\n if (config.detect()) {\n detected.push(config);\n }\n }\n\n return detected;\n}\n\nexport async function installSkill(\n skillsDir: string,\n skillName: string,\n agent: AgentConfig,\n): Promise<{ success: boolean; error?: string }> {\n const sourceFile = join(skillsDir, skillName, 'SKILL.md');\n const targetDir = join(agent.globalSkillsDir, skillName);\n const targetFile = join(targetDir, 'SKILL.md');\n\n try {\n await mkdir(targetDir, { recursive: true });\n await copyFile(sourceFile, targetFile);\n return { success: true };\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : 'Unknown error',\n };\n }\n}\n\nexport async function runInstallSkill(options: InstallSkillOptions): Promise<void> {\n const home = homedir();\n const agents = createAgents(home);\n const skillsDir = getSkillsDir();\n const skills = await discoverSkills(skillsDir);\n\n const targetSkills = options.skill ? skills.filter((s) => options.skill!.includes(s)) : skills;\n\n if (targetSkills.length === 0) {\n console.error(chalk.red('No matching skills found.'));\n console.log('Available skills:', skills.join(', '));\n process.exit(1);\n }\n\n const targetAgents = detectAgents(agents, options.agent);\n\n if (targetAgents.length === 0) {\n if (options.agent) {\n console.error(chalk.red('Specified agents not found.'));\n } else {\n console.error(chalk.red('No coding agents detected.'));\n }\n console.log('Supported agents:', Object.keys(agents).join(', '));\n process.exit(1);\n }\n\n console.log(chalk.bold('\\nInstalling skills...\\n'));\n\n const results: Array<{\n skill: string;\n agent: string;\n success: boolean;\n error?: string;\n }> = [];\n\n for (const skill of targetSkills) {\n for (const agent of targetAgents) {\n const result = await installSkill(skillsDir, skill, agent);\n results.push({\n skill,\n agent: agent.displayName,\n ...result,\n });\n }\n }\n\n const successful = results.filter((r) => r.success);\n const failed = results.filter((r) => !r.success);\n\n if (successful.length > 0) {\n console.log(chalk.green(`✓ Installed ${successful.length} skill(s):\\n`));\n for (const r of successful) {\n console.log(` ${chalk.cyan(r.skill)} → ${chalk.dim(r.agent)}`);\n }\n }\n\n if (failed.length > 0) {\n console.log(chalk.red(`\\n✗ Failed to install ${failed.length}:\\n`));\n for (const r of failed) {\n console.log(` ${r.skill} → ${r.agent}: ${chalk.dim(r.error)}`);\n }\n process.exit(1);\n }\n\n console.log(chalk.green('\\nDone!'));\n}\n\n/**\n * Silently install all bundled skills to all detected coding agents.\n * Errors are swallowed — this must never disrupt the calling flow.\n */\nexport async function autoInstallSkills(): Promise<void> {\n try {\n const home = homedir();\n const agents = createAgents(home);\n const skillsDir = getSkillsDir();\n const skills = await discoverSkills(skillsDir);\n const targetAgents = detectAgents(agents);\n\n if (skills.length === 0 || targetAgents.length === 0) return;\n\n for (const skill of skills) {\n for (const agent of targetAgents) {\n await installSkill(skillsDir, skill, agent);\n }\n }\n } catch {\n // Intentionally swallowed — skill install is best-effort\n }\n}\n"]}
1
+ {"version":3,"file":"install-skill.js","sourceRoot":"","sources":["../../src/commands/install-skill.ts"],"names":[],"mappings":"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","sourcesContent":["import { homedir } from 'os';\nimport { dirname, join } from 'path';\nimport { existsSync } from 'fs';\nimport { mkdir, mkdtemp, cp, rename, rm, readdir, readFile, stat, access, writeFile } from 'fs/promises';\nimport chalk from 'chalk';\nimport { getSkillsDir as getSkillsPackageDir } from '@workos/skills';\n\nexport const SKILL_VERSION_MARKER_FILENAME = '.workos-skill-version';\n\n// Stale-orphan cutoff for `.workos.tmp-*` / `.workos.bak-*` siblings left behind\n// by a crashed prior run. Anything younger may belong to a concurrent install\n// and must NOT be removed.\nconst ORPHAN_STALE_MS = 60 * 60 * 1000;\n\n/** Async equivalent of `existsSync` — `access` rejects with ENOENT when missing. */\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 * Read the bundled @workos/skills version by walking up from the skills\n * directory to the package.json. The package's `exports` map doesn't expose\n * package.json, so we resolve it by filesystem convention.\n * Returns null if the version can't be determined — callers treat that as\n * \"no marker written\" rather than failing the install.\n */\nexport async function getBundledSkillsVersion(skillsDir: string = getSkillsPackageDir()): Promise<string | null> {\n try {\n // skillsDir = <packageRoot>/plugins/workos/skills\n const packageRoot = dirname(dirname(dirname(skillsDir)));\n const pkgJson = JSON.parse(await readFile(join(packageRoot, 'package.json'), 'utf8'));\n return typeof pkgJson.version === 'string' ? pkgJson.version : null;\n } catch {\n return null;\n }\n}\n\nexport interface AgentConfig {\n name: string;\n displayName: string;\n globalSkillsDir: string;\n detect: () => boolean;\n}\n\nexport function createAgents(home: string): Record<string, AgentConfig> {\n return {\n 'claude-code': {\n name: 'claude-code',\n displayName: 'Claude Code',\n globalSkillsDir: join(home, '.claude/skills'),\n detect: () => existsSync(join(home, '.claude')),\n },\n codex: {\n name: 'codex',\n displayName: 'Codex',\n globalSkillsDir: join(home, '.codex/skills'),\n detect: () => existsSync(join(home, '.codex')),\n },\n cursor: {\n name: 'cursor',\n displayName: 'Cursor',\n globalSkillsDir: join(home, '.cursor/skills'),\n detect: () => existsSync(join(home, '.cursor')),\n },\n goose: {\n name: 'goose',\n displayName: 'Goose',\n globalSkillsDir: join(home, '.config/goose/skills'),\n detect: () => existsSync(join(home, '.config/goose')),\n },\n };\n}\n\nexport interface InstallSkillOptions {\n skill?: string[];\n agent?: string[];\n}\n\nexport function getSkillsDir(): string {\n return getSkillsPackageDir();\n}\n\nexport async function discoverSkills(skillsDir: string): Promise<string[]> {\n const entries = await readdir(skillsDir, { withFileTypes: true });\n\n const dirs = entries.filter((e) => e.isDirectory());\n const checks = await Promise.all(dirs.map((e) => pathExists(join(skillsDir, e.name, 'SKILL.md'))));\n return dirs.filter((_, i) => checks[i]).map((e) => e.name);\n}\n\nexport function detectAgents(agents: Record<string, AgentConfig>, filter?: string[]): AgentConfig[] {\n const detected: AgentConfig[] = [];\n\n for (const [key, config] of Object.entries(agents)) {\n if (filter && !filter.includes(key)) continue;\n if (config.detect()) {\n detected.push(config);\n }\n }\n\n return detected;\n}\n\n/**\n * Recursively install a skill directory (SKILL.md + references/ + any other\n * files) with prune-replace semantics. Uses a sibling temp dir + backup-rename\n * pattern so the operation is effectively atomic per skill: the target either\n * matches the source exactly, or (on rollback) is restored to its prior state.\n *\n * Returns `{ success, error }` rather than throwing — callers (autoInstallSkills,\n * runInstallSkill) accumulate failures across the (skill × agent) matrix.\n */\nexport async function installSkill(\n skillsDir: string,\n skillName: string,\n agent: AgentConfig,\n): Promise<{ success: boolean; error?: string }> {\n const sourceDir = join(skillsDir, skillName);\n const targetDir = join(agent.globalSkillsDir, skillName);\n const parent = dirname(targetDir);\n\n // Setup (mkdir parent, mkdtemp) is inside the try so EACCES / ENOTDIR / etc.\n // surface as `{ success: false }` rather than rejecting — runInstallSkill and\n // refreshWorkOSSkills accumulate failures across the (skill × agent) matrix\n // and would otherwise abort the whole batch on a single bad agent dir.\n let tempDir: string | undefined;\n try {\n await mkdir(parent, { recursive: true });\n // Best-effort cleanup of OLD orphans only — never current-run paths.\n await cleanupStaleOrphans(parent, skillName).catch(() => {});\n\n // mkdtemp gives us atomic creation + a random suffix that prevents\n // collisions between concurrent installers.\n tempDir = await mkdtemp(join(parent, `.workos.tmp-${skillName}-`));\n const backupDir = tempDir.replace('.workos.tmp-', '.workos.bak-');\n\n await cp(sourceDir, tempDir, { recursive: true, errorOnExist: false });\n\n const targetExisted = await pathExists(targetDir);\n if (targetExisted) {\n await rename(targetDir, backupDir);\n }\n try {\n await rename(tempDir, targetDir);\n } catch (renameErr) {\n if (targetExisted) {\n await rename(backupDir, targetDir).catch(() => {});\n }\n throw renameErr;\n }\n // Backup cleanup is best-effort: target is already in place, so failure\n // here leaves a stale backup that the next run's cleanup handles after 1h.\n if (targetExisted) {\n await rm(backupDir, { recursive: true, force: true }).catch(() => {});\n }\n return { success: true };\n } catch (error) {\n if (tempDir) {\n await rm(tempDir, { recursive: true, force: true }).catch(() => {});\n }\n return {\n success: false,\n error: error instanceof Error ? error.message : 'Unknown error',\n };\n }\n}\n\n/**\n * Remove `.workos.tmp-{skillName}-*` and `.workos.bak-{skillName}-*` siblings\n * older than ORPHAN_STALE_MS. Fresh siblings (from a concurrent install) are\n * preserved — destroying them would race the other run's final rename.\n */\nasync function cleanupStaleOrphans(parent: string, skillName: string): Promise<void> {\n if (!(await pathExists(parent))) return;\n const entries = await readdir(parent).catch(() => []);\n const cutoff = Date.now() - ORPHAN_STALE_MS;\n for (const entry of entries) {\n const isOrphan = entry.startsWith(`.workos.tmp-${skillName}-`) || entry.startsWith(`.workos.bak-${skillName}-`);\n if (!isOrphan) continue;\n const path = join(parent, entry);\n const st = await stat(path).catch(() => null);\n if (st && st.mtimeMs < cutoff) {\n await rm(path, { recursive: true, force: true }).catch(() => {});\n }\n }\n}\n\nexport async function runInstallSkill(options: InstallSkillOptions): Promise<void> {\n const home = homedir();\n const agents = createAgents(home);\n const skillsDir = getSkillsDir();\n const skills = await discoverSkills(skillsDir);\n\n const targetSkills = options.skill ? skills.filter((s) => options.skill!.includes(s)) : skills;\n\n if (targetSkills.length === 0) {\n console.error(chalk.red('No matching skills found.'));\n console.log('Available skills:', skills.join(', '));\n process.exit(1);\n }\n\n const targetAgents = detectAgents(agents, options.agent);\n\n if (targetAgents.length === 0) {\n if (options.agent) {\n console.error(chalk.red('Specified agents not found.'));\n } else {\n console.error(chalk.red('No coding agents detected.'));\n }\n console.log('Supported agents:', Object.keys(agents).join(', '));\n process.exit(1);\n }\n\n console.log(chalk.bold('\\nInstalling skills...\\n'));\n\n const results: Array<{\n skill: string;\n agent: AgentConfig;\n success: boolean;\n error?: string;\n }> = [];\n\n for (const skill of targetSkills) {\n for (const agent of targetAgents) {\n const result = await installSkill(skillsDir, skill, agent);\n results.push({ skill, agent, ...result });\n }\n }\n\n const successful = results.filter((r) => r.success);\n const failed = results.filter((r) => !r.success);\n\n if (successful.length > 0) {\n console.log(chalk.green(`✓ Installed ${successful.length} skill(s):\\n`));\n for (const r of successful) {\n console.log(` ${chalk.cyan(r.skill)} → ${chalk.dim(r.agent.displayName)}`);\n }\n }\n\n // Write per-agent version markers for any agent that had at least one\n // successful install, so `workos doctor` doesn't immediately flag the\n // freshly-installed skills as stale or missing. Same primitive as\n // refreshWorkOSSkills — single source of truth for marker semantics.\n const version = await getBundledSkillsVersion(skillsDir);\n if (version) {\n const succeededAgents = new Set<AgentConfig>();\n for (const r of successful) succeededAgents.add(r.agent);\n for (const agent of succeededAgents) {\n await writeAgentSkillMarker(agent, version);\n }\n }\n\n if (failed.length > 0) {\n console.log(chalk.red(`\\n✗ Failed to install ${failed.length}:\\n`));\n for (const r of failed) {\n console.log(` ${r.skill} → ${r.agent.displayName}: ${chalk.dim(r.error)}`);\n }\n process.exit(1);\n }\n\n console.log(chalk.green('\\nDone!'));\n}\n\nexport interface AutoInstallResult {\n skills: string[];\n agents: string[];\n version: string | null;\n}\n\nexport interface RefreshOptions {\n /** Pre-detected agents. Default: detect from $HOME. */\n agents?: AgentConfig[];\n /** Skill names to install. Default: all bundled skills. */\n skills?: string[];\n /** Whether to write the version marker after a successful per-agent install. Default: true. */\n writeMarker?: boolean;\n}\n\nexport interface RefreshResult {\n /** Agents where at least one skill installed successfully. */\n agents: AgentConfig[];\n /** Skills that were attempted (the resolved set after filtering). */\n skills: string[];\n /** Bundled skills package version, or null if it couldn't be resolved. */\n version: string | null;\n /** Marker version per agent.name BEFORE refresh (null = no marker / unreadable). */\n perAgentBefore: Record<string, string | null>;\n /** Marker version per agent.name AFTER refresh. */\n perAgentAfter: Record<string, string | null>;\n}\n\nasync function readSkillVersionMarker(agent: AgentConfig): Promise<string | null> {\n const path = join(agent.globalSkillsDir, SKILL_VERSION_MARKER_FILENAME);\n try {\n return (await readFile(path, 'utf8')).trim() || null;\n } catch {\n return null;\n }\n}\n\n/**\n * Best-effort marker write — any failure is swallowed (filesystem permission\n * errors shouldn't fail the install; doctor treats missing markers as \"unknown\").\n * Single source of truth for the .workos-skill-version write semantics.\n */\nasync function writeAgentSkillMarker(agent: AgentConfig, version: string): Promise<void> {\n try {\n await writeFile(join(agent.globalSkillsDir, SKILL_VERSION_MARKER_FILENAME), version, 'utf8');\n } catch {\n // Marker is best-effort; doctor treats missing marker as \"unknown\".\n }\n}\n\n/**\n * Reusable primitive: discover bundled skills, install each one to each agent,\n * write per-agent version markers, and report before/after marker state.\n *\n * Both `autoInstallSkills` (best-effort hook called from install/login) and\n * `doctor --fix` (Phase 3) call this — there is no duplicate copy logic.\n *\n * Returns null when nothing applied (no agents detected, no skills found, or\n * every install attempt failed).\n */\nexport async function refreshWorkOSSkills(opts: RefreshOptions = {}): Promise<RefreshResult | null> {\n const home = homedir();\n const skillsDir = getSkillsDir();\n const detected = opts.agents ?? detectAgents(createAgents(home));\n const allSkills = await discoverSkills(skillsDir).catch(() => []);\n const skills = opts.skills ? allSkills.filter((s) => opts.skills!.includes(s)) : allSkills;\n const writeMarker = opts.writeMarker ?? true;\n\n if (skills.length === 0 || detected.length === 0) return null;\n\n const version = await getBundledSkillsVersion(skillsDir);\n const perAgentBefore: Record<string, string | null> = {};\n const perAgentAfter: Record<string, string | null> = {};\n const succeededAgents: AgentConfig[] = [];\n // Union of skills that succeeded for at least one agent. Returning the full\n // attempted list would inflate \"Installed N skills\" copy when some skills\n // failed to copy; only count what actually landed somewhere.\n const installedSkills = new Set<string>();\n\n for (const agent of detected) {\n perAgentBefore[agent.name] = await readSkillVersionMarker(agent);\n\n let agentSucceeded = false;\n for (const skill of skills) {\n const result = await installSkill(skillsDir, skill, agent);\n if (result.success) {\n agentSucceeded = true;\n installedSkills.add(skill);\n }\n }\n\n if (agentSucceeded) {\n succeededAgents.push(agent);\n if (writeMarker && version) {\n await writeAgentSkillMarker(agent, version);\n }\n }\n\n perAgentAfter[agent.name] = await readSkillVersionMarker(agent);\n }\n\n if (succeededAgents.length === 0) return null;\n\n return {\n agents: succeededAgents,\n skills: skills.filter((s) => installedSkills.has(s)),\n version,\n perAgentBefore,\n perAgentAfter,\n };\n}\n\n/**\n * Install all bundled skills to all detected coding agents.\n * Returns a summary when anything was installed, or null when nothing applied.\n * Performs minimal IO: writes a version marker file alongside installed\n * skills so `workos doctor` can detect staleness later. Errors are swallowed\n * so skill install never disrupts the calling flow.\n *\n * Thin back-compat wrapper around `refreshWorkOSSkills` — the install/auth-login\n * call sites use this; doctor `--fix` (Phase 3) calls `refreshWorkOSSkills`\n * directly to surface the per-agent before/after marker state.\n */\nexport async function autoInstallSkills(): Promise<AutoInstallResult | null> {\n try {\n const result = await refreshWorkOSSkills();\n if (!result) return null;\n return {\n skills: result.skills,\n agents: result.agents.map((a) => a.displayName),\n version: result.version,\n };\n } catch {\n return null;\n }\n}\n"]}