@cavuno/board 1.2.1 → 1.4.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.
package/dist/index.js CHANGED
@@ -21,12 +21,14 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
23
  ACCESS_TOKEN_KEY: () => ACCESS_TOKEN_KEY,
24
+ BOARD_ACCESS_GRANT_KEY: () => BOARD_ACCESS_GRANT_KEY,
24
25
  BoardApiError: () => BoardApiError,
25
26
  BoardClient: () => BoardClient,
26
27
  REFRESH_TOKEN_KEY: () => REFRESH_TOKEN_KEY,
27
28
  SDK_VERSION: () => SDK_VERSION,
28
29
  createBoardClient: () => createBoardClient,
29
30
  isBoardApiError: () => isBoardApiError,
31
+ isBoardPasswordRequired: () => isBoardPasswordRequired,
30
32
  isConflict: () => isConflict,
31
33
  isForbidden: () => isForbidden,
32
34
  isNotFound: () => isNotFound,
@@ -65,6 +67,9 @@ function isNotFound(e) {
65
67
  function isUnauthorized(e) {
66
68
  return isBoardApiError(e) && e.status === 401;
67
69
  }
70
+ function isBoardPasswordRequired(e) {
71
+ return isBoardApiError(e) && e.status === 401 && e.code === "board_password_required";
72
+ }
68
73
  function isForbidden(e) {
69
74
  return isBoardApiError(e) && e.status === 403;
70
75
  }
@@ -99,6 +104,7 @@ function toSearchParams(query) {
99
104
  // src/storage.ts
100
105
  var ACCESS_TOKEN_KEY = "cavuno_board_access_token";
101
106
  var REFRESH_TOKEN_KEY = "cavuno_board_refresh_token";
107
+ var BOARD_ACCESS_GRANT_KEY = "cavuno_board_access_grant";
102
108
  function isBrowser() {
103
109
  return typeof globalThis.document !== "undefined";
104
110
  }
@@ -143,7 +149,7 @@ async function clearSession(storage) {
143
149
  }
144
150
 
145
151
  // src/version.ts
146
- var SDK_VERSION = "1.2.1";
152
+ var SDK_VERSION = "1.4.0";
147
153
 
148
154
  // src/client.ts
149
155
  function isRawBody(body) {
@@ -185,12 +191,14 @@ var BoardClient = class {
185
191
  }
186
192
  const token = await this.storage.getItem(ACCESS_TOKEN_KEY);
187
193
  if (token !== null) headers.set("authorization", `Bearer ${token}`);
194
+ const grant = await this.storage.getItem(BOARD_ACCESS_GRANT_KEY);
195
+ if (grant !== null) headers.set("x-board-access", grant);
188
196
  if (callHeaders) {
189
197
  new Headers(callHeaders).forEach((value, key) => {
190
198
  headers.set(key, value);
191
199
  });
192
200
  }
193
- if ((method !== "GET" || headers.has("authorization")) && !headers.has("x-cavuno-sdk")) {
201
+ if ((method !== "GET" || headers.has("authorization") || headers.has("x-board-access")) && !headers.has("x-cavuno-sdk")) {
194
202
  headers.set("x-cavuno-sdk", `board@${SDK_VERSION}`);
195
203
  }
196
204
  let req = {
@@ -201,7 +209,8 @@ var BoardClient = class {
201
209
  this.options.logger?.debug(`${method} ${req.url}`);
202
210
  const res = await globalThis.fetch(req.url, req.init);
203
211
  this.options.logger?.debug(`${res.status} ${method} ${req.url}`);
204
- if (this.options.onResponse) await this.options.onResponse(res.clone(), req);
212
+ if (this.options.onResponse)
213
+ await this.options.onResponse(res.clone(), req);
205
214
  if (res.status === 204) return void 0;
206
215
  if (res.ok) return await res.json();
207
216
  let parsed;
@@ -396,6 +405,30 @@ function blogNamespace(client) {
396
405
  `/blog/posts/${encodeURIComponent(postSlug)}`,
397
406
  { ...options, query }
398
407
  );
408
+ },
409
+ /**
410
+ * The previous (older) and next (newer) posts for prev/next navigation.
411
+ *
412
+ * @example
413
+ * const { previous, next } = await board.blog.posts.adjacent('hello-world');
414
+ */
415
+ adjacent(postSlug, options) {
416
+ return client.fetch(
417
+ `/blog/posts/${encodeURIComponent(postSlug)}/adjacent`,
418
+ options
419
+ );
420
+ },
421
+ /**
422
+ * Posts most similar to one post (the related-posts rail).
423
+ *
424
+ * @example
425
+ * const { data } = await board.blog.posts.similar('hello-world', { limit: 6 });
426
+ */
427
+ similar(postSlug, query, options) {
428
+ return client.fetch(
429
+ `/blog/posts/${encodeURIComponent(postSlug)}/similar`,
430
+ { ...options, query }
431
+ );
399
432
  }
400
433
  },
401
434
  tags: {
@@ -497,6 +530,123 @@ function companiesNamespace(client) {
497
530
  `/companies/${encodeURIComponent(companySlug)}/jobs`,
498
531
  { ...options, query }
499
532
  );
533
+ },
534
+ /**
535
+ * List companies similar to one company — the same ranking that powers the
536
+ * hosted company page's similar-companies rail (most open roles first),
537
+ * excluding the company itself.
538
+ *
539
+ * @example
540
+ * const { data } = await board.companies.similar('acme', { limit: 6 });
541
+ */
542
+ similar(companySlug, query, options) {
543
+ return client.fetch(
544
+ `/companies/${encodeURIComponent(companySlug)}/similar`,
545
+ { ...options, query }
546
+ );
547
+ },
548
+ /**
549
+ * List the board's company markets (sectors), ranked by company count — the
550
+ * data behind the hosted companies index's market filter. A top-N preview.
551
+ *
552
+ * @example
553
+ * const { data } = await board.companies.markets({ search: 'robotics' });
554
+ */
555
+ markets(query, options) {
556
+ return client.fetch("/companies/markets", {
557
+ ...options,
558
+ query
559
+ });
560
+ }
561
+ };
562
+ }
563
+
564
+ // src/namespaces/embed.ts
565
+ function embedNamespace(client) {
566
+ return {
567
+ /**
568
+ * List published jobs for an embeddable widget — the same featured-ranked
569
+ * cards as `board.jobs.list`, but UNGATED: the candidate paywall never
570
+ * applies, so the full page is always returned and there is no
571
+ * `gatedCount`. Powers the public "Powered by Cavuno" embed. `limit`
572
+ * defaults to 8 and is clamped to a maximum of 50.
573
+ *
574
+ * @example
575
+ * const { data, nextCursor } = await board.embed.jobs({ q: 'chef', limit: 8 });
576
+ */
577
+ jobs(query, options) {
578
+ return client.fetch("/embed/jobs", {
579
+ ...options,
580
+ query
581
+ });
582
+ }
583
+ };
584
+ }
585
+
586
+ // src/namespaces/job-alerts.ts
587
+ function jobAlertsNamespace(client) {
588
+ return {
589
+ /** Subscribe an email to job alerts. Sends a double-opt-in confirmation email. */
590
+ subscribe(input, options) {
591
+ return client.fetch("/job-alerts", {
592
+ ...options,
593
+ method: "POST",
594
+ body: input
595
+ });
596
+ },
597
+ /** Complete double opt-in with the token from the confirmation email. */
598
+ confirm(input, options) {
599
+ return client.fetch("/job-alerts/confirm", {
600
+ ...options,
601
+ method: "POST",
602
+ body: input
603
+ });
604
+ },
605
+ /** Re-send the confirmation email for an unconfirmed subscription. */
606
+ resendConfirmation(input, options) {
607
+ return client.fetch(
608
+ "/job-alerts/resend-confirmation",
609
+ { ...options, method: "POST", body: input }
610
+ );
611
+ },
612
+ /** Read a subscription + its preferences for the manage page (HMAC token). */
613
+ manage(query, options) {
614
+ return client.fetch("/job-alerts/manage", {
615
+ ...options,
616
+ query
617
+ });
618
+ },
619
+ /** Deactivate a subscription via the HMAC manage token. */
620
+ unsubscribe(input, options) {
621
+ return client.fetch("/job-alerts/unsubscribe", {
622
+ ...options,
623
+ method: "POST",
624
+ body: input
625
+ });
626
+ },
627
+ /** Re-activate a previously unsubscribed subscription via the manage token. */
628
+ resubscribe(input, options) {
629
+ return client.fetch("/job-alerts/resubscribe", {
630
+ ...options,
631
+ method: "POST",
632
+ body: input
633
+ });
634
+ },
635
+ /** Edit a preference's filters/frequency via the manage token. */
636
+ updatePreference(input, options) {
637
+ return client.fetch("/job-alerts/preferences", {
638
+ ...options,
639
+ method: "POST",
640
+ body: input
641
+ });
642
+ },
643
+ /** Delete a preference via the manage token. */
644
+ deletePreference(input, options) {
645
+ return client.fetch("/job-alerts/preferences", {
646
+ ...options,
647
+ method: "DELETE",
648
+ body: input
649
+ });
500
650
  }
501
651
  };
502
652
  }
@@ -565,6 +715,28 @@ function jobsNamespace(client) {
565
715
  };
566
716
  }
567
717
 
718
+ // src/namespaces/legal.ts
719
+ function legalNamespace(client) {
720
+ return {
721
+ /**
722
+ * Retrieve a board legal/about page — owner-authored prose as portable HTML
723
+ * (`privacy-policy` / `terms-of-service` / `cookie-policy` / `about`) plus,
724
+ * for `impressum`, structured legal-entity facts. The starter authors the
725
+ * layout, breadcrumb labels, and JSON-LD. `impressum` throws
726
+ * `board_page_not_found` (404) when the board has not enabled it.
727
+ *
728
+ * @example
729
+ * const { title, content } = await board.legal.retrieve('privacy-policy');
730
+ */
731
+ retrieve(type, options) {
732
+ return client.fetch(
733
+ `/legal/${encodeURIComponent(type)}`,
734
+ options
735
+ );
736
+ }
737
+ };
738
+ }
739
+
568
740
  // src/namespaces/me.ts
569
741
  function meNamespace(client) {
570
742
  return {
@@ -625,6 +797,30 @@ function meNamespace(client) {
625
797
  };
626
798
  }
627
799
 
800
+ // src/namespaces/password.ts
801
+ function passwordNamespace(client) {
802
+ return {
803
+ /**
804
+ * Exchange a board password for an access grant and store it. After this
805
+ * resolves, every subsequent read auto-carries the grant as the
806
+ * `X-Board-Access` header — until the password rotates, after which reads
807
+ * fail with 401 `board_password_required` and you must `verify()` again
808
+ * (the SDK never auto-retries — verify is rate-limited — and never
809
+ * auto-clears; the host re-challenges). On the server (`nostore` storage)
810
+ * the grant is returned but not persisted; pass it per-call instead.
811
+ */
812
+ async verify(password, options) {
813
+ const grant = await client.fetch("/password/verify", {
814
+ ...options,
815
+ method: "POST",
816
+ body: { password }
817
+ });
818
+ await client.storage.setItem(BOARD_ACCESS_GRANT_KEY, grant.token);
819
+ return grant;
820
+ }
821
+ };
822
+ }
823
+
628
824
  // src/namespaces/redirects.ts
629
825
  function redirectsNamespace(client) {
630
826
  return {
@@ -666,9 +862,16 @@ function taxonomyNamespace(client) {
666
862
  skills: taxonomyResolver(client, "skills"),
667
863
  places: {
668
864
  ...taxonomyResolver(client, "places"),
669
- /** List every place used by a published job, with its live job count. */
670
- list(options) {
671
- return client.fetch("/places", options);
865
+ /**
866
+ * Without `query`: list every place used by a published job, with its
867
+ * live job count (the locations directory). With `query.q` (≥2 chars):
868
+ * location autocomplete — the top name matches ranked.
869
+ */
870
+ list(query, options) {
871
+ return client.fetch("/places", {
872
+ ...options,
873
+ query
874
+ });
672
875
  }
673
876
  }
674
877
  };
@@ -713,11 +916,15 @@ function createBoardClient(options) {
713
916
  return client.fetch("/seo", options2);
714
917
  },
715
918
  jobs: jobsNamespace(client),
919
+ embed: embedNamespace(client),
716
920
  companies: companiesNamespace(client),
717
921
  blog: blogNamespace(client),
922
+ legal: legalNamespace(client),
718
923
  auth: authNamespace(client),
719
924
  me: meNamespace(client),
925
+ password: passwordNamespace(client),
720
926
  taxonomy: taxonomyNamespace(client),
721
- redirects: redirectsNamespace(client)
927
+ redirects: redirectsNamespace(client),
928
+ jobAlerts: jobAlertsNamespace(client)
722
929
  };
723
930
  }
package/dist/index.mjs CHANGED
@@ -27,6 +27,9 @@ function isNotFound(e) {
27
27
  function isUnauthorized(e) {
28
28
  return isBoardApiError(e) && e.status === 401;
29
29
  }
30
+ function isBoardPasswordRequired(e) {
31
+ return isBoardApiError(e) && e.status === 401 && e.code === "board_password_required";
32
+ }
30
33
  function isForbidden(e) {
31
34
  return isBoardApiError(e) && e.status === 403;
32
35
  }
@@ -61,6 +64,7 @@ function toSearchParams(query) {
61
64
  // src/storage.ts
62
65
  var ACCESS_TOKEN_KEY = "cavuno_board_access_token";
63
66
  var REFRESH_TOKEN_KEY = "cavuno_board_refresh_token";
67
+ var BOARD_ACCESS_GRANT_KEY = "cavuno_board_access_grant";
64
68
  function isBrowser() {
65
69
  return typeof globalThis.document !== "undefined";
66
70
  }
@@ -105,7 +109,7 @@ async function clearSession(storage) {
105
109
  }
106
110
 
107
111
  // src/version.ts
108
- var SDK_VERSION = "1.2.1";
112
+ var SDK_VERSION = "1.4.0";
109
113
 
110
114
  // src/client.ts
111
115
  function isRawBody(body) {
@@ -147,12 +151,14 @@ var BoardClient = class {
147
151
  }
148
152
  const token = await this.storage.getItem(ACCESS_TOKEN_KEY);
149
153
  if (token !== null) headers.set("authorization", `Bearer ${token}`);
154
+ const grant = await this.storage.getItem(BOARD_ACCESS_GRANT_KEY);
155
+ if (grant !== null) headers.set("x-board-access", grant);
150
156
  if (callHeaders) {
151
157
  new Headers(callHeaders).forEach((value, key) => {
152
158
  headers.set(key, value);
153
159
  });
154
160
  }
155
- if ((method !== "GET" || headers.has("authorization")) && !headers.has("x-cavuno-sdk")) {
161
+ if ((method !== "GET" || headers.has("authorization") || headers.has("x-board-access")) && !headers.has("x-cavuno-sdk")) {
156
162
  headers.set("x-cavuno-sdk", `board@${SDK_VERSION}`);
157
163
  }
158
164
  let req = {
@@ -163,7 +169,8 @@ var BoardClient = class {
163
169
  this.options.logger?.debug(`${method} ${req.url}`);
164
170
  const res = await globalThis.fetch(req.url, req.init);
165
171
  this.options.logger?.debug(`${res.status} ${method} ${req.url}`);
166
- if (this.options.onResponse) await this.options.onResponse(res.clone(), req);
172
+ if (this.options.onResponse)
173
+ await this.options.onResponse(res.clone(), req);
167
174
  if (res.status === 204) return void 0;
168
175
  if (res.ok) return await res.json();
169
176
  let parsed;
@@ -358,6 +365,30 @@ function blogNamespace(client) {
358
365
  `/blog/posts/${encodeURIComponent(postSlug)}`,
359
366
  { ...options, query }
360
367
  );
368
+ },
369
+ /**
370
+ * The previous (older) and next (newer) posts for prev/next navigation.
371
+ *
372
+ * @example
373
+ * const { previous, next } = await board.blog.posts.adjacent('hello-world');
374
+ */
375
+ adjacent(postSlug, options) {
376
+ return client.fetch(
377
+ `/blog/posts/${encodeURIComponent(postSlug)}/adjacent`,
378
+ options
379
+ );
380
+ },
381
+ /**
382
+ * Posts most similar to one post (the related-posts rail).
383
+ *
384
+ * @example
385
+ * const { data } = await board.blog.posts.similar('hello-world', { limit: 6 });
386
+ */
387
+ similar(postSlug, query, options) {
388
+ return client.fetch(
389
+ `/blog/posts/${encodeURIComponent(postSlug)}/similar`,
390
+ { ...options, query }
391
+ );
361
392
  }
362
393
  },
363
394
  tags: {
@@ -459,6 +490,123 @@ function companiesNamespace(client) {
459
490
  `/companies/${encodeURIComponent(companySlug)}/jobs`,
460
491
  { ...options, query }
461
492
  );
493
+ },
494
+ /**
495
+ * List companies similar to one company — the same ranking that powers the
496
+ * hosted company page's similar-companies rail (most open roles first),
497
+ * excluding the company itself.
498
+ *
499
+ * @example
500
+ * const { data } = await board.companies.similar('acme', { limit: 6 });
501
+ */
502
+ similar(companySlug, query, options) {
503
+ return client.fetch(
504
+ `/companies/${encodeURIComponent(companySlug)}/similar`,
505
+ { ...options, query }
506
+ );
507
+ },
508
+ /**
509
+ * List the board's company markets (sectors), ranked by company count — the
510
+ * data behind the hosted companies index's market filter. A top-N preview.
511
+ *
512
+ * @example
513
+ * const { data } = await board.companies.markets({ search: 'robotics' });
514
+ */
515
+ markets(query, options) {
516
+ return client.fetch("/companies/markets", {
517
+ ...options,
518
+ query
519
+ });
520
+ }
521
+ };
522
+ }
523
+
524
+ // src/namespaces/embed.ts
525
+ function embedNamespace(client) {
526
+ return {
527
+ /**
528
+ * List published jobs for an embeddable widget — the same featured-ranked
529
+ * cards as `board.jobs.list`, but UNGATED: the candidate paywall never
530
+ * applies, so the full page is always returned and there is no
531
+ * `gatedCount`. Powers the public "Powered by Cavuno" embed. `limit`
532
+ * defaults to 8 and is clamped to a maximum of 50.
533
+ *
534
+ * @example
535
+ * const { data, nextCursor } = await board.embed.jobs({ q: 'chef', limit: 8 });
536
+ */
537
+ jobs(query, options) {
538
+ return client.fetch("/embed/jobs", {
539
+ ...options,
540
+ query
541
+ });
542
+ }
543
+ };
544
+ }
545
+
546
+ // src/namespaces/job-alerts.ts
547
+ function jobAlertsNamespace(client) {
548
+ return {
549
+ /** Subscribe an email to job alerts. Sends a double-opt-in confirmation email. */
550
+ subscribe(input, options) {
551
+ return client.fetch("/job-alerts", {
552
+ ...options,
553
+ method: "POST",
554
+ body: input
555
+ });
556
+ },
557
+ /** Complete double opt-in with the token from the confirmation email. */
558
+ confirm(input, options) {
559
+ return client.fetch("/job-alerts/confirm", {
560
+ ...options,
561
+ method: "POST",
562
+ body: input
563
+ });
564
+ },
565
+ /** Re-send the confirmation email for an unconfirmed subscription. */
566
+ resendConfirmation(input, options) {
567
+ return client.fetch(
568
+ "/job-alerts/resend-confirmation",
569
+ { ...options, method: "POST", body: input }
570
+ );
571
+ },
572
+ /** Read a subscription + its preferences for the manage page (HMAC token). */
573
+ manage(query, options) {
574
+ return client.fetch("/job-alerts/manage", {
575
+ ...options,
576
+ query
577
+ });
578
+ },
579
+ /** Deactivate a subscription via the HMAC manage token. */
580
+ unsubscribe(input, options) {
581
+ return client.fetch("/job-alerts/unsubscribe", {
582
+ ...options,
583
+ method: "POST",
584
+ body: input
585
+ });
586
+ },
587
+ /** Re-activate a previously unsubscribed subscription via the manage token. */
588
+ resubscribe(input, options) {
589
+ return client.fetch("/job-alerts/resubscribe", {
590
+ ...options,
591
+ method: "POST",
592
+ body: input
593
+ });
594
+ },
595
+ /** Edit a preference's filters/frequency via the manage token. */
596
+ updatePreference(input, options) {
597
+ return client.fetch("/job-alerts/preferences", {
598
+ ...options,
599
+ method: "POST",
600
+ body: input
601
+ });
602
+ },
603
+ /** Delete a preference via the manage token. */
604
+ deletePreference(input, options) {
605
+ return client.fetch("/job-alerts/preferences", {
606
+ ...options,
607
+ method: "DELETE",
608
+ body: input
609
+ });
462
610
  }
463
611
  };
464
612
  }
@@ -527,6 +675,28 @@ function jobsNamespace(client) {
527
675
  };
528
676
  }
529
677
 
678
+ // src/namespaces/legal.ts
679
+ function legalNamespace(client) {
680
+ return {
681
+ /**
682
+ * Retrieve a board legal/about page — owner-authored prose as portable HTML
683
+ * (`privacy-policy` / `terms-of-service` / `cookie-policy` / `about`) plus,
684
+ * for `impressum`, structured legal-entity facts. The starter authors the
685
+ * layout, breadcrumb labels, and JSON-LD. `impressum` throws
686
+ * `board_page_not_found` (404) when the board has not enabled it.
687
+ *
688
+ * @example
689
+ * const { title, content } = await board.legal.retrieve('privacy-policy');
690
+ */
691
+ retrieve(type, options) {
692
+ return client.fetch(
693
+ `/legal/${encodeURIComponent(type)}`,
694
+ options
695
+ );
696
+ }
697
+ };
698
+ }
699
+
530
700
  // src/namespaces/me.ts
531
701
  function meNamespace(client) {
532
702
  return {
@@ -587,6 +757,30 @@ function meNamespace(client) {
587
757
  };
588
758
  }
589
759
 
760
+ // src/namespaces/password.ts
761
+ function passwordNamespace(client) {
762
+ return {
763
+ /**
764
+ * Exchange a board password for an access grant and store it. After this
765
+ * resolves, every subsequent read auto-carries the grant as the
766
+ * `X-Board-Access` header — until the password rotates, after which reads
767
+ * fail with 401 `board_password_required` and you must `verify()` again
768
+ * (the SDK never auto-retries — verify is rate-limited — and never
769
+ * auto-clears; the host re-challenges). On the server (`nostore` storage)
770
+ * the grant is returned but not persisted; pass it per-call instead.
771
+ */
772
+ async verify(password, options) {
773
+ const grant = await client.fetch("/password/verify", {
774
+ ...options,
775
+ method: "POST",
776
+ body: { password }
777
+ });
778
+ await client.storage.setItem(BOARD_ACCESS_GRANT_KEY, grant.token);
779
+ return grant;
780
+ }
781
+ };
782
+ }
783
+
590
784
  // src/namespaces/redirects.ts
591
785
  function redirectsNamespace(client) {
592
786
  return {
@@ -628,9 +822,16 @@ function taxonomyNamespace(client) {
628
822
  skills: taxonomyResolver(client, "skills"),
629
823
  places: {
630
824
  ...taxonomyResolver(client, "places"),
631
- /** List every place used by a published job, with its live job count. */
632
- list(options) {
633
- return client.fetch("/places", options);
825
+ /**
826
+ * Without `query`: list every place used by a published job, with its
827
+ * live job count (the locations directory). With `query.q` (≥2 chars):
828
+ * location autocomplete — the top name matches ranked.
829
+ */
830
+ list(query, options) {
831
+ return client.fetch("/places", {
832
+ ...options,
833
+ query
834
+ });
634
835
  }
635
836
  }
636
837
  };
@@ -675,22 +876,28 @@ function createBoardClient(options) {
675
876
  return client.fetch("/seo", options2);
676
877
  },
677
878
  jobs: jobsNamespace(client),
879
+ embed: embedNamespace(client),
678
880
  companies: companiesNamespace(client),
679
881
  blog: blogNamespace(client),
882
+ legal: legalNamespace(client),
680
883
  auth: authNamespace(client),
681
884
  me: meNamespace(client),
885
+ password: passwordNamespace(client),
682
886
  taxonomy: taxonomyNamespace(client),
683
- redirects: redirectsNamespace(client)
887
+ redirects: redirectsNamespace(client),
888
+ jobAlerts: jobAlertsNamespace(client)
684
889
  };
685
890
  }
686
891
  export {
687
892
  ACCESS_TOKEN_KEY,
893
+ BOARD_ACCESS_GRANT_KEY,
688
894
  BoardApiError,
689
895
  BoardClient,
690
896
  REFRESH_TOKEN_KEY,
691
897
  SDK_VERSION,
692
898
  createBoardClient,
693
899
  isBoardApiError,
900
+ isBoardPasswordRequired,
694
901
  isConflict,
695
902
  isForbidden,
696
903
  isNotFound,
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Skill-corpus loader. Node-only (reads the shipped `skills/` directory from
3
+ * the installed package) — kept out of the isomorphic core and exposed via the
4
+ * `@cavuno/board/skills` subpath export. Both `npx @cavuno/board setup` and the
5
+ * in-admin sidekick (ADR-0033) read the corpus through here, so the two doors
6
+ * stay fed from one source.
7
+ */
8
+ interface SkillManifestEntry {
9
+ name: string;
10
+ description: string;
11
+ /** Path to the SKILL.md, relative to the package root. */
12
+ path: string;
13
+ /** Framework slug for flavor skills; `null` for framework-agnostic core skills. */
14
+ framework: string | null;
15
+ category: 'core' | 'flavor';
16
+ }
17
+ interface SkillManifest {
18
+ version: string;
19
+ skills: SkillManifestEntry[];
20
+ }
21
+ interface LoadedSkill extends SkillManifestEntry {
22
+ content: string;
23
+ }
24
+ interface SkillCorpus {
25
+ version: string;
26
+ skills: LoadedSkill[];
27
+ }
28
+ /** Resolve a package-root-relative path (e.g. a manifest `path`) to an absolute path. */
29
+ declare function resolveFromPackageRoot(relativePath: string): string;
30
+ declare function loadSkillManifest(): SkillManifest;
31
+ /**
32
+ * Load the full skill corpus — manifest metadata plus each skill's markdown
33
+ * `content`. The sidekick injects `content` into its agent context; an external
34
+ * Claude Code instead receives the files copied by the setup command.
35
+ */
36
+ declare function loadSkillCorpus(): SkillCorpus;
37
+
38
+ export { type LoadedSkill, type SkillCorpus, type SkillManifest, type SkillManifestEntry, loadSkillCorpus, loadSkillManifest, resolveFromPackageRoot };