workos 0.15.1 → 0.16.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 (138) hide show
  1. package/README.md +37 -11
  2. package/dist/bin.js +1441 -1258
  3. package/dist/bin.js.map +1 -1
  4. package/dist/cli.config.d.ts +1 -0
  5. package/dist/cli.config.js +1 -0
  6. package/dist/cli.config.js.map +1 -1
  7. package/dist/commands/api/index.js +7 -2
  8. package/dist/commands/api/index.js.map +1 -1
  9. package/dist/commands/api/interactive.js +9 -3
  10. package/dist/commands/api/interactive.js.map +1 -1
  11. package/dist/commands/claim.js +1 -1
  12. package/dist/commands/claim.js.map +1 -1
  13. package/dist/commands/debug.d.ts +2 -1
  14. package/dist/commands/debug.js +43 -3
  15. package/dist/commands/debug.js.map +1 -1
  16. package/dist/commands/dev.js +10 -4
  17. package/dist/commands/dev.js.map +1 -1
  18. package/dist/commands/doctor.js +13 -4
  19. package/dist/commands/doctor.js.map +1 -1
  20. package/dist/commands/emulate.js +6 -2
  21. package/dist/commands/emulate.js.map +1 -1
  22. package/dist/commands/env.js +5 -4
  23. package/dist/commands/env.js.map +1 -1
  24. package/dist/commands/install-skill.js +11 -8
  25. package/dist/commands/install-skill.js.map +1 -1
  26. package/dist/commands/install.js +2 -2
  27. package/dist/commands/install.js.map +1 -1
  28. package/dist/commands/login.js +4 -4
  29. package/dist/commands/login.js.map +1 -1
  30. package/dist/commands/migrations.d.ts +1 -1
  31. package/dist/commands/migrations.js +4 -1
  32. package/dist/commands/migrations.js.map +1 -1
  33. package/dist/commands/telemetry.d.ts +3 -0
  34. package/dist/commands/telemetry.js +88 -0
  35. package/dist/commands/telemetry.js.map +1 -0
  36. package/dist/commands/uninstall-skill.js +3 -2
  37. package/dist/commands/uninstall-skill.js.map +1 -1
  38. package/dist/commands/vault-run.d.ts +13 -0
  39. package/dist/commands/vault-run.js +194 -0
  40. package/dist/commands/vault-run.js.map +1 -0
  41. package/dist/commands/vault.d.ts +3 -2
  42. package/dist/commands/vault.js +41 -8
  43. package/dist/commands/vault.js.map +1 -1
  44. package/dist/doctor/checks/auth-patterns.js +2 -2
  45. package/dist/doctor/checks/auth-patterns.js.map +1 -1
  46. package/dist/doctor/checks/environment.js +1 -1
  47. package/dist/doctor/checks/environment.js.map +1 -1
  48. package/dist/lib/api-error-handler.d.ts +15 -3
  49. package/dist/lib/api-error-handler.js +52 -34
  50. package/dist/lib/api-error-handler.js.map +1 -1
  51. package/dist/lib/command-aliases.d.ts +8 -0
  52. package/dist/lib/command-aliases.js +12 -0
  53. package/dist/lib/command-aliases.js.map +1 -0
  54. package/dist/lib/constants.d.ts +0 -1
  55. package/dist/lib/constants.js +0 -1
  56. package/dist/lib/constants.js.map +1 -1
  57. package/dist/lib/credential-discovery.js +1 -1
  58. package/dist/lib/credential-discovery.js.map +1 -1
  59. package/dist/lib/dev-command.js +8 -1
  60. package/dist/lib/dev-command.js.map +1 -1
  61. package/dist/lib/device-id.d.ts +25 -0
  62. package/dist/lib/device-id.js +102 -0
  63. package/dist/lib/device-id.js.map +1 -0
  64. package/dist/lib/preferences.d.ts +101 -0
  65. package/dist/lib/preferences.js +198 -0
  66. package/dist/lib/preferences.js.map +1 -0
  67. package/dist/lib/registry.js +2 -2
  68. package/dist/lib/registry.js.map +1 -1
  69. package/dist/lib/run-with-core.js +17 -17
  70. package/dist/lib/run-with-core.js.map +1 -1
  71. package/dist/lib/settings.d.ts +6 -0
  72. package/dist/lib/settings.js +7 -0
  73. package/dist/lib/settings.js.map +1 -1
  74. package/dist/lib/telemetry-notice.d.ts +25 -0
  75. package/dist/lib/telemetry-notice.js +56 -0
  76. package/dist/lib/telemetry-notice.js.map +1 -0
  77. package/dist/lib/validation/build-validator.js +6 -1
  78. package/dist/lib/validation/build-validator.js.map +1 -1
  79. package/dist/lib/validation/quick-checks.js +2 -0
  80. package/dist/lib/validation/quick-checks.js.map +1 -1
  81. package/dist/lib/validation/validator.js +1 -1
  82. package/dist/lib/validation/validator.js.map +1 -1
  83. package/dist/steps/run-prettier.js +9 -13
  84. package/dist/steps/run-prettier.js.map +1 -1
  85. package/dist/steps/upload-environment-variables/providers/vercel.js +6 -3
  86. package/dist/steps/upload-environment-variables/providers/vercel.js.map +1 -1
  87. package/dist/test/force-insecure-storage.d.ts +1 -0
  88. package/dist/test/force-insecure-storage.js +9 -0
  89. package/dist/test/force-insecure-storage.js.map +1 -0
  90. package/dist/test/setup.d.ts +1 -0
  91. package/dist/test/setup.js +38 -0
  92. package/dist/test/setup.js.map +1 -0
  93. package/dist/utils/analytics.d.ts +41 -0
  94. package/dist/utils/analytics.js +199 -12
  95. package/dist/utils/analytics.js.map +1 -1
  96. package/dist/utils/box.d.ts +29 -1
  97. package/dist/utils/box.js +92 -4
  98. package/dist/utils/box.js.map +1 -1
  99. package/dist/utils/clack-utils.js +22 -4
  100. package/dist/utils/clack-utils.js.map +1 -1
  101. package/dist/utils/cli-exit.d.ts +15 -0
  102. package/dist/utils/cli-exit.js +11 -0
  103. package/dist/utils/cli-exit.js.map +1 -0
  104. package/dist/utils/cli-symbols.d.ts +1 -1
  105. package/dist/utils/command-telemetry.d.ts +17 -0
  106. package/dist/utils/command-telemetry.js +67 -0
  107. package/dist/utils/command-telemetry.js.map +1 -0
  108. package/dist/utils/crash-reporter.d.ts +13 -0
  109. package/dist/utils/crash-reporter.js +91 -0
  110. package/dist/utils/crash-reporter.js.map +1 -0
  111. package/dist/utils/debug.d.ts +1 -0
  112. package/dist/utils/debug.js +4 -1
  113. package/dist/utils/debug.js.map +1 -1
  114. package/dist/utils/env-parser.js +1 -1
  115. package/dist/utils/env-parser.js.map +1 -1
  116. package/dist/utils/exec-file.js +2 -1
  117. package/dist/utils/exec-file.js.map +1 -1
  118. package/dist/utils/exit-codes.d.ts +5 -0
  119. package/dist/utils/exit-codes.js +30 -1
  120. package/dist/utils/exit-codes.js.map +1 -1
  121. package/dist/utils/help-json.d.ts +6 -0
  122. package/dist/utils/help-json.js +87 -10
  123. package/dist/utils/help-json.js.map +1 -1
  124. package/dist/utils/output.d.ts +7 -2
  125. package/dist/utils/output.js +9 -2
  126. package/dist/utils/output.js.map +1 -1
  127. package/dist/utils/platform.d.ts +8 -0
  128. package/dist/utils/platform.js +7 -0
  129. package/dist/utils/platform.js.map +1 -0
  130. package/dist/utils/telemetry-client.d.ts +30 -2
  131. package/dist/utils/telemetry-client.js +122 -12
  132. package/dist/utils/telemetry-client.js.map +1 -1
  133. package/dist/utils/telemetry-store-forward.d.ts +11 -0
  134. package/dist/utils/telemetry-store-forward.js +94 -0
  135. package/dist/utils/telemetry-store-forward.js.map +1 -0
  136. package/dist/utils/telemetry-types.d.ts +58 -9
  137. package/dist/utils/telemetry-types.js.map +1 -1
  138. package/package.json +3 -3
@@ -26,7 +26,6 @@ export declare const WORKOS_DASHBOARD_URL: string;
26
26
  export declare const ISSUES_URL: string;
27
27
  export declare const ANALYTICS_ENABLED: boolean;
28
28
  export declare const INSTALLER_INTERACTION_EVENT_NAME: string;
29
- export declare const WORKOS_TELEMETRY_ENABLED: boolean;
30
29
  export declare const OAUTH_PORT: number;
31
30
  /**
32
31
  * Common glob patterns to ignore when searching for files.
@@ -18,7 +18,6 @@ export const WORKOS_DASHBOARD_URL = settings.documentation.dashboardUrl;
18
18
  export const ISSUES_URL = settings.documentation.issuesUrl;
19
19
  export const ANALYTICS_ENABLED = settings.telemetry.enabled;
20
20
  export const INSTALLER_INTERACTION_EVENT_NAME = settings.telemetry.eventName;
21
- export const WORKOS_TELEMETRY_ENABLED = process.env.WORKOS_TELEMETRY !== 'false';
22
21
  export const OAUTH_PORT = settings.legacy.oauthPort;
23
22
  /**
24
23
  * Common glob patterns to ignore when searching for files.
@@ -1 +1 @@
1
- {"version":3,"file":"constants.js","sourceRoot":"","sources":["../../src/lib/constants.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAS1C;;;GAGG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG;IAChC,MAAM,EAAE,QAAQ;IAChB,KAAK,EAAE,OAAO;IACd,aAAa,EAAE,gBAAgB;IAC/B,WAAW,EAAE,cAAc;IAC3B,SAAS,EAAE,YAAY;CACf,CAAC;AAOX,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC;AAEnF,MAAM,QAAQ,GAAG,SAAS,EAAE,CAAC;AAE7B,MAAM,CAAC,MAAM,KAAK,GAAG,QAAQ,CAAC,OAAO,CAAC,SAAS,CAAC;AAChD,MAAM,CAAC,MAAM,eAAe,GAAG,QAAQ,CAAC,aAAa,CAAC,aAAa,CAAC;AACpE,MAAM,CAAC,MAAM,oBAAoB,GAAG,QAAQ,CAAC,aAAa,CAAC,YAAY,CAAC;AACxE,MAAM,CAAC,MAAM,UAAU,GAAG,QAAQ,CAAC,aAAa,CAAC,SAAS,CAAC;AAC3D,MAAM,CAAC,MAAM,iBAAiB,GAAG,QAAQ,CAAC,SAAS,CAAC,OAAO,CAAC;AAC5D,MAAM,CAAC,MAAM,gCAAgC,GAAG,QAAQ,CAAC,SAAS,CAAC,SAAS,CAAC;AAC7E,MAAM,CAAC,MAAM,wBAAwB,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB,KAAK,OAAO,CAAC;AACjF,MAAM,CAAC,MAAM,UAAU,GAAG,QAAQ,CAAC,MAAM,CAAC,SAAS,CAAC;AAEpD;;;GAGG;AACH,MAAM,CAAC,MAAM,eAAe,GAAa;IACvC,oBAAoB;IACpB,YAAY;IACZ,aAAa;IACb,cAAc;IACd,aAAa;CACd,CAAC","sourcesContent":["import { getConfig } from './settings.js';\n\n/**\n * Integration identifier type.\n * No longer an enum — each integration self-registers via the auto-discovery registry.\n * The string value matches the integration directory name (e.g., 'nextjs', 'react-router').\n */\nexport type Integration = string;\n\n/**\n * Well-known integration names for backwards compatibility.\n * New integrations do NOT need to be added here — they're auto-discovered.\n */\nexport const KNOWN_INTEGRATIONS = {\n nextjs: 'nextjs',\n react: 'react',\n tanstackStart: 'tanstack-start',\n reactRouter: 'react-router',\n vanillaJs: 'vanilla-js',\n} as const;\n\nexport interface Args {\n debug: boolean;\n integration: Integration;\n}\n\nexport const IS_DEV = ['test', 'development'].includes(process.env.NODE_ENV ?? '');\n\nconst settings = getConfig();\n\nexport const DEBUG = settings.logging.debugMode;\nexport const WORKOS_DOCS_URL = settings.documentation.workosDocsUrl;\nexport const WORKOS_DASHBOARD_URL = settings.documentation.dashboardUrl;\nexport const ISSUES_URL = settings.documentation.issuesUrl;\nexport const ANALYTICS_ENABLED = settings.telemetry.enabled;\nexport const INSTALLER_INTERACTION_EVENT_NAME = settings.telemetry.eventName;\nexport const WORKOS_TELEMETRY_ENABLED = process.env.WORKOS_TELEMETRY !== 'false';\nexport const OAUTH_PORT = settings.legacy.oauthPort;\n\n/**\n * Common glob patterns to ignore when searching for files.\n * Used by multiple integrations.\n */\nexport const IGNORE_PATTERNS: string[] = [\n '**/node_modules/**',\n '**/dist/**',\n '**/build/**',\n '**/public/**',\n '**/.next/**',\n];\n"]}
1
+ {"version":3,"file":"constants.js","sourceRoot":"","sources":["../../src/lib/constants.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAS1C;;;GAGG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG;IAChC,MAAM,EAAE,QAAQ;IAChB,KAAK,EAAE,OAAO;IACd,aAAa,EAAE,gBAAgB;IAC/B,WAAW,EAAE,cAAc;IAC3B,SAAS,EAAE,YAAY;CACf,CAAC;AAOX,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC;AAEnF,MAAM,QAAQ,GAAG,SAAS,EAAE,CAAC;AAE7B,MAAM,CAAC,MAAM,KAAK,GAAG,QAAQ,CAAC,OAAO,CAAC,SAAS,CAAC;AAChD,MAAM,CAAC,MAAM,eAAe,GAAG,QAAQ,CAAC,aAAa,CAAC,aAAa,CAAC;AACpE,MAAM,CAAC,MAAM,oBAAoB,GAAG,QAAQ,CAAC,aAAa,CAAC,YAAY,CAAC;AACxE,MAAM,CAAC,MAAM,UAAU,GAAG,QAAQ,CAAC,aAAa,CAAC,SAAS,CAAC;AAC3D,MAAM,CAAC,MAAM,iBAAiB,GAAG,QAAQ,CAAC,SAAS,CAAC,OAAO,CAAC;AAC5D,MAAM,CAAC,MAAM,gCAAgC,GAAG,QAAQ,CAAC,SAAS,CAAC,SAAS,CAAC;AAC7E,MAAM,CAAC,MAAM,UAAU,GAAG,QAAQ,CAAC,MAAM,CAAC,SAAS,CAAC;AAEpD;;;GAGG;AACH,MAAM,CAAC,MAAM,eAAe,GAAa;IACvC,oBAAoB;IACpB,YAAY;IACZ,aAAa;IACb,cAAc;IACd,aAAa;CACd,CAAC","sourcesContent":["import { getConfig } from './settings.js';\n\n/**\n * Integration identifier type.\n * No longer an enum — each integration self-registers via the auto-discovery registry.\n * The string value matches the integration directory name (e.g., 'nextjs', 'react-router').\n */\nexport type Integration = string;\n\n/**\n * Well-known integration names for backwards compatibility.\n * New integrations do NOT need to be added here — they're auto-discovered.\n */\nexport const KNOWN_INTEGRATIONS = {\n nextjs: 'nextjs',\n react: 'react',\n tanstackStart: 'tanstack-start',\n reactRouter: 'react-router',\n vanillaJs: 'vanilla-js',\n} as const;\n\nexport interface Args {\n debug: boolean;\n integration: Integration;\n}\n\nexport const IS_DEV = ['test', 'development'].includes(process.env.NODE_ENV ?? '');\n\nconst settings = getConfig();\n\nexport const DEBUG = settings.logging.debugMode;\nexport const WORKOS_DOCS_URL = settings.documentation.workosDocsUrl;\nexport const WORKOS_DASHBOARD_URL = settings.documentation.dashboardUrl;\nexport const ISSUES_URL = settings.documentation.issuesUrl;\nexport const ANALYTICS_ENABLED = settings.telemetry.enabled;\nexport const INSTALLER_INTERACTION_EVENT_NAME = settings.telemetry.eventName;\nexport const OAUTH_PORT = settings.legacy.oauthPort;\n\n/**\n * Common glob patterns to ignore when searching for files.\n * Used by multiple integrations.\n */\nexport const IGNORE_PATTERNS: string[] = [\n '**/node_modules/**',\n '**/dist/**',\n '**/build/**',\n '**/public/**',\n '**/.next/**',\n];\n"]}
@@ -35,7 +35,7 @@ export async function checkForEnvFiles(projectDir) {
35
35
  export async function scanEnvFile(filePath) {
36
36
  const content = await fs.readFile(filePath, 'utf-8');
37
37
  // Filter out commented lines before matching
38
- const lines = content.split('\n');
38
+ const lines = content.split(/\r?\n/);
39
39
  const uncommentedContent = lines.filter((line) => !line.trim().startsWith('#')).join('\n');
40
40
  const clientIdMatch = uncommentedContent.match(WORKOS_CLIENT_ID_PATTERN);
41
41
  const apiKeyMatch = uncommentedContent.match(WORKOS_API_KEY_PATTERN);
@@ -1 +1 @@
1
- {"version":3,"file":"credential-discovery.js","sourceRoot":"","sources":["../../src/lib/credential-discovery.ts"],"names":[],"mappings":"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","sourcesContent":["import { promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport { logInfo } from '../utils/debug.js';\n\nexport interface DiscoveryResult {\n found: boolean;\n source?: 'env' | 'declined';\n clientId?: string;\n apiKey?: string;\n sourcePath?: string;\n}\n\nexport interface EnvFileInfo {\n exists: boolean;\n files: string[];\n}\n\nconst ENV_FILE_NAMES = ['.env.local', '.env.development.local', '.env.development', '.env'];\n\n// Only extract WorkOS variables - ignore everything else\nconst WORKOS_CLIENT_ID_PATTERN = /^WORKOS_CLIENT_ID=[\"']?([^\"'\\s#]+)[\"']?/m;\nconst WORKOS_API_KEY_PATTERN = /^WORKOS_API_KEY=[\"']?([^\"'\\s#]+)[\"']?/m;\n\n/**\n * Check if env files exist in the project directory (without reading contents).\n * Returns which files were found so the UI can prompt for consent.\n */\nexport async function checkForEnvFiles(projectDir: string): Promise<EnvFileInfo> {\n logInfo('[credential-discovery] Checking for env files in:', projectDir);\n const foundFiles: string[] = [];\n\n for (const fileName of ENV_FILE_NAMES) {\n const filePath = path.join(projectDir, fileName);\n try {\n await fs.access(filePath, fs.constants.R_OK);\n foundFiles.push(fileName);\n } catch {\n // continue\n }\n }\n\n logInfo('[credential-discovery] Found env files:', foundFiles);\n return {\n exists: foundFiles.length > 0,\n files: foundFiles,\n };\n}\n\n/**\n * Scan a single env file for WorkOS credentials.\n * Only extracts WORKOS_CLIENT_ID and WORKOS_API_KEY - ignores all other variables.\n */\nexport async function scanEnvFile(filePath: string): Promise<{ clientId?: string; apiKey?: string }> {\n const content = await fs.readFile(filePath, 'utf-8');\n\n // Filter out commented lines before matching\n const lines = content.split('\\n');\n const uncommentedContent = lines.filter((line) => !line.trim().startsWith('#')).join('\\n');\n\n const clientIdMatch = uncommentedContent.match(WORKOS_CLIENT_ID_PATTERN);\n const apiKeyMatch = uncommentedContent.match(WORKOS_API_KEY_PATTERN);\n\n return {\n clientId: clientIdMatch?.[1],\n apiKey: apiKeyMatch?.[1],\n };\n}\n\n/**\n * Validate client ID format.\n * WorkOS client IDs start with 'client_' prefix.\n */\nexport function isValidClientId(value: string): boolean {\n return value.startsWith('client_') && value.length > 10;\n}\n\n/**\n * Validate API key format.\n * WorkOS secret keys start with 'sk_' prefix.\n */\nexport function isValidApiKey(value: string): boolean {\n return value.startsWith('sk_') && value.length > 10;\n}\n\n/**\n * Discover WorkOS credentials from project env files.\n * Must be called AFTER user consent is given.\n *\n * Scans files in priority order: .env.local > .env.development.local > .env.development > .env\n * Returns first complete match (both clientId and apiKey preferred, but clientId-only is valid).\n */\nexport async function discoverCredentials(projectDir: string): Promise<DiscoveryResult> {\n logInfo('[credential-discovery] Scanning env files for WorkOS credentials');\n for (const fileName of ENV_FILE_NAMES) {\n const filePath = path.join(projectDir, fileName);\n\n try {\n const result = await scanEnvFile(filePath);\n\n const clientIdValid = result.clientId && isValidClientId(result.clientId);\n const apiKeyValid = result.apiKey && isValidApiKey(result.apiKey);\n\n if (result.clientId && !clientIdValid) {\n logInfo('[credential-discovery] Invalid clientId format in', fileName);\n }\n if (result.apiKey && !apiKeyValid) {\n logInfo('[credential-discovery] Invalid apiKey format in', fileName);\n }\n\n if (clientIdValid) {\n logInfo('[credential-discovery] Found valid credentials in', fileName);\n return {\n found: true,\n source: 'env',\n clientId: result.clientId,\n apiKey: apiKeyValid ? result.apiKey : undefined,\n sourcePath: fileName,\n };\n }\n } catch {\n // continue\n }\n }\n\n logInfo('[credential-discovery] No valid credentials found');\n return { found: false };\n}\n"]}
1
+ {"version":3,"file":"credential-discovery.js","sourceRoot":"","sources":["../../src/lib/credential-discovery.ts"],"names":[],"mappings":"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","sourcesContent":["import { promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport { logInfo } from '../utils/debug.js';\n\nexport interface DiscoveryResult {\n found: boolean;\n source?: 'env' | 'declined';\n clientId?: string;\n apiKey?: string;\n sourcePath?: string;\n}\n\nexport interface EnvFileInfo {\n exists: boolean;\n files: string[];\n}\n\nconst ENV_FILE_NAMES = ['.env.local', '.env.development.local', '.env.development', '.env'];\n\n// Only extract WorkOS variables - ignore everything else\nconst WORKOS_CLIENT_ID_PATTERN = /^WORKOS_CLIENT_ID=[\"']?([^\"'\\s#]+)[\"']?/m;\nconst WORKOS_API_KEY_PATTERN = /^WORKOS_API_KEY=[\"']?([^\"'\\s#]+)[\"']?/m;\n\n/**\n * Check if env files exist in the project directory (without reading contents).\n * Returns which files were found so the UI can prompt for consent.\n */\nexport async function checkForEnvFiles(projectDir: string): Promise<EnvFileInfo> {\n logInfo('[credential-discovery] Checking for env files in:', projectDir);\n const foundFiles: string[] = [];\n\n for (const fileName of ENV_FILE_NAMES) {\n const filePath = path.join(projectDir, fileName);\n try {\n await fs.access(filePath, fs.constants.R_OK);\n foundFiles.push(fileName);\n } catch {\n // continue\n }\n }\n\n logInfo('[credential-discovery] Found env files:', foundFiles);\n return {\n exists: foundFiles.length > 0,\n files: foundFiles,\n };\n}\n\n/**\n * Scan a single env file for WorkOS credentials.\n * Only extracts WORKOS_CLIENT_ID and WORKOS_API_KEY - ignores all other variables.\n */\nexport async function scanEnvFile(filePath: string): Promise<{ clientId?: string; apiKey?: string }> {\n const content = await fs.readFile(filePath, 'utf-8');\n\n // Filter out commented lines before matching\n const lines = content.split(/\\r?\\n/);\n const uncommentedContent = lines.filter((line) => !line.trim().startsWith('#')).join('\\n');\n\n const clientIdMatch = uncommentedContent.match(WORKOS_CLIENT_ID_PATTERN);\n const apiKeyMatch = uncommentedContent.match(WORKOS_API_KEY_PATTERN);\n\n return {\n clientId: clientIdMatch?.[1],\n apiKey: apiKeyMatch?.[1],\n };\n}\n\n/**\n * Validate client ID format.\n * WorkOS client IDs start with 'client_' prefix.\n */\nexport function isValidClientId(value: string): boolean {\n return value.startsWith('client_') && value.length > 10;\n}\n\n/**\n * Validate API key format.\n * WorkOS secret keys start with 'sk_' prefix.\n */\nexport function isValidApiKey(value: string): boolean {\n return value.startsWith('sk_') && value.length > 10;\n}\n\n/**\n * Discover WorkOS credentials from project env files.\n * Must be called AFTER user consent is given.\n *\n * Scans files in priority order: .env.local > .env.development.local > .env.development > .env\n * Returns first complete match (both clientId and apiKey preferred, but clientId-only is valid).\n */\nexport async function discoverCredentials(projectDir: string): Promise<DiscoveryResult> {\n logInfo('[credential-discovery] Scanning env files for WorkOS credentials');\n for (const fileName of ENV_FILE_NAMES) {\n const filePath = path.join(projectDir, fileName);\n\n try {\n const result = await scanEnvFile(filePath);\n\n const clientIdValid = result.clientId && isValidClientId(result.clientId);\n const apiKeyValid = result.apiKey && isValidApiKey(result.apiKey);\n\n if (result.clientId && !clientIdValid) {\n logInfo('[credential-discovery] Invalid clientId format in', fileName);\n }\n if (result.apiKey && !apiKeyValid) {\n logInfo('[credential-discovery] Invalid apiKey format in', fileName);\n }\n\n if (clientIdValid) {\n logInfo('[credential-discovery] Found valid credentials in', fileName);\n return {\n found: true,\n source: 'env',\n clientId: result.clientId,\n apiKey: apiKeyValid ? result.apiKey : undefined,\n sourcePath: fileName,\n };\n }\n } catch {\n // continue\n }\n }\n\n logInfo('[credential-discovery] No valid credentials found');\n return { found: false };\n}\n"]}
@@ -1,5 +1,6 @@
1
1
  import { readFileSync, existsSync } from 'node:fs';
2
2
  import { resolve, join } from 'node:path';
3
+ import { IS_WINDOWS } from '../utils/platform.js';
3
4
  /**
4
5
  * Framework-to-dev-command mapping. Checked in order after package.json detection.
5
6
  * Each entry maps a dependency name to a framework display name and default dev command.
@@ -42,7 +43,13 @@ function hasDependency(pkg, dep) {
42
43
  * otherwise returns the bare command name (assumes it's globally available).
43
44
  */
44
45
  function resolveNodeBin(projectDir, command) {
45
- const binPath = join(projectDir, 'node_modules', '.bin', command);
46
+ const binDir = join(projectDir, 'node_modules', '.bin');
47
+ if (IS_WINDOWS) {
48
+ const cmdPath = join(binDir, `${command}.cmd`);
49
+ if (existsSync(cmdPath))
50
+ return cmdPath;
51
+ }
52
+ const binPath = join(binDir, command);
46
53
  if (existsSync(binPath))
47
54
  return binPath;
48
55
  return command;
@@ -1 +1 @@
1
- {"version":3,"file":"dev-command.js","sourceRoot":"","sources":["../../src/lib/dev-command.ts"],"names":[],"mappings":"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","sourcesContent":["import { readFileSync, existsSync } from 'node:fs';\nimport { resolve, join } from 'node:path';\n\nexport interface DevCommandResult {\n command: string;\n args: string[];\n framework: string | null;\n}\n\ninterface PackageJson {\n scripts?: Record<string, string>;\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n}\n\n/**\n * Framework-to-dev-command mapping. Checked in order after package.json detection.\n * Each entry maps a dependency name to a framework display name and default dev command.\n */\nconst FRAMEWORK_DEV_COMMANDS: Array<{\n dep: string;\n framework: string;\n command: string;\n args: string[];\n}> = [\n { dep: 'next', framework: 'Next.js', command: 'next', args: ['dev'] },\n { dep: '@remix-run/dev', framework: 'Remix', command: 'remix', args: ['dev'] },\n { dep: 'react-router', framework: 'React Router', command: 'react-router', args: ['dev'] },\n { dep: '@tanstack/react-start', framework: 'TanStack Start', command: 'vinxi', args: ['dev'] },\n { dep: '@sveltejs/kit', framework: 'SvelteKit', command: 'vite', args: ['dev'] },\n { dep: 'vite', framework: 'Vite', command: 'vite', args: ['dev'] },\n { dep: 'nuxt', framework: 'Nuxt', command: 'nuxt', args: ['dev'] },\n { dep: 'express', framework: 'Express', command: 'node', args: ['index.js'] },\n];\n\n/**\n * Non-JS framework detection: checks for well-known files in the project directory.\n */\nconst NON_JS_FRAMEWORKS: Array<{\n file: string;\n framework: string;\n command: string;\n args: string[];\n}> = [\n { file: 'manage.py', framework: 'Django', command: 'python', args: ['manage.py', 'runserver'] },\n { file: 'Gemfile', framework: 'Rails', command: 'rails', args: ['server'] },\n { file: 'go.mod', framework: 'Go', command: 'go', args: ['run', '.'] },\n];\n\nfunction readPackageJson(projectDir: string): PackageJson | null {\n const pkgPath = resolve(projectDir, 'package.json');\n if (!existsSync(pkgPath)) return null;\n try {\n return JSON.parse(readFileSync(pkgPath, 'utf-8')) as PackageJson;\n } catch {\n return null;\n }\n}\n\nfunction hasDependency(pkg: PackageJson, dep: string): boolean {\n return !!(pkg.dependencies?.[dep] || pkg.devDependencies?.[dep]);\n}\n\n/**\n * Resolve the npx-style command for a given binary.\n * Returns the binary path under node_modules/.bin if it exists,\n * otherwise returns the bare command name (assumes it's globally available).\n */\nfunction resolveNodeBin(projectDir: string, command: string): string {\n const binPath = join(projectDir, 'node_modules', '.bin', command);\n if (existsSync(binPath)) return binPath;\n return command;\n}\n\n/**\n * Resolve the dev command for a project directory.\n *\n * Priority:\n * 1. `scripts.dev` from package.json (developer's config is authoritative)\n * 2. Framework-specific default based on dependency detection\n * 3. Non-JS framework detection (Django, Rails, Go)\n * 4. Error — no dev command could be resolved\n */\nexport async function resolveDevCommand(projectDir: string): Promise<DevCommandResult> {\n const pkg = readPackageJson(projectDir);\n\n if (pkg) {\n // Detect framework from dependencies first (for display purposes)\n let detectedFramework: string | null = null;\n for (const entry of FRAMEWORK_DEV_COMMANDS) {\n if (hasDependency(pkg, entry.dep)) {\n detectedFramework = entry.framework;\n break;\n }\n }\n\n // Priority 1: scripts.dev from package.json\n if (pkg.scripts?.dev) {\n // Use the package manager's run command to execute scripts.dev\n const packageManager = detectPackageManager(projectDir);\n return {\n command: packageManager,\n args: ['run', 'dev'],\n framework: detectedFramework,\n };\n }\n\n // Priority 2: Framework-specific default\n for (const entry of FRAMEWORK_DEV_COMMANDS) {\n if (hasDependency(pkg, entry.dep)) {\n return {\n command: resolveNodeBin(projectDir, entry.command),\n args: entry.args,\n framework: entry.framework,\n };\n }\n }\n }\n\n // Priority 3: Non-JS frameworks\n for (const entry of NON_JS_FRAMEWORKS) {\n if (existsSync(resolve(projectDir, entry.file))) {\n return {\n command: entry.command,\n args: entry.args,\n framework: entry.framework,\n };\n }\n }\n\n // No framework or scripts.dev found\n return {\n command: 'npm',\n args: ['run', 'dev'],\n framework: null,\n };\n}\n\n/**\n * Detect the package manager used in the project.\n */\nfunction detectPackageManager(projectDir: string): string {\n if (existsSync(resolve(projectDir, 'pnpm-lock.yaml'))) return 'pnpm';\n if (existsSync(resolve(projectDir, 'yarn.lock'))) return 'yarn';\n if (existsSync(resolve(projectDir, 'bun.lockb')) || existsSync(resolve(projectDir, 'bun.lock'))) return 'bun';\n return 'npm';\n}\n\n// Export for testing\nexport { readPackageJson as _readPackageJson, detectPackageManager as _detectPackageManager };\n"]}
1
+ {"version":3,"file":"dev-command.js","sourceRoot":"","sources":["../../src/lib/dev-command.ts"],"names":[],"mappings":"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","sourcesContent":["import { readFileSync, existsSync } from 'node:fs';\nimport { resolve, join } from 'node:path';\nimport { IS_WINDOWS } from '../utils/platform.js';\n\nexport interface DevCommandResult {\n command: string;\n args: string[];\n framework: string | null;\n}\n\ninterface PackageJson {\n scripts?: Record<string, string>;\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n}\n\n/**\n * Framework-to-dev-command mapping. Checked in order after package.json detection.\n * Each entry maps a dependency name to a framework display name and default dev command.\n */\nconst FRAMEWORK_DEV_COMMANDS: Array<{\n dep: string;\n framework: string;\n command: string;\n args: string[];\n}> = [\n { dep: 'next', framework: 'Next.js', command: 'next', args: ['dev'] },\n { dep: '@remix-run/dev', framework: 'Remix', command: 'remix', args: ['dev'] },\n { dep: 'react-router', framework: 'React Router', command: 'react-router', args: ['dev'] },\n { dep: '@tanstack/react-start', framework: 'TanStack Start', command: 'vinxi', args: ['dev'] },\n { dep: '@sveltejs/kit', framework: 'SvelteKit', command: 'vite', args: ['dev'] },\n { dep: 'vite', framework: 'Vite', command: 'vite', args: ['dev'] },\n { dep: 'nuxt', framework: 'Nuxt', command: 'nuxt', args: ['dev'] },\n { dep: 'express', framework: 'Express', command: 'node', args: ['index.js'] },\n];\n\n/**\n * Non-JS framework detection: checks for well-known files in the project directory.\n */\nconst NON_JS_FRAMEWORKS: Array<{\n file: string;\n framework: string;\n command: string;\n args: string[];\n}> = [\n { file: 'manage.py', framework: 'Django', command: 'python', args: ['manage.py', 'runserver'] },\n { file: 'Gemfile', framework: 'Rails', command: 'rails', args: ['server'] },\n { file: 'go.mod', framework: 'Go', command: 'go', args: ['run', '.'] },\n];\n\nfunction readPackageJson(projectDir: string): PackageJson | null {\n const pkgPath = resolve(projectDir, 'package.json');\n if (!existsSync(pkgPath)) return null;\n try {\n return JSON.parse(readFileSync(pkgPath, 'utf-8')) as PackageJson;\n } catch {\n return null;\n }\n}\n\nfunction hasDependency(pkg: PackageJson, dep: string): boolean {\n return !!(pkg.dependencies?.[dep] || pkg.devDependencies?.[dep]);\n}\n\n/**\n * Resolve the npx-style command for a given binary.\n * Returns the binary path under node_modules/.bin if it exists,\n * otherwise returns the bare command name (assumes it's globally available).\n */\nfunction resolveNodeBin(projectDir: string, command: string): string {\n const binDir = join(projectDir, 'node_modules', '.bin');\n if (IS_WINDOWS) {\n const cmdPath = join(binDir, `${command}.cmd`);\n if (existsSync(cmdPath)) return cmdPath;\n }\n const binPath = join(binDir, command);\n if (existsSync(binPath)) return binPath;\n return command;\n}\n\n/**\n * Resolve the dev command for a project directory.\n *\n * Priority:\n * 1. `scripts.dev` from package.json (developer's config is authoritative)\n * 2. Framework-specific default based on dependency detection\n * 3. Non-JS framework detection (Django, Rails, Go)\n * 4. Error — no dev command could be resolved\n */\nexport async function resolveDevCommand(projectDir: string): Promise<DevCommandResult> {\n const pkg = readPackageJson(projectDir);\n\n if (pkg) {\n // Detect framework from dependencies first (for display purposes)\n let detectedFramework: string | null = null;\n for (const entry of FRAMEWORK_DEV_COMMANDS) {\n if (hasDependency(pkg, entry.dep)) {\n detectedFramework = entry.framework;\n break;\n }\n }\n\n // Priority 1: scripts.dev from package.json\n if (pkg.scripts?.dev) {\n // Use the package manager's run command to execute scripts.dev\n const packageManager = detectPackageManager(projectDir);\n return {\n command: packageManager,\n args: ['run', 'dev'],\n framework: detectedFramework,\n };\n }\n\n // Priority 2: Framework-specific default\n for (const entry of FRAMEWORK_DEV_COMMANDS) {\n if (hasDependency(pkg, entry.dep)) {\n return {\n command: resolveNodeBin(projectDir, entry.command),\n args: entry.args,\n framework: entry.framework,\n };\n }\n }\n }\n\n // Priority 3: Non-JS frameworks\n for (const entry of NON_JS_FRAMEWORKS) {\n if (existsSync(resolve(projectDir, entry.file))) {\n return {\n command: entry.command,\n args: entry.args,\n framework: entry.framework,\n };\n }\n }\n\n // No framework or scripts.dev found\n return {\n command: 'npm',\n args: ['run', 'dev'],\n framework: null,\n };\n}\n\n/**\n * Detect the package manager used in the project.\n */\nfunction detectPackageManager(projectDir: string): string {\n if (existsSync(resolve(projectDir, 'pnpm-lock.yaml'))) return 'pnpm';\n if (existsSync(resolve(projectDir, 'yarn.lock'))) return 'yarn';\n if (existsSync(resolve(projectDir, 'bun.lockb')) || existsSync(resolve(projectDir, 'bun.lock'))) return 'bun';\n return 'npm';\n}\n\n// Export for testing\nexport { readPackageJson as _readPackageJson, detectPackageManager as _detectPackageManager };\n"]}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Persistent device identifier for telemetry correlation.
3
+ *
4
+ * Stored at ~/.workos/device-id as a plain UTF-8 UUID string. Not a secret
5
+ * — this is a convenience identifier that survives keyring unavailability.
6
+ * Any IO failure falls through to a one-shot UUID for the current session.
7
+ */
8
+ /**
9
+ * Asynchronously resolve (and lazily create) the device id without blocking
10
+ * the event loop. Memoized: the first call performs the IO, concurrent and
11
+ * later callers await the same promise. Populates the shared cache that the
12
+ * synchronous getDeviceId() reads, so prewarming this at startup keeps the
13
+ * synchronous telemetry path off blocking fs IO. Never rejects.
14
+ */
15
+ export declare function loadDeviceId(): Promise<string>;
16
+ /**
17
+ * Synchronous accessor for the telemetry event path. Returns the prewarmed
18
+ * value when loadDeviceId() has run; otherwise falls back to a one-time
19
+ * synchronous read of the same file (returning the persisted id, so the value
20
+ * never diverges from the async path). On any IO failure, returns a one-shot
21
+ * UUID scoped to the current process — never throws.
22
+ */
23
+ export declare function getDeviceId(): string;
24
+ /** Test seam — resets the in-memory cache between test cases. */
25
+ export declare function __resetDeviceIdCache(): void;
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Persistent device identifier for telemetry correlation.
3
+ *
4
+ * Stored at ~/.workos/device-id as a plain UTF-8 UUID string. Not a secret
5
+ * — this is a convenience identifier that survives keyring unavailability.
6
+ * Any IO failure falls through to a one-shot UUID for the current session.
7
+ */
8
+ import fs from 'node:fs';
9
+ import { mkdir, readFile, writeFile } from 'node:fs/promises';
10
+ import path from 'node:path';
11
+ import os from 'node:os';
12
+ import crypto from 'node:crypto';
13
+ // RFC 4122 v4 format — matches what `crypto.randomUUID()` produces.
14
+ // Rejects non-UUID strings like "------------------------------------".
15
+ const UUID_V4_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
16
+ let cached;
17
+ let pending;
18
+ function getDeviceIdPath() {
19
+ return path.join(os.homedir(), '.workos', 'device-id');
20
+ }
21
+ /**
22
+ * Asynchronously resolve (and lazily create) the device id without blocking
23
+ * the event loop. Memoized: the first call performs the IO, concurrent and
24
+ * later callers await the same promise. Populates the shared cache that the
25
+ * synchronous getDeviceId() reads, so prewarming this at startup keeps the
26
+ * synchronous telemetry path off blocking fs IO. Never rejects.
27
+ */
28
+ export function loadDeviceId() {
29
+ if (cached)
30
+ return Promise.resolve(cached);
31
+ if (pending)
32
+ return pending;
33
+ pending = (async () => {
34
+ const filePath = getDeviceIdPath();
35
+ try {
36
+ try {
37
+ const raw = (await readFile(filePath, 'utf8')).trim();
38
+ if (UUID_V4_REGEX.test(raw)) {
39
+ cached = raw;
40
+ return raw;
41
+ }
42
+ }
43
+ catch {
44
+ // Missing/unreadable file — fall through and create it.
45
+ }
46
+ const id = crypto.randomUUID();
47
+ await mkdir(path.dirname(filePath), { recursive: true, mode: 0o700 });
48
+ await writeFile(filePath, id, { encoding: 'utf8', mode: 0o600 });
49
+ cached = id;
50
+ return id;
51
+ }
52
+ catch {
53
+ // IO failure (readonly FS, permission denied, etc.) — fall through to
54
+ // a session-scoped UUID, cached for the rest of this process.
55
+ cached = crypto.randomUUID();
56
+ return cached;
57
+ }
58
+ finally {
59
+ pending = undefined;
60
+ }
61
+ })();
62
+ return pending;
63
+ }
64
+ /**
65
+ * Synchronous accessor for the telemetry event path. Returns the prewarmed
66
+ * value when loadDeviceId() has run; otherwise falls back to a one-time
67
+ * synchronous read of the same file (returning the persisted id, so the value
68
+ * never diverges from the async path). On any IO failure, returns a one-shot
69
+ * UUID scoped to the current process — never throws.
70
+ */
71
+ export function getDeviceId() {
72
+ if (cached)
73
+ return cached;
74
+ const filePath = getDeviceIdPath();
75
+ try {
76
+ if (fs.existsSync(filePath)) {
77
+ const raw = fs.readFileSync(filePath, 'utf8').trim();
78
+ if (UUID_V4_REGEX.test(raw)) {
79
+ cached = raw;
80
+ return raw;
81
+ }
82
+ }
83
+ const id = crypto.randomUUID();
84
+ fs.mkdirSync(path.dirname(filePath), { recursive: true, mode: 0o700 });
85
+ fs.writeFileSync(filePath, id, { encoding: 'utf8', mode: 0o600 });
86
+ cached = id;
87
+ return id;
88
+ }
89
+ catch {
90
+ // IO failure (readonly FS, permission denied, etc.) — fall through to
91
+ // a session-scoped UUID. Cache it so subsequent calls in this process
92
+ // return the same value; the next process run will retry IO.
93
+ cached = crypto.randomUUID();
94
+ return cached;
95
+ }
96
+ }
97
+ /** Test seam — resets the in-memory cache between test cases. */
98
+ export function __resetDeviceIdCache() {
99
+ cached = undefined;
100
+ pending = undefined;
101
+ }
102
+ //# sourceMappingURL=device-id.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"device-id.js","sourceRoot":"","sources":["../../src/lib/device-id.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,MAAM,MAAM,aAAa,CAAC;AAEjC,oEAAoE;AACpE,wEAAwE;AACxE,MAAM,aAAa,GAAG,wEAAwE,CAAC;AAE/F,IAAI,MAA0B,CAAC;AAC/B,IAAI,OAAoC,CAAC;AAEzC,SAAS,eAAe;IACtB,OAAO,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,WAAW,CAAC,CAAC;AACzD,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,YAAY;IAC1B,IAAI,MAAM;QAAE,OAAO,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAC3C,IAAI,OAAO;QAAE,OAAO,OAAO,CAAC;IAE5B,OAAO,GAAG,CAAC,KAAK,IAAI,EAAE;QACpB,MAAM,QAAQ,GAAG,eAAe,EAAE,CAAC;QACnC,IAAI,CAAC;YACH,IAAI,CAAC;gBACH,MAAM,GAAG,GAAG,CAAC,MAAM,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;gBACtD,IAAI,aAAa,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;oBAC5B,MAAM,GAAG,GAAG,CAAC;oBACb,OAAO,GAAG,CAAC;gBACb,CAAC;YACH,CAAC;YAAC,MAAM,CAAC;gBACP,wDAAwD;YAC1D,CAAC;YAED,MAAM,EAAE,GAAG,MAAM,CAAC,UAAU,EAAE,CAAC;YAC/B,MAAM,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;YACtE,MAAM,SAAS,CAAC,QAAQ,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;YACjE,MAAM,GAAG,EAAE,CAAC;YACZ,OAAO,EAAE,CAAC;QACZ,CAAC;QAAC,MAAM,CAAC;YACP,sEAAsE;YACtE,8DAA8D;YAC9D,MAAM,GAAG,MAAM,CAAC,UAAU,EAAE,CAAC;YAC7B,OAAO,MAAM,CAAC;QAChB,CAAC;gBAAS,CAAC;YACT,OAAO,GAAG,SAAS,CAAC;QACtB,CAAC;IACH,CAAC,CAAC,EAAE,CAAC;IAEL,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,WAAW;IACzB,IAAI,MAAM;QAAE,OAAO,MAAM,CAAC;IAE1B,MAAM,QAAQ,GAAG,eAAe,EAAE,CAAC;IACnC,IAAI,CAAC;QACH,IAAI,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC5B,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;YACrD,IAAI,aAAa,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;gBAC5B,MAAM,GAAG,GAAG,CAAC;gBACb,OAAO,GAAG,CAAC;YACb,CAAC;QACH,CAAC;QAED,MAAM,EAAE,GAAG,MAAM,CAAC,UAAU,EAAE,CAAC;QAC/B,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;QACvE,EAAE,CAAC,aAAa,CAAC,QAAQ,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;QAClE,MAAM,GAAG,EAAE,CAAC;QACZ,OAAO,EAAE,CAAC;IACZ,CAAC;IAAC,MAAM,CAAC;QACP,sEAAsE;QACtE,sEAAsE;QACtE,6DAA6D;QAC7D,MAAM,GAAG,MAAM,CAAC,UAAU,EAAE,CAAC;QAC7B,OAAO,MAAM,CAAC;IAChB,CAAC;AACH,CAAC;AAED,iEAAiE;AACjE,MAAM,UAAU,oBAAoB;IAClC,MAAM,GAAG,SAAS,CAAC;IACnB,OAAO,GAAG,SAAS,CAAC;AACtB,CAAC","sourcesContent":["/**\n * Persistent device identifier for telemetry correlation.\n *\n * Stored at ~/.workos/device-id as a plain UTF-8 UUID string. Not a secret\n * — this is a convenience identifier that survives keyring unavailability.\n * Any IO failure falls through to a one-shot UUID for the current session.\n */\n\nimport fs from 'node:fs';\nimport { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport os from 'node:os';\nimport crypto from 'node:crypto';\n\n// RFC 4122 v4 format — matches what `crypto.randomUUID()` produces.\n// Rejects non-UUID strings like \"------------------------------------\".\nconst UUID_V4_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;\n\nlet cached: string | undefined;\nlet pending: Promise<string> | undefined;\n\nfunction getDeviceIdPath(): string {\n return path.join(os.homedir(), '.workos', 'device-id');\n}\n\n/**\n * Asynchronously resolve (and lazily create) the device id without blocking\n * the event loop. Memoized: the first call performs the IO, concurrent and\n * later callers await the same promise. Populates the shared cache that the\n * synchronous getDeviceId() reads, so prewarming this at startup keeps the\n * synchronous telemetry path off blocking fs IO. Never rejects.\n */\nexport function loadDeviceId(): Promise<string> {\n if (cached) return Promise.resolve(cached);\n if (pending) return pending;\n\n pending = (async () => {\n const filePath = getDeviceIdPath();\n try {\n try {\n const raw = (await readFile(filePath, 'utf8')).trim();\n if (UUID_V4_REGEX.test(raw)) {\n cached = raw;\n return raw;\n }\n } catch {\n // Missing/unreadable file — fall through and create it.\n }\n\n const id = crypto.randomUUID();\n await mkdir(path.dirname(filePath), { recursive: true, mode: 0o700 });\n await writeFile(filePath, id, { encoding: 'utf8', mode: 0o600 });\n cached = id;\n return id;\n } catch {\n // IO failure (readonly FS, permission denied, etc.) — fall through to\n // a session-scoped UUID, cached for the rest of this process.\n cached = crypto.randomUUID();\n return cached;\n } finally {\n pending = undefined;\n }\n })();\n\n return pending;\n}\n\n/**\n * Synchronous accessor for the telemetry event path. Returns the prewarmed\n * value when loadDeviceId() has run; otherwise falls back to a one-time\n * synchronous read of the same file (returning the persisted id, so the value\n * never diverges from the async path). On any IO failure, returns a one-shot\n * UUID scoped to the current process — never throws.\n */\nexport function getDeviceId(): string {\n if (cached) return cached;\n\n const filePath = getDeviceIdPath();\n try {\n if (fs.existsSync(filePath)) {\n const raw = fs.readFileSync(filePath, 'utf8').trim();\n if (UUID_V4_REGEX.test(raw)) {\n cached = raw;\n return raw;\n }\n }\n\n const id = crypto.randomUUID();\n fs.mkdirSync(path.dirname(filePath), { recursive: true, mode: 0o700 });\n fs.writeFileSync(filePath, id, { encoding: 'utf8', mode: 0o600 });\n cached = id;\n return id;\n } catch {\n // IO failure (readonly FS, permission denied, etc.) — fall through to\n // a session-scoped UUID. Cache it so subsequent calls in this process\n // return the same value; the next process run will retry IO.\n cached = crypto.randomUUID();\n return cached;\n }\n}\n\n/** Test seam — resets the in-memory cache between test cases. */\nexport function __resetDeviceIdCache(): void {\n cached = undefined;\n pending = undefined;\n}\n"]}
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Plain CLI preferences store.
3
+ *
4
+ * Stored at ~/.workos/preferences.json as plain JSON. These are NOT secrets —
5
+ * knowing that someone opted out of telemetry leaks nothing — so this
6
+ * deliberately avoids the keyring abstraction (config-store.ts) to prevent a
7
+ * non-secret write from ever triggering the insecure-fallback security warning.
8
+ *
9
+ * Mirrors the structural pattern of device-id.ts: a synchronous accessor backed
10
+ * by a cache, an async prewarm that populates that cache off the blocking-fs
11
+ * path, and a never-throws contract on every read/parse so a corrupt file can
12
+ * never break a command.
13
+ */
14
+ export interface CliPreferences {
15
+ telemetry?: {
16
+ /** true => the user explicitly opted out of telemetry. */
17
+ optedOut?: boolean;
18
+ /** ISO timestamp the first-run notice was shown — written in Phase 2 only. */
19
+ noticeShownAt?: string;
20
+ };
21
+ }
22
+ /** Effective source of the resolved telemetry-enabled decision. */
23
+ export type TelemetrySource = 'env' | 'preference' | 'default';
24
+ export declare function getPreferencesPath(): string;
25
+ /**
26
+ * Asynchronously load preferences and warm the cache the synchronous
27
+ * getPreferences() reads. Memoized: the first call performs the IO, concurrent
28
+ * and later callers await the same promise. Prewarming this at startup keeps
29
+ * the synchronous telemetry path off blocking fs IO. Never rejects.
30
+ */
31
+ export declare function loadPreferences(): Promise<CliPreferences>;
32
+ /**
33
+ * Synchronous accessor. Returns the prewarmed value when loadPreferences() has
34
+ * run; otherwise performs a one-time synchronous read of the same file. On any
35
+ * IO/parse failure returns {} (telemetry stays at its default-on state). Never
36
+ * throws.
37
+ */
38
+ export declare function getPreferences(): CliPreferences;
39
+ /**
40
+ * Read-modify-write the on-disk preferences, merging `next` over the current
41
+ * persisted value so future fields are never clobbered, then update the cache.
42
+ * Throws on write failure so callers on the command path (opt-out/opt-in) can
43
+ * surface a clear error — the read path swallows, the write path does not.
44
+ */
45
+ export declare function savePreferences(next: CliPreferences): void;
46
+ /** Whether the user has explicitly opted out via the saved preference. */
47
+ export declare function isTelemetryOptedOut(): boolean;
48
+ /** Persist the opt-out flag. Throws on write failure (see savePreferences). */
49
+ export declare function setTelemetryOptedOut(value: boolean): void;
50
+ /** Whether the first-run telemetry notice has already been shown (ever). */
51
+ export declare function isNoticeShown(): boolean;
52
+ /**
53
+ * Persist the first-run notice as shown, stamping the current time. Uses the
54
+ * read-modify-write savePreferences so it never clobbers the optedOut flag.
55
+ * Throws on write failure (see savePreferences) — the caller in
56
+ * telemetry-notice.ts swallows it so a read-only FS never blocks a command.
57
+ */
58
+ export declare function markNoticeShown(): void;
59
+ /**
60
+ * Tri-state env override for telemetry.
61
+ *
62
+ * Only the explicit strings 'true' / 'false' count as an override. Any other
63
+ * value — including unset or garbage like '1' — returns undefined and falls
64
+ * through to the saved preference.
65
+ *
66
+ * This is a deliberate, documented change from the old `WORKOS_TELEMETRY !==
67
+ * 'false'` behaviour: previously `WORKOS_TELEMETRY=1` forced telemetry on even
68
+ * for opted-out users; now an opt-out is respected unless the env var
69
+ * explicitly says 'true'.
70
+ */
71
+ export declare function envTelemetryOverride(): boolean | undefined;
72
+ /**
73
+ * Effective telemetry-enabled decision.
74
+ *
75
+ * Resolution order:
76
+ * 1. envTelemetryOverride() if defined — env wins in BOTH directions.
77
+ * 2. otherwise !isTelemetryOptedOut() — default-on unless explicitly opted out.
78
+ */
79
+ export declare function isTelemetryEnabled(): boolean;
80
+ /**
81
+ * Which signal produced the effective telemetry decision. Mirrors the
82
+ * precedence in isTelemetryEnabled() so the `telemetry status` command and the
83
+ * resolver can never drift.
84
+ *
85
+ * Note: an explicit opt-in (optedOut === false) reads as 'default', not
86
+ * 'preference' — its outcome is identical to a fresh install, and the resolver
87
+ * only treats optedOut === true as a non-default signal, so reporting
88
+ * 'preference' here would imply a behavioral difference that does not exist.
89
+ */
90
+ export declare function getTelemetrySource(): TelemetrySource;
91
+ /**
92
+ * Delete the preferences file, returning telemetry to its fresh-install state
93
+ * (opted-in, first-run notice unseen). Used by `debug reset` to wipe stored CLI
94
+ * state alongside credentials and config. No-op if the file does not exist;
95
+ * throws on a real delete failure (e.g. permission denied) so the caller can
96
+ * surface it, mirroring clearConfig/clearCredentials. Resets the in-memory
97
+ * cache so subsequent reads in this process reflect the cleared state.
98
+ */
99
+ export declare function clearPreferences(): void;
100
+ /** Test seam — resets the in-memory cache between test cases. */
101
+ export declare function __resetPreferencesCache(): void;
@@ -0,0 +1,198 @@
1
+ /**
2
+ * Plain CLI preferences store.
3
+ *
4
+ * Stored at ~/.workos/preferences.json as plain JSON. These are NOT secrets —
5
+ * knowing that someone opted out of telemetry leaks nothing — so this
6
+ * deliberately avoids the keyring abstraction (config-store.ts) to prevent a
7
+ * non-secret write from ever triggering the insecure-fallback security warning.
8
+ *
9
+ * Mirrors the structural pattern of device-id.ts: a synchronous accessor backed
10
+ * by a cache, an async prewarm that populates that cache off the blocking-fs
11
+ * path, and a never-throws contract on every read/parse so a corrupt file can
12
+ * never break a command.
13
+ */
14
+ import fs from 'node:fs';
15
+ import { readFile } from 'node:fs/promises';
16
+ import path from 'node:path';
17
+ import os from 'node:os';
18
+ let cached;
19
+ let pending;
20
+ export function getPreferencesPath() {
21
+ return path.join(os.homedir(), '.workos', 'preferences.json');
22
+ }
23
+ function parsePreferences(raw) {
24
+ try {
25
+ const value = JSON.parse(raw);
26
+ // Defend against a file that parses to a non-object (e.g. `"true"`, `42`).
27
+ if (value && typeof value === 'object' && !Array.isArray(value)) {
28
+ return value;
29
+ }
30
+ }
31
+ catch {
32
+ // Corrupt JSON — fall through to empty preferences. A later savePreferences
33
+ // overwrites it cleanly; we never auto-delete.
34
+ }
35
+ return {};
36
+ }
37
+ /**
38
+ * Asynchronously load preferences and warm the cache the synchronous
39
+ * getPreferences() reads. Memoized: the first call performs the IO, concurrent
40
+ * and later callers await the same promise. Prewarming this at startup keeps
41
+ * the synchronous telemetry path off blocking fs IO. Never rejects.
42
+ */
43
+ export function loadPreferences() {
44
+ if (cached)
45
+ return Promise.resolve(cached);
46
+ if (pending)
47
+ return pending;
48
+ pending = (async () => {
49
+ try {
50
+ const raw = await readFile(getPreferencesPath(), 'utf8');
51
+ cached = parsePreferences(raw);
52
+ }
53
+ catch {
54
+ // Missing/unreadable file — treat as no preferences set.
55
+ cached = {};
56
+ }
57
+ finally {
58
+ pending = undefined;
59
+ }
60
+ return cached;
61
+ })();
62
+ return pending;
63
+ }
64
+ /**
65
+ * Synchronous accessor. Returns the prewarmed value when loadPreferences() has
66
+ * run; otherwise performs a one-time synchronous read of the same file. On any
67
+ * IO/parse failure returns {} (telemetry stays at its default-on state). Never
68
+ * throws.
69
+ */
70
+ export function getPreferences() {
71
+ if (cached)
72
+ return cached;
73
+ try {
74
+ const raw = fs.readFileSync(getPreferencesPath(), 'utf8');
75
+ cached = parsePreferences(raw);
76
+ }
77
+ catch {
78
+ // Missing/unreadable/corrupt — telemetry stays at its default-on state.
79
+ cached = {};
80
+ }
81
+ return cached;
82
+ }
83
+ /**
84
+ * Read-modify-write the on-disk preferences, merging `next` over the current
85
+ * persisted value so future fields are never clobbered, then update the cache.
86
+ * Throws on write failure so callers on the command path (opt-out/opt-in) can
87
+ * surface a clear error — the read path swallows, the write path does not.
88
+ */
89
+ export function savePreferences(next) {
90
+ const filePath = getPreferencesPath();
91
+ // Read-modify-write over the CURRENT on-disk value (not the cache) so a field
92
+ // written by another process / a future phase is preserved.
93
+ let current = {};
94
+ try {
95
+ current = parsePreferences(fs.readFileSync(filePath, 'utf8'));
96
+ }
97
+ catch {
98
+ // No existing file (or unreadable) — start from empty.
99
+ }
100
+ const merged = {
101
+ ...current,
102
+ ...next,
103
+ ...(current.telemetry || next.telemetry ? { telemetry: { ...current.telemetry, ...next.telemetry } } : {}),
104
+ };
105
+ fs.mkdirSync(path.dirname(filePath), { recursive: true, mode: 0o700 });
106
+ fs.writeFileSync(filePath, JSON.stringify(merged), { encoding: 'utf8', mode: 0o600 });
107
+ cached = merged;
108
+ }
109
+ /** Whether the user has explicitly opted out via the saved preference. */
110
+ export function isTelemetryOptedOut() {
111
+ return getPreferences().telemetry?.optedOut === true;
112
+ }
113
+ /** Persist the opt-out flag. Throws on write failure (see savePreferences). */
114
+ export function setTelemetryOptedOut(value) {
115
+ savePreferences({ telemetry: { optedOut: value } });
116
+ }
117
+ /** Whether the first-run telemetry notice has already been shown (ever). */
118
+ export function isNoticeShown() {
119
+ return !!getPreferences().telemetry?.noticeShownAt;
120
+ }
121
+ /**
122
+ * Persist the first-run notice as shown, stamping the current time. Uses the
123
+ * read-modify-write savePreferences so it never clobbers the optedOut flag.
124
+ * Throws on write failure (see savePreferences) — the caller in
125
+ * telemetry-notice.ts swallows it so a read-only FS never blocks a command.
126
+ */
127
+ export function markNoticeShown() {
128
+ savePreferences({ telemetry: { noticeShownAt: new Date().toISOString() } });
129
+ }
130
+ /**
131
+ * Tri-state env override for telemetry.
132
+ *
133
+ * Only the explicit strings 'true' / 'false' count as an override. Any other
134
+ * value — including unset or garbage like '1' — returns undefined and falls
135
+ * through to the saved preference.
136
+ *
137
+ * This is a deliberate, documented change from the old `WORKOS_TELEMETRY !==
138
+ * 'false'` behaviour: previously `WORKOS_TELEMETRY=1` forced telemetry on even
139
+ * for opted-out users; now an opt-out is respected unless the env var
140
+ * explicitly says 'true'.
141
+ */
142
+ export function envTelemetryOverride() {
143
+ const value = process.env.WORKOS_TELEMETRY;
144
+ if (value === 'true')
145
+ return true;
146
+ if (value === 'false')
147
+ return false;
148
+ return undefined;
149
+ }
150
+ /**
151
+ * Effective telemetry-enabled decision.
152
+ *
153
+ * Resolution order:
154
+ * 1. envTelemetryOverride() if defined — env wins in BOTH directions.
155
+ * 2. otherwise !isTelemetryOptedOut() — default-on unless explicitly opted out.
156
+ */
157
+ export function isTelemetryEnabled() {
158
+ const override = envTelemetryOverride();
159
+ if (override !== undefined)
160
+ return override;
161
+ return !isTelemetryOptedOut();
162
+ }
163
+ /**
164
+ * Which signal produced the effective telemetry decision. Mirrors the
165
+ * precedence in isTelemetryEnabled() so the `telemetry status` command and the
166
+ * resolver can never drift.
167
+ *
168
+ * Note: an explicit opt-in (optedOut === false) reads as 'default', not
169
+ * 'preference' — its outcome is identical to a fresh install, and the resolver
170
+ * only treats optedOut === true as a non-default signal, so reporting
171
+ * 'preference' here would imply a behavioral difference that does not exist.
172
+ */
173
+ export function getTelemetrySource() {
174
+ if (envTelemetryOverride() !== undefined)
175
+ return 'env';
176
+ if (isTelemetryOptedOut())
177
+ return 'preference';
178
+ return 'default';
179
+ }
180
+ /**
181
+ * Delete the preferences file, returning telemetry to its fresh-install state
182
+ * (opted-in, first-run notice unseen). Used by `debug reset` to wipe stored CLI
183
+ * state alongside credentials and config. No-op if the file does not exist;
184
+ * throws on a real delete failure (e.g. permission denied) so the caller can
185
+ * surface it, mirroring clearConfig/clearCredentials. Resets the in-memory
186
+ * cache so subsequent reads in this process reflect the cleared state.
187
+ */
188
+ export function clearPreferences() {
189
+ fs.rmSync(getPreferencesPath(), { force: true });
190
+ cached = {};
191
+ pending = undefined;
192
+ }
193
+ /** Test seam — resets the in-memory cache between test cases. */
194
+ export function __resetPreferencesCache() {
195
+ cached = undefined;
196
+ pending = undefined;
197
+ }
198
+ //# sourceMappingURL=preferences.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preferences.js","sourceRoot":"","sources":["../../src/lib/preferences.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Plain CLI preferences store.\n *\n * Stored at ~/.workos/preferences.json as plain JSON. These are NOT secrets —\n * knowing that someone opted out of telemetry leaks nothing — so this\n * deliberately avoids the keyring abstraction (config-store.ts) to prevent a\n * non-secret write from ever triggering the insecure-fallback security warning.\n *\n * Mirrors the structural pattern of device-id.ts: a synchronous accessor backed\n * by a cache, an async prewarm that populates that cache off the blocking-fs\n * path, and a never-throws contract on every read/parse so a corrupt file can\n * never break a command.\n */\n\nimport fs from 'node:fs';\nimport { readFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport os from 'node:os';\n\nexport interface CliPreferences {\n telemetry?: {\n /** true => the user explicitly opted out of telemetry. */\n optedOut?: boolean;\n /** ISO timestamp the first-run notice was shown — written in Phase 2 only. */\n noticeShownAt?: string;\n };\n}\n\n/** Effective source of the resolved telemetry-enabled decision. */\nexport type TelemetrySource = 'env' | 'preference' | 'default';\n\nlet cached: CliPreferences | undefined;\nlet pending: Promise<CliPreferences> | undefined;\n\nexport function getPreferencesPath(): string {\n return path.join(os.homedir(), '.workos', 'preferences.json');\n}\n\nfunction parsePreferences(raw: string): CliPreferences {\n try {\n const value = JSON.parse(raw);\n // Defend against a file that parses to a non-object (e.g. `\"true\"`, `42`).\n if (value && typeof value === 'object' && !Array.isArray(value)) {\n return value as CliPreferences;\n }\n } catch {\n // Corrupt JSON — fall through to empty preferences. A later savePreferences\n // overwrites it cleanly; we never auto-delete.\n }\n return {};\n}\n\n/**\n * Asynchronously load preferences and warm the cache the synchronous\n * getPreferences() reads. Memoized: the first call performs the IO, concurrent\n * and later callers await the same promise. Prewarming this at startup keeps\n * the synchronous telemetry path off blocking fs IO. Never rejects.\n */\nexport function loadPreferences(): Promise<CliPreferences> {\n if (cached) return Promise.resolve(cached);\n if (pending) return pending;\n\n pending = (async () => {\n try {\n const raw = await readFile(getPreferencesPath(), 'utf8');\n cached = parsePreferences(raw);\n } catch {\n // Missing/unreadable file — treat as no preferences set.\n cached = {};\n } finally {\n pending = undefined;\n }\n return cached;\n })();\n\n return pending;\n}\n\n/**\n * Synchronous accessor. Returns the prewarmed value when loadPreferences() has\n * run; otherwise performs a one-time synchronous read of the same file. On any\n * IO/parse failure returns {} (telemetry stays at its default-on state). Never\n * throws.\n */\nexport function getPreferences(): CliPreferences {\n if (cached) return cached;\n\n try {\n const raw = fs.readFileSync(getPreferencesPath(), 'utf8');\n cached = parsePreferences(raw);\n } catch {\n // Missing/unreadable/corrupt — telemetry stays at its default-on state.\n cached = {};\n }\n return cached;\n}\n\n/**\n * Read-modify-write the on-disk preferences, merging `next` over the current\n * persisted value so future fields are never clobbered, then update the cache.\n * Throws on write failure so callers on the command path (opt-out/opt-in) can\n * surface a clear error — the read path swallows, the write path does not.\n */\nexport function savePreferences(next: CliPreferences): void {\n const filePath = getPreferencesPath();\n\n // Read-modify-write over the CURRENT on-disk value (not the cache) so a field\n // written by another process / a future phase is preserved.\n let current: CliPreferences = {};\n try {\n current = parsePreferences(fs.readFileSync(filePath, 'utf8'));\n } catch {\n // No existing file (or unreadable) — start from empty.\n }\n\n const merged: CliPreferences = {\n ...current,\n ...next,\n ...(current.telemetry || next.telemetry ? { telemetry: { ...current.telemetry, ...next.telemetry } } : {}),\n };\n\n fs.mkdirSync(path.dirname(filePath), { recursive: true, mode: 0o700 });\n fs.writeFileSync(filePath, JSON.stringify(merged), { encoding: 'utf8', mode: 0o600 });\n cached = merged;\n}\n\n/** Whether the user has explicitly opted out via the saved preference. */\nexport function isTelemetryOptedOut(): boolean {\n return getPreferences().telemetry?.optedOut === true;\n}\n\n/** Persist the opt-out flag. Throws on write failure (see savePreferences). */\nexport function setTelemetryOptedOut(value: boolean): void {\n savePreferences({ telemetry: { optedOut: value } });\n}\n\n/** Whether the first-run telemetry notice has already been shown (ever). */\nexport function isNoticeShown(): boolean {\n return !!getPreferences().telemetry?.noticeShownAt;\n}\n\n/**\n * Persist the first-run notice as shown, stamping the current time. Uses the\n * read-modify-write savePreferences so it never clobbers the optedOut flag.\n * Throws on write failure (see savePreferences) — the caller in\n * telemetry-notice.ts swallows it so a read-only FS never blocks a command.\n */\nexport function markNoticeShown(): void {\n savePreferences({ telemetry: { noticeShownAt: new Date().toISOString() } });\n}\n\n/**\n * Tri-state env override for telemetry.\n *\n * Only the explicit strings 'true' / 'false' count as an override. Any other\n * value — including unset or garbage like '1' — returns undefined and falls\n * through to the saved preference.\n *\n * This is a deliberate, documented change from the old `WORKOS_TELEMETRY !==\n * 'false'` behaviour: previously `WORKOS_TELEMETRY=1` forced telemetry on even\n * for opted-out users; now an opt-out is respected unless the env var\n * explicitly says 'true'.\n */\nexport function envTelemetryOverride(): boolean | undefined {\n const value = process.env.WORKOS_TELEMETRY;\n if (value === 'true') return true;\n if (value === 'false') return false;\n return undefined;\n}\n\n/**\n * Effective telemetry-enabled decision.\n *\n * Resolution order:\n * 1. envTelemetryOverride() if defined — env wins in BOTH directions.\n * 2. otherwise !isTelemetryOptedOut() — default-on unless explicitly opted out.\n */\nexport function isTelemetryEnabled(): boolean {\n const override = envTelemetryOverride();\n if (override !== undefined) return override;\n return !isTelemetryOptedOut();\n}\n\n/**\n * Which signal produced the effective telemetry decision. Mirrors the\n * precedence in isTelemetryEnabled() so the `telemetry status` command and the\n * resolver can never drift.\n *\n * Note: an explicit opt-in (optedOut === false) reads as 'default', not\n * 'preference' — its outcome is identical to a fresh install, and the resolver\n * only treats optedOut === true as a non-default signal, so reporting\n * 'preference' here would imply a behavioral difference that does not exist.\n */\nexport function getTelemetrySource(): TelemetrySource {\n if (envTelemetryOverride() !== undefined) return 'env';\n if (isTelemetryOptedOut()) return 'preference';\n return 'default';\n}\n\n/**\n * Delete the preferences file, returning telemetry to its fresh-install state\n * (opted-in, first-run notice unseen). Used by `debug reset` to wipe stored CLI\n * state alongside credentials and config. No-op if the file does not exist;\n * throws on a real delete failure (e.g. permission denied) so the caller can\n * surface it, mirroring clearConfig/clearCredentials. Resets the in-memory\n * cache so subsequent reads in this process reflect the cleared state.\n */\nexport function clearPreferences(): void {\n fs.rmSync(getPreferencesPath(), { force: true });\n cached = {};\n pending = undefined;\n}\n\n/** Test seam — resets the in-memory cache between test cases. */\nexport function __resetPreferencesCache(): void {\n cached = undefined;\n pending = undefined;\n}\n"]}
@@ -1,6 +1,6 @@
1
1
  import { readdirSync, existsSync } from 'node:fs';
2
2
  import { join, dirname } from 'node:path';
3
- import { fileURLToPath } from 'node:url';
3
+ import { fileURLToPath, pathToFileURL } from 'node:url';
4
4
  /**
5
5
  * Build the integration registry by discovering all integration modules.
6
6
  * Scans `src/integrations/` (or `dist/integrations/` at runtime) for directories
@@ -33,7 +33,7 @@ export async function buildRegistry() {
33
33
  continue;
34
34
  }
35
35
  try {
36
- const mod = (await import(join(integrationsDir, dir, 'index.js')));
36
+ const mod = (await import(pathToFileURL(join(integrationsDir, dir, 'index.js')).href));
37
37
  if (!mod.config || !mod.run) {
38
38
  console.warn(`Integration ${dir} missing 'config' or 'run' export, skipping`);
39
39
  continue;