workos 0.15.2 → 0.17.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 (134) hide show
  1. package/README.md +44 -14
  2. package/dist/bin.js +1449 -1261
  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/adapters/cli-adapter.d.ts +7 -0
  43. package/dist/lib/adapters/cli-adapter.js +49 -0
  44. package/dist/lib/adapters/cli-adapter.js.map +1 -1
  45. package/dist/lib/adapters/dashboard-adapter.d.ts +4 -0
  46. package/dist/lib/adapters/dashboard-adapter.js +24 -0
  47. package/dist/lib/adapters/dashboard-adapter.js.map +1 -1
  48. package/dist/lib/adapters/headless-adapter.d.ts +6 -0
  49. package/dist/lib/adapters/headless-adapter.js +26 -1
  50. package/dist/lib/adapters/headless-adapter.js.map +1 -1
  51. package/dist/lib/api-error-handler.d.ts +15 -3
  52. package/dist/lib/api-error-handler.js +52 -34
  53. package/dist/lib/api-error-handler.js.map +1 -1
  54. package/dist/lib/command-aliases.d.ts +8 -0
  55. package/dist/lib/command-aliases.js +12 -0
  56. package/dist/lib/command-aliases.js.map +1 -0
  57. package/dist/lib/constants.d.ts +0 -1
  58. package/dist/lib/constants.js +0 -1
  59. package/dist/lib/constants.js.map +1 -1
  60. package/dist/lib/device-id.d.ts +25 -0
  61. package/dist/lib/device-id.js +102 -0
  62. package/dist/lib/device-id.js.map +1 -0
  63. package/dist/lib/events.d.ts +15 -0
  64. package/dist/lib/events.js.map +1 -1
  65. package/dist/lib/installer-core.d.ts +61 -1
  66. package/dist/lib/installer-core.js +132 -6
  67. package/dist/lib/installer-core.js.map +1 -1
  68. package/dist/lib/installer-core.types.d.ts +24 -0
  69. package/dist/lib/installer-core.types.js.map +1 -1
  70. package/dist/lib/preferences.d.ts +101 -0
  71. package/dist/lib/preferences.js +198 -0
  72. package/dist/lib/preferences.js.map +1 -0
  73. package/dist/lib/run-with-core.js +40 -15
  74. package/dist/lib/run-with-core.js.map +1 -1
  75. package/dist/lib/scaffold/index.d.ts +1 -0
  76. package/dist/lib/scaffold/index.js +2 -0
  77. package/dist/lib/scaffold/index.js.map +1 -0
  78. package/dist/lib/scaffold/scaffold.d.ts +66 -0
  79. package/dist/lib/scaffold/scaffold.js +156 -0
  80. package/dist/lib/scaffold/scaffold.js.map +1 -0
  81. package/dist/lib/settings.d.ts +6 -0
  82. package/dist/lib/settings.js +7 -0
  83. package/dist/lib/settings.js.map +1 -1
  84. package/dist/lib/telemetry-notice.d.ts +25 -0
  85. package/dist/lib/telemetry-notice.js +56 -0
  86. package/dist/lib/telemetry-notice.js.map +1 -0
  87. package/dist/run.d.ts +2 -2
  88. package/dist/run.js +2 -1
  89. package/dist/run.js.map +1 -1
  90. package/dist/test/force-insecure-storage.d.ts +1 -0
  91. package/dist/test/force-insecure-storage.js +9 -0
  92. package/dist/test/force-insecure-storage.js.map +1 -0
  93. package/dist/test/setup.d.ts +1 -0
  94. package/dist/test/setup.js +38 -0
  95. package/dist/test/setup.js.map +1 -0
  96. package/dist/utils/analytics.d.ts +41 -0
  97. package/dist/utils/analytics.js +199 -12
  98. package/dist/utils/analytics.js.map +1 -1
  99. package/dist/utils/box.d.ts +29 -1
  100. package/dist/utils/box.js +92 -4
  101. package/dist/utils/box.js.map +1 -1
  102. package/dist/utils/cli-exit.d.ts +15 -0
  103. package/dist/utils/cli-exit.js +11 -0
  104. package/dist/utils/cli-exit.js.map +1 -0
  105. package/dist/utils/cli-symbols.d.ts +1 -1
  106. package/dist/utils/command-telemetry.d.ts +17 -0
  107. package/dist/utils/command-telemetry.js +67 -0
  108. package/dist/utils/command-telemetry.js.map +1 -0
  109. package/dist/utils/crash-reporter.d.ts +13 -0
  110. package/dist/utils/crash-reporter.js +91 -0
  111. package/dist/utils/crash-reporter.js.map +1 -0
  112. package/dist/utils/debug.d.ts +1 -0
  113. package/dist/utils/debug.js +4 -1
  114. package/dist/utils/debug.js.map +1 -1
  115. package/dist/utils/exit-codes.d.ts +5 -0
  116. package/dist/utils/exit-codes.js +30 -1
  117. package/dist/utils/exit-codes.js.map +1 -1
  118. package/dist/utils/help-json.d.ts +6 -0
  119. package/dist/utils/help-json.js +87 -10
  120. package/dist/utils/help-json.js.map +1 -1
  121. package/dist/utils/output.d.ts +7 -2
  122. package/dist/utils/output.js +9 -2
  123. package/dist/utils/output.js.map +1 -1
  124. package/dist/utils/telemetry-client.d.ts +30 -2
  125. package/dist/utils/telemetry-client.js +122 -12
  126. package/dist/utils/telemetry-client.js.map +1 -1
  127. package/dist/utils/telemetry-store-forward.d.ts +11 -0
  128. package/dist/utils/telemetry-store-forward.js +94 -0
  129. package/dist/utils/telemetry-store-forward.js.map +1 -0
  130. package/dist/utils/telemetry-types.d.ts +58 -9
  131. package/dist/utils/telemetry-types.js.map +1 -1
  132. package/dist/utils/types.d.ts +10 -4
  133. package/dist/utils/types.js.map +1 -1
  134. 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":"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","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"]}
@@ -144,6 +144,21 @@ export interface InstallerEvents {
144
144
  issueCount: number;
145
145
  durationMs: number;
146
146
  };
147
+ 'scaffold:checking': Record<string, never>;
148
+ 'scaffold:prompt': {
149
+ packageManager: string;
150
+ };
151
+ 'scaffold:start': {
152
+ packageManager: string;
153
+ };
154
+ 'scaffold:progress': {
155
+ text: string;
156
+ };
157
+ 'scaffold:complete': Record<string, never>;
158
+ 'scaffold:failed': {
159
+ error: string;
160
+ };
161
+ 'scaffold:skipped': Record<string, never>;
147
162
  'branch:checking': Record<string, never>;
148
163
  'branch:protected': {
149
164
  branch: string;
@@ -1 +1 @@
1
- {"version":3,"file":"events.js","sourceRoot":"","sources":["../../src/lib/events.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AA2FtC,MAAM,OAAO,qBAAsB,SAAQ,YAAY;IACrD,IAAI,CAA+B,KAAQ,EAAE,OAA2B;QACtE,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IACpC,CAAC;IAED,EAAE,CAA+B,KAAQ,EAAE,QAA+C;QACxF,OAAO,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;IACnC,CAAC;IAED,GAAG,CAA+B,KAAQ,EAAE,QAA+C;QACzF,OAAO,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;IACpC,CAAC;IAED,IAAI,CAA+B,KAAQ,EAAE,QAA+C;QAC1F,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;IACrC,CAAC;CACF;AAED,MAAM,UAAU,2BAA2B;IACzC,OAAO,IAAI,qBAAqB,EAAE,CAAC;AACrC,CAAC","sourcesContent":["import { EventEmitter } from 'events';\n\nexport interface InstallerEvents {\n status: { message: string };\n output: { text: string; isError?: boolean };\n 'file:write': { path: string; content: string };\n 'file:edit': { path: string; oldContent: string; newContent: string };\n 'prompt:request': { id: string; message: string; options?: string[] };\n 'prompt:response': { id: string; value: string };\n 'confirm:request': { id: string; message: string; warning?: string; files?: string[] };\n 'confirm:response': { id: string; confirmed: boolean };\n 'credentials:request': { requiresApiKey: boolean };\n 'credentials:response': { apiKey: string; clientId: string };\n complete: { success: boolean; summary?: string };\n error: { message: string; stack?: string };\n\n 'state:enter': { state: string };\n 'state:exit': { state: string };\n 'auth:checking': Record<string, never>;\n 'auth:required': Record<string, never>;\n 'auth:success': Record<string, never>;\n 'auth:failure': { message: string };\n 'detection:start': Record<string, never>;\n 'detection:complete': { integration: string };\n 'detection:none': Record<string, never>;\n 'git:checking': Record<string, never>;\n 'git:clean': Record<string, never>;\n 'git:dirty': { files: string[] };\n 'git:dirty:confirmed': Record<string, never>;\n 'git:dirty:cancelled': Record<string, never>;\n 'credentials:gathering': { requiresApiKey: boolean };\n 'credentials:found': Record<string, never>;\n // Credential discovery events\n 'credentials:env:detected': { files: string[] };\n 'credentials:env:prompt': { files: string[] };\n 'credentials:env:scanning': Record<string, never>;\n 'credentials:env:found': { sourcePath: string };\n 'credentials:env:notfound': Record<string, never>;\n // Device auth events\n 'device:started': { verificationUri: string; verificationUriComplete: string; userCode: string };\n 'device:polling': Record<string, never>;\n 'device:success': { email?: string };\n 'device:timeout': Record<string, never>;\n 'device:error': { message: string };\n // Staging API events\n 'staging:fetching': Record<string, never>;\n 'staging:success': Record<string, never>;\n 'staging:error': { message: string; statusCode?: number };\n 'config:start': Record<string, never>;\n 'config:complete': Record<string, never>;\n 'agent:start': Record<string, never>;\n 'agent:progress': { step: string; detail?: string };\n 'agent:success': { summary?: string };\n 'agent:failure': { message: string; stack?: string };\n 'agent:retry': { attempt: number; maxRetries: number };\n\n 'validation:retry:start': { attempt: number };\n 'validation:retry:complete': { attempt: number; passed: boolean };\n\n 'validation:start': { framework: string };\n 'validation:issues': { issues: import('./validation/types.js').ValidationIssue[] };\n 'validation:complete': { passed: boolean; issueCount: number; durationMs: number };\n\n // Branch check events\n 'branch:checking': Record<string, never>;\n 'branch:protected': { branch: string };\n 'branch:prompt': { branch: string };\n 'branch:created': { branch: string };\n 'branch:create:failed': { error: string };\n 'branch:skipped': Record<string, never>;\n\n // Post-install events\n 'postinstall:changes': { files: string[] };\n 'postinstall:nochanges': Record<string, never>;\n 'postinstall:commit:prompt': Record<string, never>;\n 'postinstall:commit:generating': Record<string, never>;\n 'postinstall:commit:committing': { message: string };\n 'postinstall:commit:success': { message: string };\n 'postinstall:commit:failed': { error: string };\n 'postinstall:pr:prompt': Record<string, never>;\n 'postinstall:pr:generating': Record<string, never>;\n 'postinstall:pr:pushing': Record<string, never>;\n 'postinstall:pr:creating': Record<string, never>;\n 'postinstall:pr:success': { url: string };\n 'postinstall:pr:failed': { error: string };\n 'postinstall:push:failed': { error: string };\n 'postinstall:manual': { instructions: string };\n}\n\nexport type InstallerEventName = keyof InstallerEvents;\n\nexport class InstallerEventEmitter extends EventEmitter {\n emit<K extends InstallerEventName>(event: K, payload: InstallerEvents[K]): boolean {\n return super.emit(event, payload);\n }\n\n on<K extends InstallerEventName>(event: K, listener: (payload: InstallerEvents[K]) => void): this {\n return super.on(event, listener);\n }\n\n off<K extends InstallerEventName>(event: K, listener: (payload: InstallerEvents[K]) => void): this {\n return super.off(event, listener);\n }\n\n once<K extends InstallerEventName>(event: K, listener: (payload: InstallerEvents[K]) => void): this {\n return super.once(event, listener);\n }\n}\n\nexport function createInstallerEventEmitter(): InstallerEventEmitter {\n return new InstallerEventEmitter();\n}\n"]}
1
+ {"version":3,"file":"events.js","sourceRoot":"","sources":["../../src/lib/events.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAoGtC,MAAM,OAAO,qBAAsB,SAAQ,YAAY;IACrD,IAAI,CAA+B,KAAQ,EAAE,OAA2B;QACtE,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IACpC,CAAC;IAED,EAAE,CAA+B,KAAQ,EAAE,QAA+C;QACxF,OAAO,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;IACnC,CAAC;IAED,GAAG,CAA+B,KAAQ,EAAE,QAA+C;QACzF,OAAO,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;IACpC,CAAC;IAED,IAAI,CAA+B,KAAQ,EAAE,QAA+C;QAC1F,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;IACrC,CAAC;CACF;AAED,MAAM,UAAU,2BAA2B;IACzC,OAAO,IAAI,qBAAqB,EAAE,CAAC;AACrC,CAAC","sourcesContent":["import { EventEmitter } from 'events';\n\nexport interface InstallerEvents {\n status: { message: string };\n output: { text: string; isError?: boolean };\n 'file:write': { path: string; content: string };\n 'file:edit': { path: string; oldContent: string; newContent: string };\n 'prompt:request': { id: string; message: string; options?: string[] };\n 'prompt:response': { id: string; value: string };\n 'confirm:request': { id: string; message: string; warning?: string; files?: string[] };\n 'confirm:response': { id: string; confirmed: boolean };\n 'credentials:request': { requiresApiKey: boolean };\n 'credentials:response': { apiKey: string; clientId: string };\n complete: { success: boolean; summary?: string };\n error: { message: string; stack?: string };\n\n 'state:enter': { state: string };\n 'state:exit': { state: string };\n 'auth:checking': Record<string, never>;\n 'auth:required': Record<string, never>;\n 'auth:success': Record<string, never>;\n 'auth:failure': { message: string };\n 'detection:start': Record<string, never>;\n 'detection:complete': { integration: string };\n 'detection:none': Record<string, never>;\n 'git:checking': Record<string, never>;\n 'git:clean': Record<string, never>;\n 'git:dirty': { files: string[] };\n 'git:dirty:confirmed': Record<string, never>;\n 'git:dirty:cancelled': Record<string, never>;\n 'credentials:gathering': { requiresApiKey: boolean };\n 'credentials:found': Record<string, never>;\n // Credential discovery events\n 'credentials:env:detected': { files: string[] };\n 'credentials:env:prompt': { files: string[] };\n 'credentials:env:scanning': Record<string, never>;\n 'credentials:env:found': { sourcePath: string };\n 'credentials:env:notfound': Record<string, never>;\n // Device auth events\n 'device:started': { verificationUri: string; verificationUriComplete: string; userCode: string };\n 'device:polling': Record<string, never>;\n 'device:success': { email?: string };\n 'device:timeout': Record<string, never>;\n 'device:error': { message: string };\n // Staging API events\n 'staging:fetching': Record<string, never>;\n 'staging:success': Record<string, never>;\n 'staging:error': { message: string; statusCode?: number };\n 'config:start': Record<string, never>;\n 'config:complete': Record<string, never>;\n 'agent:start': Record<string, never>;\n 'agent:progress': { step: string; detail?: string };\n 'agent:success': { summary?: string };\n 'agent:failure': { message: string; stack?: string };\n 'agent:retry': { attempt: number; maxRetries: number };\n\n 'validation:retry:start': { attempt: number };\n 'validation:retry:complete': { attempt: number; passed: boolean };\n\n 'validation:start': { framework: string };\n 'validation:issues': { issues: import('./validation/types.js').ValidationIssue[] };\n 'validation:complete': { passed: boolean; issueCount: number; durationMs: number };\n\n // Scaffold events (empty-directory app scaffolding)\n 'scaffold:checking': Record<string, never>;\n 'scaffold:prompt': { packageManager: string };\n 'scaffold:start': { packageManager: string };\n 'scaffold:progress': { text: string };\n 'scaffold:complete': Record<string, never>;\n 'scaffold:failed': { error: string };\n 'scaffold:skipped': Record<string, never>;\n\n // Branch check events\n 'branch:checking': Record<string, never>;\n 'branch:protected': { branch: string };\n 'branch:prompt': { branch: string };\n 'branch:created': { branch: string };\n 'branch:create:failed': { error: string };\n 'branch:skipped': Record<string, never>;\n\n // Post-install events\n 'postinstall:changes': { files: string[] };\n 'postinstall:nochanges': Record<string, never>;\n 'postinstall:commit:prompt': Record<string, never>;\n 'postinstall:commit:generating': Record<string, never>;\n 'postinstall:commit:committing': { message: string };\n 'postinstall:commit:success': { message: string };\n 'postinstall:commit:failed': { error: string };\n 'postinstall:pr:prompt': Record<string, never>;\n 'postinstall:pr:generating': Record<string, never>;\n 'postinstall:pr:pushing': Record<string, never>;\n 'postinstall:pr:creating': Record<string, never>;\n 'postinstall:pr:success': { url: string };\n 'postinstall:pr:failed': { error: string };\n 'postinstall:push:failed': { error: string };\n 'postinstall:manual': { instructions: string };\n}\n\nexport type InstallerEventName = keyof InstallerEvents;\n\nexport class InstallerEventEmitter extends EventEmitter {\n emit<K extends InstallerEventName>(event: K, payload: InstallerEvents[K]): boolean {\n return super.emit(event, payload);\n }\n\n on<K extends InstallerEventName>(event: K, listener: (payload: InstallerEvents[K]) => void): this {\n return super.on(event, listener);\n }\n\n off<K extends InstallerEventName>(event: K, listener: (payload: InstallerEvents[K]) => void): this {\n return super.off(event, listener);\n }\n\n once<K extends InstallerEventName>(event: K, listener: (payload: InstallerEvents[K]) => void): this {\n return super.once(event, listener);\n }\n}\n\nexport function createInstallerEventEmitter(): InstallerEventEmitter {\n return new InstallerEventEmitter();\n}\n"]}
@@ -1,5 +1,5 @@
1
1
  import { type ActorRefFrom } from 'xstate';
2
- import type { InstallerMachineContext, InstallerMachineInput, DetectionOutput, GitCheckOutput, AgentOutput, EnvFileInfo, DiscoveryResult, BranchCheckOutput } from './installer-core.types.js';
2
+ import type { InstallerMachineContext, InstallerMachineInput, DetectionOutput, GitCheckOutput, AgentOutput, EnvFileInfo, DiscoveryResult, BranchCheckOutput, WorkspaceCheckOutput } from './installer-core.types.js';
3
3
  import type { InstallerOptions } from '../utils/types.js';
4
4
  import type { DeviceAuthResult, DeviceAuthResponse } from './device-auth.js';
5
5
  import type { StagingCredentials } from './staging-api.js';
@@ -11,6 +11,10 @@ export declare const installerMachine: import("xstate").StateMachine<InstallerMa
11
11
  type: "GIT_CONFIRMED";
12
12
  } | {
13
13
  type: "GIT_CANCELLED";
14
+ } | {
15
+ type: "SCAFFOLD_CONFIRMED";
16
+ } | {
17
+ type: "SCAFFOLD_CANCELLED";
14
18
  } | {
15
19
  type: "CREDENTIALS_SUBMITTED";
16
20
  apiKey: string;
@@ -44,6 +48,10 @@ export declare const installerMachine: import("xstate").StateMachine<InstallerMa
44
48
  cwd: string;
45
49
  }, import("xstate").EventObject>> | import("xstate").ActorRefFromLogic<import("xstate").PromiseActorLogic<boolean, {
46
50
  options: InstallerOptions;
51
+ }, import("xstate").EventObject>> | import("xstate").ActorRefFromLogic<import("xstate").PromiseActorLogic<WorkspaceCheckOutput, {
52
+ options: InstallerOptions;
53
+ }, import("xstate").EventObject>> | import("xstate").ActorRefFromLogic<import("xstate").PromiseActorLogic<void, {
54
+ context: InstallerMachineContext;
47
55
  }, import("xstate").EventObject>> | import("xstate").ActorRefFromLogic<import("xstate").PromiseActorLogic<DetectionOutput, {
48
56
  options: InstallerOptions;
49
57
  }, import("xstate").EventObject>> | import("xstate").ActorRefFromLogic<import("xstate").PromiseActorLogic<GitCheckOutput, {
@@ -98,6 +106,18 @@ export declare const installerMachine: import("xstate").StateMachine<InstallerMa
98
106
  options: InstallerOptions;
99
107
  }, import("xstate").EventObject>;
100
108
  id: string | undefined;
109
+ } | {
110
+ src: "checkWorkspace";
111
+ logic: import("xstate").PromiseActorLogic<WorkspaceCheckOutput, {
112
+ options: InstallerOptions;
113
+ }, import("xstate").EventObject>;
114
+ id: string | undefined;
115
+ } | {
116
+ src: "runScaffold";
117
+ logic: import("xstate").PromiseActorLogic<void, {
118
+ context: InstallerMachineContext;
119
+ }, import("xstate").EventObject>;
120
+ id: string | undefined;
101
121
  } | {
102
122
  src: "detectIntegration";
103
123
  logic: import("xstate").PromiseActorLogic<DetectionOutput, {
@@ -244,6 +264,30 @@ export declare const installerMachine: import("xstate").StateMachine<InstallerMa
244
264
  } | {
245
265
  type: "emitGitCancelled";
246
266
  params: unknown;
267
+ } | {
268
+ type: "emitScaffoldChecking";
269
+ params: unknown;
270
+ } | {
271
+ type: "emitScaffoldPrompt";
272
+ params: unknown;
273
+ } | {
274
+ type: "emitScaffoldStart";
275
+ params: unknown;
276
+ } | {
277
+ type: "emitScaffoldComplete";
278
+ params: unknown;
279
+ } | {
280
+ type: "emitScaffoldFailed";
281
+ params: unknown;
282
+ } | {
283
+ type: "emitScaffoldSkipped";
284
+ params: unknown;
285
+ } | {
286
+ type: "assignWorkspaceResult";
287
+ params: unknown;
288
+ } | {
289
+ type: "assignScaffolded";
290
+ params: unknown;
247
291
  } | {
248
292
  type: "emitBranchChecking";
249
293
  params: unknown;
@@ -416,7 +460,15 @@ export declare const installerMachine: import("xstate").StateMachine<InstallerMa
416
460
  } | {
417
461
  type: "hasGhCli";
418
462
  params: unknown;
463
+ } | {
464
+ type: "notScaffoldable";
465
+ params: unknown;
466
+ } | {
467
+ type: "shouldAutoScaffold";
468
+ params: unknown;
419
469
  }, never, "error" | "cancelled" | "complete" | "idle" | "authenticating" | "configuring" | "runningAgent" | {
470
+ scaffold: "done" | "checking" | "running" | "prompting";
471
+ } | {
420
472
  preparing: {
421
473
  detection: "done" | "running";
422
474
  gitCheck: "done" | "running" | "evaluating" | "awaitingConfirmation";
@@ -431,6 +483,14 @@ export declare const installerMachine: import("xstate").StateMachine<InstallerMa
431
483
  states: {
432
484
  readonly idle: {};
433
485
  readonly authenticating: {};
486
+ readonly scaffold: {
487
+ states: {
488
+ readonly checking: {};
489
+ readonly prompting: {};
490
+ readonly running: {};
491
+ readonly done: {};
492
+ };
493
+ };
434
494
  readonly preparing: {
435
495
  states: {
436
496
  readonly detection: {