@cavuno/board 2.0.0 → 2.0.2
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/CHANGELOG.md +26 -0
- package/CONTRIBUTING.md +25 -0
- package/LICENSE +21 -0
- package/README.md +12 -7
- package/SECURITY.md +28 -0
- package/TRADEMARKS.md +16 -0
- package/dist/_spec-sW85fuvT.d.mts +2731 -0
- package/dist/_spec-sW85fuvT.d.ts +2731 -0
- package/dist/bin.mjs +16 -348
- package/dist/{board-B8tJGX4i.d.ts → board-DKXyxwNN.d.ts} +2 -2
- package/dist/{board-DmRuCA8m.d.mts → board-QFeHT4UN.d.mts} +2 -2
- package/dist/doctor.d.mts +6 -19
- package/dist/doctor.d.ts +6 -19
- package/dist/doctor.js +16 -344
- package/dist/doctor.mjs +15 -343
- package/dist/filters.d.mts +5 -5
- package/dist/filters.d.ts +5 -5
- package/dist/format.d.mts +17 -19
- package/dist/format.d.ts +17 -19
- package/dist/go.d.mts +2 -2
- package/dist/go.d.ts +2 -2
- package/dist/go.js +1 -1
- package/dist/go.mjs +1 -1
- package/dist/index.d.mts +16 -18
- package/dist/index.d.ts +16 -18
- package/dist/index.js +39 -28
- package/dist/index.mjs +39 -28
- package/dist/{jobs-BEYJY97F.d.ts → jobs-CuRzTDb1.d.mts} +15 -13
- package/dist/{jobs-DJiISpqX.d.mts → jobs-DTnCZGtb.d.ts} +15 -13
- package/dist/paths.d.mts +6 -6
- package/dist/paths.d.ts +6 -6
- package/dist/paths.js +1 -1
- package/dist/paths.mjs +1 -1
- package/dist/roles-vY0AXhoc.d.mts +3 -3
- package/dist/roles-vY0AXhoc.d.ts +3 -3
- package/dist/route-contract.d.mts +16 -16
- package/dist/route-contract.d.ts +16 -16
- package/dist/route-contract.js +1 -1
- package/dist/route-contract.mjs +1 -1
- package/dist/{salaries-D6q7h1J6.d.ts → salaries-BOpR51Ny.d.mts} +3 -3
- package/dist/{salaries-B_Xb5k2s.d.mts → salaries-QoNpzRCT.d.ts} +3 -3
- package/dist/{search-CnOIZEuc.d.ts → search-B5ROY6PK.d.ts} +2 -2
- package/dist/{search-uC_ijbdu.d.mts → search-f93nrSg3.d.mts} +2 -2
- package/dist/seo.d.mts +17 -18
- package/dist/seo.d.ts +17 -18
- package/dist/server.d.mts +14 -32
- package/dist/server.d.ts +14 -32
- package/dist/sitemap.d.mts +7 -7
- package/dist/sitemap.d.ts +7 -7
- package/dist/sitemap.js +4 -2
- package/dist/sitemap.mjs +4 -2
- package/dist/skills.d.mts +1 -1
- package/dist/skills.d.ts +1 -1
- package/dist/skills.js +1 -1
- package/dist/suggest.d.mts +2 -2
- package/dist/suggest.d.ts +2 -2
- package/dist/theme.d.mts +10 -13
- package/dist/theme.d.ts +10 -13
- package/dist/theme.js +1 -1
- package/dist/theme.mjs +1 -1
- package/dist/{ui-copy-CKfFTtLk.d.mts → ui-copy-CL-VN1xb.d.mts} +2 -3
- package/dist/{ui-copy-CKfFTtLk.d.ts → ui-copy-CL-VN1xb.d.ts} +2 -3
- package/dist/well-known.d.mts +3 -3
- package/dist/well-known.d.ts +3 -3
- package/dist/well-known.js +1 -1
- package/dist/well-known.mjs +1 -1
- package/package.json +46 -38
- package/skills/cavuno-board-client/SKILL.md +1 -2
- package/skills/cavuno-board-filters/SKILL.md +2 -2
- package/skills/cavuno-board-format/SKILL.md +1 -1
- package/skills/cavuno-board-i18n/SKILL.md +3 -4
- package/skills/cavuno-board-job-posting/SKILL.md +1 -1
- package/skills/cavuno-board-messaging/SKILL.md +2 -3
- package/skills/cavuno-board-paywall/SKILL.md +1 -1
- package/skills/cavuno-board-salaries/SKILL.md +1 -1
- package/skills/cavuno-board-seo/SKILL.md +4 -6
- package/skills/cavuno-board-server/SKILL.md +1 -1
- package/skills/cavuno-board-setup/SKILL.md +2 -1
- package/skills/cavuno-board-sitemap/SKILL.md +1 -1
- package/skills/cavuno-board-smoke-test/SKILL.md +4 -13
- package/skills/flavors/tanstack-start/SKILL.md +1 -2
- package/skills/manifest.json +1 -1
- package/dist/_spec-C7vqY_b3.d.mts +0 -5202
- package/dist/_spec-C7vqY_b3.d.ts +0 -5202
package/dist/doctor.mjs
CHANGED
|
@@ -392,86 +392,18 @@ async function runReadProbes(fetchImpl, frontendUrl) {
|
|
|
392
392
|
];
|
|
393
393
|
}
|
|
394
394
|
|
|
395
|
-
// src/doctor/sandbox.ts
|
|
396
|
-
var SANDBOX_GATE = record("sandbox.persona-gate", 1);
|
|
397
|
-
var EXPECTED_PERSONA_COUNT = 8;
|
|
398
|
-
function skipSandboxPersonaGate(reason) {
|
|
399
|
-
return SANDBOX_GATE("skip", reason);
|
|
400
|
-
}
|
|
401
|
-
async function checkSandboxPersonaGate(fetchImpl, env, boardContext) {
|
|
402
|
-
if (boardContext === null) {
|
|
403
|
-
return SANDBOX_GATE(
|
|
404
|
-
"skip",
|
|
405
|
-
"board did not resolve \u2014 fix static.board first"
|
|
406
|
-
);
|
|
407
|
-
}
|
|
408
|
-
const url = `${apiBase(env.apiUrl)}/v1/boards/${encodeURIComponent(env.boardKey)}/sandbox/personas`;
|
|
409
|
-
const result = await probe(fetchImpl, url);
|
|
410
|
-
if (boardContext.sandbox !== true) {
|
|
411
|
-
return result.status === 404 ? SANDBOX_GATE(
|
|
412
|
-
"pass",
|
|
413
|
-
"sandbox persona endpoint refuses (404) on a non-sandbox board"
|
|
414
|
-
) : SANDBOX_GATE(
|
|
415
|
-
"fail",
|
|
416
|
-
`sandbox persona endpoint is reachable on a NON-sandbox board (HTTP ${result.status}) \u2014 the capability gate is not holding`
|
|
417
|
-
);
|
|
418
|
-
}
|
|
419
|
-
if (!result.ok) {
|
|
420
|
-
return SANDBOX_GATE(
|
|
421
|
-
"fail",
|
|
422
|
-
`sandbox persona roster did not resolve (HTTP ${result.status}) \u2014 the persona seed may have failed`
|
|
423
|
-
);
|
|
424
|
-
}
|
|
425
|
-
let personas;
|
|
426
|
-
let password;
|
|
427
|
-
try {
|
|
428
|
-
const parsed = JSON.parse(result.body);
|
|
429
|
-
personas = parsed.personas;
|
|
430
|
-
password = parsed.password;
|
|
431
|
-
} catch {
|
|
432
|
-
return SANDBOX_GATE(
|
|
433
|
-
"fail",
|
|
434
|
-
"sandbox persona endpoint returned 200 but not JSON \u2014 proxy or captive portal in the way?"
|
|
435
|
-
);
|
|
436
|
-
}
|
|
437
|
-
if (!Array.isArray(personas) || personas.length !== EXPECTED_PERSONA_COUNT) {
|
|
438
|
-
return SANDBOX_GATE(
|
|
439
|
-
"fail",
|
|
440
|
-
`expected ${EXPECTED_PERSONA_COUNT} personas, got ${Array.isArray(personas) ? personas.length : "a non-array"} \u2014 the persona seed is incomplete`
|
|
441
|
-
);
|
|
442
|
-
}
|
|
443
|
-
const unseeded = personas.filter((persona) => persona.seeded !== true);
|
|
444
|
-
if (unseeded.length > 0) {
|
|
445
|
-
const ids = unseeded.map((persona) => typeof persona.id === "string" ? persona.id : "?").join(", ");
|
|
446
|
-
return SANDBOX_GATE(
|
|
447
|
-
"fail",
|
|
448
|
-
`${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`
|
|
449
|
-
);
|
|
450
|
-
}
|
|
451
|
-
if (typeof password !== "string" || password.length === 0) {
|
|
452
|
-
return SANDBOX_GATE(
|
|
453
|
-
"fail",
|
|
454
|
-
"roster present but the published password is missing"
|
|
455
|
-
);
|
|
456
|
-
}
|
|
457
|
-
return SANDBOX_GATE(
|
|
458
|
-
"pass",
|
|
459
|
-
`sandbox roster complete and seeded (${EXPECTED_PERSONA_COUNT} personas)`
|
|
460
|
-
);
|
|
461
|
-
}
|
|
462
|
-
|
|
463
395
|
// src/doctor/theme.ts
|
|
464
396
|
import { createHash } from "crypto";
|
|
465
397
|
import { existsSync as existsSync2, readFileSync as readFileSync3 } from "fs";
|
|
466
398
|
import { join as join2 } from "path";
|
|
467
399
|
var THEME = record("static.theme", 1);
|
|
468
|
-
function checkThemeFreshness(projectRoot
|
|
400
|
+
function checkThemeFreshness(projectRoot) {
|
|
469
401
|
const tokensPath = join2(projectRoot, "src/tokens.css");
|
|
470
402
|
if (!existsSync2(tokensPath)) {
|
|
471
403
|
return [
|
|
472
404
|
THEME(
|
|
473
405
|
"skip",
|
|
474
|
-
"
|
|
406
|
+
"no src/tokens.css \u2014 local theme freshness check not applicable"
|
|
475
407
|
)
|
|
476
408
|
];
|
|
477
409
|
}
|
|
@@ -488,234 +420,11 @@ function checkThemeFreshness(projectRoot, context) {
|
|
|
488
420
|
)
|
|
489
421
|
];
|
|
490
422
|
}
|
|
491
|
-
if (!context) {
|
|
492
|
-
return [
|
|
493
|
-
THEME(
|
|
494
|
-
"skip",
|
|
495
|
-
"local derivations fresh; platform snapshot unverified \u2014 board context unavailable"
|
|
496
|
-
)
|
|
497
|
-
];
|
|
498
|
-
}
|
|
499
|
-
if (context.themeSnapshotHash !== tokensHash) {
|
|
500
|
-
return [
|
|
501
|
-
THEME(
|
|
502
|
-
"fail",
|
|
503
|
-
`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\``
|
|
504
|
-
)
|
|
505
|
-
];
|
|
506
|
-
}
|
|
507
423
|
return [
|
|
508
|
-
THEME(
|
|
509
|
-
"pass",
|
|
510
|
-
`tokens.css \u21C4 resolved module \u21C4 platform snapshot (${tokensHash.slice(0, 12)}\u2026)`
|
|
511
|
-
)
|
|
424
|
+
THEME("pass", `tokens.css \u21C4 resolved module (${tokensHash.slice(0, 12)}\u2026)`)
|
|
512
425
|
];
|
|
513
426
|
}
|
|
514
427
|
|
|
515
|
-
// src/doctor/writes.ts
|
|
516
|
-
var WRITE = {
|
|
517
|
-
board: record("write.board", 3),
|
|
518
|
-
register: record("write.register", 3),
|
|
519
|
-
login: record("write.login", 3),
|
|
520
|
-
email: record("write.email", 3),
|
|
521
|
-
apply: record("write.apply", 3),
|
|
522
|
-
alert: record("write.alert", 3)
|
|
523
|
-
};
|
|
524
|
-
var DEPENDENT_CHECKS = [
|
|
525
|
-
WRITE.register,
|
|
526
|
-
WRITE.login,
|
|
527
|
-
WRITE.email,
|
|
528
|
-
WRITE.apply,
|
|
529
|
-
WRITE.alert
|
|
530
|
-
];
|
|
531
|
-
function skipWriteProbes(reason) {
|
|
532
|
-
return Object.values(WRITE).map((make) => make("skip", reason));
|
|
533
|
-
}
|
|
534
|
-
var FETCH_TIMEOUT_MS2 = 1e4;
|
|
535
|
-
var RESEND_API_BASE = "https://api.resend.com";
|
|
536
|
-
async function request(fetchImpl, url, init) {
|
|
537
|
-
try {
|
|
538
|
-
const response = await fetchImpl(url, {
|
|
539
|
-
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS2),
|
|
540
|
-
...init
|
|
541
|
-
});
|
|
542
|
-
let json = null;
|
|
543
|
-
try {
|
|
544
|
-
json = await response.json();
|
|
545
|
-
} catch {
|
|
546
|
-
json = null;
|
|
547
|
-
}
|
|
548
|
-
return { ok: response.ok, status: response.status, json };
|
|
549
|
-
} catch (error) {
|
|
550
|
-
return {
|
|
551
|
-
ok: false,
|
|
552
|
-
status: 0,
|
|
553
|
-
json: { error: error instanceof Error ? error.message : String(error) }
|
|
554
|
-
};
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
function post(fetchImpl, url, body, bearer) {
|
|
558
|
-
return request(fetchImpl, url, {
|
|
559
|
-
method: "POST",
|
|
560
|
-
headers: {
|
|
561
|
-
"content-type": "application/json",
|
|
562
|
-
accept: "application/json",
|
|
563
|
-
...bearer ? { authorization: `Bearer ${bearer}` } : {}
|
|
564
|
-
},
|
|
565
|
-
body: JSON.stringify(body)
|
|
566
|
-
});
|
|
567
|
-
}
|
|
568
|
-
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
569
|
-
async function observeVerificationEmail(fetchImpl, apiKey, recipient, pollMs, timeoutMs) {
|
|
570
|
-
const deadline = Date.now() + timeoutMs;
|
|
571
|
-
const subjectMarker = `[To: ${recipient}]`;
|
|
572
|
-
do {
|
|
573
|
-
const list = await request(
|
|
574
|
-
fetchImpl,
|
|
575
|
-
`${RESEND_API_BASE}/emails?limit=20`,
|
|
576
|
-
{
|
|
577
|
-
headers: { authorization: `Bearer ${apiKey}` }
|
|
578
|
-
}
|
|
579
|
-
);
|
|
580
|
-
if (!list.ok) {
|
|
581
|
-
return WRITE.email(
|
|
582
|
-
"fail",
|
|
583
|
-
`Resend list returned HTTP ${list.status} \u2014 wrong RESEND_API_KEY?`
|
|
584
|
-
);
|
|
585
|
-
}
|
|
586
|
-
const items = list.json?.data ?? [];
|
|
587
|
-
const match = items.find(
|
|
588
|
-
(item) => item.subject?.includes(subjectMarker) || item.to?.includes(recipient)
|
|
589
|
-
);
|
|
590
|
-
if (match) {
|
|
591
|
-
return WRITE.email(
|
|
592
|
-
"pass",
|
|
593
|
-
`verification send observed in Resend for ${recipient}`
|
|
594
|
-
);
|
|
595
|
-
}
|
|
596
|
-
await sleep(pollMs);
|
|
597
|
-
} while (Date.now() < deadline);
|
|
598
|
-
return WRITE.email(
|
|
599
|
-
"fail",
|
|
600
|
-
`no verification send for ${recipient} appeared in Resend within ${timeoutMs}ms`
|
|
601
|
-
);
|
|
602
|
-
}
|
|
603
|
-
async function runWriteProbes(options) {
|
|
604
|
-
const { fetchImpl } = options;
|
|
605
|
-
const base = `${apiBase(options.env.apiUrl)}/v1/boards/${encodeURIComponent(options.env.boardKey)}`;
|
|
606
|
-
const nonce = options.nonce ?? `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 8)}`;
|
|
607
|
-
const email = `doctor+${nonce}@cavuno.com`;
|
|
608
|
-
const password = `doctor-probe-${nonce}-A1!`;
|
|
609
|
-
let context = options.boardContext ?? null;
|
|
610
|
-
if (context === null) {
|
|
611
|
-
const r = await request(fetchImpl, base);
|
|
612
|
-
if (!r.ok) {
|
|
613
|
-
return [
|
|
614
|
-
WRITE.board("fail", `board context did not resolve (HTTP ${r.status})`),
|
|
615
|
-
...DEPENDENT_CHECKS.map(
|
|
616
|
-
(make) => make("skip", "not run \u2014 the write gate failed (see write.board)")
|
|
617
|
-
)
|
|
618
|
-
];
|
|
619
|
-
}
|
|
620
|
-
context = r.json ?? {};
|
|
621
|
-
}
|
|
622
|
-
if (context.sandbox !== true) {
|
|
623
|
-
return [
|
|
624
|
-
WRITE.board(
|
|
625
|
-
"fail",
|
|
626
|
-
"refusing write probes: this board is not the platform sandbox (`sandbox` is not true) \u2014 point PUBLIC_CAVUNO_BOARD at the sandbox pk_"
|
|
627
|
-
),
|
|
628
|
-
...DEPENDENT_CHECKS.map(
|
|
629
|
-
(make) => make("skip", "not run \u2014 the write gate failed (see write.board)")
|
|
630
|
-
)
|
|
631
|
-
];
|
|
632
|
-
}
|
|
633
|
-
const results = [
|
|
634
|
-
WRITE.board("pass", "board is the platform sandbox")
|
|
635
|
-
];
|
|
636
|
-
const registered = await post(fetchImpl, `${base}/auth/register`, {
|
|
637
|
-
role: "candidate",
|
|
638
|
-
method: "emailpass",
|
|
639
|
-
email,
|
|
640
|
-
password,
|
|
641
|
-
displayName: "Doctor Probe"
|
|
642
|
-
});
|
|
643
|
-
const sessionOk = (r) => r.ok && typeof r.json?.accessToken === "string";
|
|
644
|
-
results.push(
|
|
645
|
-
sessionOk(registered) ? WRITE.register("pass", `candidate registered (${email})`) : WRITE.register(
|
|
646
|
-
"fail",
|
|
647
|
-
`register returned HTTP ${registered.status}` + (registered.status === 429 ? " \u2014 rate-limited: register allows 5 signups / 15 min per IP; wait before rerunning" : "")
|
|
648
|
-
)
|
|
649
|
-
);
|
|
650
|
-
let accessToken = null;
|
|
651
|
-
if (sessionOk(registered)) {
|
|
652
|
-
const login = await post(fetchImpl, `${base}/auth/login`, {
|
|
653
|
-
email,
|
|
654
|
-
password
|
|
655
|
-
});
|
|
656
|
-
if (sessionOk(login)) {
|
|
657
|
-
accessToken = login.json.accessToken;
|
|
658
|
-
results.push(WRITE.login("pass", "emailpass login returns a session"));
|
|
659
|
-
} else {
|
|
660
|
-
results.push(WRITE.login("fail", `login returned HTTP ${login.status}`));
|
|
661
|
-
}
|
|
662
|
-
} else {
|
|
663
|
-
results.push(WRITE.login("skip", "not run \u2014 register failed"));
|
|
664
|
-
}
|
|
665
|
-
if (!sessionOk(registered)) {
|
|
666
|
-
results.push(WRITE.email("skip", "not run \u2014 register failed"));
|
|
667
|
-
} else if (!options.resendApiKey) {
|
|
668
|
-
results.push(
|
|
669
|
-
WRITE.email(
|
|
670
|
-
"skip",
|
|
671
|
-
"RESEND_API_KEY not set \u2014 the verification send was NOT verified (platform-operator check)"
|
|
672
|
-
)
|
|
673
|
-
);
|
|
674
|
-
} else {
|
|
675
|
-
results.push(
|
|
676
|
-
await observeVerificationEmail(
|
|
677
|
-
fetchImpl,
|
|
678
|
-
options.resendApiKey,
|
|
679
|
-
email,
|
|
680
|
-
options.emailPollMs ?? 2e3,
|
|
681
|
-
options.emailTimeoutMs ?? 3e4
|
|
682
|
-
)
|
|
683
|
-
);
|
|
684
|
-
}
|
|
685
|
-
if (!accessToken) {
|
|
686
|
-
results.push(WRITE.apply("skip", "not run \u2014 no session (see above)"));
|
|
687
|
-
} else {
|
|
688
|
-
const listing = await request(fetchImpl, `${base}/jobs?limit=1`);
|
|
689
|
-
const card = listing.json?.data?.[0];
|
|
690
|
-
if (!listing.ok || !card?.slug) {
|
|
691
|
-
results.push(
|
|
692
|
-
WRITE.apply(
|
|
693
|
-
"fail",
|
|
694
|
-
listing.ok ? "the board has no jobs to apply to \u2014 is the sandbox seeded?" : `jobs listing returned HTTP ${listing.status}`
|
|
695
|
-
)
|
|
696
|
-
);
|
|
697
|
-
} else {
|
|
698
|
-
const applied = await post(
|
|
699
|
-
fetchImpl,
|
|
700
|
-
`${base}/jobs/${encodeURIComponent(card.slug)}/apply`,
|
|
701
|
-
{},
|
|
702
|
-
accessToken
|
|
703
|
-
);
|
|
704
|
-
results.push(
|
|
705
|
-
applied.ok && typeof applied.json?.id === "string" ? WRITE.apply("pass", `applied to ${card.slug}`) : WRITE.apply("fail", `apply returned HTTP ${applied.status}`)
|
|
706
|
-
);
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
|
-
const alert = await post(fetchImpl, `${base}/job-alerts`, {
|
|
710
|
-
email,
|
|
711
|
-
consent: true
|
|
712
|
-
});
|
|
713
|
-
results.push(
|
|
714
|
-
alert.ok ? WRITE.alert("pass", "job-alert subscription accepted") : WRITE.alert("fail", `job-alerts returned HTTP ${alert.status}`)
|
|
715
|
-
);
|
|
716
|
-
return results;
|
|
717
|
-
}
|
|
718
|
-
|
|
719
428
|
// src/doctor/run.ts
|
|
720
429
|
import { existsSync as existsSync3, readFileSync as readFileSync4 } from "fs";
|
|
721
430
|
import { join as join3 } from "path";
|
|
@@ -752,31 +461,21 @@ async function checkBoardResolves(fetchImpl, env) {
|
|
|
752
461
|
`${apiBase(env.apiUrl)}/v1/boards/${encodeURIComponent(env.boardKey)}`
|
|
753
462
|
);
|
|
754
463
|
if (!result.ok) {
|
|
755
|
-
return
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
),
|
|
760
|
-
context: null
|
|
761
|
-
};
|
|
464
|
+
return STATIC_BOARD(
|
|
465
|
+
"fail",
|
|
466
|
+
`board did not resolve for this key (HTTP ${result.status}) \u2014 revoked or wrong key?`
|
|
467
|
+
);
|
|
762
468
|
}
|
|
763
|
-
let parsed = null;
|
|
764
469
|
try {
|
|
765
|
-
parsed = JSON.parse(result.body);
|
|
470
|
+
const parsed = JSON.parse(result.body);
|
|
766
471
|
if (typeof parsed !== "object" || parsed === null) throw new Error("shape");
|
|
767
472
|
} catch {
|
|
768
|
-
return
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
),
|
|
773
|
-
context: null
|
|
774
|
-
};
|
|
473
|
+
return STATIC_BOARD(
|
|
474
|
+
"fail",
|
|
475
|
+
"board endpoint returned 200 but not JSON \u2014 proxy or captive portal in the way?"
|
|
476
|
+
);
|
|
775
477
|
}
|
|
776
|
-
return
|
|
777
|
-
result: STATIC_BOARD("pass", "publishable key resolves the board"),
|
|
778
|
-
context: parsed
|
|
779
|
-
};
|
|
478
|
+
return STATIC_BOARD("pass", "publishable key resolves the board");
|
|
780
479
|
}
|
|
781
480
|
var SKILL_ROOTS = [
|
|
782
481
|
".claude/skills",
|
|
@@ -839,46 +538,19 @@ async function runDoctor(options) {
|
|
|
839
538
|
} else {
|
|
840
539
|
results.push(STATIC_API("skip", "PUBLIC_CAVUNO_API_URL not set"));
|
|
841
540
|
}
|
|
842
|
-
let boardContext = null;
|
|
843
541
|
if (envOk) {
|
|
844
|
-
|
|
845
|
-
boardContext = resolved.context;
|
|
846
|
-
results.push(resolved.result);
|
|
542
|
+
results.push(await checkBoardResolves(fetchImpl, env));
|
|
847
543
|
} else {
|
|
848
544
|
results.push(STATIC_BOARD("skip", "env checks failed \u2014 fix them first"));
|
|
849
545
|
}
|
|
850
546
|
results.push(checkSkillsFreshness(options.projectRoot ?? process.cwd()));
|
|
851
|
-
results.push(
|
|
852
|
-
...checkThemeFreshness(options.projectRoot ?? process.cwd(), boardContext)
|
|
853
|
-
);
|
|
547
|
+
results.push(...checkThemeFreshness(options.projectRoot ?? process.cwd()));
|
|
854
548
|
results.push(
|
|
855
549
|
...checkCookieCodecConformance(options.projectRoot ?? process.cwd())
|
|
856
550
|
);
|
|
857
|
-
results.push(
|
|
858
|
-
envOk ? await checkSandboxPersonaGate(fetchImpl, env, boardContext) : skipSandboxPersonaGate("env checks failed \u2014 fix them first")
|
|
859
|
-
);
|
|
860
551
|
results.push(
|
|
861
552
|
...options.frontendUrl ? await runReadProbes(fetchImpl, options.frontendUrl) : skipReadProbes("no --frontend <url> given")
|
|
862
553
|
);
|
|
863
|
-
if (!options.sandbox) {
|
|
864
|
-
results.push(
|
|
865
|
-
...skipWriteProbes(
|
|
866
|
-
"write probes not enabled \u2014 run with --sandbox against the platform sandbox"
|
|
867
|
-
)
|
|
868
|
-
);
|
|
869
|
-
} else if (!envOk) {
|
|
870
|
-
results.push(...skipWriteProbes("env checks failed \u2014 fix them first"));
|
|
871
|
-
} else {
|
|
872
|
-
results.push(
|
|
873
|
-
...await runWriteProbes({
|
|
874
|
-
env,
|
|
875
|
-
fetchImpl,
|
|
876
|
-
resendApiKey: options.resendApiKey,
|
|
877
|
-
nonce: options.writeProbeNonce,
|
|
878
|
-
boardContext
|
|
879
|
-
})
|
|
880
|
-
);
|
|
881
|
-
}
|
|
882
554
|
return { results, summary: summarize(results) };
|
|
883
555
|
}
|
|
884
556
|
export {
|
package/dist/filters.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { B as BoardLabelOverrides } from './ui-copy-
|
|
2
|
-
import { J as JobSort, E as EmploymentType, R as RemoteOption, S as Seniority } from './jobs-
|
|
3
|
-
import './_spec-
|
|
1
|
+
import { B as BoardLabelOverrides } from './ui-copy-CL-VN1xb.mjs';
|
|
2
|
+
import { J as JobSort, E as EmploymentType, R as RemoteOption, S as Seniority } from './jobs-CuRzTDb1.mjs';
|
|
3
|
+
import './_spec-sW85fuvT.mjs';
|
|
4
4
|
|
|
5
5
|
declare const REMOTE_OPTIONS: readonly RemoteOption[];
|
|
6
6
|
/**
|
|
@@ -23,7 +23,7 @@ declare const DEFAULT_SORT: JobSort;
|
|
|
23
23
|
declare function seniorityLabels(locale: string, labels?: BoardLabelOverrides): Record<Seniority, string>;
|
|
24
24
|
/**
|
|
25
25
|
* Sort-dropdown labels in the board language, resolved from the copy
|
|
26
|
-
* catalog (`uiCopy(locale).jobCard
|
|
26
|
+
* catalog (`uiCopy(locale).jobCard`) ⊕ stored operator overrides
|
|
27
27
|
* (`jobCardLabels.aiRankedLabel` / `sortNewestLabel` / `sortSalaryHighLabel`)
|
|
28
28
|
* when the `labels` bag is passed.
|
|
29
29
|
*/
|
|
@@ -40,7 +40,7 @@ interface ListingFilters {
|
|
|
40
40
|
* wire as `companySlug` (`jobs.list` query / `jobs.search` filters).
|
|
41
41
|
*/
|
|
42
42
|
company?: string[];
|
|
43
|
-
/** Result ordering
|
|
43
|
+
/** Result ordering; absent ⇒ `relevance`. */
|
|
44
44
|
sort?: JobSort;
|
|
45
45
|
}
|
|
46
46
|
/**
|
package/dist/filters.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { B as BoardLabelOverrides } from './ui-copy-
|
|
2
|
-
import { J as JobSort, E as EmploymentType, R as RemoteOption, S as Seniority } from './jobs-
|
|
3
|
-
import './_spec-
|
|
1
|
+
import { B as BoardLabelOverrides } from './ui-copy-CL-VN1xb.js';
|
|
2
|
+
import { J as JobSort, E as EmploymentType, R as RemoteOption, S as Seniority } from './jobs-DTnCZGtb.js';
|
|
3
|
+
import './_spec-sW85fuvT.js';
|
|
4
4
|
|
|
5
5
|
declare const REMOTE_OPTIONS: readonly RemoteOption[];
|
|
6
6
|
/**
|
|
@@ -23,7 +23,7 @@ declare const DEFAULT_SORT: JobSort;
|
|
|
23
23
|
declare function seniorityLabels(locale: string, labels?: BoardLabelOverrides): Record<Seniority, string>;
|
|
24
24
|
/**
|
|
25
25
|
* Sort-dropdown labels in the board language, resolved from the copy
|
|
26
|
-
* catalog (`uiCopy(locale).jobCard
|
|
26
|
+
* catalog (`uiCopy(locale).jobCard`) ⊕ stored operator overrides
|
|
27
27
|
* (`jobCardLabels.aiRankedLabel` / `sortNewestLabel` / `sortSalaryHighLabel`)
|
|
28
28
|
* when the `labels` bag is passed.
|
|
29
29
|
*/
|
|
@@ -40,7 +40,7 @@ interface ListingFilters {
|
|
|
40
40
|
* wire as `companySlug` (`jobs.list` query / `jobs.search` filters).
|
|
41
41
|
*/
|
|
42
42
|
company?: string[];
|
|
43
|
-
/** Result ordering
|
|
43
|
+
/** Result ordering; absent ⇒ `relevance`. */
|
|
44
44
|
sort?: JobSort;
|
|
45
45
|
}
|
|
46
46
|
/**
|
package/dist/format.d.mts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { P as PublicJob, a as PublicJobCard, C as CustomFieldValues } from './jobs-
|
|
2
|
-
import { B as BoardLabelOverrides } from './ui-copy-
|
|
3
|
-
export { A as AlertsCopy, a as ApplyCopy, b as BlogCopy, c as BreadcrumbsCopy, C as CopyLinkCopy, E as EntityCopy, F as FooterCopy, J as JobCardCopy, d as JobDetailCopy, e as JobSearchCopy, N as NavCopy, P as PUBLIC_LABEL_GROUPS, f as PaginationCopy, S as SalaryCopy, U as UiCopy, u as uiCopy } from './ui-copy-
|
|
4
|
-
import { C as CustomFieldDefinition } from './board-
|
|
5
|
-
import './_spec-
|
|
1
|
+
import { P as PublicJob, a as PublicJobCard, C as CustomFieldValues } from './jobs-CuRzTDb1.mjs';
|
|
2
|
+
import { B as BoardLabelOverrides } from './ui-copy-CL-VN1xb.mjs';
|
|
3
|
+
export { A as AlertsCopy, a as ApplyCopy, b as BlogCopy, c as BreadcrumbsCopy, C as CopyLinkCopy, E as EntityCopy, F as FooterCopy, J as JobCardCopy, d as JobDetailCopy, e as JobSearchCopy, N as NavCopy, P as PUBLIC_LABEL_GROUPS, f as PaginationCopy, S as SalaryCopy, U as UiCopy, u as uiCopy } from './ui-copy-CL-VN1xb.mjs';
|
|
4
|
+
import { C as CustomFieldDefinition } from './board-QFeHT4UN.mjs';
|
|
5
|
+
import './_spec-sW85fuvT.mjs';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Date display helpers in the board language (required leading parameter,
|
|
9
|
-
*
|
|
10
|
-
*
|
|
9
|
+
* ) — transcribed from the hosted board's `date-labels.ts` and
|
|
10
|
+
* tested against the hosted behavior.
|
|
11
11
|
*
|
|
12
12
|
* Job cards and the job-detail header render the RELATIVE form
|
|
13
13
|
* (`formatPublishedRelativeDate` — "5d ago"); the absolute medium date
|
|
@@ -28,7 +28,7 @@ declare function formatDate(locale: string, value: string | number | null): stri
|
|
|
28
28
|
declare function formatPublishedRelativeDate(locale: string, value: string | number | null, referenceNowMs?: number): string | null;
|
|
29
29
|
/**
|
|
30
30
|
* Month-year label, e.g. "Jun 2023" — transcribed from the hosted board's
|
|
31
|
-
* `formatMonthYear
|
|
31
|
+
* `formatMonthYear`. The `T00:00:00` suffix
|
|
32
32
|
* prevents timezone-related off-by-one-month issues on date-only strings.
|
|
33
33
|
*/
|
|
34
34
|
declare function formatMonthYear(locale: string, dateStr: string): string;
|
|
@@ -38,7 +38,7 @@ declare function formatMonthYear(locale: string, dateStr: string): string;
|
|
|
38
38
|
* `PublicJobCard` shape a list card renders — computing the card's
|
|
39
39
|
* pre-resolved `locationLabel`/`remoteLocationLabel` from the full job's
|
|
40
40
|
* fields. Board language is required because the computed location label is
|
|
41
|
-
* display text
|
|
41
|
+
* display text.
|
|
42
42
|
*/
|
|
43
43
|
declare function fullJobToCard(locale: string, job: PublicJob): PublicJobCard;
|
|
44
44
|
|
|
@@ -59,7 +59,7 @@ declare function fieldLabel(locale: string, value: string | null, labels?: Board
|
|
|
59
59
|
|
|
60
60
|
/**
|
|
61
61
|
* Location display labels for job cards and detail pages, in the board
|
|
62
|
-
* language
|
|
62
|
+
* language. The wrapper words
|
|
63
63
|
* ("Remote", "(hybrid)", "Worldwide") follow the hosted board's current
|
|
64
64
|
* English-only rendering; place names arrive already board-language from the
|
|
65
65
|
* wire (the API serves canonical, localized labels).
|
|
@@ -84,8 +84,7 @@ declare function cardLocationLabel(locale: string, job: Pick<PublicJobCard, 'rem
|
|
|
84
84
|
* `<title>`/`<description>` sentence FRAMES, keyed by the board language
|
|
85
85
|
* (`board.context().language`), NOT by any visitor locale.
|
|
86
86
|
*
|
|
87
|
-
* Transcribed from the hosted board's `salary-lexicon.ts`
|
|
88
|
-
* ADR-0012) and golden-tested against it in-monorepo (ADR-0057): the two
|
|
87
|
+
* Transcribed from the hosted board's `salary-lexicon.ts` and tested against it : the two
|
|
89
88
|
* copies cannot drift silently. The `de` strings are native-reviewed.
|
|
90
89
|
* Unseeded locales fall back to the English source WORDS while number and
|
|
91
90
|
* currency formatting stays locale-correct.
|
|
@@ -229,7 +228,7 @@ interface SalaryTimeframeOverrides {
|
|
|
229
228
|
}
|
|
230
229
|
/**
|
|
231
230
|
* Format a per-job salary range in the BOARD language
|
|
232
|
-
* (`board.context().language` — required, never defaulted; see
|
|
231
|
+
* (`board.context().language` — required, never defaulted; see ).
|
|
233
232
|
* The fixed words (timeframe, "From"/"Up to") come from
|
|
234
233
|
* `getSalaryLexicon(language)` with precedence owner-override > lexicon >
|
|
235
234
|
* English source. Currency placement is gated: `en` keeps the legacy manual
|
|
@@ -251,7 +250,7 @@ declare function companyIntro(summary: string | null, description: string | null
|
|
|
251
250
|
* permit taxonomy (`GET /v1/taxonomies/remote-permits`), published so a
|
|
252
251
|
* picker on any surface offers exactly the set the API accepts. Mirrors the
|
|
253
252
|
* platform's canonical `COUNTRIES` dataset (`world-countries` minus `KP`,
|
|
254
|
-
* plus `XK`/Kosovo); an
|
|
253
|
+
* plus `XK`/Kosovo); an parity test asserts the mirror cannot
|
|
255
254
|
* drift. Codes only — display names localize at render via
|
|
256
255
|
* `countryOptions(language)`.
|
|
257
256
|
*/
|
|
@@ -263,7 +262,7 @@ interface CountryOption {
|
|
|
263
262
|
}
|
|
264
263
|
/**
|
|
265
264
|
* The complete country option list, display-named in the BOARD language
|
|
266
|
-
*
|
|
265
|
+
* and sorted by that language's
|
|
267
266
|
* collation — the shape a country picker renders. Falls back to the bare
|
|
268
267
|
* code when the runtime cannot resolve the language or name a region.
|
|
269
268
|
*/
|
|
@@ -271,15 +270,14 @@ declare function countryOptions(language: string): CountryOption[];
|
|
|
271
270
|
|
|
272
271
|
/**
|
|
273
272
|
* Resolve a job's opaque `customFieldValues` into ordered, display-ready
|
|
274
|
-
* entries for the "Additional details" section
|
|
275
|
-
*
|
|
276
|
-
* custom-field-display.ts). Pure. Iterates the *definitions* (from board
|
|
273
|
+
* entries for the "Additional details" section, matching the hosted board's
|
|
274
|
+
* display behavior. Pure. Iterates the *definitions* (from board
|
|
277
275
|
* context) in config order, so display order is operator-controlled, orphan
|
|
278
276
|
* values (deleted definitions) are never inspected, and empty values drop per
|
|
279
277
|
* field. Select values resolve their stored option KEY to the current label
|
|
280
278
|
* (rename-safe); an option with no current match is dropped. Booleans stay raw
|
|
281
279
|
* (a discriminated `kind`) — the render layer authors the "Yes"/"No" chrome
|
|
282
|
-
* (English; localized copy is the use-intl
|
|
280
|
+
* (English; localized copy is the use-intl future release).
|
|
283
281
|
*/
|
|
284
282
|
type CustomFieldDisplayEntry = {
|
|
285
283
|
key: string;
|
package/dist/format.d.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { P as PublicJob, a as PublicJobCard, C as CustomFieldValues } from './jobs-
|
|
2
|
-
import { B as BoardLabelOverrides } from './ui-copy-
|
|
3
|
-
export { A as AlertsCopy, a as ApplyCopy, b as BlogCopy, c as BreadcrumbsCopy, C as CopyLinkCopy, E as EntityCopy, F as FooterCopy, J as JobCardCopy, d as JobDetailCopy, e as JobSearchCopy, N as NavCopy, P as PUBLIC_LABEL_GROUPS, f as PaginationCopy, S as SalaryCopy, U as UiCopy, u as uiCopy } from './ui-copy-
|
|
4
|
-
import { C as CustomFieldDefinition } from './board-
|
|
5
|
-
import './_spec-
|
|
1
|
+
import { P as PublicJob, a as PublicJobCard, C as CustomFieldValues } from './jobs-DTnCZGtb.js';
|
|
2
|
+
import { B as BoardLabelOverrides } from './ui-copy-CL-VN1xb.js';
|
|
3
|
+
export { A as AlertsCopy, a as ApplyCopy, b as BlogCopy, c as BreadcrumbsCopy, C as CopyLinkCopy, E as EntityCopy, F as FooterCopy, J as JobCardCopy, d as JobDetailCopy, e as JobSearchCopy, N as NavCopy, P as PUBLIC_LABEL_GROUPS, f as PaginationCopy, S as SalaryCopy, U as UiCopy, u as uiCopy } from './ui-copy-CL-VN1xb.js';
|
|
4
|
+
import { C as CustomFieldDefinition } from './board-DKXyxwNN.js';
|
|
5
|
+
import './_spec-sW85fuvT.js';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Date display helpers in the board language (required leading parameter,
|
|
9
|
-
*
|
|
10
|
-
*
|
|
9
|
+
* ) — transcribed from the hosted board's `date-labels.ts` and
|
|
10
|
+
* tested against the hosted behavior.
|
|
11
11
|
*
|
|
12
12
|
* Job cards and the job-detail header render the RELATIVE form
|
|
13
13
|
* (`formatPublishedRelativeDate` — "5d ago"); the absolute medium date
|
|
@@ -28,7 +28,7 @@ declare function formatDate(locale: string, value: string | number | null): stri
|
|
|
28
28
|
declare function formatPublishedRelativeDate(locale: string, value: string | number | null, referenceNowMs?: number): string | null;
|
|
29
29
|
/**
|
|
30
30
|
* Month-year label, e.g. "Jun 2023" — transcribed from the hosted board's
|
|
31
|
-
* `formatMonthYear
|
|
31
|
+
* `formatMonthYear`. The `T00:00:00` suffix
|
|
32
32
|
* prevents timezone-related off-by-one-month issues on date-only strings.
|
|
33
33
|
*/
|
|
34
34
|
declare function formatMonthYear(locale: string, dateStr: string): string;
|
|
@@ -38,7 +38,7 @@ declare function formatMonthYear(locale: string, dateStr: string): string;
|
|
|
38
38
|
* `PublicJobCard` shape a list card renders — computing the card's
|
|
39
39
|
* pre-resolved `locationLabel`/`remoteLocationLabel` from the full job's
|
|
40
40
|
* fields. Board language is required because the computed location label is
|
|
41
|
-
* display text
|
|
41
|
+
* display text.
|
|
42
42
|
*/
|
|
43
43
|
declare function fullJobToCard(locale: string, job: PublicJob): PublicJobCard;
|
|
44
44
|
|
|
@@ -59,7 +59,7 @@ declare function fieldLabel(locale: string, value: string | null, labels?: Board
|
|
|
59
59
|
|
|
60
60
|
/**
|
|
61
61
|
* Location display labels for job cards and detail pages, in the board
|
|
62
|
-
* language
|
|
62
|
+
* language. The wrapper words
|
|
63
63
|
* ("Remote", "(hybrid)", "Worldwide") follow the hosted board's current
|
|
64
64
|
* English-only rendering; place names arrive already board-language from the
|
|
65
65
|
* wire (the API serves canonical, localized labels).
|
|
@@ -84,8 +84,7 @@ declare function cardLocationLabel(locale: string, job: Pick<PublicJobCard, 'rem
|
|
|
84
84
|
* `<title>`/`<description>` sentence FRAMES, keyed by the board language
|
|
85
85
|
* (`board.context().language`), NOT by any visitor locale.
|
|
86
86
|
*
|
|
87
|
-
* Transcribed from the hosted board's `salary-lexicon.ts`
|
|
88
|
-
* ADR-0012) and golden-tested against it in-monorepo (ADR-0057): the two
|
|
87
|
+
* Transcribed from the hosted board's `salary-lexicon.ts` and tested against it : the two
|
|
89
88
|
* copies cannot drift silently. The `de` strings are native-reviewed.
|
|
90
89
|
* Unseeded locales fall back to the English source WORDS while number and
|
|
91
90
|
* currency formatting stays locale-correct.
|
|
@@ -229,7 +228,7 @@ interface SalaryTimeframeOverrides {
|
|
|
229
228
|
}
|
|
230
229
|
/**
|
|
231
230
|
* Format a per-job salary range in the BOARD language
|
|
232
|
-
* (`board.context().language` — required, never defaulted; see
|
|
231
|
+
* (`board.context().language` — required, never defaulted; see ).
|
|
233
232
|
* The fixed words (timeframe, "From"/"Up to") come from
|
|
234
233
|
* `getSalaryLexicon(language)` with precedence owner-override > lexicon >
|
|
235
234
|
* English source. Currency placement is gated: `en` keeps the legacy manual
|
|
@@ -251,7 +250,7 @@ declare function companyIntro(summary: string | null, description: string | null
|
|
|
251
250
|
* permit taxonomy (`GET /v1/taxonomies/remote-permits`), published so a
|
|
252
251
|
* picker on any surface offers exactly the set the API accepts. Mirrors the
|
|
253
252
|
* platform's canonical `COUNTRIES` dataset (`world-countries` minus `KP`,
|
|
254
|
-
* plus `XK`/Kosovo); an
|
|
253
|
+
* plus `XK`/Kosovo); an parity test asserts the mirror cannot
|
|
255
254
|
* drift. Codes only — display names localize at render via
|
|
256
255
|
* `countryOptions(language)`.
|
|
257
256
|
*/
|
|
@@ -263,7 +262,7 @@ interface CountryOption {
|
|
|
263
262
|
}
|
|
264
263
|
/**
|
|
265
264
|
* The complete country option list, display-named in the BOARD language
|
|
266
|
-
*
|
|
265
|
+
* and sorted by that language's
|
|
267
266
|
* collation — the shape a country picker renders. Falls back to the bare
|
|
268
267
|
* code when the runtime cannot resolve the language or name a region.
|
|
269
268
|
*/
|
|
@@ -271,15 +270,14 @@ declare function countryOptions(language: string): CountryOption[];
|
|
|
271
270
|
|
|
272
271
|
/**
|
|
273
272
|
* Resolve a job's opaque `customFieldValues` into ordered, display-ready
|
|
274
|
-
* entries for the "Additional details" section
|
|
275
|
-
*
|
|
276
|
-
* custom-field-display.ts). Pure. Iterates the *definitions* (from board
|
|
273
|
+
* entries for the "Additional details" section, matching the hosted board's
|
|
274
|
+
* display behavior. Pure. Iterates the *definitions* (from board
|
|
277
275
|
* context) in config order, so display order is operator-controlled, orphan
|
|
278
276
|
* values (deleted definitions) are never inspected, and empty values drop per
|
|
279
277
|
* field. Select values resolve their stored option KEY to the current label
|
|
280
278
|
* (rename-safe); an option with no current match is dropped. Booleans stay raw
|
|
281
279
|
* (a discriminated `kind`) — the render layer authors the "Yes"/"No" chrome
|
|
282
|
-
* (English; localized copy is the use-intl
|
|
280
|
+
* (English; localized copy is the use-intl future release).
|
|
283
281
|
*/
|
|
284
282
|
type CustomFieldDisplayEntry = {
|
|
285
283
|
key: string;
|