@avi770/testteam 1.2.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 (325) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/LICENSE +21 -0
  3. package/README.md +167 -0
  4. package/agents/01-analyst.ts +100 -0
  5. package/agents/02-seed-architect.ts +59 -0
  6. package/agents/03-test-generator.ts +191 -0
  7. package/agents/04-unit-runner.ts +160 -0
  8. package/agents/05-browser-crawler.ts +790 -0
  9. package/agents/06-api-exerciser.ts +311 -0
  10. package/agents/07-security-scout.ts +188 -0
  11. package/agents/08-a11y-guardian.ts +212 -0
  12. package/agents/09-healer.ts +228 -0
  13. package/agents/10-reporter.ts +266 -0
  14. package/agents/11-fixer.ts +253 -0
  15. package/agents/12-ux-inspector.ts +444 -0
  16. package/agents/13-performance-profiler.ts +271 -0
  17. package/agents/14-data-integrity-auditor.ts +417 -0
  18. package/agents/15-regression-sentinel.ts +307 -0
  19. package/agents/16-chaos-agent.ts +228 -0
  20. package/agents/17-documentation-validator.ts +266 -0
  21. package/agents/18-integration-watchdog.ts +178 -0
  22. package/agents/19-tenant-isolation-auditor.ts +199 -0
  23. package/agents/20-workflow-completion-tester.ts +203 -0
  24. package/agents/21-state-session-tester.ts +262 -0
  25. package/agents/22-email-notification-verifier.ts +244 -0
  26. package/agents/23-migration-tester.ts +80 -0
  27. package/agents/__tests__/01-analyst.test.ts +188 -0
  28. package/agents/__tests__/02-seed-architect.test.ts +152 -0
  29. package/agents/__tests__/03-test-generator-full.test.ts +321 -0
  30. package/agents/__tests__/03-test-generator.test.ts +318 -0
  31. package/agents/__tests__/04-unit-runner.test.ts +320 -0
  32. package/agents/__tests__/05-browser-crawler-beta.test.ts +492 -0
  33. package/agents/__tests__/05-browser-crawler-release.test.ts +412 -0
  34. package/agents/__tests__/05-browser-crawler-uat.test.ts +578 -0
  35. package/agents/__tests__/05-browser-crawler.test.ts +518 -0
  36. package/agents/__tests__/06-api-exerciser.test.ts +619 -0
  37. package/agents/__tests__/07-security-scout.test.ts +382 -0
  38. package/agents/__tests__/08-a11y-guardian.test.ts +530 -0
  39. package/agents/__tests__/09-healer.test.ts +384 -0
  40. package/agents/__tests__/10-reporter.test.ts +366 -0
  41. package/agents/__tests__/11-fixer.test.ts +406 -0
  42. package/agents/__tests__/12-ux-inspector-extended.test.ts +465 -0
  43. package/agents/__tests__/12-ux-inspector.test.ts +443 -0
  44. package/agents/__tests__/13-performance-profiler.test.ts +411 -0
  45. package/agents/__tests__/14-data-integrity-auditor-extended.test.ts +573 -0
  46. package/agents/__tests__/14-data-integrity-auditor.test.ts +407 -0
  47. package/agents/__tests__/15-regression-sentinel.test.ts +657 -0
  48. package/agents/__tests__/16-chaos-agent.test.ts +427 -0
  49. package/agents/__tests__/17-documentation-validator.test.ts +402 -0
  50. package/agents/__tests__/18-integration-watchdog.test.ts +263 -0
  51. package/agents/__tests__/19-tenant-isolation-auditor.test.ts +400 -0
  52. package/agents/__tests__/20-workflow-completion-tester.test.ts +586 -0
  53. package/agents/__tests__/21-state-session-tester.test.ts +374 -0
  54. package/agents/__tests__/22-email-notification-verifier.test.ts +441 -0
  55. package/agents/__tests__/23-migration-tester.test.ts +145 -0
  56. package/agents/__tests__/base-agent.test.ts +188 -0
  57. package/agents/__tests__/registry.test.ts +218 -0
  58. package/agents/base-agent.ts +77 -0
  59. package/agents/registry.ts +136 -0
  60. package/baselines/api-schemas/.gitkeep +0 -0
  61. package/baselines/performance/.gitkeep +0 -0
  62. package/baselines/screenshots/.gitkeep +0 -0
  63. package/bin/testteam.js +10 -0
  64. package/core/__tests__/ci-output.test.ts +430 -0
  65. package/core/__tests__/cli.test.ts +387 -0
  66. package/core/__tests__/config.test.ts +78 -0
  67. package/core/__tests__/cost-tracker.test.ts +158 -0
  68. package/core/__tests__/evidence.test.ts +265 -0
  69. package/core/__tests__/fix-loop.test.ts +210 -0
  70. package/core/__tests__/health-check.test.ts +44 -0
  71. package/core/__tests__/init.test.ts +609 -0
  72. package/core/__tests__/integration.test.ts +204 -0
  73. package/core/__tests__/license-gen.test.ts +227 -0
  74. package/core/__tests__/license.test.ts +326 -0
  75. package/core/__tests__/multi-browser.test.ts +278 -0
  76. package/core/__tests__/orchestrator.test.ts +519 -0
  77. package/core/__tests__/phase-gate.test.ts +43 -0
  78. package/core/__tests__/report-html.test.ts +398 -0
  79. package/core/__tests__/report-upload.test.ts +325 -0
  80. package/core/__tests__/run-counter.test.ts +234 -0
  81. package/core/ci-output.ts +240 -0
  82. package/core/cli.ts +232 -0
  83. package/core/config.ts +178 -0
  84. package/core/cost-tracker.ts +59 -0
  85. package/core/evidence.ts +132 -0
  86. package/core/fix-loop.ts +85 -0
  87. package/core/health-check.ts +54 -0
  88. package/core/init.ts +546 -0
  89. package/core/license-gen.ts +212 -0
  90. package/core/license.ts +211 -0
  91. package/core/messages.ts +67 -0
  92. package/core/multi-browser.ts +136 -0
  93. package/core/orchestrator.ts +354 -0
  94. package/core/phase-gate.ts +55 -0
  95. package/core/report-html.ts +657 -0
  96. package/core/report-upload.ts +188 -0
  97. package/core/run-counter.ts +175 -0
  98. package/core/types.ts +57 -0
  99. package/dist/agents/01-analyst.d.ts +11 -0
  100. package/dist/agents/01-analyst.d.ts.map +1 -0
  101. package/dist/agents/01-analyst.js +75 -0
  102. package/dist/agents/01-analyst.js.map +1 -0
  103. package/dist/agents/02-seed-architect.d.ts +11 -0
  104. package/dist/agents/02-seed-architect.d.ts.map +1 -0
  105. package/dist/agents/02-seed-architect.js +51 -0
  106. package/dist/agents/02-seed-architect.js.map +1 -0
  107. package/dist/agents/03-test-generator.d.ts +9 -0
  108. package/dist/agents/03-test-generator.d.ts.map +1 -0
  109. package/dist/agents/03-test-generator.js +167 -0
  110. package/dist/agents/03-test-generator.js.map +1 -0
  111. package/dist/agents/04-unit-runner.d.ts +9 -0
  112. package/dist/agents/04-unit-runner.d.ts.map +1 -0
  113. package/dist/agents/04-unit-runner.js +113 -0
  114. package/dist/agents/04-unit-runner.js.map +1 -0
  115. package/dist/agents/05-browser-crawler.d.ts +30 -0
  116. package/dist/agents/05-browser-crawler.d.ts.map +1 -0
  117. package/dist/agents/05-browser-crawler.js +685 -0
  118. package/dist/agents/05-browser-crawler.js.map +1 -0
  119. package/dist/agents/06-api-exerciser.d.ts +23 -0
  120. package/dist/agents/06-api-exerciser.d.ts.map +1 -0
  121. package/dist/agents/06-api-exerciser.js +253 -0
  122. package/dist/agents/06-api-exerciser.js.map +1 -0
  123. package/dist/agents/07-security-scout.d.ts +11 -0
  124. package/dist/agents/07-security-scout.d.ts.map +1 -0
  125. package/dist/agents/07-security-scout.js +142 -0
  126. package/dist/agents/07-security-scout.js.map +1 -0
  127. package/dist/agents/08-a11y-guardian.d.ts +13 -0
  128. package/dist/agents/08-a11y-guardian.d.ts.map +1 -0
  129. package/dist/agents/08-a11y-guardian.js +176 -0
  130. package/dist/agents/08-a11y-guardian.js.map +1 -0
  131. package/dist/agents/09-healer.d.ts +33 -0
  132. package/dist/agents/09-healer.d.ts.map +1 -0
  133. package/dist/agents/09-healer.js +167 -0
  134. package/dist/agents/09-healer.js.map +1 -0
  135. package/dist/agents/10-reporter.d.ts +26 -0
  136. package/dist/agents/10-reporter.d.ts.map +1 -0
  137. package/dist/agents/10-reporter.js +215 -0
  138. package/dist/agents/10-reporter.js.map +1 -0
  139. package/dist/agents/11-fixer.d.ts +26 -0
  140. package/dist/agents/11-fixer.d.ts.map +1 -0
  141. package/dist/agents/11-fixer.js +195 -0
  142. package/dist/agents/11-fixer.js.map +1 -0
  143. package/dist/agents/12-ux-inspector.d.ts +15 -0
  144. package/dist/agents/12-ux-inspector.d.ts.map +1 -0
  145. package/dist/agents/12-ux-inspector.js +364 -0
  146. package/dist/agents/12-ux-inspector.js.map +1 -0
  147. package/dist/agents/13-performance-profiler.d.ts +13 -0
  148. package/dist/agents/13-performance-profiler.d.ts.map +1 -0
  149. package/dist/agents/13-performance-profiler.js +216 -0
  150. package/dist/agents/13-performance-profiler.js.map +1 -0
  151. package/dist/agents/14-data-integrity-auditor.d.ts +12 -0
  152. package/dist/agents/14-data-integrity-auditor.d.ts.map +1 -0
  153. package/dist/agents/14-data-integrity-auditor.js +356 -0
  154. package/dist/agents/14-data-integrity-auditor.js.map +1 -0
  155. package/dist/agents/15-regression-sentinel.d.ts +25 -0
  156. package/dist/agents/15-regression-sentinel.d.ts.map +1 -0
  157. package/dist/agents/15-regression-sentinel.js +251 -0
  158. package/dist/agents/15-regression-sentinel.js.map +1 -0
  159. package/dist/agents/16-chaos-agent.d.ts +9 -0
  160. package/dist/agents/16-chaos-agent.d.ts.map +1 -0
  161. package/dist/agents/16-chaos-agent.js +207 -0
  162. package/dist/agents/16-chaos-agent.js.map +1 -0
  163. package/dist/agents/17-documentation-validator.d.ts +31 -0
  164. package/dist/agents/17-documentation-validator.d.ts.map +1 -0
  165. package/dist/agents/17-documentation-validator.js +246 -0
  166. package/dist/agents/17-documentation-validator.js.map +1 -0
  167. package/dist/agents/18-integration-watchdog.d.ts +10 -0
  168. package/dist/agents/18-integration-watchdog.d.ts.map +1 -0
  169. package/dist/agents/18-integration-watchdog.js +138 -0
  170. package/dist/agents/18-integration-watchdog.js.map +1 -0
  171. package/dist/agents/19-tenant-isolation-auditor.d.ts +9 -0
  172. package/dist/agents/19-tenant-isolation-auditor.d.ts.map +1 -0
  173. package/dist/agents/19-tenant-isolation-auditor.js +166 -0
  174. package/dist/agents/19-tenant-isolation-auditor.js.map +1 -0
  175. package/dist/agents/20-workflow-completion-tester.d.ts +12 -0
  176. package/dist/agents/20-workflow-completion-tester.d.ts.map +1 -0
  177. package/dist/agents/20-workflow-completion-tester.js +159 -0
  178. package/dist/agents/20-workflow-completion-tester.js.map +1 -0
  179. package/dist/agents/21-state-session-tester.d.ts +10 -0
  180. package/dist/agents/21-state-session-tester.d.ts.map +1 -0
  181. package/dist/agents/21-state-session-tester.js +233 -0
  182. package/dist/agents/21-state-session-tester.js.map +1 -0
  183. package/dist/agents/22-email-notification-verifier.d.ts +11 -0
  184. package/dist/agents/22-email-notification-verifier.d.ts.map +1 -0
  185. package/dist/agents/22-email-notification-verifier.js +199 -0
  186. package/dist/agents/22-email-notification-verifier.js.map +1 -0
  187. package/dist/agents/23-migration-tester.d.ts +10 -0
  188. package/dist/agents/23-migration-tester.d.ts.map +1 -0
  189. package/dist/agents/23-migration-tester.js +74 -0
  190. package/dist/agents/23-migration-tester.js.map +1 -0
  191. package/dist/agents/base-agent.d.ts +19 -0
  192. package/dist/agents/base-agent.d.ts.map +1 -0
  193. package/dist/agents/base-agent.js +67 -0
  194. package/dist/agents/base-agent.js.map +1 -0
  195. package/dist/agents/registry.d.ts +29 -0
  196. package/dist/agents/registry.d.ts.map +1 -0
  197. package/dist/agents/registry.js +117 -0
  198. package/dist/agents/registry.js.map +1 -0
  199. package/dist/core/ci-output.d.ts +35 -0
  200. package/dist/core/ci-output.d.ts.map +1 -0
  201. package/dist/core/ci-output.js +193 -0
  202. package/dist/core/ci-output.js.map +1 -0
  203. package/dist/core/cli.d.ts +11 -0
  204. package/dist/core/cli.d.ts.map +1 -0
  205. package/dist/core/cli.js +197 -0
  206. package/dist/core/cli.js.map +1 -0
  207. package/dist/core/config.d.ts +111 -0
  208. package/dist/core/config.d.ts.map +1 -0
  209. package/dist/core/config.js +42 -0
  210. package/dist/core/config.js.map +1 -0
  211. package/dist/core/cost-tracker.d.ts +22 -0
  212. package/dist/core/cost-tracker.d.ts.map +1 -0
  213. package/dist/core/cost-tracker.js +41 -0
  214. package/dist/core/cost-tracker.js.map +1 -0
  215. package/dist/core/evidence.d.ts +28 -0
  216. package/dist/core/evidence.d.ts.map +1 -0
  217. package/dist/core/evidence.js +95 -0
  218. package/dist/core/evidence.js.map +1 -0
  219. package/dist/core/fix-loop.d.ts +29 -0
  220. package/dist/core/fix-loop.d.ts.map +1 -0
  221. package/dist/core/fix-loop.js +70 -0
  222. package/dist/core/fix-loop.js.map +1 -0
  223. package/dist/core/health-check.d.ts +21 -0
  224. package/dist/core/health-check.d.ts.map +1 -0
  225. package/dist/core/health-check.js +26 -0
  226. package/dist/core/health-check.js.map +1 -0
  227. package/dist/core/init.d.ts +2 -0
  228. package/dist/core/init.d.ts.map +1 -0
  229. package/dist/core/init.js +435 -0
  230. package/dist/core/init.js.map +1 -0
  231. package/dist/core/license-gen.d.ts +12 -0
  232. package/dist/core/license-gen.d.ts.map +1 -0
  233. package/dist/core/license-gen.js +169 -0
  234. package/dist/core/license-gen.js.map +1 -0
  235. package/dist/core/license.d.ts +33 -0
  236. package/dist/core/license.d.ts.map +1 -0
  237. package/dist/core/license.js +170 -0
  238. package/dist/core/license.js.map +1 -0
  239. package/dist/core/messages.d.ts +10 -0
  240. package/dist/core/messages.d.ts.map +1 -0
  241. package/dist/core/messages.js +47 -0
  242. package/dist/core/messages.js.map +1 -0
  243. package/dist/core/multi-browser.d.ts +36 -0
  244. package/dist/core/multi-browser.d.ts.map +1 -0
  245. package/dist/core/multi-browser.js +88 -0
  246. package/dist/core/multi-browser.js.map +1 -0
  247. package/dist/core/orchestrator.d.ts +48 -0
  248. package/dist/core/orchestrator.d.ts.map +1 -0
  249. package/dist/core/orchestrator.js +291 -0
  250. package/dist/core/orchestrator.js.map +1 -0
  251. package/dist/core/phase-gate.d.ts +4 -0
  252. package/dist/core/phase-gate.d.ts.map +1 -0
  253. package/dist/core/phase-gate.js +39 -0
  254. package/dist/core/phase-gate.js.map +1 -0
  255. package/dist/core/report-html.d.ts +9 -0
  256. package/dist/core/report-html.d.ts.map +1 -0
  257. package/dist/core/report-html.js +617 -0
  258. package/dist/core/report-html.js.map +1 -0
  259. package/dist/core/report-upload.d.ts +16 -0
  260. package/dist/core/report-upload.d.ts.map +1 -0
  261. package/dist/core/report-upload.js +124 -0
  262. package/dist/core/report-upload.js.map +1 -0
  263. package/dist/core/run-counter.d.ts +40 -0
  264. package/dist/core/run-counter.d.ts.map +1 -0
  265. package/dist/core/run-counter.js +120 -0
  266. package/dist/core/run-counter.js.map +1 -0
  267. package/dist/core/types.d.ts +53 -0
  268. package/dist/core/types.d.ts.map +1 -0
  269. package/dist/core/types.js +2 -0
  270. package/dist/core/types.js.map +1 -0
  271. package/dist/helpers/api-client.d.ts +30 -0
  272. package/dist/helpers/api-client.d.ts.map +1 -0
  273. package/dist/helpers/api-client.js +77 -0
  274. package/dist/helpers/api-client.js.map +1 -0
  275. package/dist/helpers/element-discovery.d.ts +18 -0
  276. package/dist/helpers/element-discovery.d.ts.map +1 -0
  277. package/dist/helpers/element-discovery.js +82 -0
  278. package/dist/helpers/element-discovery.js.map +1 -0
  279. package/dist/helpers/env-resolver.d.ts +29 -0
  280. package/dist/helpers/env-resolver.d.ts.map +1 -0
  281. package/dist/helpers/env-resolver.js +51 -0
  282. package/dist/helpers/env-resolver.js.map +1 -0
  283. package/dist/helpers/form-filler.d.ts +13 -0
  284. package/dist/helpers/form-filler.d.ts.map +1 -0
  285. package/dist/helpers/form-filler.js +98 -0
  286. package/dist/helpers/form-filler.js.map +1 -0
  287. package/dist/helpers/modal-handler.d.ts +16 -0
  288. package/dist/helpers/modal-handler.d.ts.map +1 -0
  289. package/dist/helpers/modal-handler.js +95 -0
  290. package/dist/helpers/modal-handler.js.map +1 -0
  291. package/dist/helpers/navigation.d.ts +37 -0
  292. package/dist/helpers/navigation.d.ts.map +1 -0
  293. package/dist/helpers/navigation.js +83 -0
  294. package/dist/helpers/navigation.js.map +1 -0
  295. package/dist/helpers/quality-gate.d.ts +17 -0
  296. package/dist/helpers/quality-gate.d.ts.map +1 -0
  297. package/dist/helpers/quality-gate.js +144 -0
  298. package/dist/helpers/quality-gate.js.map +1 -0
  299. package/dist/helpers/screenshot.d.ts +24 -0
  300. package/dist/helpers/screenshot.d.ts.map +1 -0
  301. package/dist/helpers/screenshot.js +76 -0
  302. package/dist/helpers/screenshot.js.map +1 -0
  303. package/dist/helpers/seed-validator.d.ts +15 -0
  304. package/dist/helpers/seed-validator.d.ts.map +1 -0
  305. package/dist/helpers/seed-validator.js +53 -0
  306. package/dist/helpers/seed-validator.js.map +1 -0
  307. package/helpers/__tests__/api-client.test.ts +199 -0
  308. package/helpers/__tests__/element-discovery.test.ts +202 -0
  309. package/helpers/__tests__/form-filler-extended.test.ts +212 -0
  310. package/helpers/__tests__/form-filler.test.ts +99 -0
  311. package/helpers/__tests__/modal-handler.test.ts +152 -0
  312. package/helpers/__tests__/navigation.test.ts +214 -0
  313. package/helpers/__tests__/quality-gate.test.ts +117 -0
  314. package/helpers/__tests__/screenshot.test.ts +139 -0
  315. package/helpers/__tests__/seed-validator.test.ts +114 -0
  316. package/helpers/api-client.ts +111 -0
  317. package/helpers/element-discovery.ts +105 -0
  318. package/helpers/env-resolver.ts +69 -0
  319. package/helpers/form-filler.ts +126 -0
  320. package/helpers/modal-handler.ts +108 -0
  321. package/helpers/navigation.ts +100 -0
  322. package/helpers/quality-gate.ts +180 -0
  323. package/helpers/screenshot.ts +111 -0
  324. package/helpers/seed-validator.ts +70 -0
  325. package/package.json +88 -0
@@ -0,0 +1,212 @@
1
+ import * as crypto from 'node:crypto';
2
+ import * as readline from 'node:readline';
3
+
4
+ import type { LicensePayload, LicenseTier } from './license';
5
+
6
+ // ── Base64url helpers ────────────────────────────────────────────────
7
+
8
+ function base64urlEncode(data: Buffer): string {
9
+ return data.toString('base64url');
10
+ }
11
+
12
+ // ── Key generation ───────────────────────────────────────────────────
13
+
14
+ /** Generate an ES256 (ECDSA P-256) keypair in PEM format. */
15
+ export function generateKeyPair(): { publicKey: string; privateKey: string } {
16
+ const { publicKey, privateKey } = crypto.generateKeyPairSync('ec', {
17
+ namedCurve: 'P-256',
18
+ publicKeyEncoding: { type: 'spki', format: 'pem' },
19
+ privateKeyEncoding: { type: 'pkcs8', format: 'pem' },
20
+ });
21
+ return { publicKey, privateKey };
22
+ }
23
+
24
+ // ── License key generation ───────────────────────────────────────────
25
+
26
+ /**
27
+ * Generate a signed JWT license key using ES256.
28
+ * Uses only Node.js built-in `crypto` — no external JWT libraries.
29
+ */
30
+ export function generateLicenseKey(
31
+ payload: Omit<LicensePayload, 'iat'>,
32
+ privateKey: string,
33
+ ): string {
34
+ const header = { alg: 'ES256', typ: 'JWT' };
35
+ const fullPayload: LicensePayload = {
36
+ ...payload,
37
+ iat: Math.floor(Date.now() / 1000),
38
+ };
39
+
40
+ const headerB64 = base64urlEncode(Buffer.from(JSON.stringify(header)));
41
+ const payloadB64 = base64urlEncode(Buffer.from(JSON.stringify(fullPayload)));
42
+ const signedContent = `${headerB64}.${payloadB64}`;
43
+
44
+ // Sign with ECDSA P-256 / SHA-256
45
+ const signer = crypto.createSign('SHA256');
46
+ signer.update(signedContent);
47
+ const derSignature = signer.sign({ key: privateKey, dsaEncoding: 'der' });
48
+
49
+ // Convert DER to raw (r || s) for compact JWT representation
50
+ const rawSignature = derToRaw(derSignature);
51
+ const signatureB64 = base64urlEncode(rawSignature);
52
+
53
+ return `${signedContent}.${signatureB64}`;
54
+ }
55
+
56
+ // ── Tier defaults ────────────────────────────────────────────────────
57
+
58
+ interface TierDefaults {
59
+ maxProjects: number;
60
+ maxRunsPerMonth: number;
61
+ }
62
+
63
+ const TIER_DEFAULTS: Record<LicenseTier, TierDefaults> = {
64
+ free: { maxProjects: 1, maxRunsPerMonth: 10 },
65
+ pro: { maxProjects: 3, maxRunsPerMonth: 100 },
66
+ enterprise: { maxProjects: 999, maxRunsPerMonth: 999999 },
67
+ };
68
+
69
+ // ── Interactive CLI ──────────────────────────────────────────────────
70
+
71
+ async function prompt(rl: readline.Interface, question: string): Promise<string> {
72
+ return new Promise((resolve) => {
73
+ rl.question(question, (answer) => resolve(answer.trim()));
74
+ });
75
+ }
76
+
77
+ async function main(): Promise<void> {
78
+ const rl = readline.createInterface({
79
+ input: process.stdin,
80
+ output: process.stdout,
81
+ });
82
+
83
+ try {
84
+ console.log('\n=== TestTeam License Generator ===\n');
85
+
86
+ // Tier
87
+ const tierInput = await prompt(rl, 'Tier (free / pro / enterprise): ');
88
+ const tier = tierInput as LicenseTier;
89
+ if (!['free', 'pro', 'enterprise'].includes(tier)) {
90
+ console.error(`Invalid tier: ${tierInput}`);
91
+ process.exit(1);
92
+ }
93
+
94
+ // Email / Customer ID
95
+ const sub = await prompt(rl, 'Customer email or ID: ');
96
+ if (!sub) {
97
+ console.error('Customer identifier is required.');
98
+ process.exit(1);
99
+ }
100
+
101
+ // Expiry
102
+ const daysInput = await prompt(rl, 'License duration in days (default: 365): ');
103
+ const days = daysInput ? parseInt(daysInput, 10) : 365;
104
+ if (isNaN(days) || days <= 0) {
105
+ console.error('Invalid duration.');
106
+ process.exit(1);
107
+ }
108
+
109
+ // Features
110
+ const featuresInput = await prompt(rl, 'Feature flags (comma-separated, or empty): ');
111
+ const features = featuresInput
112
+ ? featuresInput.split(',').map((f) => f.trim()).filter(Boolean)
113
+ : [];
114
+
115
+ // Generate keypair
116
+ const { publicKey, privateKey } = generateKeyPair();
117
+
118
+ const defaults = TIER_DEFAULTS[tier];
119
+ const exp = Math.floor(Date.now() / 1000) + days * 86400;
120
+
121
+ const licenseKey = generateLicenseKey(
122
+ {
123
+ tier,
124
+ exp,
125
+ sub,
126
+ maxProjects: defaults.maxProjects,
127
+ maxRunsPerMonth: defaults.maxRunsPerMonth,
128
+ features,
129
+ },
130
+ privateKey,
131
+ );
132
+
133
+ console.log('\n--- LICENSE KEY ---');
134
+ console.log(licenseKey);
135
+ console.log('\n--- PUBLIC KEY (embed in .testteam-license or set TF_LICENSE_PUBLIC_KEY) ---');
136
+ console.log(publicKey);
137
+ console.log('--- PRIVATE KEY (keep secret — for generating new keys only) ---');
138
+ console.log(privateKey);
139
+
140
+ console.log('--- .testteam-license file content ---');
141
+ console.log(`${licenseKey}\n\n${publicKey}`);
142
+ } finally {
143
+ rl.close();
144
+ }
145
+ }
146
+
147
+ // ── DER to raw conversion ────────────────────────────────────────────
148
+
149
+ /**
150
+ * Convert a DER-encoded ECDSA signature to raw (r || s) format (64 bytes for P-256).
151
+ */
152
+ function derToRaw(der: Buffer): Buffer {
153
+ // DER: 0x30 <len> 0x02 <rLen> <r> 0x02 <sLen> <s>
154
+ let offset = 2; // skip 0x30 and total length
155
+
156
+ // Read r
157
+ if (der[offset] !== 0x02) throw new Error('Invalid DER: expected 0x02 for r');
158
+ offset++;
159
+ const rLen = der[offset];
160
+ offset++;
161
+ let r = der.subarray(offset, offset + rLen);
162
+ offset += rLen;
163
+
164
+ // Read s
165
+ if (der[offset] !== 0x02) throw new Error('Invalid DER: expected 0x02 for s');
166
+ offset++;
167
+ const sLen = der[offset];
168
+ offset++;
169
+ let s = der.subarray(offset, offset + sLen);
170
+
171
+ // Normalize to exactly 32 bytes each (strip leading zero padding, or left-pad)
172
+ r = normalizeInteger(r, 32);
173
+ s = normalizeInteger(s, 32);
174
+
175
+ return Buffer.concat([r, s]);
176
+ }
177
+
178
+ function normalizeInteger(buf: Buffer, targetLen: number): Buffer {
179
+ if (buf.length === targetLen) return buf;
180
+ if (buf.length > targetLen) {
181
+ // Strip leading zeros
182
+ return buf.subarray(buf.length - targetLen);
183
+ }
184
+ // Left-pad with zeros
185
+ const padded = Buffer.alloc(targetLen, 0);
186
+ buf.copy(padded, targetLen - buf.length);
187
+ return padded;
188
+ }
189
+
190
+ // ── ESM entry point detection ────────────────────────────────────────
191
+
192
+ const isDirectRun = (() => {
193
+ try {
194
+ // In ESM, check if this module is the entry point
195
+ const moduleUrl = import.meta.url;
196
+ const argv1 = process.argv[1];
197
+ if (!argv1) return false;
198
+
199
+ // Normalize for comparison
200
+ const scriptUrl = new URL(`file:///${argv1.replace(/\\/g, '/')}`).href;
201
+ return moduleUrl === scriptUrl || moduleUrl.endsWith(argv1.replace(/\\/g, '/'));
202
+ } catch {
203
+ return false;
204
+ }
205
+ })();
206
+
207
+ if (isDirectRun) {
208
+ main().catch((err: unknown) => {
209
+ console.error('License generation failed:', err);
210
+ process.exit(1);
211
+ });
212
+ }
@@ -0,0 +1,211 @@
1
+ import * as crypto from 'node:crypto';
2
+ import * as fs from 'node:fs';
3
+ import * as path from 'node:path';
4
+
5
+ /** License tiers supported by TestTeam. */
6
+ export type LicenseTier = 'free' | 'pro' | 'enterprise';
7
+
8
+ /** JWT payload embedded in a TestTeam license key. */
9
+ export interface LicensePayload {
10
+ tier: LicenseTier;
11
+ exp: number;
12
+ iat: number;
13
+ sub: string;
14
+ maxProjects: number;
15
+ maxRunsPerMonth: number;
16
+ features: string[];
17
+ }
18
+
19
+ /** In-memory cache so we only validate once per process. */
20
+ let cachedLicense: LicensePayload | null | undefined;
21
+
22
+ const LICENSE_FILENAME = '.testteam-license';
23
+
24
+ // ── Base64url helpers ────────────────────────────────────────────────
25
+
26
+ function base64urlEncode(data: Buffer): string {
27
+ return data.toString('base64url');
28
+ }
29
+
30
+ function base64urlDecode(str: string): Buffer {
31
+ return Buffer.from(str, 'base64url');
32
+ }
33
+
34
+ // ── JWT helpers (ES256 / ECDSA P-256 with SHA-256) ───────────────────
35
+
36
+ /**
37
+ * Decode and verify a JWT signed with ES256.
38
+ * Returns the payload on success or `null` on any failure
39
+ * (bad format, invalid signature, expired, malformed JSON).
40
+ */
41
+ export function validateLicense(
42
+ key: string,
43
+ publicKey: string,
44
+ ): LicensePayload | null {
45
+ try {
46
+ const parts = key.trim().split('.');
47
+ if (parts.length !== 3) return null;
48
+
49
+ const [headerB64, payloadB64, signatureB64] = parts;
50
+
51
+ // Verify header declares ES256
52
+ const header: unknown = JSON.parse(
53
+ base64urlDecode(headerB64).toString('utf8'),
54
+ );
55
+ if (
56
+ typeof header !== 'object' ||
57
+ header === null ||
58
+ (header as Record<string, unknown>)['alg'] !== 'ES256'
59
+ ) {
60
+ return null;
61
+ }
62
+
63
+ // Verify signature
64
+ const signedContent = `${headerB64}.${payloadB64}`;
65
+ const signature = base64urlDecode(signatureB64);
66
+
67
+ // ES256 produces a 64-byte raw (r || s) signature.
68
+ // Node.js crypto.verify expects DER encoding, so convert if raw.
69
+ const derSignature = rawToDer(signature);
70
+
71
+ const verifier = crypto.createVerify('SHA256');
72
+ verifier.update(signedContent);
73
+ const valid = verifier.verify(
74
+ { key: publicKey, dsaEncoding: 'der' },
75
+ derSignature,
76
+ );
77
+ if (!valid) return null;
78
+
79
+ // Decode payload
80
+ const payloadJson = base64urlDecode(payloadB64).toString('utf8');
81
+ const payload: unknown = JSON.parse(payloadJson);
82
+
83
+ if (!isLicensePayload(payload)) return null;
84
+
85
+ // Check expiry
86
+ const nowSec = Math.floor(Date.now() / 1000);
87
+ if (payload.exp <= nowSec) return null;
88
+
89
+ return payload;
90
+ } catch {
91
+ return null;
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Load a license from the `.testteam-license` file in the given directory.
97
+ * The file must contain the JWT on the first non-empty line.
98
+ * A second non-empty line, if present, is treated as the PEM public key
99
+ * (or the public key can be embedded as `TF_LICENSE_PUBLIC_KEY` env var).
100
+ */
101
+ export function loadLicense(configDir: string): LicensePayload | null {
102
+ // Return cached result if available
103
+ if (cachedLicense !== undefined) return cachedLicense;
104
+
105
+ try {
106
+ const filePath = path.join(configDir, LICENSE_FILENAME);
107
+ if (!fs.existsSync(filePath)) {
108
+ cachedLicense = null;
109
+ return null;
110
+ }
111
+
112
+ const content = fs.readFileSync(filePath, 'utf8').trim();
113
+ if (!content) {
114
+ cachedLicense = null;
115
+ return null;
116
+ }
117
+
118
+ // Split into sections separated by blank lines
119
+ const sections = content.split(/\n\s*\n/);
120
+ const licenseKey = sections[0].trim();
121
+
122
+ // Public key: either embedded after the JWT or from env
123
+ let publicKey = process.env['TF_LICENSE_PUBLIC_KEY'] ?? '';
124
+ if (sections.length > 1) {
125
+ publicKey = sections.slice(1).join('\n\n').trim();
126
+ }
127
+
128
+ if (!publicKey) {
129
+ cachedLicense = null;
130
+ return null;
131
+ }
132
+
133
+ const result = validateLicense(licenseKey, publicKey);
134
+ cachedLicense = result;
135
+ return result;
136
+ } catch {
137
+ cachedLicense = null;
138
+ return null;
139
+ }
140
+ }
141
+
142
+ /**
143
+ * Get the license tier for the current project.
144
+ * Falls back to `'free'` when no valid license is found.
145
+ */
146
+ export function getLicenseTier(configDir?: string): LicenseTier {
147
+ const dir = configDir ?? process.cwd();
148
+ const license = loadLicense(dir);
149
+ return license?.tier ?? 'free';
150
+ }
151
+
152
+ /** Reset the in-memory license cache (useful for testing). */
153
+ export function resetLicenseCache(): void {
154
+ cachedLicense = undefined;
155
+ }
156
+
157
+ // ── Internal helpers ─────────────────────────────────────────────────
158
+
159
+ /** Type guard for LicensePayload. */
160
+ function isLicensePayload(value: unknown): value is LicensePayload {
161
+ if (typeof value !== 'object' || value === null) return false;
162
+ const obj = value as Record<string, unknown>;
163
+
164
+ if (!['free', 'pro', 'enterprise'].includes(obj['tier'] as string)) return false;
165
+ if (typeof obj['exp'] !== 'number') return false;
166
+ if (typeof obj['iat'] !== 'number') return false;
167
+ if (typeof obj['sub'] !== 'string') return false;
168
+ if (typeof obj['maxProjects'] !== 'number') return false;
169
+ if (typeof obj['maxRunsPerMonth'] !== 'number') return false;
170
+ if (!Array.isArray(obj['features'])) return false;
171
+ if (!obj['features'].every((f: unknown) => typeof f === 'string')) return false;
172
+
173
+ return true;
174
+ }
175
+
176
+ /**
177
+ * Convert a raw ECDSA signature (r || s, 64 bytes for P-256) to DER encoding.
178
+ * If the input is already DER-encoded (starts with 0x30), return as-is.
179
+ */
180
+ function rawToDer(raw: Buffer): Buffer {
181
+ // If already DER-encoded, return as-is
182
+ if (raw.length > 0 && raw[0] === 0x30) return raw;
183
+
184
+ if (raw.length !== 64) return raw; // Not a valid raw P-256 signature
185
+
186
+ const r = raw.subarray(0, 32);
187
+ const s = raw.subarray(32, 64);
188
+
189
+ const encodeInteger = (int: Buffer): Buffer => {
190
+ // Strip leading zeros but keep at least one byte
191
+ let start = 0;
192
+ while (start < int.length - 1 && int[start] === 0) start++;
193
+ let trimmed = int.subarray(start);
194
+
195
+ // If high bit is set, prepend a 0x00 byte
196
+ if (trimmed[0] & 0x80) {
197
+ trimmed = Buffer.concat([Buffer.from([0x00]), trimmed]);
198
+ }
199
+
200
+ return Buffer.concat([
201
+ Buffer.from([0x02, trimmed.length]),
202
+ trimmed,
203
+ ]);
204
+ };
205
+
206
+ const rDer = encodeInteger(r);
207
+ const sDer = encodeInteger(s);
208
+ const body = Buffer.concat([rDer, sDer]);
209
+
210
+ return Buffer.concat([Buffer.from([0x30, body.length]), body]);
211
+ }
@@ -0,0 +1,67 @@
1
+ import type { LicenseTier } from './license';
2
+
3
+ const PRICING_URL = 'https://testteam.dev/pricing';
4
+
5
+ /** Agent names keyed by agent number. */
6
+ const AGENT_NAMES: Record<number, string> = {
7
+ 1: 'Analyst',
8
+ 2: 'Seed Architect',
9
+ 3: 'Test Generator',
10
+ 4: 'Unit Runner',
11
+ 5: 'Browser Crawler',
12
+ 6: 'API Exerciser',
13
+ 7: 'Security Scout',
14
+ 8: 'A11y Guardian',
15
+ 9: 'Healer',
16
+ 10: 'Reporter',
17
+ 11: 'Fixer',
18
+ 12: 'UX Inspector',
19
+ 13: 'Performance Profiler',
20
+ 14: 'Data Integrity Auditor',
21
+ 15: 'Regression Sentinel',
22
+ 16: 'Chaos Agent',
23
+ 17: 'Documentation Validator',
24
+ 18: 'Integration Watchdog',
25
+ 19: 'Tenant Isolation Auditor',
26
+ 20: 'Workflow Completion Tester',
27
+ 21: 'State & Session Tester',
28
+ 22: 'Email Notification Verifier',
29
+ 23: 'Migration Tester',
30
+ };
31
+
32
+ /** Look up the display name for an agent number. */
33
+ export function getAgentDisplayName(agentNumber: number): string {
34
+ return AGENT_NAMES[agentNumber] ?? `Agent ${agentNumber}`;
35
+ }
36
+
37
+ /** Message shown when an agent is gated by license tier. */
38
+ export function agentGatedMessage(
39
+ agentNumber: number,
40
+ agentName: string,
41
+ requiredTier: LicenseTier,
42
+ currentTier: LicenseTier,
43
+ ): string {
44
+ return `[Agent ${agentNumber}: ${agentName}] requires ${capitalise(requiredTier)} tier — skipped (current: ${currentTier})`;
45
+ }
46
+
47
+ /** Message shown when a phase is gated by license tier. */
48
+ export function phaseGatedMessage(
49
+ phase: string,
50
+ requiredTier: LicenseTier,
51
+ currentTier: LicenseTier,
52
+ ): string {
53
+ return `${capitalise(phase)} phase requires ${capitalise(requiredTier)} tier — upgrade at ${PRICING_URL} (current: ${currentTier})`;
54
+ }
55
+
56
+ /** Message shown when the run limit has been reached. */
57
+ export function runLimitMessage(
58
+ used: number,
59
+ limit: number,
60
+ tier: LicenseTier,
61
+ ): string {
62
+ return `Run limit reached: ${used}/${limit} runs used on ${capitalise(tier)} tier — upgrade at ${PRICING_URL}`;
63
+ }
64
+
65
+ function capitalise(str: string): string {
66
+ return str.charAt(0).toUpperCase() + str.slice(1);
67
+ }
@@ -0,0 +1,136 @@
1
+ import type { AgentResult, Finding, Phase } from './types';
2
+ import type { TestTeamConfig } from './config';
3
+ import type { BaseAgent } from '../agents/base-agent';
4
+
5
+ export type BrowserEngine = 'chromium' | 'firefox' | 'webkit';
6
+
7
+ export interface MultiBrowserConfig {
8
+ engines: BrowserEngine[];
9
+ parallel: boolean;
10
+ compareResults: boolean;
11
+ }
12
+
13
+ export interface MultiBrowserResult {
14
+ engine: BrowserEngine;
15
+ result: AgentResult;
16
+ duration: number;
17
+ }
18
+
19
+ export interface CrossBrowserDiff {
20
+ engine1: BrowserEngine;
21
+ engine2: BrowserEngine;
22
+ differences: Finding[];
23
+ }
24
+
25
+ /** Default configuration for multi-browser execution. */
26
+ export const DEFAULT_MULTI_BROWSER_CONFIG: MultiBrowserConfig = {
27
+ engines: ['chromium'],
28
+ parallel: true,
29
+ compareResults: true,
30
+ };
31
+
32
+ /**
33
+ * Run an agent across multiple browser engines, optionally in parallel,
34
+ * and compare findings across browsers.
35
+ */
36
+ export async function runMultiBrowser(
37
+ agent: BaseAgent,
38
+ _config: TestTeamConfig,
39
+ _phase: Phase,
40
+ browserConfig: MultiBrowserConfig,
41
+ ): Promise<{ results: MultiBrowserResult[]; diffs: CrossBrowserDiff[] }> {
42
+ const runSingleEngine = async (engine: BrowserEngine): Promise<MultiBrowserResult> => {
43
+ const start = Date.now();
44
+ const result = await agent.run();
45
+ const duration = Date.now() - start;
46
+
47
+ // Tag each finding with the browser engine for traceability
48
+ const taggedFindings: Finding[] = result.findings.map(f => ({
49
+ ...f,
50
+ description: `[${engine}] ${f.description}`,
51
+ }));
52
+
53
+ return {
54
+ engine,
55
+ result: { ...result, findings: taggedFindings },
56
+ duration,
57
+ };
58
+ };
59
+
60
+ let results: MultiBrowserResult[];
61
+
62
+ if (browserConfig.parallel) {
63
+ results = await Promise.all(
64
+ browserConfig.engines.map(engine => runSingleEngine(engine)),
65
+ );
66
+ } else {
67
+ results = [];
68
+ for (const engine of browserConfig.engines) {
69
+ results.push(await runSingleEngine(engine));
70
+ }
71
+ }
72
+
73
+ const diffs = browserConfig.compareResults
74
+ ? compareBrowserResults(results)
75
+ : [];
76
+
77
+ return { results, diffs };
78
+ }
79
+
80
+ /**
81
+ * Compare findings across browser results.
82
+ * A finding is considered unique to a browser if no finding in the other
83
+ * browser shares the same base description (stripping the engine prefix tag).
84
+ */
85
+ export function compareBrowserResults(
86
+ results: MultiBrowserResult[],
87
+ ): CrossBrowserDiff[] {
88
+ if (results.length < 2) {
89
+ return [];
90
+ }
91
+
92
+ const diffs: CrossBrowserDiff[] = [];
93
+
94
+ for (let i = 0; i < results.length; i++) {
95
+ for (let j = i + 1; j < results.length; j++) {
96
+ const r1 = results[i];
97
+ const r2 = results[j];
98
+
99
+ const descriptions1 = new Set(r1.result.findings.map(f => stripEngineTag(f.description)));
100
+ const descriptions2 = new Set(r2.result.findings.map(f => stripEngineTag(f.description)));
101
+
102
+ const differences: Finding[] = [];
103
+
104
+ // Findings in engine1 but not in engine2
105
+ for (const finding of r1.result.findings) {
106
+ const baseDesc = stripEngineTag(finding.description);
107
+ if (!descriptions2.has(baseDesc)) {
108
+ differences.push(finding);
109
+ }
110
+ }
111
+
112
+ // Findings in engine2 but not in engine1
113
+ for (const finding of r2.result.findings) {
114
+ const baseDesc = stripEngineTag(finding.description);
115
+ if (!descriptions1.has(baseDesc)) {
116
+ differences.push(finding);
117
+ }
118
+ }
119
+
120
+ if (differences.length > 0) {
121
+ diffs.push({
122
+ engine1: r1.engine,
123
+ engine2: r2.engine,
124
+ differences,
125
+ });
126
+ }
127
+ }
128
+ }
129
+
130
+ return diffs;
131
+ }
132
+
133
+ /** Strip the "[engine] " prefix tag from a finding description. */
134
+ function stripEngineTag(description: string): string {
135
+ return description.replace(/^\[(chromium|firefox|webkit)\]\s*/, '');
136
+ }