@cavuno/board 2.0.3 → 3.0.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 (74) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/CONTRIBUTING.md +0 -11
  3. package/README.md +8 -7
  4. package/dist/{_spec-BKtVFcV9.d.mts → _spec-nkWAraS0.d.mts} +4 -18
  5. package/dist/{_spec-BKtVFcV9.d.ts → _spec-nkWAraS0.d.ts} +4 -18
  6. package/dist/bin.mjs +40 -57
  7. package/dist/{board-3Xj_Y-Tx.d.ts → board-NE98PlwN.d.ts} +2 -3
  8. package/dist/{board-pNN710Zc.d.mts → board-muu8BI8P.d.mts} +2 -3
  9. package/dist/doctor.js +21 -47
  10. package/dist/doctor.mjs +24 -50
  11. package/dist/filters.d.mts +2 -2
  12. package/dist/filters.d.ts +2 -2
  13. package/dist/format.d.mts +3 -3
  14. package/dist/format.d.ts +3 -3
  15. package/dist/index.d.mts +11 -27
  16. package/dist/index.d.ts +11 -27
  17. package/dist/index.js +3 -3
  18. package/dist/index.mjs +3 -3
  19. package/dist/{jobs-C4qRfCdP.d.ts → jobs-BiEvL9d7.d.ts} +1 -1
  20. package/dist/{jobs-k84C0v_8.d.mts → jobs-lVvKCBCU.d.mts} +1 -1
  21. package/dist/{salaries--J5mTylN.d.ts → salaries-BahBpxWT.d.ts} +2 -2
  22. package/dist/{salaries-C9YLO5NG.d.mts → salaries-ChSqDBAM.d.mts} +2 -2
  23. package/dist/{search-CE_qQiM7.d.mts → search-CCyFWPi3.d.mts} +1 -1
  24. package/dist/{search-CLRQ_El9.d.ts → search-CyRvVMUp.d.ts} +1 -1
  25. package/dist/seo.d.mts +4 -4
  26. package/dist/seo.d.ts +4 -4
  27. package/dist/server.d.mts +5 -5
  28. package/dist/server.d.ts +5 -5
  29. package/dist/sitemap.d.mts +5 -5
  30. package/dist/sitemap.d.ts +5 -5
  31. package/dist/skills.d.mts +15 -4
  32. package/dist/skills.d.ts +15 -4
  33. package/dist/skills.js +30 -7
  34. package/dist/skills.mjs +30 -8
  35. package/dist/suggest.d.mts +2 -2
  36. package/dist/suggest.d.ts +2 -2
  37. package/package.json +3 -13
  38. package/skills/cavuno-board-account/SKILL.md +54 -66
  39. package/skills/cavuno-board-api-client/SKILL.md +74 -0
  40. package/skills/cavuno-board-applications/SKILL.md +38 -52
  41. package/skills/cavuno-board-auth/SKILL.md +36 -61
  42. package/skills/cavuno-board-blog/SKILL.md +60 -49
  43. package/skills/cavuno-board-companies/SKILL.md +71 -53
  44. package/skills/cavuno-board-errors/SKILL.md +29 -46
  45. package/skills/cavuno-board-filters/SKILL.md +64 -75
  46. package/skills/cavuno-board-format/SKILL.md +63 -68
  47. package/skills/cavuno-board-i18n/SKILL.md +33 -62
  48. package/skills/cavuno-board-job-alerts/SKILL.md +68 -57
  49. package/skills/cavuno-board-jobs/SKILL.md +63 -55
  50. package/skills/cavuno-board-messaging/SKILL.md +53 -59
  51. package/skills/cavuno-board-paywall/SKILL.md +48 -54
  52. package/skills/cavuno-board-post-a-job/SKILL.md +118 -0
  53. package/skills/cavuno-board-salaries/SKILL.md +73 -50
  54. package/skills/cavuno-board-search-suggestions/SKILL.md +82 -0
  55. package/skills/cavuno-board-seo/BLOG.md +40 -0
  56. package/skills/cavuno-board-seo/JOB_AND_BREADCRUMBS.md +49 -0
  57. package/skills/cavuno-board-seo/LISTING.md +36 -0
  58. package/skills/cavuno-board-seo/SALARY.md +61 -0
  59. package/skills/cavuno-board-seo/SKILL.md +32 -163
  60. package/skills/cavuno-board-server-sessions/SKILL.md +143 -0
  61. package/skills/cavuno-board-setup/SKILL.md +43 -80
  62. package/skills/cavuno-board-sitemap/SKILL.md +76 -93
  63. package/skills/cavuno-board-smoke-test/SKILL.md +34 -69
  64. package/skills/manifest.json +43 -57
  65. package/dist/theme.d.mts +0 -115
  66. package/dist/theme.d.ts +0 -115
  67. package/dist/theme.js +0 -195
  68. package/dist/theme.mjs +0 -174
  69. package/skills/cavuno-board-client/SKILL.md +0 -89
  70. package/skills/cavuno-board-job-posting/SKILL.md +0 -116
  71. package/skills/cavuno-board-server/SKILL.md +0 -229
  72. package/skills/cavuno-board-suggest/SKILL.md +0 -119
  73. package/skills/cavuno-board-theme/SKILL.md +0 -57
  74. package/skills/flavors/tanstack-start/SKILL.md +0 -100
package/CHANGELOG.md CHANGED
@@ -3,6 +3,37 @@
3
3
  This changelog records changes that affect Board API compatibility, exported
4
4
  types, runtime behavior, or supported integration patterns.
5
5
 
6
+ ## 3.0.0 — 2026-08-03
7
+
8
+ - Removed runtime theme data from `board.context()` and removed the
9
+ `PublicBoardTheme` type and `@cavuno/board/theme` export. Applications now
10
+ own their presentation tokens and styling independently of Board API data.
11
+ - Retired the Cavuno theme skill and the bundled TanStack Start flavor. The
12
+ framework-neutral SDK skills were rewritten around concrete integration
13
+ tasks, with clearer names for API client setup, server sessions, search
14
+ suggestions, and public job posting.
15
+
16
+ ## 2.1.0 — 2026-08-01
17
+
18
+ - Removed private starter-repository links from the public SDK documentation.
19
+ - Repaired the audited public-repository export metadata and standalone
20
+ package lockfile used by trusted publishing.
21
+
22
+ ## 2.0.5 — 2026-07-30
23
+
24
+ - Added the optional `correction` object to board job catalog responses. It is
25
+ present only when a free-text search that matched nothing was silently
26
+ retried with one misspelled word corrected; `data` and `count` then belong to
27
+ the corrected query. Absent on every other response, so existing consumers
28
+ are unaffected.
29
+
30
+ ## 2.0.4 — 2026-07-24
31
+
32
+ - Aligned npm, GitHub, README, and website metadata around building custom job
33
+ boards and careers pages with Cavuno’s TypeScript SDK.
34
+ - Made the private Cavuno source the canonical input to audited public
35
+ repository synchronization and trusted npm publishing.
36
+
6
37
  ## 2.0.3 — 2026-07-24
7
38
 
8
39
  - Added verifiable npm provenance generated from the public Cavuno SDK source
package/CONTRIBUTING.md CHANGED
@@ -23,14 +23,3 @@ the documented Board API.
23
23
 
24
24
  By submitting a contribution, you agree that it may be distributed under the
25
25
  MIT License and that you have the right to submit it.
26
-
27
- ## Releases
28
-
29
- Releases are published from `main` by the repository's `Publish to npm`
30
- workflow. Maintainers update the package version and changelog in a pull
31
- request, merge it after CI passes, and then run the publishing workflow from
32
- `main`.
33
-
34
- The workflow uses npm trusted publishing with short-lived GitHub Actions
35
- credentials. Published packages include provenance linking the npm artifact to
36
- the public source commit and workflow that produced it.
package/README.md CHANGED
@@ -1,9 +1,10 @@
1
1
  # Cavuno Board SDK
2
2
 
3
- `@cavuno/board` is the official typed, isomorphic client for the
4
- [Cavuno job board platform](https://cavuno.com) Board API—the headless toolkit
5
- for building fully custom job board frontends. It has zero runtime dependencies
6
- and runs in browsers, Node.js 20 or newer, and Cloudflare Workers.
3
+ Build custom job boards and careers pages with Cavuno’s TypeScript SDK.
4
+
5
+ `@cavuno/board` is a typed, isomorphic client for the
6
+ [Cavuno job board platform](https://cavuno.com) Board API. It has zero runtime
7
+ dependencies and runs in browsers, Node.js 20 or newer, and Cloudflare Workers.
7
8
 
8
9
  You bring the framework and own the layout; the SDK brings the job board:
9
10
  jobs and search, companies, salaries, blog, board-user auth, saved jobs,
@@ -37,7 +38,7 @@ const board = createBoardClient({
37
38
  board: process.env.PUBLIC_CAVUNO_BOARD!, // pk_… publishable key
38
39
  });
39
40
 
40
- const { name, theme, features } = await board.context();
41
+ const { name, language, features } = await board.context();
41
42
  const page = await board.jobs.list({ limit: 20 });
42
43
  const job = await board.jobs.retrieve('senior-chef');
43
44
 
@@ -93,7 +94,7 @@ SDK doesn't cover yet.
93
94
  - OpenAPI document: `GET https://api.cavuno.com/v1/openapi.json`
94
95
  - Source: <https://github.com/wollemiahq/cavuno-sdk>
95
96
  - Issues: <https://github.com/wollemiahq/cavuno-sdk/issues>
96
- - Reference starter (TanStack Start on Cloudflare Workers):
97
- <https://github.com/wollemiahq/cavuno-board-starter>
97
+ - Clone-and-own TanStack Start + shadcn/ui template:
98
+ <https://github.com/wollemiahq/cavuno-tanstack-start-shadcn-job-board-template>
98
99
 
99
100
  MIT © Wollemia
@@ -1674,22 +1674,6 @@ interface components {
1674
1674
  linkedInPartnerId: string | null;
1675
1675
  cookieConsentRequired: boolean;
1676
1676
  };
1677
- theme: {
1678
- mode: string;
1679
- schemeId: string;
1680
- typography: {
1681
- fontSans: string;
1682
- fontHeading?: string | null;
1683
- };
1684
- colors: {
1685
- light: {
1686
- [key: string]: unknown;
1687
- };
1688
- dark: {
1689
- [key: string]: unknown;
1690
- };
1691
- };
1692
- } | null;
1693
1677
  /** @description Operator-defined custom job-field definitions, in display order. Board-wide; the frontend uses these to render and localize each job's opaque `customFieldValues`. Empty when the board defines none. Display-only: not filterable or searchable in v1. */
1694
1678
  customFields: components["schemas"]["CustomFieldDefinition"][];
1695
1679
  /** @description Stored operator label overrides by config group (`jobCardLabels`, `navLabels`, `breadcrumbsLabels`, …): plain-text chrome copy to merge over the `@cavuno/board` `uiCopy(language)` catalog via `uiCopy(language, labels)`. Empty object when the board stores none. */
@@ -1842,12 +1826,12 @@ interface components {
1842
1826
  company: components["schemas"]["JobCompany"];
1843
1827
  /** @description Physical office locations associated with the job. */
1844
1828
  officeLocations: components["schemas"]["JobOfficeLocation"][];
1845
- /** @description Resolved job categories (slug + board display name): same shape as the card; names joined server-side. */
1829
+ /** @description Resolved job categories (slug + board display name): same shape as the card; names joined server-side. Guaranteed resolvable: every emitted slug resolves via `GET /v1/boards/:identifier/categories/:slug` — consumers never need to re-verify before linking. */
1846
1830
  categories: {
1847
1831
  slug: string;
1848
1832
  name: string;
1849
1833
  }[];
1850
- /** @description Resolved job skills (slug + board display name). */
1834
+ /** @description Resolved job skills (slug + board display name). Guaranteed resolvable: every emitted slug resolves via `GET /v1/boards/:identifier/skills/:slug` — consumers never need to re-verify before linking. */
1851
1835
  skills: {
1852
1836
  slug: string;
1853
1837
  name: string;
@@ -1947,10 +1931,12 @@ interface components {
1947
1931
  name: string;
1948
1932
  logoUrl: string | null;
1949
1933
  } | null;
1934
+ /** @description Job categories (slug + board display name). Guaranteed resolvable: every emitted slug resolves via `GET /v1/boards/:identifier/categories/:slug`. */
1950
1935
  categories: {
1951
1936
  slug: string;
1952
1937
  name: string;
1953
1938
  }[];
1939
+ /** @description Job skills (slug + board display name). Guaranteed resolvable: every emitted slug resolves via `GET /v1/boards/:identifier/skills/:slug`. */
1954
1940
  skills: {
1955
1941
  slug: string;
1956
1942
  name: string;
@@ -1674,22 +1674,6 @@ interface components {
1674
1674
  linkedInPartnerId: string | null;
1675
1675
  cookieConsentRequired: boolean;
1676
1676
  };
1677
- theme: {
1678
- mode: string;
1679
- schemeId: string;
1680
- typography: {
1681
- fontSans: string;
1682
- fontHeading?: string | null;
1683
- };
1684
- colors: {
1685
- light: {
1686
- [key: string]: unknown;
1687
- };
1688
- dark: {
1689
- [key: string]: unknown;
1690
- };
1691
- };
1692
- } | null;
1693
1677
  /** @description Operator-defined custom job-field definitions, in display order. Board-wide; the frontend uses these to render and localize each job's opaque `customFieldValues`. Empty when the board defines none. Display-only: not filterable or searchable in v1. */
1694
1678
  customFields: components["schemas"]["CustomFieldDefinition"][];
1695
1679
  /** @description Stored operator label overrides by config group (`jobCardLabels`, `navLabels`, `breadcrumbsLabels`, …): plain-text chrome copy to merge over the `@cavuno/board` `uiCopy(language)` catalog via `uiCopy(language, labels)`. Empty object when the board stores none. */
@@ -1842,12 +1826,12 @@ interface components {
1842
1826
  company: components["schemas"]["JobCompany"];
1843
1827
  /** @description Physical office locations associated with the job. */
1844
1828
  officeLocations: components["schemas"]["JobOfficeLocation"][];
1845
- /** @description Resolved job categories (slug + board display name): same shape as the card; names joined server-side. */
1829
+ /** @description Resolved job categories (slug + board display name): same shape as the card; names joined server-side. Guaranteed resolvable: every emitted slug resolves via `GET /v1/boards/:identifier/categories/:slug` — consumers never need to re-verify before linking. */
1846
1830
  categories: {
1847
1831
  slug: string;
1848
1832
  name: string;
1849
1833
  }[];
1850
- /** @description Resolved job skills (slug + board display name). */
1834
+ /** @description Resolved job skills (slug + board display name). Guaranteed resolvable: every emitted slug resolves via `GET /v1/boards/:identifier/skills/:slug` — consumers never need to re-verify before linking. */
1851
1835
  skills: {
1852
1836
  slug: string;
1853
1837
  name: string;
@@ -1947,10 +1931,12 @@ interface components {
1947
1931
  name: string;
1948
1932
  logoUrl: string | null;
1949
1933
  } | null;
1934
+ /** @description Job categories (slug + board display name). Guaranteed resolvable: every emitted slug resolves via `GET /v1/boards/:identifier/categories/:slug`. */
1950
1935
  categories: {
1951
1936
  slug: string;
1952
1937
  name: string;
1953
1938
  }[];
1939
+ /** @description Job skills (slug + board display name). Guaranteed resolvable: every emitted slug resolves via `GET /v1/boards/:identifier/skills/:slug`. */
1954
1940
  skills: {
1955
1941
  slug: string;
1956
1942
  name: string;
package/dist/bin.mjs CHANGED
@@ -4,7 +4,7 @@
4
4
  var DEFAULT_CAVUNO_API_URL = "https://api.cavuno.com";
5
5
 
6
6
  // src/skills.ts
7
- import { readFileSync } from "fs";
7
+ import { readdirSync, readFileSync } from "fs";
8
8
  import { dirname, resolve } from "path";
9
9
  import { fileURLToPath } from "url";
10
10
  function packageRoot() {
@@ -21,13 +21,21 @@ function loadSkillCorpus(baseDir) {
21
21
  const manifest = loadSkillManifest(baseDir);
22
22
  return {
23
23
  version: manifest.version,
24
- skills: manifest.skills.map((skill) => ({
25
- ...skill,
26
- content: readFileSync(
27
- resolveFromPackageRoot(skill.path, baseDir),
28
- "utf8"
29
- )
30
- }))
24
+ skills: manifest.skills.map((skill) => {
25
+ const skillPath = resolveFromPackageRoot(skill.path, baseDir);
26
+ const skillDir = dirname(skillPath);
27
+ const references = readdirSync(skillDir, { withFileTypes: true }).filter(
28
+ (entry) => entry.isFile() && entry.name !== "SKILL.md" && entry.name.endsWith(".md")
29
+ ).map((entry) => ({
30
+ path: entry.name,
31
+ content: readFileSync(resolve(skillDir, entry.name), "utf8")
32
+ })).sort((a, b) => a.path.localeCompare(b.path));
33
+ return {
34
+ ...skill,
35
+ content: readFileSync(skillPath, "utf8"),
36
+ references
37
+ };
38
+ })
31
39
  };
32
40
  }
33
41
 
@@ -105,7 +113,7 @@ function summarize(results) {
105
113
  }
106
114
 
107
115
  // src/doctor/cookie-conformance.ts
108
- import { existsSync, readdirSync, readFileSync as readFileSync2, statSync } from "fs";
116
+ import { existsSync, readdirSync as readdirSync2, readFileSync as readFileSync2, statSync } from "fs";
109
117
  import { join, relative } from "path";
110
118
  var COOKIE = record("static.cookie-codec", 1);
111
119
  var SOURCE_EXT = /\.(ts|tsx|js|jsx|mjs|cjs|astro|svelte|vue)$/;
@@ -211,7 +219,7 @@ function hasGeneratedBanner(text) {
211
219
  function walkSourceFiles(dir, out) {
212
220
  let entries;
213
221
  try {
214
- entries = readdirSync(dir);
222
+ entries = readdirSync2(dir);
215
223
  } catch {
216
224
  return;
217
225
  }
@@ -394,42 +402,9 @@ async function runReadProbes(fetchImpl, frontendUrl) {
394
402
  ];
395
403
  }
396
404
 
397
- // src/doctor/theme.ts
398
- import { createHash } from "crypto";
405
+ // src/doctor/run.ts
399
406
  import { existsSync as existsSync2, readFileSync as readFileSync3 } from "fs";
400
407
  import { join as join2 } from "path";
401
- var THEME = record("static.theme", 1);
402
- function checkThemeFreshness(projectRoot) {
403
- const tokensPath = join2(projectRoot, "src/tokens.css");
404
- if (!existsSync2(tokensPath)) {
405
- return [
406
- THEME(
407
- "skip",
408
- "no src/tokens.css \u2014 local theme freshness check not applicable"
409
- )
410
- ];
411
- }
412
- const tokensHash = createHash("sha256").update(readFileSync3(tokensPath, "utf8"), "utf8").digest("hex");
413
- const resolvedPath = join2(projectRoot, "src/theme/resolved.ts");
414
- const resolvedHash = existsSync2(resolvedPath) ? readFileSync3(resolvedPath, "utf8").match(
415
- /tokensHash = '([0-9a-f]{64})'/
416
- )?.[1] ?? null : null;
417
- if (resolvedHash !== tokensHash) {
418
- return [
419
- THEME(
420
- "fail",
421
- `src/theme/resolved.ts is ${resolvedHash ? "stale" : "missing"} \u2014 run \`npm run gen:theme\` (OG images render from it)`
422
- )
423
- ];
424
- }
425
- return [
426
- THEME("pass", `tokens.css \u21C4 resolved module (${tokensHash.slice(0, 12)}\u2026)`)
427
- ];
428
- }
429
-
430
- // src/doctor/run.ts
431
- import { existsSync as existsSync3, readFileSync as readFileSync4 } from "fs";
432
- import { join as join3 } from "path";
433
408
  var STATIC_API = record("static.api", 1);
434
409
  var STATIC_BOARD = record("static.board", 1);
435
410
  var STATIC_SKILLS = record("static.skills", 1);
@@ -485,8 +460,8 @@ var SKILL_ROOTS = [
485
460
  ".cursor/skills"
486
461
  ];
487
462
  function checkSkillsFreshness(projectRoot) {
488
- const roots = SKILL_ROOTS.map((root) => join3(projectRoot, root)).filter(
489
- (root) => existsSync3(root)
463
+ const roots = SKILL_ROOTS.map((root) => join2(projectRoot, root)).filter(
464
+ (root) => existsSync2(root)
490
465
  );
491
466
  if (roots.length === 0) {
492
467
  return STATIC_SKILLS(
@@ -499,10 +474,10 @@ function checkSkillsFreshness(projectRoot) {
499
474
  const seen = /* @__PURE__ */ new Set();
500
475
  for (const root of roots) {
501
476
  for (const skill of corpus.skills) {
502
- const copied = join3(root, skill.name, "SKILL.md");
503
- if (!existsSync3(copied)) continue;
477
+ const copied = join2(root, skill.name, "SKILL.md");
478
+ if (!existsSync2(copied)) continue;
504
479
  seen.add(skill.name);
505
- if (readFileSync4(copied, "utf8") !== skill.content) {
480
+ if (readFileSync3(copied, "utf8") !== skill.content) {
506
481
  stale.add(skill.name);
507
482
  }
508
483
  }
@@ -546,7 +521,6 @@ async function runDoctor(options) {
546
521
  results.push(STATIC_BOARD("skip", "env checks failed \u2014 fix them first"));
547
522
  }
548
523
  results.push(checkSkillsFreshness(options.projectRoot ?? process.cwd()));
549
- results.push(...checkThemeFreshness(options.projectRoot ?? process.cwd()));
550
524
  results.push(
551
525
  ...checkCookieCodecConformance(options.projectRoot ?? process.cwd())
552
526
  );
@@ -557,12 +531,20 @@ async function runDoctor(options) {
557
531
  }
558
532
 
559
533
  // src/setup/run.ts
560
- import { cpSync, existsSync as existsSync4, mkdirSync, readFileSync as readFileSync5 } from "fs";
534
+ import { cpSync, existsSync as existsSync3, mkdirSync, readFileSync as readFileSync4, rmSync } from "fs";
561
535
  import { dirname as dirname2, resolve as resolve2 } from "path";
536
+ var RETIRED_SKILL_NAMES = [
537
+ "cavuno-board-theme",
538
+ "cavuno-board-tanstack-start",
539
+ "cavuno-board-client",
540
+ "cavuno-board-server",
541
+ "cavuno-board-suggest",
542
+ "cavuno-board-job-posting"
543
+ ];
562
544
  function detectFramework(cwd) {
563
545
  const pkgPath = resolve2(cwd, "package.json");
564
- if (!existsSync4(pkgPath)) return null;
565
- const pkg = JSON.parse(readFileSync5(pkgPath, "utf8"));
546
+ if (!existsSync3(pkgPath)) return null;
547
+ const pkg = JSON.parse(readFileSync4(pkgPath, "utf8"));
566
548
  const deps = { ...pkg.dependencies, ...pkg.devDependencies };
567
549
  if (deps["@tanstack/react-start"]) return "tanstack-start";
568
550
  return null;
@@ -570,20 +552,21 @@ function detectFramework(cwd) {
570
552
  function runSetup(cwd = process.cwd()) {
571
553
  const manifest = loadSkillManifest();
572
554
  const framework = detectFramework(cwd);
573
- const chosen = manifest.skills.filter(
574
- (skill) => skill.category === "core" || skill.framework === framework
575
- );
555
+ const chosen = manifest.skills.filter((skill) => skill.category === "core");
576
556
  const roots = [
577
557
  resolve2(cwd, ".agents", "skills"),
578
558
  resolve2(cwd, ".claude", "skills"),
579
559
  resolve2(cwd, ".codex", "skills"),
580
560
  resolve2(cwd, ".cursor", "skills")
581
561
  ];
582
- const existing = roots.filter((root) => existsSync4(root));
562
+ const existing = roots.filter((root) => existsSync3(root));
583
563
  const targetDirs = existing.length > 0 ? existing : [roots[0]];
584
564
  const copied = [];
585
565
  for (const targetDir of targetDirs) {
586
566
  mkdirSync(targetDir, { recursive: true });
567
+ for (const name of RETIRED_SKILL_NAMES) {
568
+ rmSync(resolve2(targetDir, name), { recursive: true, force: true });
569
+ }
587
570
  for (const skill of chosen) {
588
571
  const sourceDir = dirname2(resolveFromPackageRoot(skill.path));
589
572
  const destDir = resolve2(targetDir, skill.name);
@@ -1,9 +1,8 @@
1
- import { S as Schemas } from './_spec-BKtVFcV9.js';
1
+ import { S as Schemas } from './_spec-nkWAraS0.js';
2
2
 
3
3
  type PublicBoard = Schemas['PublicBoardContext'];
4
4
  type PublicBoardFeatures = PublicBoard['features'];
5
5
  type PublicBoardAnalytics = PublicBoard['analytics'];
6
- type PublicBoardTheme = NonNullable<PublicBoard['theme']>;
7
6
  /**
8
7
  * An operator-defined custom job-field definition. Board-wide;
9
8
  * use it to render and localize a job's opaque `customFieldValues` (resolve
@@ -14,4 +13,4 @@ type CustomFieldDefinition = Schemas['CustomFieldDefinition'];
14
13
  type CustomFieldType = CustomFieldDefinition['type'];
15
14
  type CustomFieldOption = Schemas['CustomFieldOption'];
16
15
 
17
- export type { CustomFieldDefinition as C, PublicBoard as P, CustomFieldOption as a, CustomFieldType as b, PublicBoardAnalytics as c, PublicBoardFeatures as d, PublicBoardTheme as e };
16
+ export type { CustomFieldDefinition as C, PublicBoard as P, CustomFieldOption as a, CustomFieldType as b, PublicBoardAnalytics as c, PublicBoardFeatures as d };
@@ -1,9 +1,8 @@
1
- import { S as Schemas } from './_spec-BKtVFcV9.mjs';
1
+ import { S as Schemas } from './_spec-nkWAraS0.mjs';
2
2
 
3
3
  type PublicBoard = Schemas['PublicBoardContext'];
4
4
  type PublicBoardFeatures = PublicBoard['features'];
5
5
  type PublicBoardAnalytics = PublicBoard['analytics'];
6
- type PublicBoardTheme = NonNullable<PublicBoard['theme']>;
7
6
  /**
8
7
  * An operator-defined custom job-field definition. Board-wide;
9
8
  * use it to render and localize a job's opaque `customFieldValues` (resolve
@@ -14,4 +13,4 @@ type CustomFieldDefinition = Schemas['CustomFieldDefinition'];
14
13
  type CustomFieldType = CustomFieldDefinition['type'];
15
14
  type CustomFieldOption = Schemas['CustomFieldOption'];
16
15
 
17
- export type { CustomFieldDefinition as C, PublicBoard as P, CustomFieldOption as a, CustomFieldType as b, PublicBoardAnalytics as c, PublicBoardFeatures as d, PublicBoardTheme as e };
16
+ export type { CustomFieldDefinition as C, PublicBoard as P, CustomFieldOption as a, CustomFieldType as b, PublicBoardAnalytics as c, PublicBoardFeatures as d };
package/dist/doctor.js CHANGED
@@ -49,13 +49,21 @@ function loadSkillCorpus(baseDir) {
49
49
  const manifest = loadSkillManifest(baseDir);
50
50
  return {
51
51
  version: manifest.version,
52
- skills: manifest.skills.map((skill) => ({
53
- ...skill,
54
- content: (0, import_node_fs.readFileSync)(
55
- resolveFromPackageRoot(skill.path, baseDir),
56
- "utf8"
57
- )
58
- }))
52
+ skills: manifest.skills.map((skill) => {
53
+ const skillPath = resolveFromPackageRoot(skill.path, baseDir);
54
+ const skillDir = (0, import_node_path.dirname)(skillPath);
55
+ const references = (0, import_node_fs.readdirSync)(skillDir, { withFileTypes: true }).filter(
56
+ (entry) => entry.isFile() && entry.name !== "SKILL.md" && entry.name.endsWith(".md")
57
+ ).map((entry) => ({
58
+ path: entry.name,
59
+ content: (0, import_node_fs.readFileSync)((0, import_node_path.resolve)(skillDir, entry.name), "utf8")
60
+ })).sort((a, b) => a.path.localeCompare(b.path));
61
+ return {
62
+ ...skill,
63
+ content: (0, import_node_fs.readFileSync)(skillPath, "utf8"),
64
+ references
65
+ };
66
+ })
59
67
  };
60
68
  }
61
69
 
@@ -422,42 +430,9 @@ async function runReadProbes(fetchImpl, frontendUrl) {
422
430
  ];
423
431
  }
424
432
 
425
- // src/doctor/theme.ts
426
- var import_node_crypto = require("crypto");
433
+ // src/doctor/run.ts
427
434
  var import_node_fs3 = require("fs");
428
435
  var import_node_path3 = require("path");
429
- var THEME = record("static.theme", 1);
430
- function checkThemeFreshness(projectRoot) {
431
- const tokensPath = (0, import_node_path3.join)(projectRoot, "src/tokens.css");
432
- if (!(0, import_node_fs3.existsSync)(tokensPath)) {
433
- return [
434
- THEME(
435
- "skip",
436
- "no src/tokens.css \u2014 local theme freshness check not applicable"
437
- )
438
- ];
439
- }
440
- const tokensHash = (0, import_node_crypto.createHash)("sha256").update((0, import_node_fs3.readFileSync)(tokensPath, "utf8"), "utf8").digest("hex");
441
- const resolvedPath = (0, import_node_path3.join)(projectRoot, "src/theme/resolved.ts");
442
- const resolvedHash = (0, import_node_fs3.existsSync)(resolvedPath) ? (0, import_node_fs3.readFileSync)(resolvedPath, "utf8").match(
443
- /tokensHash = '([0-9a-f]{64})'/
444
- )?.[1] ?? null : null;
445
- if (resolvedHash !== tokensHash) {
446
- return [
447
- THEME(
448
- "fail",
449
- `src/theme/resolved.ts is ${resolvedHash ? "stale" : "missing"} \u2014 run \`npm run gen:theme\` (OG images render from it)`
450
- )
451
- ];
452
- }
453
- return [
454
- THEME("pass", `tokens.css \u21C4 resolved module (${tokensHash.slice(0, 12)}\u2026)`)
455
- ];
456
- }
457
-
458
- // src/doctor/run.ts
459
- var import_node_fs4 = require("fs");
460
- var import_node_path4 = require("path");
461
436
  var STATIC_API = record("static.api", 1);
462
437
  var STATIC_BOARD = record("static.board", 1);
463
438
  var STATIC_SKILLS = record("static.skills", 1);
@@ -513,8 +488,8 @@ var SKILL_ROOTS = [
513
488
  ".cursor/skills"
514
489
  ];
515
490
  function checkSkillsFreshness(projectRoot) {
516
- const roots = SKILL_ROOTS.map((root) => (0, import_node_path4.join)(projectRoot, root)).filter(
517
- (root) => (0, import_node_fs4.existsSync)(root)
491
+ const roots = SKILL_ROOTS.map((root) => (0, import_node_path3.join)(projectRoot, root)).filter(
492
+ (root) => (0, import_node_fs3.existsSync)(root)
518
493
  );
519
494
  if (roots.length === 0) {
520
495
  return STATIC_SKILLS(
@@ -527,10 +502,10 @@ function checkSkillsFreshness(projectRoot) {
527
502
  const seen = /* @__PURE__ */ new Set();
528
503
  for (const root of roots) {
529
504
  for (const skill of corpus.skills) {
530
- const copied = (0, import_node_path4.join)(root, skill.name, "SKILL.md");
531
- if (!(0, import_node_fs4.existsSync)(copied)) continue;
505
+ const copied = (0, import_node_path3.join)(root, skill.name, "SKILL.md");
506
+ if (!(0, import_node_fs3.existsSync)(copied)) continue;
532
507
  seen.add(skill.name);
533
- if ((0, import_node_fs4.readFileSync)(copied, "utf8") !== skill.content) {
508
+ if ((0, import_node_fs3.readFileSync)(copied, "utf8") !== skill.content) {
534
509
  stale.add(skill.name);
535
510
  }
536
511
  }
@@ -574,7 +549,6 @@ async function runDoctor(options) {
574
549
  results.push(STATIC_BOARD("skip", "env checks failed \u2014 fix them first"));
575
550
  }
576
551
  results.push(checkSkillsFreshness(options.projectRoot ?? process.cwd()));
577
- results.push(...checkThemeFreshness(options.projectRoot ?? process.cwd()));
578
552
  results.push(
579
553
  ...checkCookieCodecConformance(options.projectRoot ?? process.cwd())
580
554
  );
package/dist/doctor.mjs CHANGED
@@ -2,7 +2,7 @@
2
2
  var DEFAULT_CAVUNO_API_URL = "https://api.cavuno.com";
3
3
 
4
4
  // src/skills.ts
5
- import { readFileSync } from "fs";
5
+ import { readdirSync, readFileSync } from "fs";
6
6
  import { dirname, resolve } from "path";
7
7
  import { fileURLToPath } from "url";
8
8
  function packageRoot() {
@@ -19,13 +19,21 @@ function loadSkillCorpus(baseDir) {
19
19
  const manifest = loadSkillManifest(baseDir);
20
20
  return {
21
21
  version: manifest.version,
22
- skills: manifest.skills.map((skill) => ({
23
- ...skill,
24
- content: readFileSync(
25
- resolveFromPackageRoot(skill.path, baseDir),
26
- "utf8"
27
- )
28
- }))
22
+ skills: manifest.skills.map((skill) => {
23
+ const skillPath = resolveFromPackageRoot(skill.path, baseDir);
24
+ const skillDir = dirname(skillPath);
25
+ const references = readdirSync(skillDir, { withFileTypes: true }).filter(
26
+ (entry) => entry.isFile() && entry.name !== "SKILL.md" && entry.name.endsWith(".md")
27
+ ).map((entry) => ({
28
+ path: entry.name,
29
+ content: readFileSync(resolve(skillDir, entry.name), "utf8")
30
+ })).sort((a, b) => a.path.localeCompare(b.path));
31
+ return {
32
+ ...skill,
33
+ content: readFileSync(skillPath, "utf8"),
34
+ references
35
+ };
36
+ })
29
37
  };
30
38
  }
31
39
 
@@ -103,7 +111,7 @@ function summarize(results) {
103
111
  }
104
112
 
105
113
  // src/doctor/cookie-conformance.ts
106
- import { existsSync, readdirSync, readFileSync as readFileSync2, statSync } from "fs";
114
+ import { existsSync, readdirSync as readdirSync2, readFileSync as readFileSync2, statSync } from "fs";
107
115
  import { join, relative } from "path";
108
116
  var COOKIE = record("static.cookie-codec", 1);
109
117
  var SOURCE_EXT = /\.(ts|tsx|js|jsx|mjs|cjs|astro|svelte|vue)$/;
@@ -209,7 +217,7 @@ function hasGeneratedBanner(text) {
209
217
  function walkSourceFiles(dir, out) {
210
218
  let entries;
211
219
  try {
212
- entries = readdirSync(dir);
220
+ entries = readdirSync2(dir);
213
221
  } catch {
214
222
  return;
215
223
  }
@@ -392,42 +400,9 @@ async function runReadProbes(fetchImpl, frontendUrl) {
392
400
  ];
393
401
  }
394
402
 
395
- // src/doctor/theme.ts
396
- import { createHash } from "crypto";
403
+ // src/doctor/run.ts
397
404
  import { existsSync as existsSync2, readFileSync as readFileSync3 } from "fs";
398
405
  import { join as join2 } from "path";
399
- var THEME = record("static.theme", 1);
400
- function checkThemeFreshness(projectRoot) {
401
- const tokensPath = join2(projectRoot, "src/tokens.css");
402
- if (!existsSync2(tokensPath)) {
403
- return [
404
- THEME(
405
- "skip",
406
- "no src/tokens.css \u2014 local theme freshness check not applicable"
407
- )
408
- ];
409
- }
410
- const tokensHash = createHash("sha256").update(readFileSync3(tokensPath, "utf8"), "utf8").digest("hex");
411
- const resolvedPath = join2(projectRoot, "src/theme/resolved.ts");
412
- const resolvedHash = existsSync2(resolvedPath) ? readFileSync3(resolvedPath, "utf8").match(
413
- /tokensHash = '([0-9a-f]{64})'/
414
- )?.[1] ?? null : null;
415
- if (resolvedHash !== tokensHash) {
416
- return [
417
- THEME(
418
- "fail",
419
- `src/theme/resolved.ts is ${resolvedHash ? "stale" : "missing"} \u2014 run \`npm run gen:theme\` (OG images render from it)`
420
- )
421
- ];
422
- }
423
- return [
424
- THEME("pass", `tokens.css \u21C4 resolved module (${tokensHash.slice(0, 12)}\u2026)`)
425
- ];
426
- }
427
-
428
- // src/doctor/run.ts
429
- import { existsSync as existsSync3, readFileSync as readFileSync4 } from "fs";
430
- import { join as join3 } from "path";
431
406
  var STATIC_API = record("static.api", 1);
432
407
  var STATIC_BOARD = record("static.board", 1);
433
408
  var STATIC_SKILLS = record("static.skills", 1);
@@ -483,8 +458,8 @@ var SKILL_ROOTS = [
483
458
  ".cursor/skills"
484
459
  ];
485
460
  function checkSkillsFreshness(projectRoot) {
486
- const roots = SKILL_ROOTS.map((root) => join3(projectRoot, root)).filter(
487
- (root) => existsSync3(root)
461
+ const roots = SKILL_ROOTS.map((root) => join2(projectRoot, root)).filter(
462
+ (root) => existsSync2(root)
488
463
  );
489
464
  if (roots.length === 0) {
490
465
  return STATIC_SKILLS(
@@ -497,10 +472,10 @@ function checkSkillsFreshness(projectRoot) {
497
472
  const seen = /* @__PURE__ */ new Set();
498
473
  for (const root of roots) {
499
474
  for (const skill of corpus.skills) {
500
- const copied = join3(root, skill.name, "SKILL.md");
501
- if (!existsSync3(copied)) continue;
475
+ const copied = join2(root, skill.name, "SKILL.md");
476
+ if (!existsSync2(copied)) continue;
502
477
  seen.add(skill.name);
503
- if (readFileSync4(copied, "utf8") !== skill.content) {
478
+ if (readFileSync3(copied, "utf8") !== skill.content) {
504
479
  stale.add(skill.name);
505
480
  }
506
481
  }
@@ -544,7 +519,6 @@ async function runDoctor(options) {
544
519
  results.push(STATIC_BOARD("skip", "env checks failed \u2014 fix them first"));
545
520
  }
546
521
  results.push(checkSkillsFreshness(options.projectRoot ?? process.cwd()));
547
- results.push(...checkThemeFreshness(options.projectRoot ?? process.cwd()));
548
522
  results.push(
549
523
  ...checkCookieCodecConformance(options.projectRoot ?? process.cwd())
550
524
  );
@@ -1,6 +1,6 @@
1
1
  import { B as BoardLabelOverrides } from './ui-copy-CCkz6CVF.mjs';
2
- import { J as JobSort, E as EmploymentType, R as RemoteOption, S as Seniority } from './jobs-k84C0v_8.mjs';
3
- import './_spec-BKtVFcV9.mjs';
2
+ import { J as JobSort, E as EmploymentType, R as RemoteOption, S as Seniority } from './jobs-lVvKCBCU.mjs';
3
+ import './_spec-nkWAraS0.mjs';
4
4
 
5
5
  declare const REMOTE_OPTIONS: readonly RemoteOption[];
6
6
  /**
package/dist/filters.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { B as BoardLabelOverrides } from './ui-copy-CCkz6CVF.js';
2
- import { J as JobSort, E as EmploymentType, R as RemoteOption, S as Seniority } from './jobs-C4qRfCdP.js';
3
- import './_spec-BKtVFcV9.js';
2
+ import { J as JobSort, E as EmploymentType, R as RemoteOption, S as Seniority } from './jobs-BiEvL9d7.js';
3
+ import './_spec-nkWAraS0.js';
4
4
 
5
5
  declare const REMOTE_OPTIONS: readonly RemoteOption[];
6
6
  /**