@taskforcehq/taskforce 0.3.304 → 0.3.305

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/README.md +140 -247
  2. package/dist/TaskforceCore.js +165 -70
  3. package/dist/TaskforceCore.test.js +799 -90
  4. package/dist/compat/workspaceSyncCompat.js +6 -0
  5. package/dist/components/features/DocumentIndex.d.ts +1 -1
  6. package/dist/components/features/DocumentIndex.js +1 -1
  7. package/dist/components/features/DocumentViewer.d.ts +1 -1
  8. package/dist/components/features/DocumentViewer.js +32 -9
  9. package/dist/components/features/DocumentWorkspace.d.ts +5 -22
  10. package/dist/components/features/DocumentWorkspace.js +12 -155
  11. package/dist/components/features/DocumentWorkspace.test.js +193 -6
  12. package/dist/components/features/TaskSettings.js +29 -3
  13. package/dist/components/features/TaskSettings.test.js +42 -1
  14. package/dist/components/features/documentWorkspaceModel.d.ts +24 -0
  15. package/dist/components/features/documentWorkspaceModel.js +57 -0
  16. package/dist/components/features/useDocumentWorkspaceController.d.ts +34 -0
  17. package/dist/components/features/useDocumentWorkspaceController.js +113 -0
  18. package/dist/components/task/TaskForm.d.ts +8 -8
  19. package/dist/components/task/TaskForm.js +358 -221
  20. package/dist/components/task/TaskForm.test.js +456 -381
  21. package/dist/components/task/TaskKanban.js +2 -1
  22. package/dist/components/views/PlanComparisonPage.js +5 -8
  23. package/dist/components/views/PlanComparisonPage.test.js +51 -41
  24. package/dist/components/views/PlansPage.js +126 -84
  25. package/dist/components/views/PlansPage.test.js +308 -39
  26. package/dist/components/views/StandaloneLayout.js +28 -22
  27. package/dist/components/views/panels/FilterToolbar.test.js +6 -4
  28. package/dist/components/views/standalone/modals/AccountHubModal.d.ts +2 -14
  29. package/dist/components/views/standalone/modals/AccountHubModal.js +12 -14
  30. package/dist/components/views/standalone/modals/AccountHubModal.test.js +24 -1
  31. package/dist/config/envSchema.js +1 -1
  32. package/dist/core/AiProfileService.d.ts +6 -1
  33. package/dist/core/AiProfileService.js +161 -16
  34. package/dist/core/AiProfileService.test.js +3 -1
  35. package/dist/core/AiProfiles.test.js +200 -0
  36. package/dist/core/AuthTokenService.test.d.ts +1 -0
  37. package/dist/core/AuthTokenService.test.js +78 -0
  38. package/dist/core/EntitlementsPolicy.test.js +75 -13
  39. package/dist/core/PlanEntitlementService.d.ts +7 -1
  40. package/dist/core/PlanEntitlementService.js +117 -42
  41. package/dist/core/PlanVersionPolicy.test.js +19 -26
  42. package/dist/core/SignupMonetizationSettings.test.js +46 -21
  43. package/dist/core/SystemAdmin.test.js +212 -56
  44. package/dist/core/TaskTaxonomyValidation.test.js +53 -0
  45. package/dist/core/Taskforce.d.ts +3 -0
  46. package/dist/core/Taskforce.js +50 -10
  47. package/dist/core/WorkspacePlanMode.test.js +12 -12
  48. package/dist/core/shared.d.ts +2 -0
  49. package/dist/core/shared.js +11 -0
  50. package/dist/hooks/sync/auth.js +3 -1
  51. package/dist/hooks/sync/transfers.d.ts +2 -1
  52. package/dist/hooks/sync/transfers.js +8 -4
  53. package/dist/hooks/useSyncOrchestrator.d.ts +7 -4
  54. package/dist/hooks/useSyncOrchestrator.js +75 -7
  55. package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +80 -10
  56. package/dist/hooks/useTaskMutations.d.ts +2 -1
  57. package/dist/hooks/useTaskMutations.js +12 -1
  58. package/dist/hooks/useTaskMutations.test.js +30 -1
  59. package/dist/hooks/useTaskforce.d.ts +5 -2
  60. package/dist/hooks/useTaskforce.js +17 -5
  61. package/dist/hooks/useTaskforce.sync-behavior.test.js +78 -0
  62. package/dist/hooks/useWorkspaceSyncController.d.ts +3 -1
  63. package/dist/hooks/useWorkspaceSyncController.js +11 -1
  64. package/dist/hooks/useWorkspaceSyncController.test.js +1 -0
  65. package/dist/mcp/adminWorkspaceRegistrar.d.ts +2 -0
  66. package/dist/mcp/adminWorkspaceRegistrar.js +154 -0
  67. package/dist/mcp/collaborationRegistrar.d.ts +2 -0
  68. package/dist/mcp/collaborationRegistrar.js +71 -0
  69. package/dist/mcp/documentAssetRegistrar.d.ts +2 -0
  70. package/dist/mcp/documentAssetRegistrar.js +346 -0
  71. package/dist/mcp/runtime.js +2362 -3530
  72. package/dist/mcp/runtime.test.js +159 -0
  73. package/dist/mcp/taskPlanningRegistrar.d.ts +2 -0
  74. package/dist/mcp/taskPlanningRegistrar.js +418 -0
  75. package/dist/mcp/toolCatalog.d.ts +35 -0
  76. package/dist/mcp/toolCatalog.js +3 -0
  77. package/dist/server/routes/admin.d.ts +1 -5
  78. package/dist/server/routes/admin.js +138 -61
  79. package/dist/server/routes/annotatedAttachments.d.ts +1 -1
  80. package/dist/server/routes/annotatedAttachments.js +1 -1
  81. package/dist/server/routes/auth.d.ts +1 -4
  82. package/dist/server/routes/auth.js +23 -63
  83. package/dist/server/routes/authSupport.d.ts +30 -0
  84. package/dist/server/routes/authSupport.js +81 -0
  85. package/dist/server/routes/billing.d.ts +1 -1
  86. package/dist/server/routes/billing.js +104 -188
  87. package/dist/server/routes/billing.test.js +214 -86
  88. package/dist/server/routes/documentReviews.d.ts +1 -1
  89. package/dist/server/routes/documentReviews.js +1 -1
  90. package/dist/server/routes/documents.d.ts +4 -2
  91. package/dist/server/routes/documents.js +73 -7
  92. package/dist/server/routes/primitives.d.ts +11 -0
  93. package/dist/server/routes/primitives.js +73 -0
  94. package/dist/server/routes/resources.d.ts +1 -1
  95. package/dist/server/routes/resources.js +1 -1
  96. package/dist/server/routes/shared.d.ts +1 -1
  97. package/dist/server/routes/sync.d.ts +1 -1
  98. package/dist/server/routes/sync.js +1 -1
  99. package/dist/server/routes/tasks.d.ts +1 -1
  100. package/dist/server/routes/tasks.js +1 -1
  101. package/dist/server/routes/workspaces.d.ts +1 -1
  102. package/dist/server/routes/workspaces.js +1 -1
  103. package/dist/server/routes.d.ts +2 -9
  104. package/dist/server/routes.js +76 -162
  105. package/dist/server/routes.test.js +672 -30
  106. package/dist/sync/contentSyncState.js +16 -7
  107. package/dist/sync/syncApplyHandlers.test.js +2 -0
  108. package/dist/sync/syncService.d.ts +1 -0
  109. package/dist/sync/syncService.js +8 -0
  110. package/dist/sync/syncService.test.js +14 -0
  111. package/dist/sync/workspaceSyncModel.d.ts +3 -0
  112. package/dist/sync/workspaceSyncModel.js +12 -3
  113. package/dist/sync/workspaceSyncModel.test.js +34 -0
  114. package/dist/sync/workspaceSyncState.d.ts +2 -0
  115. package/dist/sync/workspaceSyncState.js +1 -0
  116. package/dist/ui/assets/{AgentsModule-BLWVbuKP.js → AgentsModule-N2MnQBZk.js} +1 -1
  117. package/dist/ui/assets/{AnnotatedAttachmentWorkspace-BlS-cqnl.js → AnnotatedAttachmentWorkspace-BG6P_GVW.js} +1 -1
  118. package/dist/ui/assets/DocumentWorkspace-B3nV_Gc5.css +1 -0
  119. package/dist/ui/assets/DocumentWorkspace-DRbDMwh8.js +252 -0
  120. package/dist/ui/assets/{InitiativesModule-BjR6iBf9.js → InitiativesModule-Dhm7M8e7.js} +1 -1
  121. package/dist/ui/assets/PlansPage-Cq0zXj3I.js +1 -0
  122. package/dist/ui/assets/TaskSettings-ln0aF7xc.js +9 -0
  123. package/dist/ui/assets/{WorkflowsModule-DnFqdUME.js → WorkflowsModule-BcS4afRn.js} +1 -1
  124. package/dist/ui/assets/{index-Ii0B0rTO.css → index-C2-OXZV7.css} +1 -1
  125. package/dist/ui/assets/index-DcSI5F86.js +6 -0
  126. package/dist/ui/assets/{vendor-icons-I7ZwG1z_.js → vendor-icons-BSUaRwF8.js} +1 -1
  127. package/dist/ui/index.html +3 -3
  128. package/dist/utils/commercialLifecyclePresentation.d.ts +19 -0
  129. package/dist/utils/commercialLifecyclePresentation.js +196 -0
  130. package/package.json +8 -1
  131. package/scripts/check-cloud-mcp.mjs +83 -0
  132. package/scripts/playwright-auth.sh +5 -1
  133. package/scripts/run-billing-performance-smoke.sh +24 -0
  134. package/dist/ui/assets/DocumentWorkspace-BH_6DF6x.js +0 -250
  135. package/dist/ui/assets/DocumentWorkspace-C_8T8oz-.css +0 -1
  136. package/dist/ui/assets/PlansPage-CSDQ4sLa.js +0 -1
  137. package/dist/ui/assets/TaskSettings-CEDy34Jo.js +0 -9
  138. package/dist/ui/assets/index-DXUdtH1B.js +0 -6
@@ -8,13 +8,13 @@
8
8
  <link rel="icon" type="image/png" sizes="16x16" href="/favicon/favicon-16x16.png" />
9
9
  <link rel="apple-touch-icon" href="/taskforce/favicon/apple-touch-icon.png" />
10
10
  <title>Taskforce</title>
11
- <script type="module" crossorigin src="/taskforce/assets/index-DXUdtH1B.js"></script>
11
+ <script type="module" crossorigin src="/taskforce/assets/index-DcSI5F86.js"></script>
12
12
  <link rel="modulepreload" crossorigin href="/taskforce/assets/vendor-react-CKJs5o3c.js">
13
- <link rel="modulepreload" crossorigin href="/taskforce/assets/vendor-icons-I7ZwG1z_.js">
13
+ <link rel="modulepreload" crossorigin href="/taskforce/assets/vendor-icons-BSUaRwF8.js">
14
14
  <link rel="modulepreload" crossorigin href="/taskforce/assets/vendor-markdown-BUxTU7dS.js">
15
15
  <link rel="modulepreload" crossorigin href="/taskforce/assets/vendor-dnd-DRzYolkg.js">
16
16
  <link rel="modulepreload" crossorigin href="/taskforce/assets/vendor-router-BbWMxlnO.js">
17
- <link rel="stylesheet" crossorigin href="/taskforce/assets/index-Ii0B0rTO.css">
17
+ <link rel="stylesheet" crossorigin href="/taskforce/assets/index-C2-OXZV7.css">
18
18
  </head>
19
19
  <body>
20
20
  <div id="root"></div>
@@ -0,0 +1,19 @@
1
+ import type { AccountProfileSummary } from './accountProfileSummaryCache';
2
+ export type CommercialLifecyclePrimaryAction = 'choose_plan' | 'open_plans' | 'manage_billing' | 'none';
3
+ export type CommercialLifecycleTone = 'success' | 'info' | 'error';
4
+ export interface CommercialLifecyclePresentation {
5
+ entitlementState: string | null;
6
+ gate: string | null;
7
+ isActive: boolean;
8
+ allowReturnToApp: boolean;
9
+ statusTone: CommercialLifecycleTone;
10
+ message: string | null;
11
+ primaryAction: CommercialLifecyclePrimaryAction;
12
+ primaryLabel: string | null;
13
+ showManageBilling: boolean;
14
+ markCurrentPlan: boolean;
15
+ }
16
+ type AccountProfileSummaryLike = Pick<AccountProfileSummary, 'billingConflictCode' | 'effectiveUntil' | 'entitlementState' | 'gate' | 'message' | 'planSelectionRequired' | 'stripeCustomerId' | 'stripeSubscriptionId' | 'trialEnd'> | null | undefined;
17
+ export declare function isCommercialEntitlementActive(raw: string | null | undefined): boolean;
18
+ export declare function resolveCommercialLifecyclePresentation(summary: AccountProfileSummaryLike, fallbackGate?: string | null): CommercialLifecyclePresentation;
19
+ export {};
@@ -0,0 +1,196 @@
1
+ const ACTIVE_ENTITLEMENT_STATES = new Set(['active', 'trialing', 'grace']);
2
+ const BILLING_CONFLICT_MESSAGES = {
3
+ CHECKOUT_SESSION_EXPIRED: 'Your previous checkout expired. Choose a plan to continue.',
4
+ CHECKOUT_SESSION_SUPERSEDED: 'A newer checkout replaced your previous checkout. Choose a plan to continue.'
5
+ };
6
+ const GATE_MESSAGES = {
7
+ planSelectionRequired: 'Choose a plan to continue.',
8
+ checkoutPending: 'Complete checkout to continue.',
9
+ misconfigured: 'Onboarding policy is misconfigured. Contact support or a system administrator.',
10
+ missingEntitlement: 'No entitlement is linked to this account.'
11
+ };
12
+ function normalizeString(value) {
13
+ return String(value || '').trim();
14
+ }
15
+ function normalizeEntitlementState(value) {
16
+ return normalizeString(value).toLowerCase();
17
+ }
18
+ function normalizeGate(value) {
19
+ return normalizeString(value).toLowerCase();
20
+ }
21
+ function hasBillingContext(summary) {
22
+ return Boolean(normalizeString(summary?.stripeCustomerId) || normalizeString(summary?.stripeSubscriptionId));
23
+ }
24
+ function resolveTrialMessage(summary) {
25
+ const trialEnd = normalizeString(summary?.trialEnd || summary?.effectiveUntil);
26
+ if (trialEnd) {
27
+ const formatted = new Date(trialEnd);
28
+ if (!Number.isNaN(formatted.getTime())) {
29
+ return `Your trial is active through ${formatted.toLocaleString()}.`;
30
+ }
31
+ }
32
+ return 'Your trial is active. Continue to Taskforce or manage billing anytime.';
33
+ }
34
+ export function isCommercialEntitlementActive(raw) {
35
+ return ACTIVE_ENTITLEMENT_STATES.has(normalizeEntitlementState(raw));
36
+ }
37
+ export function resolveCommercialLifecyclePresentation(summary, fallbackGate) {
38
+ const entitlementState = normalizeEntitlementState(summary?.entitlementState);
39
+ const gate = normalizeGate(summary?.gate || fallbackGate);
40
+ const billingConflictCode = normalizeString(summary?.billingConflictCode).toUpperCase();
41
+ const summaryMessage = normalizeString(summary?.message);
42
+ const isActive = isCommercialEntitlementActive(entitlementState);
43
+ const billingAvailable = hasBillingContext(summary);
44
+ if (billingConflictCode === 'CHECKOUT_SESSION_EXPIRED') {
45
+ return {
46
+ entitlementState: entitlementState || null,
47
+ gate: gate || null,
48
+ isActive: false,
49
+ allowReturnToApp: false,
50
+ statusTone: 'info',
51
+ message: BILLING_CONFLICT_MESSAGES.CHECKOUT_SESSION_EXPIRED,
52
+ primaryAction: 'choose_plan',
53
+ primaryLabel: 'Choose Plan',
54
+ showManageBilling: billingAvailable,
55
+ markCurrentPlan: false,
56
+ };
57
+ }
58
+ if (billingConflictCode === 'CHECKOUT_SESSION_SUPERSEDED') {
59
+ return {
60
+ entitlementState: entitlementState || null,
61
+ gate: gate || null,
62
+ isActive: false,
63
+ allowReturnToApp: false,
64
+ statusTone: 'info',
65
+ message: BILLING_CONFLICT_MESSAGES.CHECKOUT_SESSION_SUPERSEDED,
66
+ primaryAction: 'choose_plan',
67
+ primaryLabel: 'Choose Plan',
68
+ showManageBilling: billingAvailable,
69
+ markCurrentPlan: false,
70
+ };
71
+ }
72
+ if (entitlementState === 'trialing') {
73
+ return {
74
+ entitlementState,
75
+ gate: gate || null,
76
+ isActive: true,
77
+ allowReturnToApp: true,
78
+ statusTone: 'success',
79
+ message: resolveTrialMessage(summary),
80
+ primaryAction: billingAvailable ? 'manage_billing' : 'none',
81
+ primaryLabel: billingAvailable ? 'Manage Billing' : null,
82
+ showManageBilling: billingAvailable,
83
+ markCurrentPlan: true,
84
+ };
85
+ }
86
+ if (isActive) {
87
+ return {
88
+ entitlementState: entitlementState || null,
89
+ gate: gate || null,
90
+ isActive: true,
91
+ allowReturnToApp: true,
92
+ statusTone: 'success',
93
+ message: summaryMessage || null,
94
+ primaryAction: 'none',
95
+ primaryLabel: null,
96
+ showManageBilling: billingAvailable,
97
+ markCurrentPlan: true,
98
+ };
99
+ }
100
+ if (entitlementState === 'suspended') {
101
+ return {
102
+ entitlementState,
103
+ gate: gate || null,
104
+ isActive: false,
105
+ allowReturnToApp: false,
106
+ statusTone: 'error',
107
+ message: 'Your access is suspended because billing needs attention. Update billing to restore access.',
108
+ primaryAction: billingAvailable ? 'manage_billing' : 'open_plans',
109
+ primaryLabel: billingAvailable ? 'Manage Billing' : 'Open Plans',
110
+ showManageBilling: billingAvailable,
111
+ markCurrentPlan: false,
112
+ };
113
+ }
114
+ if (entitlementState === 'canceled') {
115
+ return {
116
+ entitlementState,
117
+ gate: gate || null,
118
+ isActive: false,
119
+ allowReturnToApp: false,
120
+ statusTone: 'error',
121
+ message: 'Your access has ended. Choose a plan to reactivate your workspace.',
122
+ primaryAction: 'choose_plan',
123
+ primaryLabel: 'Choose Plan',
124
+ showManageBilling: billingAvailable,
125
+ markCurrentPlan: false,
126
+ };
127
+ }
128
+ if (entitlementState === 'pending_plan_selection' || summary?.planSelectionRequired === true || gate === 'plan_selection_required') {
129
+ return {
130
+ entitlementState: entitlementState || 'pending_plan_selection',
131
+ gate: gate || 'plan_selection_required',
132
+ isActive: false,
133
+ allowReturnToApp: false,
134
+ statusTone: 'info',
135
+ message: summaryMessage || GATE_MESSAGES.planSelectionRequired,
136
+ primaryAction: 'choose_plan',
137
+ primaryLabel: 'Choose Plan',
138
+ showManageBilling: false,
139
+ markCurrentPlan: false,
140
+ };
141
+ }
142
+ if (entitlementState === 'checkout_pending' || gate === 'checkout_pending') {
143
+ return {
144
+ entitlementState: entitlementState || 'checkout_pending',
145
+ gate: gate || 'checkout_pending',
146
+ isActive: false,
147
+ allowReturnToApp: false,
148
+ statusTone: 'info',
149
+ message: summaryMessage || GATE_MESSAGES.checkoutPending,
150
+ primaryAction: 'open_plans',
151
+ primaryLabel: 'Open Plans',
152
+ showManageBilling: billingAvailable,
153
+ markCurrentPlan: false,
154
+ };
155
+ }
156
+ if (gate === 'misconfigured') {
157
+ return {
158
+ entitlementState: entitlementState || null,
159
+ gate,
160
+ isActive: false,
161
+ allowReturnToApp: false,
162
+ statusTone: 'error',
163
+ message: GATE_MESSAGES.misconfigured,
164
+ primaryAction: 'none',
165
+ primaryLabel: null,
166
+ showManageBilling: false,
167
+ markCurrentPlan: false,
168
+ };
169
+ }
170
+ if (gate === 'missing_entitlement') {
171
+ return {
172
+ entitlementState: entitlementState || null,
173
+ gate,
174
+ isActive: false,
175
+ allowReturnToApp: false,
176
+ statusTone: 'error',
177
+ message: GATE_MESSAGES.missingEntitlement,
178
+ primaryAction: 'choose_plan',
179
+ primaryLabel: 'Choose Plan',
180
+ showManageBilling: false,
181
+ markCurrentPlan: false,
182
+ };
183
+ }
184
+ return {
185
+ entitlementState: entitlementState || null,
186
+ gate: gate || null,
187
+ isActive: false,
188
+ allowReturnToApp: false,
189
+ statusTone: summaryMessage ? 'error' : 'info',
190
+ message: summaryMessage || null,
191
+ primaryAction: 'none',
192
+ primaryLabel: null,
193
+ showManageBilling: billingAvailable,
194
+ markCurrentPlan: false,
195
+ };
196
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taskforcehq/taskforce",
3
- "version": "0.3.304",
3
+ "version": "0.3.305",
4
4
  "description": "Mission Command center for AI-powered task management (beta - under active development)",
5
5
  "author": "Taskforce HQ <hello@taskforcehq.ai> (https://taskforcehq.ai)",
6
6
  "license": "MIT",
@@ -76,6 +76,12 @@
76
76
  "test:e2e:realtime:attachments": "npm run test:e2e:realtime -- tests/e2e/attachment-sync.spec.ts",
77
77
  "test:e2e:realtime:multiclient": "npm run test:e2e:realtime -- tests/e2e/realtime-multiclient.spec.ts",
78
78
  "test:e2e:realtime:reconnect": "npm run test:e2e:realtime -- tests/e2e/realtime-reconnect.spec.ts",
79
+ "test:e2e:onboarding": "npx playwright test -c playwright.e2e.config.ts tests/e2e/auth-runtime.spec.ts tests/e2e/billing-runtime.spec.ts",
80
+ "test:billing:deterministic": "npx vitest run src/server/routes/billing.test.ts src/components/views/PlansPage.test.tsx src/components/views/PlanComparisonPage.test.tsx src/migrations/billingSchemaParity.test.ts",
81
+ "test:billing:runtime:mock": "npx playwright test -c playwright.e2e.config.ts tests/e2e/billing-runtime.spec.ts",
82
+ "test:billing:local": "npm run test:billing:deterministic && npm run test:billing:runtime:mock",
83
+ "test:billing:performance:smoke": "sh scripts/run-billing-performance-smoke.sh",
84
+ "test:billing:performance:smoke:live": "TASKFORCE_BILLING_ENABLE_LIVE_PURCHASE=1 sh scripts/run-billing-performance-smoke.sh",
79
85
  "playwright:auth:bootstrap": "node scripts/bootstrap-playwright-auth.mjs",
80
86
  "playwright:auth": "sh scripts/playwright-auth.sh",
81
87
  "playwright:interactive": "playwright test -c playwright.interactive.config.ts tests/e2e/interactive.smoke.spec.ts",
@@ -83,6 +89,7 @@
83
89
  "qa:open": "node scripts/qa-open.mjs",
84
90
  "check:env": "node --import tsx scripts/check-env.ts --target=server --strict=true",
85
91
  "check:env:soak": "node --import tsx scripts/check-env.ts --target=soak --strict=true",
92
+ "check:mcp-smoke": "node scripts/check-cloud-mcp.mjs",
86
93
  "check:compat": "node scripts/check-compat-boundaries.mjs",
87
94
  "gen:env-example": "node --import tsx scripts/generate-env-example.ts",
88
95
  "check:sync-smoke-preflight": "node --import tsx src/soak/preflight.cli.ts",
@@ -0,0 +1,83 @@
1
+ #!/usr/bin/env node
2
+
3
+ const DEFAULT_ENDPOINTS = {
4
+ production: 'https://mcp.taskforcehq.ai/mcp',
5
+ staging: 'https://staging-mcp.taskforcehq.ai/mcp',
6
+ performance: 'https://performance-mcp.taskforcehq.ai/mcp'
7
+ };
8
+
9
+ const REQUEST_TIMEOUT_MS = 10000;
10
+
11
+ function normalizeTargets(argv) {
12
+ const values = argv.length > 0 ? argv : Object.keys(DEFAULT_ENDPOINTS);
13
+ return values.map((value) => {
14
+ const normalized = String(value || '').trim().toLowerCase();
15
+ if (DEFAULT_ENDPOINTS[normalized]) {
16
+ return {
17
+ label: normalized,
18
+ url: DEFAULT_ENDPOINTS[normalized]
19
+ };
20
+ }
21
+ return {
22
+ label: normalized || 'custom',
23
+ url: String(value || '').trim()
24
+ };
25
+ });
26
+ }
27
+
28
+ async function checkEndpoint(target) {
29
+ const controller = new AbortController();
30
+ const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
31
+ try {
32
+ const response = await fetch(target.url, {
33
+ method: 'GET',
34
+ headers: {
35
+ Accept: 'application/json'
36
+ },
37
+ signal: controller.signal
38
+ });
39
+ const contentType = String(response.headers.get('content-type') || '');
40
+ const responseText = await response.text();
41
+ let payload = null;
42
+ try {
43
+ payload = responseText ? JSON.parse(responseText) : null;
44
+ } catch {
45
+ payload = null;
46
+ }
47
+ const message = typeof payload?.error?.message === 'string'
48
+ ? payload.error.message.trim()
49
+ : '';
50
+
51
+ if (response.status !== 401) {
52
+ throw new Error(`expected HTTP 401 but received ${response.status}`);
53
+ }
54
+ if (!contentType.includes('application/json')) {
55
+ throw new Error(`expected application/json response but received ${contentType || 'unknown content-type'}`);
56
+ }
57
+ if (message !== 'Unauthorized.') {
58
+ throw new Error(`expected MCP Unauthorized. payload but received message=${JSON.stringify(message || responseText.slice(0, 200))}`);
59
+ }
60
+
61
+ console.log(`[mcp-smoke] ${target.label} ok ${target.url} -> 401 Unauthorized.`);
62
+ } catch (error) {
63
+ const message = error instanceof Error ? error.message : String(error);
64
+ throw new Error(`[mcp-smoke] ${target.label} failed ${target.url}: ${message}`);
65
+ } finally {
66
+ clearTimeout(timeout);
67
+ }
68
+ }
69
+
70
+ async function main() {
71
+ const targets = normalizeTargets(process.argv.slice(2));
72
+ for (const target of targets) {
73
+ if (!target.url) {
74
+ throw new Error(`[mcp-smoke] ${target.label} has no URL to check.`);
75
+ }
76
+ await checkEndpoint(target);
77
+ }
78
+ }
79
+
80
+ main().catch((error) => {
81
+ console.error(error instanceof Error ? error.message : String(error));
82
+ process.exitCode = 1;
83
+ });
@@ -1,15 +1,19 @@
1
1
  #!/usr/bin/env sh
2
2
  set -eu
3
3
 
4
- LOCAL_ENV_FILE="${TASKFORCE_PLAYWRIGHT_ENV_FILE:-/Users/rossburke/.codex/taskforce-playwright.env}"
4
+ LOCAL_ENV_FILE="${TASKFORCE_PLAYWRIGHT_ENV_FILE:-$HOME/.codex/taskforce-playwright.env}"
5
5
  REPO_E2E_ENV_FILE="${TASKFORCE_PLAYWRIGHT_REPO_E2E_ENV_FILE:-.env.e2e}"
6
6
 
7
7
  if [ -f "$LOCAL_ENV_FILE" ]; then
8
+ set -a
8
9
  # shellcheck disable=SC1090
9
10
  . "$LOCAL_ENV_FILE"
11
+ set +a
10
12
  elif [ -f "$REPO_E2E_ENV_FILE" ]; then
13
+ set -a
11
14
  # shellcheck disable=SC1090
12
15
  . "$REPO_E2E_ENV_FILE"
16
+ set +a
13
17
  fi
14
18
 
15
19
  exec node scripts/bootstrap-playwright-auth.mjs "$@"
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env sh
2
+ set -eu
3
+
4
+ LOCAL_ENV_FILE="${TASKFORCE_PLAYWRIGHT_ENV_FILE:-$HOME/.codex/taskforce-playwright.env}"
5
+ REPO_E2E_ENV_FILE="${TASKFORCE_PLAYWRIGHT_REPO_E2E_ENV_FILE:-.env.e2e}"
6
+
7
+ if [ -f "$LOCAL_ENV_FILE" ]; then
8
+ set -a
9
+ # shellcheck disable=SC1090
10
+ . "$LOCAL_ENV_FILE"
11
+ set +a
12
+ elif [ -f "$REPO_E2E_ENV_FILE" ]; then
13
+ set -a
14
+ # shellcheck disable=SC1090
15
+ . "$REPO_E2E_ENV_FILE"
16
+ set +a
17
+ fi
18
+
19
+ TASKFORCE_PLAYWRIGHT_CLOUD_BASE_URL="${TASKFORCE_PLAYWRIGHT_CLOUD_BASE_URL:-https://performance-app.taskforcehq.ai}"
20
+ export TASKFORCE_PLAYWRIGHT_CLOUD_BASE_URL
21
+ export TASKFORCE_PLAYWRIGHT_BASE_URL="${TASKFORCE_PLAYWRIGHT_BASE_URL:-$TASKFORCE_PLAYWRIGHT_CLOUD_BASE_URL}"
22
+
23
+ node scripts/bootstrap-playwright-auth.mjs
24
+ exec npx playwright test -c playwright.interactive.config.ts tests/e2e/billing-performance.smoke.spec.ts "$@"