@cavuno/board 2.0.0 → 2.0.1

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 (84) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/CONTRIBUTING.md +25 -0
  3. package/LICENSE +21 -0
  4. package/README.md +12 -7
  5. package/SECURITY.md +28 -0
  6. package/TRADEMARKS.md +16 -0
  7. package/dist/_spec-sW85fuvT.d.mts +2731 -0
  8. package/dist/_spec-sW85fuvT.d.ts +2731 -0
  9. package/dist/bin.mjs +16 -348
  10. package/dist/{board-B8tJGX4i.d.ts → board-DKXyxwNN.d.ts} +2 -2
  11. package/dist/{board-DmRuCA8m.d.mts → board-QFeHT4UN.d.mts} +2 -2
  12. package/dist/doctor.d.mts +6 -19
  13. package/dist/doctor.d.ts +6 -19
  14. package/dist/doctor.js +16 -344
  15. package/dist/doctor.mjs +15 -343
  16. package/dist/filters.d.mts +5 -5
  17. package/dist/filters.d.ts +5 -5
  18. package/dist/format.d.mts +17 -19
  19. package/dist/format.d.ts +17 -19
  20. package/dist/go.d.mts +2 -2
  21. package/dist/go.d.ts +2 -2
  22. package/dist/go.js +1 -1
  23. package/dist/go.mjs +1 -1
  24. package/dist/index.d.mts +16 -18
  25. package/dist/index.d.ts +16 -18
  26. package/dist/index.js +39 -28
  27. package/dist/index.mjs +39 -28
  28. package/dist/{jobs-BEYJY97F.d.ts → jobs-CuRzTDb1.d.mts} +15 -13
  29. package/dist/{jobs-DJiISpqX.d.mts → jobs-DTnCZGtb.d.ts} +15 -13
  30. package/dist/paths.d.mts +6 -6
  31. package/dist/paths.d.ts +6 -6
  32. package/dist/paths.js +1 -1
  33. package/dist/paths.mjs +1 -1
  34. package/dist/roles-vY0AXhoc.d.mts +3 -3
  35. package/dist/roles-vY0AXhoc.d.ts +3 -3
  36. package/dist/route-contract.d.mts +16 -16
  37. package/dist/route-contract.d.ts +16 -16
  38. package/dist/route-contract.js +1 -1
  39. package/dist/route-contract.mjs +1 -1
  40. package/dist/{salaries-D6q7h1J6.d.ts → salaries-BOpR51Ny.d.mts} +3 -3
  41. package/dist/{salaries-B_Xb5k2s.d.mts → salaries-QoNpzRCT.d.ts} +3 -3
  42. package/dist/{search-CnOIZEuc.d.ts → search-B5ROY6PK.d.ts} +2 -2
  43. package/dist/{search-uC_ijbdu.d.mts → search-f93nrSg3.d.mts} +2 -2
  44. package/dist/seo.d.mts +17 -18
  45. package/dist/seo.d.ts +17 -18
  46. package/dist/server.d.mts +14 -32
  47. package/dist/server.d.ts +14 -32
  48. package/dist/sitemap.d.mts +7 -7
  49. package/dist/sitemap.d.ts +7 -7
  50. package/dist/sitemap.js +4 -2
  51. package/dist/sitemap.mjs +4 -2
  52. package/dist/skills.d.mts +1 -1
  53. package/dist/skills.d.ts +1 -1
  54. package/dist/skills.js +1 -1
  55. package/dist/suggest.d.mts +2 -2
  56. package/dist/suggest.d.ts +2 -2
  57. package/dist/theme.d.mts +10 -13
  58. package/dist/theme.d.ts +10 -13
  59. package/dist/theme.js +1 -1
  60. package/dist/theme.mjs +1 -1
  61. package/dist/{ui-copy-CKfFTtLk.d.mts → ui-copy-CL-VN1xb.d.mts} +2 -3
  62. package/dist/{ui-copy-CKfFTtLk.d.ts → ui-copy-CL-VN1xb.d.ts} +2 -3
  63. package/dist/well-known.d.mts +3 -3
  64. package/dist/well-known.d.ts +3 -3
  65. package/dist/well-known.js +1 -1
  66. package/dist/well-known.mjs +1 -1
  67. package/package.json +46 -38
  68. package/skills/cavuno-board-client/SKILL.md +1 -2
  69. package/skills/cavuno-board-filters/SKILL.md +2 -2
  70. package/skills/cavuno-board-format/SKILL.md +1 -1
  71. package/skills/cavuno-board-i18n/SKILL.md +3 -3
  72. package/skills/cavuno-board-job-posting/SKILL.md +1 -1
  73. package/skills/cavuno-board-messaging/SKILL.md +2 -3
  74. package/skills/cavuno-board-paywall/SKILL.md +1 -1
  75. package/skills/cavuno-board-salaries/SKILL.md +1 -1
  76. package/skills/cavuno-board-seo/SKILL.md +4 -6
  77. package/skills/cavuno-board-server/SKILL.md +1 -1
  78. package/skills/cavuno-board-setup/SKILL.md +2 -1
  79. package/skills/cavuno-board-sitemap/SKILL.md +1 -1
  80. package/skills/cavuno-board-smoke-test/SKILL.md +4 -13
  81. package/skills/flavors/tanstack-start/SKILL.md +1 -2
  82. package/skills/manifest.json +1 -1
  83. package/dist/_spec-C7vqY_b3.d.mts +0 -5202
  84. package/dist/_spec-C7vqY_b3.d.ts +0 -5202
package/dist/doctor.js CHANGED
@@ -24,7 +24,7 @@ __export(doctor_exports, {
24
24
  });
25
25
  module.exports = __toCommonJS(doctor_exports);
26
26
 
27
- // ../../node_modules/.pnpm/tsup@8.5.1_jiti@2.7.0_postcss@8.5.14_tsx@4.21.0_typescript@5.9.3_yaml@2.8.4/node_modules/tsup/assets/cjs_shims.js
27
+ // ../../node_modules/.pnpm/tsup@8.5.1_jiti@2.7.0_postcss@8.5.14_tsx@4.21.0_typescript@5.9.3_yaml@2.9.0/node_modules/tsup/assets/cjs_shims.js
28
28
  var getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.tagName.toUpperCase() === "SCRIPT" ? document.currentScript.src : new URL("main.js", document.baseURI).href;
29
29
  var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
30
30
 
@@ -422,86 +422,18 @@ async function runReadProbes(fetchImpl, frontendUrl) {
422
422
  ];
423
423
  }
424
424
 
425
- // src/doctor/sandbox.ts
426
- var SANDBOX_GATE = record("sandbox.persona-gate", 1);
427
- var EXPECTED_PERSONA_COUNT = 8;
428
- function skipSandboxPersonaGate(reason) {
429
- return SANDBOX_GATE("skip", reason);
430
- }
431
- async function checkSandboxPersonaGate(fetchImpl, env, boardContext) {
432
- if (boardContext === null) {
433
- return SANDBOX_GATE(
434
- "skip",
435
- "board did not resolve \u2014 fix static.board first"
436
- );
437
- }
438
- const url = `${apiBase(env.apiUrl)}/v1/boards/${encodeURIComponent(env.boardKey)}/sandbox/personas`;
439
- const result = await probe(fetchImpl, url);
440
- if (boardContext.sandbox !== true) {
441
- return result.status === 404 ? SANDBOX_GATE(
442
- "pass",
443
- "sandbox persona endpoint refuses (404) on a non-sandbox board"
444
- ) : SANDBOX_GATE(
445
- "fail",
446
- `sandbox persona endpoint is reachable on a NON-sandbox board (HTTP ${result.status}) \u2014 the capability gate is not holding`
447
- );
448
- }
449
- if (!result.ok) {
450
- return SANDBOX_GATE(
451
- "fail",
452
- `sandbox persona roster did not resolve (HTTP ${result.status}) \u2014 the persona seed may have failed`
453
- );
454
- }
455
- let personas;
456
- let password;
457
- try {
458
- const parsed = JSON.parse(result.body);
459
- personas = parsed.personas;
460
- password = parsed.password;
461
- } catch {
462
- return SANDBOX_GATE(
463
- "fail",
464
- "sandbox persona endpoint returned 200 but not JSON \u2014 proxy or captive portal in the way?"
465
- );
466
- }
467
- if (!Array.isArray(personas) || personas.length !== EXPECTED_PERSONA_COUNT) {
468
- return SANDBOX_GATE(
469
- "fail",
470
- `expected ${EXPECTED_PERSONA_COUNT} personas, got ${Array.isArray(personas) ? personas.length : "a non-array"} \u2014 the persona seed is incomplete`
471
- );
472
- }
473
- const unseeded = personas.filter((persona) => persona.seeded !== true);
474
- if (unseeded.length > 0) {
475
- const ids = unseeded.map((persona) => typeof persona.id === "string" ? persona.id : "?").join(", ");
476
- return SANDBOX_GATE(
477
- "fail",
478
- `${unseeded.length} of ${EXPECTED_PERSONA_COUNT} personas are not seeded on the board (${ids}) \u2014 the persona seed failed or was purged; run POST /v1/boards/{key}/sandbox/reseed to restore the roster`
479
- );
480
- }
481
- if (typeof password !== "string" || password.length === 0) {
482
- return SANDBOX_GATE(
483
- "fail",
484
- "roster present but the published password is missing"
485
- );
486
- }
487
- return SANDBOX_GATE(
488
- "pass",
489
- `sandbox roster complete and seeded (${EXPECTED_PERSONA_COUNT} personas)`
490
- );
491
- }
492
-
493
425
  // src/doctor/theme.ts
494
426
  var import_node_crypto = require("crypto");
495
427
  var import_node_fs3 = require("fs");
496
428
  var import_node_path3 = require("path");
497
429
  var THEME = record("static.theme", 1);
498
- function checkThemeFreshness(projectRoot, context) {
430
+ function checkThemeFreshness(projectRoot) {
499
431
  const tokensPath = (0, import_node_path3.join)(projectRoot, "src/tokens.css");
500
432
  if (!(0, import_node_fs3.existsSync)(tokensPath)) {
501
433
  return [
502
434
  THEME(
503
435
  "skip",
504
- "board is not tokens-migrated (ADR-0065) \u2014 no src/tokens.css"
436
+ "no src/tokens.css \u2014 local theme freshness check not applicable"
505
437
  )
506
438
  ];
507
439
  }
@@ -518,234 +450,11 @@ function checkThemeFreshness(projectRoot, context) {
518
450
  )
519
451
  ];
520
452
  }
521
- if (!context) {
522
- return [
523
- THEME(
524
- "skip",
525
- "local derivations fresh; platform snapshot unverified \u2014 board context unavailable"
526
- )
527
- ];
528
- }
529
- if (context.themeSnapshotHash !== tokensHash) {
530
- return [
531
- THEME(
532
- "fail",
533
- `platform theme snapshot is ${context.themeSnapshotHash ? "stale" : "missing"} \u2014 emails render ${context.themeSnapshotHash ? "an old" : "the legacy"} theme; sync it: \`npm run gen:theme -- --payload | npx convex run boards/themeSnapshot:sync\``
534
- )
535
- ];
536
- }
537
453
  return [
538
- THEME(
539
- "pass",
540
- `tokens.css \u21C4 resolved module \u21C4 platform snapshot (${tokensHash.slice(0, 12)}\u2026)`
541
- )
454
+ THEME("pass", `tokens.css \u21C4 resolved module (${tokensHash.slice(0, 12)}\u2026)`)
542
455
  ];
543
456
  }
544
457
 
545
- // src/doctor/writes.ts
546
- var WRITE = {
547
- board: record("write.board", 3),
548
- register: record("write.register", 3),
549
- login: record("write.login", 3),
550
- email: record("write.email", 3),
551
- apply: record("write.apply", 3),
552
- alert: record("write.alert", 3)
553
- };
554
- var DEPENDENT_CHECKS = [
555
- WRITE.register,
556
- WRITE.login,
557
- WRITE.email,
558
- WRITE.apply,
559
- WRITE.alert
560
- ];
561
- function skipWriteProbes(reason) {
562
- return Object.values(WRITE).map((make) => make("skip", reason));
563
- }
564
- var FETCH_TIMEOUT_MS2 = 1e4;
565
- var RESEND_API_BASE = "https://api.resend.com";
566
- async function request(fetchImpl, url, init) {
567
- try {
568
- const response = await fetchImpl(url, {
569
- signal: AbortSignal.timeout(FETCH_TIMEOUT_MS2),
570
- ...init
571
- });
572
- let json = null;
573
- try {
574
- json = await response.json();
575
- } catch {
576
- json = null;
577
- }
578
- return { ok: response.ok, status: response.status, json };
579
- } catch (error) {
580
- return {
581
- ok: false,
582
- status: 0,
583
- json: { error: error instanceof Error ? error.message : String(error) }
584
- };
585
- }
586
- }
587
- function post(fetchImpl, url, body, bearer) {
588
- return request(fetchImpl, url, {
589
- method: "POST",
590
- headers: {
591
- "content-type": "application/json",
592
- accept: "application/json",
593
- ...bearer ? { authorization: `Bearer ${bearer}` } : {}
594
- },
595
- body: JSON.stringify(body)
596
- });
597
- }
598
- var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
599
- async function observeVerificationEmail(fetchImpl, apiKey, recipient, pollMs, timeoutMs) {
600
- const deadline = Date.now() + timeoutMs;
601
- const subjectMarker = `[To: ${recipient}]`;
602
- do {
603
- const list = await request(
604
- fetchImpl,
605
- `${RESEND_API_BASE}/emails?limit=20`,
606
- {
607
- headers: { authorization: `Bearer ${apiKey}` }
608
- }
609
- );
610
- if (!list.ok) {
611
- return WRITE.email(
612
- "fail",
613
- `Resend list returned HTTP ${list.status} \u2014 wrong RESEND_API_KEY?`
614
- );
615
- }
616
- const items = list.json?.data ?? [];
617
- const match = items.find(
618
- (item) => item.subject?.includes(subjectMarker) || item.to?.includes(recipient)
619
- );
620
- if (match) {
621
- return WRITE.email(
622
- "pass",
623
- `verification send observed in Resend for ${recipient}`
624
- );
625
- }
626
- await sleep(pollMs);
627
- } while (Date.now() < deadline);
628
- return WRITE.email(
629
- "fail",
630
- `no verification send for ${recipient} appeared in Resend within ${timeoutMs}ms`
631
- );
632
- }
633
- async function runWriteProbes(options) {
634
- const { fetchImpl } = options;
635
- const base = `${apiBase(options.env.apiUrl)}/v1/boards/${encodeURIComponent(options.env.boardKey)}`;
636
- const nonce = options.nonce ?? `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 8)}`;
637
- const email = `doctor+${nonce}@cavuno.com`;
638
- const password = `doctor-probe-${nonce}-A1!`;
639
- let context = options.boardContext ?? null;
640
- if (context === null) {
641
- const r = await request(fetchImpl, base);
642
- if (!r.ok) {
643
- return [
644
- WRITE.board("fail", `board context did not resolve (HTTP ${r.status})`),
645
- ...DEPENDENT_CHECKS.map(
646
- (make) => make("skip", "not run \u2014 the write gate failed (see write.board)")
647
- )
648
- ];
649
- }
650
- context = r.json ?? {};
651
- }
652
- if (context.sandbox !== true) {
653
- return [
654
- WRITE.board(
655
- "fail",
656
- "refusing write probes: this board is not the platform sandbox (`sandbox` is not true) \u2014 point PUBLIC_CAVUNO_BOARD at the sandbox pk_"
657
- ),
658
- ...DEPENDENT_CHECKS.map(
659
- (make) => make("skip", "not run \u2014 the write gate failed (see write.board)")
660
- )
661
- ];
662
- }
663
- const results = [
664
- WRITE.board("pass", "board is the platform sandbox")
665
- ];
666
- const registered = await post(fetchImpl, `${base}/auth/register`, {
667
- role: "candidate",
668
- method: "emailpass",
669
- email,
670
- password,
671
- displayName: "Doctor Probe"
672
- });
673
- const sessionOk = (r) => r.ok && typeof r.json?.accessToken === "string";
674
- results.push(
675
- sessionOk(registered) ? WRITE.register("pass", `candidate registered (${email})`) : WRITE.register(
676
- "fail",
677
- `register returned HTTP ${registered.status}` + (registered.status === 429 ? " \u2014 rate-limited: register allows 5 signups / 15 min per IP; wait before rerunning" : "")
678
- )
679
- );
680
- let accessToken = null;
681
- if (sessionOk(registered)) {
682
- const login = await post(fetchImpl, `${base}/auth/login`, {
683
- email,
684
- password
685
- });
686
- if (sessionOk(login)) {
687
- accessToken = login.json.accessToken;
688
- results.push(WRITE.login("pass", "emailpass login returns a session"));
689
- } else {
690
- results.push(WRITE.login("fail", `login returned HTTP ${login.status}`));
691
- }
692
- } else {
693
- results.push(WRITE.login("skip", "not run \u2014 register failed"));
694
- }
695
- if (!sessionOk(registered)) {
696
- results.push(WRITE.email("skip", "not run \u2014 register failed"));
697
- } else if (!options.resendApiKey) {
698
- results.push(
699
- WRITE.email(
700
- "skip",
701
- "RESEND_API_KEY not set \u2014 the verification send was NOT verified (platform-operator check)"
702
- )
703
- );
704
- } else {
705
- results.push(
706
- await observeVerificationEmail(
707
- fetchImpl,
708
- options.resendApiKey,
709
- email,
710
- options.emailPollMs ?? 2e3,
711
- options.emailTimeoutMs ?? 3e4
712
- )
713
- );
714
- }
715
- if (!accessToken) {
716
- results.push(WRITE.apply("skip", "not run \u2014 no session (see above)"));
717
- } else {
718
- const listing = await request(fetchImpl, `${base}/jobs?limit=1`);
719
- const card = listing.json?.data?.[0];
720
- if (!listing.ok || !card?.slug) {
721
- results.push(
722
- WRITE.apply(
723
- "fail",
724
- listing.ok ? "the board has no jobs to apply to \u2014 is the sandbox seeded?" : `jobs listing returned HTTP ${listing.status}`
725
- )
726
- );
727
- } else {
728
- const applied = await post(
729
- fetchImpl,
730
- `${base}/jobs/${encodeURIComponent(card.slug)}/apply`,
731
- {},
732
- accessToken
733
- );
734
- results.push(
735
- applied.ok && typeof applied.json?.id === "string" ? WRITE.apply("pass", `applied to ${card.slug}`) : WRITE.apply("fail", `apply returned HTTP ${applied.status}`)
736
- );
737
- }
738
- }
739
- const alert = await post(fetchImpl, `${base}/job-alerts`, {
740
- email,
741
- consent: true
742
- });
743
- results.push(
744
- alert.ok ? WRITE.alert("pass", "job-alert subscription accepted") : WRITE.alert("fail", `job-alerts returned HTTP ${alert.status}`)
745
- );
746
- return results;
747
- }
748
-
749
458
  // src/doctor/run.ts
750
459
  var import_node_fs4 = require("fs");
751
460
  var import_node_path4 = require("path");
@@ -782,31 +491,21 @@ async function checkBoardResolves(fetchImpl, env) {
782
491
  `${apiBase(env.apiUrl)}/v1/boards/${encodeURIComponent(env.boardKey)}`
783
492
  );
784
493
  if (!result.ok) {
785
- return {
786
- result: STATIC_BOARD(
787
- "fail",
788
- `board did not resolve for this key (HTTP ${result.status}) \u2014 revoked or wrong key?`
789
- ),
790
- context: null
791
- };
494
+ return STATIC_BOARD(
495
+ "fail",
496
+ `board did not resolve for this key (HTTP ${result.status}) \u2014 revoked or wrong key?`
497
+ );
792
498
  }
793
- let parsed = null;
794
499
  try {
795
- parsed = JSON.parse(result.body);
500
+ const parsed = JSON.parse(result.body);
796
501
  if (typeof parsed !== "object" || parsed === null) throw new Error("shape");
797
502
  } catch {
798
- return {
799
- result: STATIC_BOARD(
800
- "fail",
801
- "board endpoint returned 200 but not JSON \u2014 proxy or captive portal in the way?"
802
- ),
803
- context: null
804
- };
503
+ return STATIC_BOARD(
504
+ "fail",
505
+ "board endpoint returned 200 but not JSON \u2014 proxy or captive portal in the way?"
506
+ );
805
507
  }
806
- return {
807
- result: STATIC_BOARD("pass", "publishable key resolves the board"),
808
- context: parsed
809
- };
508
+ return STATIC_BOARD("pass", "publishable key resolves the board");
810
509
  }
811
510
  var SKILL_ROOTS = [
812
511
  ".claude/skills",
@@ -869,46 +568,19 @@ async function runDoctor(options) {
869
568
  } else {
870
569
  results.push(STATIC_API("skip", "PUBLIC_CAVUNO_API_URL not set"));
871
570
  }
872
- let boardContext = null;
873
571
  if (envOk) {
874
- const resolved = await checkBoardResolves(fetchImpl, env);
875
- boardContext = resolved.context;
876
- results.push(resolved.result);
572
+ results.push(await checkBoardResolves(fetchImpl, env));
877
573
  } else {
878
574
  results.push(STATIC_BOARD("skip", "env checks failed \u2014 fix them first"));
879
575
  }
880
576
  results.push(checkSkillsFreshness(options.projectRoot ?? process.cwd()));
881
- results.push(
882
- ...checkThemeFreshness(options.projectRoot ?? process.cwd(), boardContext)
883
- );
577
+ results.push(...checkThemeFreshness(options.projectRoot ?? process.cwd()));
884
578
  results.push(
885
579
  ...checkCookieCodecConformance(options.projectRoot ?? process.cwd())
886
580
  );
887
- results.push(
888
- envOk ? await checkSandboxPersonaGate(fetchImpl, env, boardContext) : skipSandboxPersonaGate("env checks failed \u2014 fix them first")
889
- );
890
581
  results.push(
891
582
  ...options.frontendUrl ? await runReadProbes(fetchImpl, options.frontendUrl) : skipReadProbes("no --frontend <url> given")
892
583
  );
893
- if (!options.sandbox) {
894
- results.push(
895
- ...skipWriteProbes(
896
- "write probes not enabled \u2014 run with --sandbox against the platform sandbox"
897
- )
898
- );
899
- } else if (!envOk) {
900
- results.push(...skipWriteProbes("env checks failed \u2014 fix them first"));
901
- } else {
902
- results.push(
903
- ...await runWriteProbes({
904
- env,
905
- fetchImpl,
906
- resendApiKey: options.resendApiKey,
907
- nonce: options.writeProbeNonce,
908
- boardContext
909
- })
910
- );
911
- }
912
584
  return { results, summary: summarize(results) };
913
585
  }
914
586
  // Annotate the CommonJS export names for ESM import in node: