@cavuno/board 1.28.0 → 1.29.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 +387 -58
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +12 -5
- package/dist/index.mjs +12 -5
- package/dist/server.js +13 -6
- package/dist/server.mjs +13 -6
- package/dist/skills-types.d.mts +20 -0
- package/dist/skills-types.d.ts +20 -0
- package/dist/skills-types.js +18 -0
- package/dist/skills-types.mjs +0 -0
- package/dist/skills.d.mts +15 -8
- package/dist/skills.d.ts +15 -8
- package/dist/skills.js +10 -7
- package/dist/skills.mjs +10 -7
- package/package.json +11 -1
- package/skills/cavuno-board-smoke-test/SKILL.md +14 -0
- package/skills/manifest.json +1 -1
package/dist/bin.mjs
CHANGED
|
@@ -1,11 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
|
-
var __esm = (fn, res) => function __init() {
|
|
4
|
-
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
5
|
-
};
|
|
6
|
-
var __commonJS = (cb, mod) => function __require() {
|
|
7
|
-
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
8
|
-
};
|
|
9
2
|
|
|
10
3
|
// src/skills.ts
|
|
11
4
|
import { readFileSync } from "fs";
|
|
@@ -14,26 +7,335 @@ import { fileURLToPath } from "url";
|
|
|
14
7
|
function packageRoot() {
|
|
15
8
|
return resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
16
9
|
}
|
|
17
|
-
function resolveFromPackageRoot(relativePath) {
|
|
18
|
-
return resolve(packageRoot(), relativePath);
|
|
10
|
+
function resolveFromPackageRoot(relativePath, baseDir) {
|
|
11
|
+
return resolve(baseDir ?? packageRoot(), relativePath);
|
|
19
12
|
}
|
|
20
|
-
function loadSkillManifest() {
|
|
21
|
-
const manifestPath = resolveFromPackageRoot("skills/manifest.json");
|
|
13
|
+
function loadSkillManifest(baseDir) {
|
|
14
|
+
const manifestPath = resolveFromPackageRoot("skills/manifest.json", baseDir);
|
|
22
15
|
return JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
23
16
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
17
|
+
function loadSkillCorpus(baseDir) {
|
|
18
|
+
const manifest = loadSkillManifest(baseDir);
|
|
19
|
+
return {
|
|
20
|
+
version: manifest.version,
|
|
21
|
+
skills: manifest.skills.map((skill) => ({
|
|
22
|
+
...skill,
|
|
23
|
+
content: readFileSync(
|
|
24
|
+
resolveFromPackageRoot(skill.path, baseDir),
|
|
25
|
+
"utf8"
|
|
26
|
+
)
|
|
27
|
+
}))
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// src/doctor/checks.ts
|
|
32
|
+
function record(id, tier) {
|
|
33
|
+
return (status, detail) => ({
|
|
34
|
+
id,
|
|
35
|
+
tier,
|
|
36
|
+
status,
|
|
37
|
+
detail
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
var ENV_API_URL = record("env.api-url", 1);
|
|
41
|
+
var ENV_BOARD_KEY = record("env.board-key", 1);
|
|
42
|
+
var PK_RE = /^pk_[0-9a-f]{32}$/;
|
|
43
|
+
function checkEnv(env) {
|
|
44
|
+
const results = [];
|
|
45
|
+
if (!env.apiUrl) {
|
|
46
|
+
results.push(ENV_API_URL("fail", "PUBLIC_CAVUNO_API_URL is not set"));
|
|
47
|
+
} else {
|
|
48
|
+
let origin = null;
|
|
49
|
+
try {
|
|
50
|
+
origin = new URL(env.apiUrl).origin;
|
|
51
|
+
} catch {
|
|
52
|
+
origin = null;
|
|
53
|
+
}
|
|
54
|
+
results.push(
|
|
55
|
+
origin ? ENV_API_URL("pass", origin) : ENV_API_URL(
|
|
56
|
+
"fail",
|
|
57
|
+
`PUBLIC_CAVUNO_API_URL is not a valid URL: ${env.apiUrl}`
|
|
58
|
+
)
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
if (!env.boardKey) {
|
|
62
|
+
results.push(ENV_BOARD_KEY("fail", "PUBLIC_CAVUNO_BOARD is not set"));
|
|
63
|
+
} else {
|
|
64
|
+
results.push(
|
|
65
|
+
PK_RE.test(env.boardKey) ? ENV_BOARD_KEY("pass", "pk_ key") : ENV_BOARD_KEY(
|
|
66
|
+
"fail",
|
|
67
|
+
"PUBLIC_CAVUNO_BOARD must be a publishable key (pk_ + 32 hex chars)"
|
|
68
|
+
)
|
|
69
|
+
);
|
|
27
70
|
}
|
|
28
|
-
|
|
71
|
+
return results;
|
|
72
|
+
}
|
|
73
|
+
var JOB_DETAIL_LINK_RE = /href="(\/companies\/[a-z0-9-]+\/jobs\/[a-z0-9-]+)"/i;
|
|
74
|
+
function extractJobDetailLink(html) {
|
|
75
|
+
return html.match(JOB_DETAIL_LINK_RE)?.[1] ?? null;
|
|
76
|
+
}
|
|
77
|
+
var JSON_LD_RE = /<script[^>]*type="application\/ld\+json"[^>]*>([\s\S]*?)<\/script>/gi;
|
|
78
|
+
function extractJobPostingJsonLd(html) {
|
|
79
|
+
for (const match of html.matchAll(JSON_LD_RE)) {
|
|
80
|
+
try {
|
|
81
|
+
const parsed = JSON.parse(match[1].trim());
|
|
82
|
+
if (parsed["@type"] === "JobPosting") return parsed;
|
|
83
|
+
} catch {
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
var LOC_RE = /<loc>([^<]+)<\/loc>/gi;
|
|
89
|
+
function parseSitemap(xml) {
|
|
90
|
+
const kind = /<urlset[\s>]/i.test(xml) ? "urlset" : /<sitemapindex[\s>]/i.test(xml) ? "index" : null;
|
|
91
|
+
if (!kind) return null;
|
|
92
|
+
return { kind, urls: [...xml.matchAll(LOC_RE)].map((m) => m[1].trim()) };
|
|
93
|
+
}
|
|
94
|
+
function summarize(results) {
|
|
95
|
+
const passed = results.filter((r) => r.status === "pass").map((r) => r.id);
|
|
96
|
+
const failed = results.filter((r) => r.status === "fail").map((r) => r.id);
|
|
97
|
+
const skipped = results.filter((r) => r.status === "skip").map((r) => r.id);
|
|
98
|
+
return { exitCode: failed.length > 0 ? 1 : 0, passed, failed, skipped };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// src/doctor/run.ts
|
|
102
|
+
import { existsSync, readFileSync as readFileSync2 } from "fs";
|
|
103
|
+
import { join } from "path";
|
|
104
|
+
var FETCH_TIMEOUT_MS = 1e4;
|
|
105
|
+
var READ = {
|
|
106
|
+
home: record("read.home", 2),
|
|
107
|
+
jobs: record("read.jobs", 2),
|
|
108
|
+
jsonld: record("read.jsonld", 2),
|
|
109
|
+
sitemap: record("read.sitemap", 2),
|
|
110
|
+
robots: record("read.robots", 2)
|
|
111
|
+
};
|
|
112
|
+
var STATIC_API = record("static.api", 1);
|
|
113
|
+
var STATIC_BOARD = record("static.board", 1);
|
|
114
|
+
var STATIC_SKILLS = record("static.skills", 1);
|
|
115
|
+
async function probe(fetchImpl, url) {
|
|
116
|
+
try {
|
|
117
|
+
const response = await fetchImpl(url, {
|
|
118
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
|
|
119
|
+
});
|
|
120
|
+
return {
|
|
121
|
+
ok: response.ok,
|
|
122
|
+
status: response.status,
|
|
123
|
+
body: await response.text()
|
|
124
|
+
};
|
|
125
|
+
} catch (error) {
|
|
126
|
+
return {
|
|
127
|
+
ok: false,
|
|
128
|
+
status: 0,
|
|
129
|
+
body: error instanceof Error ? error.message : String(error)
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
async function checkApiReachable(fetchImpl, apiUrl) {
|
|
134
|
+
let origin;
|
|
135
|
+
try {
|
|
136
|
+
origin = new URL(apiUrl).origin;
|
|
137
|
+
} catch {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
const spec = await probe(fetchImpl, `${origin}/api/v1/openapi.json`);
|
|
141
|
+
const result = spec.ok ? spec : await probe(fetchImpl, `${origin}/v1/openapi.json`);
|
|
142
|
+
if (!result.ok) {
|
|
143
|
+
return STATIC_API(
|
|
144
|
+
"fail",
|
|
145
|
+
`OpenAPI spec unreachable (HTTP ${result.status})`
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
try {
|
|
149
|
+
const parsed = JSON.parse(result.body);
|
|
150
|
+
if (typeof parsed.openapi !== "string") throw new Error("not a spec");
|
|
151
|
+
} catch {
|
|
152
|
+
return STATIC_API(
|
|
153
|
+
"fail",
|
|
154
|
+
"endpoint returned 200 but not an OpenAPI document \u2014 proxy or captive portal in the way?"
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
return STATIC_API("pass", "OpenAPI spec reachable");
|
|
158
|
+
}
|
|
159
|
+
async function checkBoardResolves(fetchImpl, env) {
|
|
160
|
+
const origin = new URL(env.apiUrl).origin;
|
|
161
|
+
const result = await probe(
|
|
162
|
+
fetchImpl,
|
|
163
|
+
`${origin}/v1/boards/${encodeURIComponent(env.boardKey)}`
|
|
164
|
+
);
|
|
165
|
+
if (!result.ok) {
|
|
166
|
+
return STATIC_BOARD(
|
|
167
|
+
"fail",
|
|
168
|
+
`board did not resolve for this key (HTTP ${result.status}) \u2014 revoked or wrong key?`
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
try {
|
|
172
|
+
const parsed = JSON.parse(result.body);
|
|
173
|
+
if (typeof parsed !== "object" || parsed === null) throw new Error("shape");
|
|
174
|
+
} catch {
|
|
175
|
+
return STATIC_BOARD(
|
|
176
|
+
"fail",
|
|
177
|
+
"board endpoint returned 200 but not JSON \u2014 proxy or captive portal in the way?"
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
return STATIC_BOARD("pass", "publishable key resolves the board");
|
|
181
|
+
}
|
|
182
|
+
var SKILL_ROOTS = [".claude/skills", ".agents/skills"];
|
|
183
|
+
function checkSkillsFreshness(projectRoot) {
|
|
184
|
+
const roots = SKILL_ROOTS.map((root) => join(projectRoot, root)).filter(
|
|
185
|
+
(root) => existsSync(root)
|
|
186
|
+
);
|
|
187
|
+
if (roots.length === 0) {
|
|
188
|
+
return STATIC_SKILLS(
|
|
189
|
+
"skip",
|
|
190
|
+
"no .claude/skills or .agents/skills directory \u2014 run `npx @cavuno/board setup` to install agent skills"
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
const corpus = loadSkillCorpus();
|
|
194
|
+
const stale = /* @__PURE__ */ new Set();
|
|
195
|
+
const seen = /* @__PURE__ */ new Set();
|
|
196
|
+
for (const root of roots) {
|
|
197
|
+
for (const skill of corpus.skills) {
|
|
198
|
+
const copied = join(root, skill.name, "SKILL.md");
|
|
199
|
+
if (!existsSync(copied)) continue;
|
|
200
|
+
seen.add(skill.name);
|
|
201
|
+
if (readFileSync2(copied, "utf8") !== skill.content) {
|
|
202
|
+
stale.add(skill.name);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
const found = seen.size;
|
|
207
|
+
if (found === 0) {
|
|
208
|
+
return STATIC_SKILLS(
|
|
209
|
+
"skip",
|
|
210
|
+
"no cavuno-board-* skills installed \u2014 run `npx @cavuno/board setup`"
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
return stale.size === 0 ? STATIC_SKILLS(
|
|
214
|
+
"pass",
|
|
215
|
+
`${found} installed skills match v${corpus.version}`
|
|
216
|
+
) : STATIC_SKILLS(
|
|
217
|
+
"fail",
|
|
218
|
+
`stale skills (re-run \`npx @cavuno/board setup\`): ${[...stale].join(", ")}`
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
async function probeJobsAndJsonLd(fetchImpl, base) {
|
|
222
|
+
const jobs = await probe(fetchImpl, `${base}/jobs`);
|
|
223
|
+
const jobLink = jobs.ok ? extractJobDetailLink(jobs.body) : null;
|
|
224
|
+
const jobsResult = jobs.ok && jobLink ? READ.jobs("pass", `listing renders with job detail links (${jobLink})`) : READ.jobs(
|
|
225
|
+
"fail",
|
|
226
|
+
jobs.ok ? "listing renders but contains no /companies/{c}/jobs/{s} detail links" : `listing HTTP ${jobs.status}`
|
|
227
|
+
);
|
|
228
|
+
if (!jobLink) {
|
|
229
|
+
return [
|
|
230
|
+
jobsResult,
|
|
231
|
+
READ.jsonld(
|
|
232
|
+
"skip",
|
|
233
|
+
jobs.ok ? "not probed \u2014 the listing rendered no job detail link (see read.jobs failure)" : "not probed \u2014 the /jobs listing itself failed (see read.jobs failure)"
|
|
234
|
+
)
|
|
235
|
+
];
|
|
236
|
+
}
|
|
237
|
+
const detail = await probe(fetchImpl, `${base}${jobLink}`);
|
|
238
|
+
const posting = detail.ok ? extractJobPostingJsonLd(detail.body) : null;
|
|
239
|
+
return [
|
|
240
|
+
jobsResult,
|
|
241
|
+
posting ? READ.jsonld(
|
|
242
|
+
"pass",
|
|
243
|
+
`JobPosting JSON-LD present (${String(posting.title ?? "")})`
|
|
244
|
+
) : READ.jsonld(
|
|
245
|
+
"fail",
|
|
246
|
+
detail.ok ? "job detail page has no parseable JobPosting JSON-LD" : `job detail HTTP ${detail.status}`
|
|
247
|
+
)
|
|
248
|
+
];
|
|
249
|
+
}
|
|
250
|
+
function onFrontend(base, locUrl) {
|
|
251
|
+
try {
|
|
252
|
+
const parsed = new URL(locUrl);
|
|
253
|
+
return `${base}${parsed.pathname}${parsed.search}`;
|
|
254
|
+
} catch {
|
|
255
|
+
return locUrl;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
async function probeSitemap(fetchImpl, base) {
|
|
259
|
+
const sitemap = await probe(fetchImpl, `${base}/sitemap.xml`);
|
|
260
|
+
if (!sitemap.ok) {
|
|
261
|
+
return READ.sitemap("fail", `sitemap.xml HTTP ${sitemap.status}`);
|
|
262
|
+
}
|
|
263
|
+
const doc = parseSitemap(sitemap.body);
|
|
264
|
+
if (!doc || doc.urls.length === 0) {
|
|
265
|
+
return READ.sitemap(
|
|
266
|
+
"fail",
|
|
267
|
+
doc ? "sitemap.xml has no <loc> entries" : "sitemap.xml is not a sitemap"
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
let urls = doc.urls;
|
|
271
|
+
if (doc.kind === "index") {
|
|
272
|
+
const child = await probe(fetchImpl, onFrontend(base, urls[0]));
|
|
273
|
+
const childDoc = child.ok ? parseSitemap(child.body) : null;
|
|
274
|
+
if (!childDoc || childDoc.urls.length === 0) {
|
|
275
|
+
return READ.sitemap(
|
|
276
|
+
"fail",
|
|
277
|
+
`child sitemap ${urls[0]} ${child.ok ? "has no <loc> entries" : `HTTP ${child.status}`}`
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
urls = childDoc.urls;
|
|
281
|
+
}
|
|
282
|
+
const sampleUrl = onFrontend(base, urls[0]);
|
|
283
|
+
const sample = await probe(fetchImpl, sampleUrl);
|
|
284
|
+
return sample.ok ? READ.sitemap("pass", `${urls.length} entries; sample page resolves`) : READ.sitemap("fail", `sitemap page ${sampleUrl} \u2192 HTTP ${sample.status}`);
|
|
285
|
+
}
|
|
286
|
+
async function runDoctor(options) {
|
|
287
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
288
|
+
const results = [];
|
|
289
|
+
const envResults = checkEnv(options.env);
|
|
290
|
+
results.push(...envResults);
|
|
291
|
+
const envOk = envResults.every((r) => r.status === "pass");
|
|
292
|
+
if (options.env.apiUrl) {
|
|
293
|
+
const api = await checkApiReachable(fetchImpl, options.env.apiUrl);
|
|
294
|
+
results.push(
|
|
295
|
+
api ?? STATIC_API("skip", "API URL malformed \u2014 fix env.api-url first")
|
|
296
|
+
);
|
|
297
|
+
} else {
|
|
298
|
+
results.push(STATIC_API("skip", "PUBLIC_CAVUNO_API_URL not set"));
|
|
299
|
+
}
|
|
300
|
+
results.push(
|
|
301
|
+
envOk ? await checkBoardResolves(fetchImpl, options.env) : STATIC_BOARD("skip", "env checks failed \u2014 fix them first")
|
|
302
|
+
);
|
|
303
|
+
results.push(checkSkillsFreshness(options.projectRoot ?? process.cwd()));
|
|
304
|
+
if (!options.frontendUrl) {
|
|
305
|
+
for (const make of Object.values(READ)) {
|
|
306
|
+
results.push(make("skip", "no --frontend <url> given"));
|
|
307
|
+
}
|
|
308
|
+
return { results, summary: summarize(results) };
|
|
309
|
+
}
|
|
310
|
+
const base = options.frontendUrl.replace(/\/$/, "");
|
|
311
|
+
const [home, jobsAndJsonLd, sitemap, robots] = await Promise.all([
|
|
312
|
+
probe(fetchImpl, base),
|
|
313
|
+
probeJobsAndJsonLd(fetchImpl, base),
|
|
314
|
+
probeSitemap(fetchImpl, base),
|
|
315
|
+
probe(fetchImpl, `${base}/robots.txt`)
|
|
316
|
+
]);
|
|
317
|
+
results.push(
|
|
318
|
+
home.ok && /<(html|body|div|main)[\s>]/i.test(home.body) ? READ.home("pass", "home renders") : READ.home(
|
|
319
|
+
"fail",
|
|
320
|
+
home.ok ? "home returned 200 but no HTML document \u2014 captive portal or empty shell?" : `home HTTP ${home.status}`
|
|
321
|
+
),
|
|
322
|
+
...jobsAndJsonLd,
|
|
323
|
+
sitemap,
|
|
324
|
+
robots.ok && /user-agent\s*:/i.test(robots.body) ? READ.robots("pass", "present") : READ.robots(
|
|
325
|
+
"fail",
|
|
326
|
+
robots.ok ? "robots.txt returned 200 but has no User-agent directive" : `robots.txt HTTP ${robots.status}`
|
|
327
|
+
)
|
|
328
|
+
);
|
|
329
|
+
return { results, summary: summarize(results) };
|
|
330
|
+
}
|
|
29
331
|
|
|
30
332
|
// src/setup/run.ts
|
|
31
|
-
import { cpSync, existsSync, mkdirSync, readFileSync as
|
|
333
|
+
import { cpSync, existsSync as existsSync2, mkdirSync, readFileSync as readFileSync3 } from "fs";
|
|
32
334
|
import { dirname as dirname2, resolve as resolve2 } from "path";
|
|
33
335
|
function detectFramework(cwd) {
|
|
34
336
|
const pkgPath = resolve2(cwd, "package.json");
|
|
35
|
-
if (!
|
|
36
|
-
const pkg = JSON.parse(
|
|
337
|
+
if (!existsSync2(pkgPath)) return null;
|
|
338
|
+
const pkg = JSON.parse(readFileSync3(pkgPath, "utf8"));
|
|
37
339
|
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
38
340
|
if (deps["@tanstack/react-start"]) return "tanstack-start";
|
|
39
341
|
return null;
|
|
@@ -48,7 +350,7 @@ function runSetup(cwd = process.cwd()) {
|
|
|
48
350
|
resolve2(cwd, ".claude", "skills"),
|
|
49
351
|
resolve2(cwd, ".agents", "skills")
|
|
50
352
|
];
|
|
51
|
-
const existing = roots.filter((root) =>
|
|
353
|
+
const existing = roots.filter((root) => existsSync2(root));
|
|
52
354
|
const targetDirs = existing.length > 0 ? existing : [roots[0]];
|
|
53
355
|
const copied = [];
|
|
54
356
|
for (const targetDir of targetDirs) {
|
|
@@ -63,45 +365,72 @@ function runSetup(cwd = process.cwd()) {
|
|
|
63
365
|
}
|
|
64
366
|
return { version: manifest.version, framework, targetDirs, copied };
|
|
65
367
|
}
|
|
66
|
-
var init_run = __esm({
|
|
67
|
-
"src/setup/run.ts"() {
|
|
68
|
-
"use strict";
|
|
69
|
-
init_skills();
|
|
70
|
-
}
|
|
71
|
-
});
|
|
72
368
|
|
|
73
369
|
// src/bin.ts
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
@cavuno/board
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
);
|
|
103
|
-
}
|
|
104
|
-
main();
|
|
370
|
+
async function doctor(argv) {
|
|
371
|
+
const frontendFlag = argv.indexOf("--frontend");
|
|
372
|
+
const frontendUrl = frontendFlag >= 0 ? argv[frontendFlag + 1] : void 0;
|
|
373
|
+
const { results, summary } = await runDoctor({
|
|
374
|
+
env: {
|
|
375
|
+
apiUrl: process.env.PUBLIC_CAVUNO_API_URL,
|
|
376
|
+
boardKey: process.env.PUBLIC_CAVUNO_BOARD
|
|
377
|
+
},
|
|
378
|
+
frontendUrl
|
|
379
|
+
});
|
|
380
|
+
console.log("\n@cavuno/board doctor");
|
|
381
|
+
for (const result of results) {
|
|
382
|
+
const mark = result.status === "pass" ? "\u2713" : result.status === "fail" ? "\u2717" : "\u2212";
|
|
383
|
+
console.log(
|
|
384
|
+
` ${mark} [tier ${result.tier}] ${result.id} \u2014 ${result.detail}`
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
console.log(
|
|
388
|
+
`
|
|
389
|
+
${summary.passed.length} passed, ${summary.failed.length} failed, ${summary.skipped.length} skipped`
|
|
390
|
+
);
|
|
391
|
+
if (summary.skipped.length > 0) {
|
|
392
|
+
console.log(` Skipped (NOT verified): ${summary.skipped.join(", ")}`);
|
|
393
|
+
}
|
|
394
|
+
if (!frontendUrl) {
|
|
395
|
+
console.log(
|
|
396
|
+
" Pass --frontend <url> to run the read probes against your board frontend."
|
|
397
|
+
);
|
|
105
398
|
}
|
|
106
|
-
|
|
107
|
-
|
|
399
|
+
process.exit(summary.exitCode);
|
|
400
|
+
}
|
|
401
|
+
function main() {
|
|
402
|
+
const verb = process.argv[2];
|
|
403
|
+
if (verb === "doctor") {
|
|
404
|
+
doctor(process.argv.slice(3)).catch((error) => {
|
|
405
|
+
console.error("doctor crashed:", error);
|
|
406
|
+
process.exit(1);
|
|
407
|
+
});
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
if (verb !== "setup") {
|
|
411
|
+
console.error("Usage: cavuno-board <setup|doctor>");
|
|
412
|
+
process.exit(1);
|
|
413
|
+
}
|
|
414
|
+
const result = runSetup();
|
|
415
|
+
console.log(`
|
|
416
|
+
@cavuno/board setup \u2014 v${result.version}`);
|
|
417
|
+
console.log(
|
|
418
|
+
`Detected framework: ${result.framework ?? "none (core skills only)"}`
|
|
419
|
+
);
|
|
420
|
+
console.log(
|
|
421
|
+
`Copied ${result.copied.length} skills \u2192 ${result.targetDirs.join(", ")}`
|
|
422
|
+
);
|
|
423
|
+
for (const name of result.copied) console.log(` - ${name}`);
|
|
424
|
+
console.log("\nNext steps:");
|
|
425
|
+
console.log(" 1. Set your environment variables:");
|
|
426
|
+
console.log(" PUBLIC_CAVUNO_API_URL=https://api.cavuno.com");
|
|
427
|
+
console.log(
|
|
428
|
+
" PUBLIC_CAVUNO_BOARD=pk_... # your board publishable key"
|
|
429
|
+
);
|
|
430
|
+
console.log(' 2. Ask your coding agent: "set up my Cavuno board".');
|
|
431
|
+
console.log(" It reads the cavuno-board-setup skill first.");
|
|
432
|
+
console.log(
|
|
433
|
+
"\n Re-run `npx @cavuno/board setup` after upgrading to refresh skills."
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
main();
|
package/dist/index.d.mts
CHANGED
|
@@ -203,7 +203,7 @@ declare function paginate<Q extends Record<string, unknown>, P extends PageShape
|
|
|
203
203
|
* constant because the package is platform-neutral and cannot read
|
|
204
204
|
* package.json at runtime.
|
|
205
205
|
*/
|
|
206
|
-
declare const SDK_VERSION = "1.
|
|
206
|
+
declare const SDK_VERSION = "1.29.0";
|
|
207
207
|
|
|
208
208
|
type SavedJob = Schemas['SavedJob'];
|
|
209
209
|
type SavedJobsListQuery = {
|
package/dist/index.d.ts
CHANGED
|
@@ -203,7 +203,7 @@ declare function paginate<Q extends Record<string, unknown>, P extends PageShape
|
|
|
203
203
|
* constant because the package is platform-neutral and cannot read
|
|
204
204
|
* package.json at runtime.
|
|
205
205
|
*/
|
|
206
|
-
declare const SDK_VERSION = "1.
|
|
206
|
+
declare const SDK_VERSION = "1.29.0";
|
|
207
207
|
|
|
208
208
|
type SavedJob = Schemas['SavedJob'];
|
|
209
209
|
type SavedJobsListQuery = {
|
package/dist/index.js
CHANGED
|
@@ -179,7 +179,7 @@ var BoardApiError = class extends Error {
|
|
|
179
179
|
}
|
|
180
180
|
};
|
|
181
181
|
function isBoardApiError(e) {
|
|
182
|
-
return e instanceof Error && e.name === "BoardApiError";
|
|
182
|
+
return e instanceof Error && e.name === "BoardApiError" && typeof e.status === "number" && typeof e.code === "string";
|
|
183
183
|
}
|
|
184
184
|
function isNotFound(e) {
|
|
185
185
|
return isBoardApiError(e) && e.status === 404;
|
|
@@ -221,6 +221,16 @@ function toSearchParams(query) {
|
|
|
221
221
|
return params;
|
|
222
222
|
}
|
|
223
223
|
|
|
224
|
+
// src/scope.ts
|
|
225
|
+
function scopeToken(board) {
|
|
226
|
+
let hash = 5381;
|
|
227
|
+
for (let i = 0; i < board.length; i++) {
|
|
228
|
+
hash = (hash << 5) + hash + board.charCodeAt(i) >>> 0;
|
|
229
|
+
}
|
|
230
|
+
const sanitized = board.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
231
|
+
return `${sanitized}-${hash.toString(36)}`;
|
|
232
|
+
}
|
|
233
|
+
|
|
224
234
|
// src/storage.ts
|
|
225
235
|
var ACCESS_TOKEN_KEY = "cavuno_board_access_token";
|
|
226
236
|
var REFRESH_TOKEN_KEY = "cavuno_board_refresh_token";
|
|
@@ -240,9 +250,6 @@ function memoryStorage() {
|
|
|
240
250
|
}
|
|
241
251
|
};
|
|
242
252
|
}
|
|
243
|
-
function scopeToken(board) {
|
|
244
|
-
return board.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
245
|
-
}
|
|
246
253
|
function browserStorage(mode, scope) {
|
|
247
254
|
if (!isBrowser()) {
|
|
248
255
|
throw new Error(
|
|
@@ -303,7 +310,7 @@ async function clearSession(storage) {
|
|
|
303
310
|
}
|
|
304
311
|
|
|
305
312
|
// src/version.ts
|
|
306
|
-
var SDK_VERSION = "1.
|
|
313
|
+
var SDK_VERSION = "1.29.0";
|
|
307
314
|
|
|
308
315
|
// src/client.ts
|
|
309
316
|
function isRawBody(body) {
|
package/dist/index.mjs
CHANGED
|
@@ -134,7 +134,7 @@ var BoardApiError = class extends Error {
|
|
|
134
134
|
}
|
|
135
135
|
};
|
|
136
136
|
function isBoardApiError(e) {
|
|
137
|
-
return e instanceof Error && e.name === "BoardApiError";
|
|
137
|
+
return e instanceof Error && e.name === "BoardApiError" && typeof e.status === "number" && typeof e.code === "string";
|
|
138
138
|
}
|
|
139
139
|
function isNotFound(e) {
|
|
140
140
|
return isBoardApiError(e) && e.status === 404;
|
|
@@ -176,6 +176,16 @@ function toSearchParams(query) {
|
|
|
176
176
|
return params;
|
|
177
177
|
}
|
|
178
178
|
|
|
179
|
+
// src/scope.ts
|
|
180
|
+
function scopeToken(board) {
|
|
181
|
+
let hash = 5381;
|
|
182
|
+
for (let i = 0; i < board.length; i++) {
|
|
183
|
+
hash = (hash << 5) + hash + board.charCodeAt(i) >>> 0;
|
|
184
|
+
}
|
|
185
|
+
const sanitized = board.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
186
|
+
return `${sanitized}-${hash.toString(36)}`;
|
|
187
|
+
}
|
|
188
|
+
|
|
179
189
|
// src/storage.ts
|
|
180
190
|
var ACCESS_TOKEN_KEY = "cavuno_board_access_token";
|
|
181
191
|
var REFRESH_TOKEN_KEY = "cavuno_board_refresh_token";
|
|
@@ -195,9 +205,6 @@ function memoryStorage() {
|
|
|
195
205
|
}
|
|
196
206
|
};
|
|
197
207
|
}
|
|
198
|
-
function scopeToken(board) {
|
|
199
|
-
return board.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
200
|
-
}
|
|
201
208
|
function browserStorage(mode, scope) {
|
|
202
209
|
if (!isBrowser()) {
|
|
203
210
|
throw new Error(
|
|
@@ -258,7 +265,7 @@ async function clearSession(storage) {
|
|
|
258
265
|
}
|
|
259
266
|
|
|
260
267
|
// src/version.ts
|
|
261
|
-
var SDK_VERSION = "1.
|
|
268
|
+
var SDK_VERSION = "1.29.0";
|
|
262
269
|
|
|
263
270
|
// src/client.ts
|
|
264
271
|
function isRawBody(body) {
|
package/dist/server.js
CHANGED
|
@@ -37,6 +37,16 @@ __export(server_exports, {
|
|
|
37
37
|
});
|
|
38
38
|
module.exports = __toCommonJS(server_exports);
|
|
39
39
|
|
|
40
|
+
// src/scope.ts
|
|
41
|
+
function scopeToken(board) {
|
|
42
|
+
let hash = 5381;
|
|
43
|
+
for (let i = 0; i < board.length; i++) {
|
|
44
|
+
hash = (hash << 5) + hash + board.charCodeAt(i) >>> 0;
|
|
45
|
+
}
|
|
46
|
+
const sanitized = board.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
47
|
+
return `${sanitized}-${hash.toString(36)}`;
|
|
48
|
+
}
|
|
49
|
+
|
|
40
50
|
// src/server/cookie.ts
|
|
41
51
|
var COOKIE_ATTRIBUTES = "Path=/; HttpOnly; Secure; SameSite=Lax";
|
|
42
52
|
function buildCookie(name, value, maxAgeSeconds) {
|
|
@@ -57,14 +67,11 @@ function readCookie(header, name) {
|
|
|
57
67
|
return null;
|
|
58
68
|
}
|
|
59
69
|
}
|
|
60
|
-
function cookieScope(board) {
|
|
61
|
-
return board.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
62
|
-
}
|
|
63
70
|
|
|
64
71
|
// src/server/session.ts
|
|
65
72
|
var SESSION_COOKIE_NAME = "__Host-cavuno_board_session";
|
|
66
73
|
function sessionCookieName(board) {
|
|
67
|
-
return board ? `${SESSION_COOKIE_NAME}_${
|
|
74
|
+
return board ? `${SESSION_COOKIE_NAME}_${scopeToken(board)}` : SESSION_COOKIE_NAME;
|
|
68
75
|
}
|
|
69
76
|
var COOKIE_MAX_AGE_SECONDS = 30 * 24 * 60 * 60;
|
|
70
77
|
var EXPIRY_WINDOW_MS = 5 * 60 * 1e3;
|
|
@@ -104,7 +111,7 @@ function isExpiringSoon(session, now, windowMs = EXPIRY_WINDOW_MS) {
|
|
|
104
111
|
// src/server/board-access.ts
|
|
105
112
|
var BOARD_ACCESS_COOKIE_NAME = "__Host-cavuno_board_access";
|
|
106
113
|
function grantCookieName(board) {
|
|
107
|
-
return board ? `${BOARD_ACCESS_COOKIE_NAME}_${
|
|
114
|
+
return board ? `${BOARD_ACCESS_COOKIE_NAME}_${scopeToken(board)}` : BOARD_ACCESS_COOKIE_NAME;
|
|
108
115
|
}
|
|
109
116
|
var COOKIE_MAX_AGE_SECONDS2 = 24 * 60 * 60;
|
|
110
117
|
function serializeGrantCookie(token, options) {
|
|
@@ -149,7 +156,7 @@ function currentPathFromReferer(referer) {
|
|
|
149
156
|
|
|
150
157
|
// src/errors.ts
|
|
151
158
|
function isBoardApiError(e) {
|
|
152
|
-
return e instanceof Error && e.name === "BoardApiError";
|
|
159
|
+
return e instanceof Error && e.name === "BoardApiError" && typeof e.status === "number" && typeof e.code === "string";
|
|
153
160
|
}
|
|
154
161
|
function isUnauthorized(e) {
|
|
155
162
|
return isBoardApiError(e) && e.status === 401;
|
package/dist/server.mjs
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
// src/scope.ts
|
|
2
|
+
function scopeToken(board) {
|
|
3
|
+
let hash = 5381;
|
|
4
|
+
for (let i = 0; i < board.length; i++) {
|
|
5
|
+
hash = (hash << 5) + hash + board.charCodeAt(i) >>> 0;
|
|
6
|
+
}
|
|
7
|
+
const sanitized = board.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
8
|
+
return `${sanitized}-${hash.toString(36)}`;
|
|
9
|
+
}
|
|
10
|
+
|
|
1
11
|
// src/server/cookie.ts
|
|
2
12
|
var COOKIE_ATTRIBUTES = "Path=/; HttpOnly; Secure; SameSite=Lax";
|
|
3
13
|
function buildCookie(name, value, maxAgeSeconds) {
|
|
@@ -18,14 +28,11 @@ function readCookie(header, name) {
|
|
|
18
28
|
return null;
|
|
19
29
|
}
|
|
20
30
|
}
|
|
21
|
-
function cookieScope(board) {
|
|
22
|
-
return board.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
23
|
-
}
|
|
24
31
|
|
|
25
32
|
// src/server/session.ts
|
|
26
33
|
var SESSION_COOKIE_NAME = "__Host-cavuno_board_session";
|
|
27
34
|
function sessionCookieName(board) {
|
|
28
|
-
return board ? `${SESSION_COOKIE_NAME}_${
|
|
35
|
+
return board ? `${SESSION_COOKIE_NAME}_${scopeToken(board)}` : SESSION_COOKIE_NAME;
|
|
29
36
|
}
|
|
30
37
|
var COOKIE_MAX_AGE_SECONDS = 30 * 24 * 60 * 60;
|
|
31
38
|
var EXPIRY_WINDOW_MS = 5 * 60 * 1e3;
|
|
@@ -65,7 +72,7 @@ function isExpiringSoon(session, now, windowMs = EXPIRY_WINDOW_MS) {
|
|
|
65
72
|
// src/server/board-access.ts
|
|
66
73
|
var BOARD_ACCESS_COOKIE_NAME = "__Host-cavuno_board_access";
|
|
67
74
|
function grantCookieName(board) {
|
|
68
|
-
return board ? `${BOARD_ACCESS_COOKIE_NAME}_${
|
|
75
|
+
return board ? `${BOARD_ACCESS_COOKIE_NAME}_${scopeToken(board)}` : BOARD_ACCESS_COOKIE_NAME;
|
|
69
76
|
}
|
|
70
77
|
var COOKIE_MAX_AGE_SECONDS2 = 24 * 60 * 60;
|
|
71
78
|
function serializeGrantCookie(token, options) {
|
|
@@ -110,7 +117,7 @@ function currentPathFromReferer(referer) {
|
|
|
110
117
|
|
|
111
118
|
// src/errors.ts
|
|
112
119
|
function isBoardApiError(e) {
|
|
113
|
-
return e instanceof Error && e.name === "BoardApiError";
|
|
120
|
+
return e instanceof Error && e.name === "BoardApiError" && typeof e.status === "number" && typeof e.code === "string";
|
|
114
121
|
}
|
|
115
122
|
function isUnauthorized(e) {
|
|
116
123
|
return isBoardApiError(e) && e.status === 401;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill-manifest types, split from `skills.ts` so non-Node consumers (the
|
|
3
|
+
* remote-mcp worker types its `/dev` toolset against the wire shape) can
|
|
4
|
+
* import them without dragging the Node fs loader into their program.
|
|
5
|
+
*/
|
|
6
|
+
interface SkillManifestEntry {
|
|
7
|
+
name: string;
|
|
8
|
+
description: string;
|
|
9
|
+
/** Path to the SKILL.md, relative to the package root. */
|
|
10
|
+
path: string;
|
|
11
|
+
/** Framework slug for flavor skills; `null` for framework-agnostic core skills. */
|
|
12
|
+
framework: string | null;
|
|
13
|
+
category: 'core' | 'flavor';
|
|
14
|
+
}
|
|
15
|
+
interface SkillManifest {
|
|
16
|
+
version: string;
|
|
17
|
+
skills: SkillManifestEntry[];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type { SkillManifest, SkillManifestEntry };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill-manifest types, split from `skills.ts` so non-Node consumers (the
|
|
3
|
+
* remote-mcp worker types its `/dev` toolset against the wire shape) can
|
|
4
|
+
* import them without dragging the Node fs loader into their program.
|
|
5
|
+
*/
|
|
6
|
+
interface SkillManifestEntry {
|
|
7
|
+
name: string;
|
|
8
|
+
description: string;
|
|
9
|
+
/** Path to the SKILL.md, relative to the package root. */
|
|
10
|
+
path: string;
|
|
11
|
+
/** Framework slug for flavor skills; `null` for framework-agnostic core skills. */
|
|
12
|
+
framework: string | null;
|
|
13
|
+
category: 'core' | 'flavor';
|
|
14
|
+
}
|
|
15
|
+
interface SkillManifest {
|
|
16
|
+
version: string;
|
|
17
|
+
skills: SkillManifestEntry[];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type { SkillManifest, SkillManifestEntry };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __copyProps = (to, from, except, desc) => {
|
|
7
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
8
|
+
for (let key of __getOwnPropNames(from))
|
|
9
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
10
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
11
|
+
}
|
|
12
|
+
return to;
|
|
13
|
+
};
|
|
14
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
15
|
+
|
|
16
|
+
// src/skills/types.ts
|
|
17
|
+
var types_exports = {};
|
|
18
|
+
module.exports = __toCommonJS(types_exports);
|
|
File without changes
|
package/dist/skills.d.mts
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Skill-
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* in-admin sidekick (ADR-0033) read the corpus through here, so the two doors
|
|
6
|
-
* stay fed from one source.
|
|
2
|
+
* Skill-manifest types, split from `skills.ts` so non-Node consumers (the
|
|
3
|
+
* remote-mcp worker types its `/dev` toolset against the wire shape) can
|
|
4
|
+
* import them without dragging the Node fs loader into their program.
|
|
7
5
|
*/
|
|
8
6
|
interface SkillManifestEntry {
|
|
9
7
|
name: string;
|
|
@@ -18,6 +16,15 @@ interface SkillManifest {
|
|
|
18
16
|
version: string;
|
|
19
17
|
skills: SkillManifestEntry[];
|
|
20
18
|
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Skill-corpus loader. Node-only (reads the shipped `skills/` directory from
|
|
22
|
+
* the installed package) — kept out of the isomorphic core and exposed via the
|
|
23
|
+
* `@cavuno/board/skills` subpath export. Both `npx @cavuno/board setup` and the
|
|
24
|
+
* in-admin sidekick (ADR-0033) read the corpus through here, so the two doors
|
|
25
|
+
* stay fed from one source.
|
|
26
|
+
*/
|
|
27
|
+
|
|
21
28
|
interface LoadedSkill extends SkillManifestEntry {
|
|
22
29
|
content: string;
|
|
23
30
|
}
|
|
@@ -26,13 +33,13 @@ interface SkillCorpus {
|
|
|
26
33
|
skills: LoadedSkill[];
|
|
27
34
|
}
|
|
28
35
|
/** 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;
|
|
36
|
+
declare function resolveFromPackageRoot(relativePath: string, baseDir?: string): string;
|
|
37
|
+
declare function loadSkillManifest(baseDir?: string): SkillManifest;
|
|
31
38
|
/**
|
|
32
39
|
* Load the full skill corpus — manifest metadata plus each skill's markdown
|
|
33
40
|
* `content`. The sidekick injects `content` into its agent context; an external
|
|
34
41
|
* Claude Code instead receives the files copied by the setup command.
|
|
35
42
|
*/
|
|
36
|
-
declare function loadSkillCorpus(): SkillCorpus;
|
|
43
|
+
declare function loadSkillCorpus(baseDir?: string): SkillCorpus;
|
|
37
44
|
|
|
38
45
|
export { type LoadedSkill, type SkillCorpus, type SkillManifest, type SkillManifestEntry, loadSkillCorpus, loadSkillManifest, resolveFromPackageRoot };
|
package/dist/skills.d.ts
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Skill-
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* in-admin sidekick (ADR-0033) read the corpus through here, so the two doors
|
|
6
|
-
* stay fed from one source.
|
|
2
|
+
* Skill-manifest types, split from `skills.ts` so non-Node consumers (the
|
|
3
|
+
* remote-mcp worker types its `/dev` toolset against the wire shape) can
|
|
4
|
+
* import them without dragging the Node fs loader into their program.
|
|
7
5
|
*/
|
|
8
6
|
interface SkillManifestEntry {
|
|
9
7
|
name: string;
|
|
@@ -18,6 +16,15 @@ interface SkillManifest {
|
|
|
18
16
|
version: string;
|
|
19
17
|
skills: SkillManifestEntry[];
|
|
20
18
|
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Skill-corpus loader. Node-only (reads the shipped `skills/` directory from
|
|
22
|
+
* the installed package) — kept out of the isomorphic core and exposed via the
|
|
23
|
+
* `@cavuno/board/skills` subpath export. Both `npx @cavuno/board setup` and the
|
|
24
|
+
* in-admin sidekick (ADR-0033) read the corpus through here, so the two doors
|
|
25
|
+
* stay fed from one source.
|
|
26
|
+
*/
|
|
27
|
+
|
|
21
28
|
interface LoadedSkill extends SkillManifestEntry {
|
|
22
29
|
content: string;
|
|
23
30
|
}
|
|
@@ -26,13 +33,13 @@ interface SkillCorpus {
|
|
|
26
33
|
skills: LoadedSkill[];
|
|
27
34
|
}
|
|
28
35
|
/** 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;
|
|
36
|
+
declare function resolveFromPackageRoot(relativePath: string, baseDir?: string): string;
|
|
37
|
+
declare function loadSkillManifest(baseDir?: string): SkillManifest;
|
|
31
38
|
/**
|
|
32
39
|
* Load the full skill corpus — manifest metadata plus each skill's markdown
|
|
33
40
|
* `content`. The sidekick injects `content` into its agent context; an external
|
|
34
41
|
* Claude Code instead receives the files copied by the setup command.
|
|
35
42
|
*/
|
|
36
|
-
declare function loadSkillCorpus(): SkillCorpus;
|
|
43
|
+
declare function loadSkillCorpus(baseDir?: string): SkillCorpus;
|
|
37
44
|
|
|
38
45
|
export { type LoadedSkill, type SkillCorpus, type SkillManifest, type SkillManifestEntry, loadSkillCorpus, loadSkillManifest, resolveFromPackageRoot };
|
package/dist/skills.js
CHANGED
|
@@ -37,20 +37,23 @@ var import_node_url = require("url");
|
|
|
37
37
|
function packageRoot() {
|
|
38
38
|
return (0, import_node_path.resolve)((0, import_node_path.dirname)((0, import_node_url.fileURLToPath)(importMetaUrl)), "..");
|
|
39
39
|
}
|
|
40
|
-
function resolveFromPackageRoot(relativePath) {
|
|
41
|
-
return (0, import_node_path.resolve)(packageRoot(), relativePath);
|
|
40
|
+
function resolveFromPackageRoot(relativePath, baseDir) {
|
|
41
|
+
return (0, import_node_path.resolve)(baseDir ?? packageRoot(), relativePath);
|
|
42
42
|
}
|
|
43
|
-
function loadSkillManifest() {
|
|
44
|
-
const manifestPath = resolveFromPackageRoot("skills/manifest.json");
|
|
43
|
+
function loadSkillManifest(baseDir) {
|
|
44
|
+
const manifestPath = resolveFromPackageRoot("skills/manifest.json", baseDir);
|
|
45
45
|
return JSON.parse((0, import_node_fs.readFileSync)(manifestPath, "utf8"));
|
|
46
46
|
}
|
|
47
|
-
function loadSkillCorpus() {
|
|
48
|
-
const manifest = loadSkillManifest();
|
|
47
|
+
function loadSkillCorpus(baseDir) {
|
|
48
|
+
const manifest = loadSkillManifest(baseDir);
|
|
49
49
|
return {
|
|
50
50
|
version: manifest.version,
|
|
51
51
|
skills: manifest.skills.map((skill) => ({
|
|
52
52
|
...skill,
|
|
53
|
-
content: (0, import_node_fs.readFileSync)(
|
|
53
|
+
content: (0, import_node_fs.readFileSync)(
|
|
54
|
+
resolveFromPackageRoot(skill.path, baseDir),
|
|
55
|
+
"utf8"
|
|
56
|
+
)
|
|
54
57
|
}))
|
|
55
58
|
};
|
|
56
59
|
}
|
package/dist/skills.mjs
CHANGED
|
@@ -5,20 +5,23 @@ import { fileURLToPath } from "url";
|
|
|
5
5
|
function packageRoot() {
|
|
6
6
|
return resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
7
7
|
}
|
|
8
|
-
function resolveFromPackageRoot(relativePath) {
|
|
9
|
-
return resolve(packageRoot(), relativePath);
|
|
8
|
+
function resolveFromPackageRoot(relativePath, baseDir) {
|
|
9
|
+
return resolve(baseDir ?? packageRoot(), relativePath);
|
|
10
10
|
}
|
|
11
|
-
function loadSkillManifest() {
|
|
12
|
-
const manifestPath = resolveFromPackageRoot("skills/manifest.json");
|
|
11
|
+
function loadSkillManifest(baseDir) {
|
|
12
|
+
const manifestPath = resolveFromPackageRoot("skills/manifest.json", baseDir);
|
|
13
13
|
return JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
14
14
|
}
|
|
15
|
-
function loadSkillCorpus() {
|
|
16
|
-
const manifest = loadSkillManifest();
|
|
15
|
+
function loadSkillCorpus(baseDir) {
|
|
16
|
+
const manifest = loadSkillManifest(baseDir);
|
|
17
17
|
return {
|
|
18
18
|
version: manifest.version,
|
|
19
19
|
skills: manifest.skills.map((skill) => ({
|
|
20
20
|
...skill,
|
|
21
|
-
content: readFileSync(
|
|
21
|
+
content: readFileSync(
|
|
22
|
+
resolveFromPackageRoot(skill.path, baseDir),
|
|
23
|
+
"utf8"
|
|
24
|
+
)
|
|
22
25
|
}))
|
|
23
26
|
};
|
|
24
27
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cavuno/board",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.29.0",
|
|
4
4
|
"description": "Typed isomorphic client for the Cavuno Board API",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -100,6 +100,16 @@
|
|
|
100
100
|
"default": "./dist/skills.js"
|
|
101
101
|
}
|
|
102
102
|
},
|
|
103
|
+
"./skills/types": {
|
|
104
|
+
"import": {
|
|
105
|
+
"types": "./dist/skills-types.d.mts",
|
|
106
|
+
"default": "./dist/skills-types.mjs"
|
|
107
|
+
},
|
|
108
|
+
"require": {
|
|
109
|
+
"types": "./dist/skills-types.d.ts",
|
|
110
|
+
"default": "./dist/skills-types.js"
|
|
111
|
+
}
|
|
112
|
+
},
|
|
103
113
|
"./skills/*": "./skills/*"
|
|
104
114
|
},
|
|
105
115
|
"files": [
|
|
@@ -15,6 +15,20 @@ auth, and SEO surfaces behave. Verify at runtime, in this order.
|
|
|
15
15
|
- After upgrading `@cavuno/board` or rotating the `pk_…` key.
|
|
16
16
|
- When any surface renders empty and you don't know which layer is wrong.
|
|
17
17
|
|
|
18
|
+
## 0 — Run `doctor` first (deterministic pass)
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
PUBLIC_CAVUNO_API_URL=... PUBLIC_CAVUNO_BOARD=pk_... \
|
|
22
|
+
npx @cavuno/board doctor --frontend http://localhost:3000
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Doctor codifies the static checks (env shape, API reachability) and the
|
|
26
|
+
read probes (home/jobs render, JobPosting JSON-LD, sitemap, robots) as
|
|
27
|
+
pass/fail/skip with a non-zero exit on failure — anything it SKIPS is
|
|
28
|
+
named in its summary and still needs the manual checks below. Use the
|
|
29
|
+
rest of this skill for the behavioral checks doctor does not automate
|
|
30
|
+
(auth flows, gating semantics, production build).
|
|
31
|
+
|
|
18
32
|
## 1 — Probe the API directly (before blaming app code)
|
|
19
33
|
|
|
20
34
|
Use the real env values the app reads (`PUBLIC_CAVUNO_API_URL`,
|
package/skills/manifest.json
CHANGED