@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/bin.mjs +74 -0
- package/dist/index.d.mts +266 -5
- package/dist/index.d.ts +266 -5
- package/dist/index.js +214 -7
- package/dist/index.mjs +214 -7
- package/dist/skills.d.mts +38 -0
- package/dist/skills.d.ts +38 -0
- package/dist/skills.js +62 -0
- package/dist/skills.mjs +29 -0
- package/package.json +22 -5
- package/skills/cavuno-board-auth/SKILL.md +113 -0
- package/skills/cavuno-board-client/SKILL.md +93 -0
- package/skills/cavuno-board-errors/SKILL.md +86 -0
- package/skills/cavuno-board-jobs/SKILL.md +93 -0
- package/skills/cavuno-board-setup/SKILL.md +96 -0
- package/skills/flavors/tanstack-start/SKILL.md +102 -0
- package/skills/manifest.json +47 -0
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.
|
|
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)
|
|
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
|
-
/**
|
|
670
|
-
|
|
671
|
-
|
|
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.
|
|
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)
|
|
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
|
-
/**
|
|
632
|
-
|
|
633
|
-
|
|
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 };
|