workos 0.15.2 → 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 (107) hide show
  1. package/README.md +37 -11
  2. package/dist/bin.js +1439 -1257
  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/debug.d.ts +2 -1
  12. package/dist/commands/debug.js +43 -3
  13. package/dist/commands/debug.js.map +1 -1
  14. package/dist/commands/dev.js +5 -4
  15. package/dist/commands/dev.js.map +1 -1
  16. package/dist/commands/doctor.js +13 -4
  17. package/dist/commands/doctor.js.map +1 -1
  18. package/dist/commands/emulate.js +2 -2
  19. package/dist/commands/emulate.js.map +1 -1
  20. package/dist/commands/env.js +5 -4
  21. package/dist/commands/env.js.map +1 -1
  22. package/dist/commands/install-skill.js +4 -3
  23. package/dist/commands/install-skill.js.map +1 -1
  24. package/dist/commands/install.js +2 -2
  25. package/dist/commands/install.js.map +1 -1
  26. package/dist/commands/login.js +3 -3
  27. package/dist/commands/login.js.map +1 -1
  28. package/dist/commands/migrations.d.ts +1 -1
  29. package/dist/commands/migrations.js +4 -1
  30. package/dist/commands/migrations.js.map +1 -1
  31. package/dist/commands/telemetry.d.ts +3 -0
  32. package/dist/commands/telemetry.js +88 -0
  33. package/dist/commands/telemetry.js.map +1 -0
  34. package/dist/commands/uninstall-skill.js +3 -2
  35. package/dist/commands/uninstall-skill.js.map +1 -1
  36. package/dist/commands/vault-run.d.ts +13 -0
  37. package/dist/commands/vault-run.js +194 -0
  38. package/dist/commands/vault-run.js.map +1 -0
  39. package/dist/commands/vault.d.ts +3 -2
  40. package/dist/commands/vault.js +41 -8
  41. package/dist/commands/vault.js.map +1 -1
  42. package/dist/lib/api-error-handler.d.ts +15 -3
  43. package/dist/lib/api-error-handler.js +52 -34
  44. package/dist/lib/api-error-handler.js.map +1 -1
  45. package/dist/lib/command-aliases.d.ts +8 -0
  46. package/dist/lib/command-aliases.js +12 -0
  47. package/dist/lib/command-aliases.js.map +1 -0
  48. package/dist/lib/constants.d.ts +0 -1
  49. package/dist/lib/constants.js +0 -1
  50. package/dist/lib/constants.js.map +1 -1
  51. package/dist/lib/device-id.d.ts +25 -0
  52. package/dist/lib/device-id.js +102 -0
  53. package/dist/lib/device-id.js.map +1 -0
  54. package/dist/lib/preferences.d.ts +101 -0
  55. package/dist/lib/preferences.js +198 -0
  56. package/dist/lib/preferences.js.map +1 -0
  57. package/dist/lib/run-with-core.js +15 -15
  58. package/dist/lib/run-with-core.js.map +1 -1
  59. package/dist/lib/settings.d.ts +6 -0
  60. package/dist/lib/settings.js +7 -0
  61. package/dist/lib/settings.js.map +1 -1
  62. package/dist/lib/telemetry-notice.d.ts +25 -0
  63. package/dist/lib/telemetry-notice.js +56 -0
  64. package/dist/lib/telemetry-notice.js.map +1 -0
  65. package/dist/test/force-insecure-storage.d.ts +1 -0
  66. package/dist/test/force-insecure-storage.js +9 -0
  67. package/dist/test/force-insecure-storage.js.map +1 -0
  68. package/dist/test/setup.d.ts +1 -0
  69. package/dist/test/setup.js +38 -0
  70. package/dist/test/setup.js.map +1 -0
  71. package/dist/utils/analytics.d.ts +41 -0
  72. package/dist/utils/analytics.js +199 -12
  73. package/dist/utils/analytics.js.map +1 -1
  74. package/dist/utils/box.d.ts +29 -1
  75. package/dist/utils/box.js +92 -4
  76. package/dist/utils/box.js.map +1 -1
  77. package/dist/utils/cli-exit.d.ts +15 -0
  78. package/dist/utils/cli-exit.js +11 -0
  79. package/dist/utils/cli-exit.js.map +1 -0
  80. package/dist/utils/cli-symbols.d.ts +1 -1
  81. package/dist/utils/command-telemetry.d.ts +17 -0
  82. package/dist/utils/command-telemetry.js +67 -0
  83. package/dist/utils/command-telemetry.js.map +1 -0
  84. package/dist/utils/crash-reporter.d.ts +13 -0
  85. package/dist/utils/crash-reporter.js +91 -0
  86. package/dist/utils/crash-reporter.js.map +1 -0
  87. package/dist/utils/debug.d.ts +1 -0
  88. package/dist/utils/debug.js +4 -1
  89. package/dist/utils/debug.js.map +1 -1
  90. package/dist/utils/exit-codes.d.ts +5 -0
  91. package/dist/utils/exit-codes.js +30 -1
  92. package/dist/utils/exit-codes.js.map +1 -1
  93. package/dist/utils/help-json.d.ts +6 -0
  94. package/dist/utils/help-json.js +87 -10
  95. package/dist/utils/help-json.js.map +1 -1
  96. package/dist/utils/output.d.ts +7 -2
  97. package/dist/utils/output.js +9 -2
  98. package/dist/utils/output.js.map +1 -1
  99. package/dist/utils/telemetry-client.d.ts +30 -2
  100. package/dist/utils/telemetry-client.js +122 -12
  101. package/dist/utils/telemetry-client.js.map +1 -1
  102. package/dist/utils/telemetry-store-forward.d.ts +11 -0
  103. package/dist/utils/telemetry-store-forward.js +94 -0
  104. package/dist/utils/telemetry-store-forward.js.map +1 -0
  105. package/dist/utils/telemetry-types.d.ts +58 -9
  106. package/dist/utils/telemetry-types.js.map +1 -1
  107. package/package.json +1 -1
@@ -1,57 +1,75 @@
1
1
  import { WorkOSApiError } from './workos-api.js';
2
2
  import { exitWithError } from '../utils/output.js';
3
- /**
4
- * Duck-type check for @workos-inc/node SDK exceptions.
5
- *
6
- * The SDK throws typed errors (UnauthorizedException, NotFoundException, etc.)
7
- * that implement the RequestException interface: { status, message, requestID }.
8
- * We duck-type rather than instanceof to avoid coupling to the SDK's class hierarchy.
9
- */
10
- function isSdkException(error) {
3
+ export function isSdkException(error) {
11
4
  if (!(error instanceof Error))
12
5
  return false;
13
6
  const e = error;
14
7
  return typeof e.status === 'number' && typeof e.requestID === 'string';
15
8
  }
9
+ function normalizeApiError(error) {
10
+ if (error instanceof WorkOSApiError) {
11
+ return {
12
+ status: error.statusCode,
13
+ code: error.code,
14
+ errors: error.errors,
15
+ message: error.message,
16
+ };
17
+ }
18
+ if (isSdkException(error)) {
19
+ return {
20
+ status: error.status,
21
+ code: error.code,
22
+ errors: error.errors,
23
+ message: error.message,
24
+ };
25
+ }
26
+ return null;
27
+ }
28
+ function getApiErrorMessage(error, label) {
29
+ if (error.status === 401)
30
+ return 'Invalid API key. Check your environment configuration.';
31
+ if (error.status === 404)
32
+ return `${label} not found.`;
33
+ if (error.status === 422 && error.errors?.length)
34
+ return error.errors.map((e) => e.message).join(', ');
35
+ return error.message;
36
+ }
16
37
  /**
17
38
  * Create a resource-specific API error handler.
18
- * Handles both raw fetch errors (WorkOSApiError) and SDK exceptions.
19
- * Returns a `never` function that writes structured errors and exits.
39
+ * Handles raw fetch errors (WorkOSApiError), SDK exceptions, and the SDK's
40
+ * "errors is not iterable" TypeError from malformed 422 responses.
41
+ *
42
+ * `context` optionally names the specific resource instance (e.g. a vault
43
+ * object name) so 404 messages can be more specific.
20
44
  */
21
45
  export function createApiErrorHandler(resourceName) {
22
- return (error) => {
23
- // 1. Raw fetch errors (workos-api.ts)
24
- if (error instanceof WorkOSApiError) {
46
+ return (error, context) => {
47
+ if (error instanceof TypeError && error.message.includes('errors is not iterable')) {
25
48
  exitWithError({
26
- code: error.code ?? `http_${error.statusCode}`,
27
- message: error.statusCode === 401
28
- ? 'Invalid API key. Check your environment configuration.'
29
- : error.statusCode === 404
30
- ? `${resourceName} not found.`
31
- : error.statusCode === 422 && error.errors?.length
32
- ? error.errors.map((e) => e.message).join(', ')
33
- : error.message,
34
- details: error.errors,
49
+ code: 'unprocessable_entity',
50
+ message: `${resourceName} API rejected the request. Check that all required fields are provided.`,
51
+ apiContext: { resource: resourceName },
35
52
  });
36
53
  }
37
- // 2. SDK exceptions (@workos-inc/node)
38
- if (isSdkException(error)) {
54
+ const label = context ? `${resourceName} '${context}'` : resourceName;
55
+ const apiError = normalizeApiError(error);
56
+ if (apiError) {
57
+ const code = apiError.code ?? `http_${apiError.status}`;
39
58
  exitWithError({
40
- code: error.code ?? `http_${error.status}`,
41
- message: error.status === 401
42
- ? 'Invalid API key. Check your environment configuration.'
43
- : error.status === 404
44
- ? `${resourceName} not found.`
45
- : error.status === 422 && error.errors?.length
46
- ? error.errors.map((e) => e.message).join(', ')
47
- : error.message,
48
- details: error.errors,
59
+ code,
60
+ message: getApiErrorMessage(apiError, label),
61
+ details: apiError.errors,
62
+ apiContext: {
63
+ status: apiError.status,
64
+ code,
65
+ resource: resourceName,
66
+ },
49
67
  });
50
68
  }
51
- // 3. Fallback
52
69
  exitWithError({
53
70
  code: 'unknown_error',
54
71
  message: error instanceof Error ? error.message : 'Unknown error',
72
+ apiContext: { resource: resourceName },
55
73
  });
56
74
  };
57
75
  }
@@ -1 +1 @@
1
- {"version":3,"file":"api-error-handler.js","sourceRoot":"","sources":["../../src/lib/api-error-handler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAEnD;;;;;;GAMG;AACH,SAAS,cAAc,CACrB,KAAc;IAEd,IAAI,CAAC,CAAC,KAAK,YAAY,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAC5C,MAAM,CAAC,GAAG,KAA0D,CAAC;IACrE,OAAO,OAAO,CAAC,CAAC,MAAM,KAAK,QAAQ,IAAI,OAAO,CAAC,CAAC,SAAS,KAAK,QAAQ,CAAC;AACzE,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CAAC,YAAoB;IACxD,OAAO,CAAC,KAAc,EAAS,EAAE;QAC/B,sCAAsC;QACtC,IAAI,KAAK,YAAY,cAAc,EAAE,CAAC;YACpC,aAAa,CAAC;gBACZ,IAAI,EAAE,KAAK,CAAC,IAAI,IAAI,QAAQ,KAAK,CAAC,UAAU,EAAE;gBAC9C,OAAO,EACL,KAAK,CAAC,UAAU,KAAK,GAAG;oBACtB,CAAC,CAAC,wDAAwD;oBAC1D,CAAC,CAAC,KAAK,CAAC,UAAU,KAAK,GAAG;wBACxB,CAAC,CAAC,GAAG,YAAY,aAAa;wBAC9B,CAAC,CAAC,KAAK,CAAC,UAAU,KAAK,GAAG,IAAI,KAAK,CAAC,MAAM,EAAE,MAAM;4BAChD,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;4BAC/C,CAAC,CAAC,KAAK,CAAC,OAAO;gBACvB,OAAO,EAAE,KAAK,CAAC,MAAM;aACtB,CAAC,CAAC;QACL,CAAC;QAED,uCAAuC;QACvC,IAAI,cAAc,CAAC,KAAK,CAAC,EAAE,CAAC;YAC1B,aAAa,CAAC;gBACZ,IAAI,EAAE,KAAK,CAAC,IAAI,IAAI,QAAQ,KAAK,CAAC,MAAM,EAAE;gBAC1C,OAAO,EACL,KAAK,CAAC,MAAM,KAAK,GAAG;oBAClB,CAAC,CAAC,wDAAwD;oBAC1D,CAAC,CAAC,KAAK,CAAC,MAAM,KAAK,GAAG;wBACpB,CAAC,CAAC,GAAG,YAAY,aAAa;wBAC9B,CAAC,CAAC,KAAK,CAAC,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC,MAAM,EAAE,MAAM;4BAC5C,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;4BAC/C,CAAC,CAAC,KAAK,CAAC,OAAO;gBACvB,OAAO,EAAE,KAAK,CAAC,MAAM;aACtB,CAAC,CAAC;QACL,CAAC;QAED,cAAc;QACd,aAAa,CAAC;YACZ,IAAI,EAAE,eAAe;YACrB,OAAO,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe;SAClE,CAAC,CAAC;IACL,CAAC,CAAC;AACJ,CAAC","sourcesContent":["import { WorkOSApiError } from './workos-api.js';\nimport { exitWithError } from '../utils/output.js';\n\n/**\n * Duck-type check for @workos-inc/node SDK exceptions.\n *\n * The SDK throws typed errors (UnauthorizedException, NotFoundException, etc.)\n * that implement the RequestException interface: { status, message, requestID }.\n * We duck-type rather than instanceof to avoid coupling to the SDK's class hierarchy.\n */\nfunction isSdkException(\n error: unknown,\n): error is { status: number; message: string; requestID: string; code?: string; errors?: Array<{ message: string }> } {\n if (!(error instanceof Error)) return false;\n const e = error as Error & { status?: unknown; requestID?: unknown };\n return typeof e.status === 'number' && typeof e.requestID === 'string';\n}\n\n/**\n * Create a resource-specific API error handler.\n * Handles both raw fetch errors (WorkOSApiError) and SDK exceptions.\n * Returns a `never` function that writes structured errors and exits.\n */\nexport function createApiErrorHandler(resourceName: string) {\n return (error: unknown): never => {\n // 1. Raw fetch errors (workos-api.ts)\n if (error instanceof WorkOSApiError) {\n exitWithError({\n code: error.code ?? `http_${error.statusCode}`,\n message:\n error.statusCode === 401\n ? 'Invalid API key. Check your environment configuration.'\n : error.statusCode === 404\n ? `${resourceName} not found.`\n : error.statusCode === 422 && error.errors?.length\n ? error.errors.map((e) => e.message).join(', ')\n : error.message,\n details: error.errors,\n });\n }\n\n // 2. SDK exceptions (@workos-inc/node)\n if (isSdkException(error)) {\n exitWithError({\n code: error.code ?? `http_${error.status}`,\n message:\n error.status === 401\n ? 'Invalid API key. Check your environment configuration.'\n : error.status === 404\n ? `${resourceName} not found.`\n : error.status === 422 && error.errors?.length\n ? error.errors.map((e) => e.message).join(', ')\n : error.message,\n details: error.errors,\n });\n }\n\n // 3. Fallback\n exitWithError({\n code: 'unknown_error',\n message: error instanceof Error ? error.message : 'Unknown error',\n });\n };\n}\n"]}
1
+ {"version":3,"file":"api-error-handler.js","sourceRoot":"","sources":["../../src/lib/api-error-handler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAEnD,MAAM,UAAU,cAAc,CAC5B,KAAc;IAEd,IAAI,CAAC,CAAC,KAAK,YAAY,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAC5C,MAAM,CAAC,GAAG,KAA0D,CAAC;IACrE,OAAO,OAAO,CAAC,CAAC,MAAM,KAAK,QAAQ,IAAI,OAAO,CAAC,CAAC,SAAS,KAAK,QAAQ,CAAC;AACzE,CAAC;AASD,SAAS,iBAAiB,CAAC,KAAc;IACvC,IAAI,KAAK,YAAY,cAAc,EAAE,CAAC;QACpC,OAAO;YACL,MAAM,EAAE,KAAK,CAAC,UAAU;YACxB,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,OAAO,EAAE,KAAK,CAAC,OAAO;SACvB,CAAC;IACJ,CAAC;IAED,IAAI,cAAc,CAAC,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO;YACL,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,OAAO,EAAE,KAAK,CAAC,OAAO;SACvB,CAAC;IACJ,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,kBAAkB,CAAC,KAAyB,EAAE,KAAa;IAClE,IAAI,KAAK,CAAC,MAAM,KAAK,GAAG;QAAE,OAAO,wDAAwD,CAAC;IAC1F,IAAI,KAAK,CAAC,MAAM,KAAK,GAAG;QAAE,OAAO,GAAG,KAAK,aAAa,CAAC;IACvD,IAAI,KAAK,CAAC,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC,MAAM,EAAE,MAAM;QAAE,OAAO,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACvG,OAAO,KAAK,CAAC,OAAO,CAAC;AACvB,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,qBAAqB,CAAC,YAAoB;IACxD,OAAO,CAAC,KAAc,EAAE,OAAgB,EAAS,EAAE;QACjD,IAAI,KAAK,YAAY,SAAS,IAAI,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,wBAAwB,CAAC,EAAE,CAAC;YACnF,aAAa,CAAC;gBACZ,IAAI,EAAE,sBAAsB;gBAC5B,OAAO,EAAE,GAAG,YAAY,yEAAyE;gBACjG,UAAU,EAAE,EAAE,QAAQ,EAAE,YAAY,EAAE;aACvC,CAAC,CAAC;QACL,CAAC;QAED,MAAM,KAAK,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,YAAY,KAAK,OAAO,GAAG,CAAC,CAAC,CAAC,YAAY,CAAC;QACtE,MAAM,QAAQ,GAAG,iBAAiB,CAAC,KAAK,CAAC,CAAC;QAC1C,IAAI,QAAQ,EAAE,CAAC;YACb,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,IAAI,QAAQ,QAAQ,CAAC,MAAM,EAAE,CAAC;YACxD,aAAa,CAAC;gBACZ,IAAI;gBACJ,OAAO,EAAE,kBAAkB,CAAC,QAAQ,EAAE,KAAK,CAAC;gBAC5C,OAAO,EAAE,QAAQ,CAAC,MAAM;gBACxB,UAAU,EAAE;oBACV,MAAM,EAAE,QAAQ,CAAC,MAAM;oBACvB,IAAI;oBACJ,QAAQ,EAAE,YAAY;iBACvB;aACF,CAAC,CAAC;QACL,CAAC;QAED,aAAa,CAAC;YACZ,IAAI,EAAE,eAAe;YACrB,OAAO,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe;YACjE,UAAU,EAAE,EAAE,QAAQ,EAAE,YAAY,EAAE;SACvC,CAAC,CAAC;IACL,CAAC,CAAC;AACJ,CAAC","sourcesContent":["import { WorkOSApiError } from './workos-api.js';\nimport { exitWithError } from '../utils/output.js';\n\nexport function isSdkException(\n error: unknown,\n): error is { status: number; message: string; requestID: string; code?: string; errors?: Array<{ message: string }> } {\n if (!(error instanceof Error)) return false;\n const e = error as Error & { status?: unknown; requestID?: unknown };\n return typeof e.status === 'number' && typeof e.requestID === 'string';\n}\n\ninterface NormalizedApiError {\n status: number;\n code?: string;\n errors?: Array<{ message: string }>;\n message: string;\n}\n\nfunction normalizeApiError(error: unknown): NormalizedApiError | null {\n if (error instanceof WorkOSApiError) {\n return {\n status: error.statusCode,\n code: error.code,\n errors: error.errors,\n message: error.message,\n };\n }\n\n if (isSdkException(error)) {\n return {\n status: error.status,\n code: error.code,\n errors: error.errors,\n message: error.message,\n };\n }\n\n return null;\n}\n\nfunction getApiErrorMessage(error: NormalizedApiError, label: string): string {\n if (error.status === 401) return 'Invalid API key. Check your environment configuration.';\n if (error.status === 404) return `${label} not found.`;\n if (error.status === 422 && error.errors?.length) return error.errors.map((e) => e.message).join(', ');\n return error.message;\n}\n\n/**\n * Create a resource-specific API error handler.\n * Handles raw fetch errors (WorkOSApiError), SDK exceptions, and the SDK's\n * \"errors is not iterable\" TypeError from malformed 422 responses.\n *\n * `context` optionally names the specific resource instance (e.g. a vault\n * object name) so 404 messages can be more specific.\n */\nexport function createApiErrorHandler(resourceName: string) {\n return (error: unknown, context?: string): never => {\n if (error instanceof TypeError && error.message.includes('errors is not iterable')) {\n exitWithError({\n code: 'unprocessable_entity',\n message: `${resourceName} API rejected the request. Check that all required fields are provided.`,\n apiContext: { resource: resourceName },\n });\n }\n\n const label = context ? `${resourceName} '${context}'` : resourceName;\n const apiError = normalizeApiError(error);\n if (apiError) {\n const code = apiError.code ?? `http_${apiError.status}`;\n exitWithError({\n code,\n message: getApiErrorMessage(apiError, label),\n details: apiError.errors,\n apiContext: {\n status: apiError.status,\n code,\n resource: resourceName,\n },\n });\n }\n\n exitWithError({\n code: 'unknown_error',\n message: error instanceof Error ? error.message : 'Unknown error',\n apiContext: { resource: resourceName },\n });\n };\n}\n"]}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Shared canonical command alias map.
3
+ * Single source of truth for both telemetry and help-json.
4
+ *
5
+ * Keys are user-facing aliases, values are canonical command names.
6
+ * Adding an alias here updates both metrics aggregation and --help --json output.
7
+ */
8
+ export declare const COMMAND_ALIASES: Record<string, string>;
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Shared canonical command alias map.
3
+ * Single source of truth for both telemetry and help-json.
4
+ *
5
+ * Keys are user-facing aliases, values are canonical command names.
6
+ * Adding an alias here updates both metrics aggregation and --help --json output.
7
+ */
8
+ export const COMMAND_ALIASES = {
9
+ org: 'organization',
10
+ claim: 'env.claim',
11
+ };
12
+ //# sourceMappingURL=command-aliases.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command-aliases.js","sourceRoot":"","sources":["../../src/lib/command-aliases.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,eAAe,GAA2B;IACrD,GAAG,EAAE,cAAc;IACnB,KAAK,EAAE,WAAW;CACnB,CAAC","sourcesContent":["/**\n * Shared canonical command alias map.\n * Single source of truth for both telemetry and help-json.\n *\n * Keys are user-facing aliases, values are canonical command names.\n * Adding an alias here updates both metrics aggregation and --help --json output.\n */\nexport const COMMAND_ALIASES: Record<string, string> = {\n org: 'organization',\n claim: 'env.claim',\n};\n"]}
@@ -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"]}
@@ -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":"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","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