contentbit 0.1.0 → 0.2.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/README.md +4 -0
- package/dist/bin.js +1064 -88
- package/dist/commands/agents.d.ts +11 -0
- package/dist/commands/agents.d.ts.map +1 -0
- package/dist/commands/agents.js +197 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +411 -39
- package/dist/commands/stats.d.ts +3 -0
- package/dist/commands/stats.d.ts.map +1 -0
- package/dist/commands/stats.js +49 -0
- package/dist/commands/validate.d.ts.map +1 -1
- package/dist/commands/validate.js +5 -2
- package/dist/load-registry.d.ts.map +1 -1
- package/dist/load-registry.js +10 -1
- package/dist/run.d.ts +1 -1
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +1062 -86
- package/package.json +7 -4
package/dist/bin.js
CHANGED
|
@@ -320,6 +320,323 @@ var init_parser = __esm({
|
|
|
320
320
|
}
|
|
321
321
|
});
|
|
322
322
|
|
|
323
|
+
// ../core/dist/frontmatter.js
|
|
324
|
+
function stripFrontmatter(source) {
|
|
325
|
+
const match = FM_RE.exec(source);
|
|
326
|
+
if (!match)
|
|
327
|
+
return source;
|
|
328
|
+
return match[0].replace(/[^\n]+/g, "") + source.slice(match[0].length);
|
|
329
|
+
}
|
|
330
|
+
function extractFrontmatter(source) {
|
|
331
|
+
const match = source.match(FM_RE);
|
|
332
|
+
if (!match)
|
|
333
|
+
return null;
|
|
334
|
+
const blockLines = match[0].replace(/\r?\n$/, "").split(/\r?\n/);
|
|
335
|
+
const inner = blockLines.slice(1, -1);
|
|
336
|
+
const { data, keys } = parseYamlSubset(inner);
|
|
337
|
+
return { raw: inner.join("\n"), data, keys, lines: { start: 1, end: blockLines.length } };
|
|
338
|
+
}
|
|
339
|
+
function parseYamlSubset(lines) {
|
|
340
|
+
const data = {};
|
|
341
|
+
const keys = [];
|
|
342
|
+
let i = 0;
|
|
343
|
+
while (i < lines.length) {
|
|
344
|
+
const line = lines[i];
|
|
345
|
+
const trimmed = line.trim();
|
|
346
|
+
if (trimmed === "" || trimmed.startsWith("#") || /^[ \t]/.test(line)) {
|
|
347
|
+
i++;
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
const m = line.match(KEY_RE2);
|
|
351
|
+
if (!m) {
|
|
352
|
+
i++;
|
|
353
|
+
continue;
|
|
354
|
+
}
|
|
355
|
+
const [, key, rawValue] = m;
|
|
356
|
+
const value = rawValue.trim();
|
|
357
|
+
i++;
|
|
358
|
+
const indented = [];
|
|
359
|
+
while (i < lines.length && /^[ \t]/.test(lines[i]) && lines[i].trim() !== "") {
|
|
360
|
+
indented.push(lines[i]);
|
|
361
|
+
i++;
|
|
362
|
+
}
|
|
363
|
+
keys.push(key);
|
|
364
|
+
data[key] = parseValue(value, indented);
|
|
365
|
+
}
|
|
366
|
+
return { data, keys };
|
|
367
|
+
}
|
|
368
|
+
function parseValue(value, indented) {
|
|
369
|
+
if (/^[|>][+-]?$/.test(value))
|
|
370
|
+
return dedent(indented).join("\n");
|
|
371
|
+
if (value === "") {
|
|
372
|
+
if (indented.length === 0)
|
|
373
|
+
return null;
|
|
374
|
+
const items = dedent(indented);
|
|
375
|
+
if (items.every((l) => l.startsWith("- ")))
|
|
376
|
+
return items.map((l) => parseScalar(l.slice(2).trim()));
|
|
377
|
+
return items.join("\n");
|
|
378
|
+
}
|
|
379
|
+
return parseScalar(value);
|
|
380
|
+
}
|
|
381
|
+
function dedent(lines) {
|
|
382
|
+
const indent = Math.min(...lines.map((l) => l.match(/^[ \t]*/)[0].length));
|
|
383
|
+
return lines.map((l) => l.slice(indent));
|
|
384
|
+
}
|
|
385
|
+
function parseScalar(value) {
|
|
386
|
+
if (value === "" || value === "null" || value === "~")
|
|
387
|
+
return null;
|
|
388
|
+
if (value === "true")
|
|
389
|
+
return true;
|
|
390
|
+
if (value === "false")
|
|
391
|
+
return false;
|
|
392
|
+
if (/^[+-]?\d+$/.test(value))
|
|
393
|
+
return Number.parseInt(value, 10);
|
|
394
|
+
if (/^[+-]?\d*\.\d+$/.test(value))
|
|
395
|
+
return Number.parseFloat(value);
|
|
396
|
+
if (value.startsWith('"') && value.endsWith('"') && value.length >= 2) {
|
|
397
|
+
try {
|
|
398
|
+
return JSON.parse(value);
|
|
399
|
+
} catch {
|
|
400
|
+
return value.slice(1, -1);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
if (value.startsWith("'") && value.endsWith("'") && value.length >= 2) {
|
|
404
|
+
return value.slice(1, -1).replace(/''/g, "'");
|
|
405
|
+
}
|
|
406
|
+
if (value.startsWith("[") && value.endsWith("]")) {
|
|
407
|
+
const inner = value.slice(1, -1).trim();
|
|
408
|
+
if (inner === "")
|
|
409
|
+
return [];
|
|
410
|
+
return splitInlineItems(inner).map((item) => parseScalar(item.trim()));
|
|
411
|
+
}
|
|
412
|
+
return value;
|
|
413
|
+
}
|
|
414
|
+
function splitInlineItems(inner) {
|
|
415
|
+
const items = [];
|
|
416
|
+
let current = "";
|
|
417
|
+
let quote = null;
|
|
418
|
+
for (const ch of inner) {
|
|
419
|
+
if (quote) {
|
|
420
|
+
current += ch;
|
|
421
|
+
if (ch === quote)
|
|
422
|
+
quote = null;
|
|
423
|
+
} else if (ch === '"' || ch === "'") {
|
|
424
|
+
current += ch;
|
|
425
|
+
quote = ch;
|
|
426
|
+
} else if (ch === ",") {
|
|
427
|
+
items.push(current);
|
|
428
|
+
current = "";
|
|
429
|
+
} else {
|
|
430
|
+
current += ch;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
items.push(current);
|
|
434
|
+
return items;
|
|
435
|
+
}
|
|
436
|
+
var FM_RE, KEY_RE2;
|
|
437
|
+
var init_frontmatter = __esm({
|
|
438
|
+
"../core/dist/frontmatter.js"() {
|
|
439
|
+
"use strict";
|
|
440
|
+
FM_RE = /^---[ \t]*\r?\n(?:[\s\S]*?\r?\n)?---[ \t]*(?:\r?\n|$)/;
|
|
441
|
+
KEY_RE2 = /^([A-Za-z0-9_.-]+):(.*)$/;
|
|
442
|
+
}
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
// ../core/dist/analyze.js
|
|
446
|
+
function utf8Length(source) {
|
|
447
|
+
let bytes = 0;
|
|
448
|
+
for (const ch of source) {
|
|
449
|
+
const cp = ch.codePointAt(0);
|
|
450
|
+
bytes += cp <= 127 ? 1 : cp <= 2047 ? 2 : cp <= 65535 ? 3 : 4;
|
|
451
|
+
}
|
|
452
|
+
return bytes;
|
|
453
|
+
}
|
|
454
|
+
function analyzeDocument(source, options = {}) {
|
|
455
|
+
const frontmatter = extractFrontmatter(source);
|
|
456
|
+
const { document } = parseDocument(stripFrontmatter(source));
|
|
457
|
+
const outline = [];
|
|
458
|
+
const blocks = { total: 0, byName: {}, maxDepth: 0, instances: [] };
|
|
459
|
+
const links = {
|
|
460
|
+
total: 0,
|
|
461
|
+
external: 0,
|
|
462
|
+
internal: 0,
|
|
463
|
+
domains: [],
|
|
464
|
+
items: []
|
|
465
|
+
};
|
|
466
|
+
const images = { total: 0, missingAlt: 0 };
|
|
467
|
+
const code2 = { fences: 0, languages: [], inlineSpans: 0 };
|
|
468
|
+
const structure = { listItems: 0, tables: 0, blockquotes: 0 };
|
|
469
|
+
const languages = /* @__PURE__ */ new Set();
|
|
470
|
+
const domains = /* @__PURE__ */ new Set();
|
|
471
|
+
let words = 0;
|
|
472
|
+
function addWords(count) {
|
|
473
|
+
words += count;
|
|
474
|
+
const section = outline[outline.length - 1];
|
|
475
|
+
if (section)
|
|
476
|
+
section.words += count;
|
|
477
|
+
}
|
|
478
|
+
function recordLink(url2, text, line) {
|
|
479
|
+
const external = EXTERNAL_URL_RE.test(url2);
|
|
480
|
+
links.total++;
|
|
481
|
+
if (external) {
|
|
482
|
+
links.external++;
|
|
483
|
+
const host = url2.match(/^(?:https?:)?\/\/(?:[^/?#@]*@)?([^/?#:]+)/i);
|
|
484
|
+
if (host)
|
|
485
|
+
domains.add(host[1].toLowerCase());
|
|
486
|
+
} else {
|
|
487
|
+
links.internal++;
|
|
488
|
+
}
|
|
489
|
+
links.items.push({ url: url2, text, line, external });
|
|
490
|
+
}
|
|
491
|
+
function inlineToProse(text, line) {
|
|
492
|
+
return text.replace(CODE_SPAN_RE, () => {
|
|
493
|
+
code2.inlineSpans++;
|
|
494
|
+
return " ";
|
|
495
|
+
}).replace(IMAGE_RE, (_, alt) => {
|
|
496
|
+
images.total++;
|
|
497
|
+
if (alt.trim() === "")
|
|
498
|
+
images.missingAlt++;
|
|
499
|
+
return " ";
|
|
500
|
+
}).replace(LINK_RE, (_, label, url2) => {
|
|
501
|
+
recordLink(url2, label, line);
|
|
502
|
+
return label;
|
|
503
|
+
}).replace(AUTOLINK_RE, (_, url2) => {
|
|
504
|
+
recordLink(url2, url2, line);
|
|
505
|
+
return " ";
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
function countWords(text) {
|
|
509
|
+
return text.split(/\s+/).filter((token) => /[\p{L}\p{N}]/u.test(token)).length;
|
|
510
|
+
}
|
|
511
|
+
function scanMarkdown(value, startLine) {
|
|
512
|
+
let fence = null;
|
|
513
|
+
let inBlockquote = false;
|
|
514
|
+
let prevLineHasPipe = false;
|
|
515
|
+
const lines = value.split("\n");
|
|
516
|
+
for (let i = 0; i < lines.length; i++) {
|
|
517
|
+
const line = lines[i];
|
|
518
|
+
const lineNo = startLine + i;
|
|
519
|
+
const trimmed = line.trim();
|
|
520
|
+
const fenceMatch = trimmed.match(CODE_FENCE_RE2);
|
|
521
|
+
if (fence !== null) {
|
|
522
|
+
if (fenceMatch && fenceMatch[1][0] === fence[0] && fenceMatch[1].length >= fence.length) {
|
|
523
|
+
fence = null;
|
|
524
|
+
}
|
|
525
|
+
continue;
|
|
526
|
+
}
|
|
527
|
+
if (fenceMatch) {
|
|
528
|
+
fence = fenceMatch[1];
|
|
529
|
+
code2.fences++;
|
|
530
|
+
const lang = fenceMatch[2].trim().split(/\s+/)[0];
|
|
531
|
+
if (lang)
|
|
532
|
+
languages.add(lang);
|
|
533
|
+
inBlockquote = false;
|
|
534
|
+
prevLineHasPipe = false;
|
|
535
|
+
continue;
|
|
536
|
+
}
|
|
537
|
+
if (trimmed === "") {
|
|
538
|
+
inBlockquote = false;
|
|
539
|
+
prevLineHasPipe = false;
|
|
540
|
+
continue;
|
|
541
|
+
}
|
|
542
|
+
const heading = trimmed.match(HEADING_RE);
|
|
543
|
+
if (heading) {
|
|
544
|
+
const raw = heading[2].replace(/\s+#+\s*$/, "");
|
|
545
|
+
const text = inlineToProse(raw, lineNo).replace(/\s+/g, " ").trim();
|
|
546
|
+
outline.push({ level: heading[1].length, text, line: lineNo, words: 0 });
|
|
547
|
+
addWords(countWords(text));
|
|
548
|
+
inBlockquote = false;
|
|
549
|
+
prevLineHasPipe = false;
|
|
550
|
+
continue;
|
|
551
|
+
}
|
|
552
|
+
let content = trimmed;
|
|
553
|
+
if (content.startsWith(">")) {
|
|
554
|
+
if (!inBlockquote) {
|
|
555
|
+
structure.blockquotes++;
|
|
556
|
+
inBlockquote = true;
|
|
557
|
+
}
|
|
558
|
+
content = content.replace(/^(>\s*)+/, "");
|
|
559
|
+
} else {
|
|
560
|
+
inBlockquote = false;
|
|
561
|
+
}
|
|
562
|
+
if (LIST_ITEM_RE.test(content)) {
|
|
563
|
+
structure.listItems++;
|
|
564
|
+
content = content.replace(LIST_ITEM_RE, "");
|
|
565
|
+
}
|
|
566
|
+
if (content.includes("|")) {
|
|
567
|
+
if (TABLE_SEPARATOR_RE.test(content) && content.includes("-") && prevLineHasPipe) {
|
|
568
|
+
structure.tables++;
|
|
569
|
+
}
|
|
570
|
+
prevLineHasPipe = true;
|
|
571
|
+
content = inlineToProse(content, lineNo).replaceAll("|", " ");
|
|
572
|
+
} else {
|
|
573
|
+
prevLineHasPipe = false;
|
|
574
|
+
content = inlineToProse(content, lineNo);
|
|
575
|
+
}
|
|
576
|
+
addWords(countWords(content));
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
function walk(nodes, depth) {
|
|
580
|
+
for (const node of nodes) {
|
|
581
|
+
if (node.type === "block") {
|
|
582
|
+
blocks.total++;
|
|
583
|
+
blocks.byName[node.name] = (blocks.byName[node.name] ?? 0) + 1;
|
|
584
|
+
blocks.maxDepth = Math.max(blocks.maxDepth, depth);
|
|
585
|
+
blocks.instances.push({ name: node.name, line: node.openPosition.start.line, depth });
|
|
586
|
+
walk(node.children, depth + 1);
|
|
587
|
+
} else {
|
|
588
|
+
scanMarkdown(node.value, node.position.start.line);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
walk(document.children, 1);
|
|
593
|
+
code2.languages = [...languages];
|
|
594
|
+
links.domains = [...domains].sort();
|
|
595
|
+
const sourceLines = source === "" ? 0 : source.split("\n").length - (source.endsWith("\n") ? 1 : 0);
|
|
596
|
+
return {
|
|
597
|
+
file: {
|
|
598
|
+
path: options.path ?? null,
|
|
599
|
+
bytes: utf8Length(source),
|
|
600
|
+
lines: sourceLines
|
|
601
|
+
},
|
|
602
|
+
frontmatter: frontmatter ? {
|
|
603
|
+
present: true,
|
|
604
|
+
keys: frontmatter.keys,
|
|
605
|
+
data: frontmatter.data,
|
|
606
|
+
lines: frontmatter.lines
|
|
607
|
+
} : { present: false, keys: [], data: {}, lines: null },
|
|
608
|
+
length: {
|
|
609
|
+
words,
|
|
610
|
+
characters: source.length,
|
|
611
|
+
readingMinutes: Math.ceil(words / 200),
|
|
612
|
+
approxTokens: Math.ceil(source.length / 4)
|
|
613
|
+
},
|
|
614
|
+
outline,
|
|
615
|
+
blocks,
|
|
616
|
+
links,
|
|
617
|
+
images,
|
|
618
|
+
code: code2,
|
|
619
|
+
structure
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
var CODE_FENCE_RE2, HEADING_RE, LIST_ITEM_RE, TABLE_SEPARATOR_RE, CODE_SPAN_RE, IMAGE_RE, LINK_RE, AUTOLINK_RE, EXTERNAL_URL_RE;
|
|
623
|
+
var init_analyze = __esm({
|
|
624
|
+
"../core/dist/analyze.js"() {
|
|
625
|
+
"use strict";
|
|
626
|
+
init_frontmatter();
|
|
627
|
+
init_parser();
|
|
628
|
+
CODE_FENCE_RE2 = /^(`{3,}|~{3,})(.*)$/;
|
|
629
|
+
HEADING_RE = /^(#{1,6})\s+(.*)$/;
|
|
630
|
+
LIST_ITEM_RE = /^\s*(?:[-*+]|\d+[.)])\s+/;
|
|
631
|
+
TABLE_SEPARATOR_RE = /^\|?[\s:|-]+$/;
|
|
632
|
+
CODE_SPAN_RE = /`[^`]+`/g;
|
|
633
|
+
IMAGE_RE = /!\[([^\]]*)\]\(([^)]*)\)/g;
|
|
634
|
+
LINK_RE = /\[([^\]]+)\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g;
|
|
635
|
+
AUTOLINK_RE = /<(https?:\/\/[^>\s]+)>/g;
|
|
636
|
+
EXTERNAL_URL_RE = /^(?:https?:)?\/\//i;
|
|
637
|
+
}
|
|
638
|
+
});
|
|
639
|
+
|
|
323
640
|
// ../core/dist/position.js
|
|
324
641
|
function bodyLineRange(node, bodyLineIndex) {
|
|
325
642
|
const bodyLines = node.body.split("\n");
|
|
@@ -340,6 +657,45 @@ var init_position = __esm({
|
|
|
340
657
|
});
|
|
341
658
|
|
|
342
659
|
// ../core/dist/authoring.js
|
|
660
|
+
function typeLabel(def) {
|
|
661
|
+
switch (def.type) {
|
|
662
|
+
case "enum":
|
|
663
|
+
return `one of ${Object.values(def.entries ?? {}).join("|")}`;
|
|
664
|
+
case "literal":
|
|
665
|
+
return `one of ${(def.values ?? []).map(String).join("|")}`;
|
|
666
|
+
case "union":
|
|
667
|
+
return `one of ${(def.options ?? []).map((o) => typeLabel(o.def).replace(/^one of /, "")).join("|")}`;
|
|
668
|
+
default:
|
|
669
|
+
return def.type;
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
function describeProps(schema) {
|
|
673
|
+
const root = schema.def;
|
|
674
|
+
if (root.type !== "object" || !root.shape)
|
|
675
|
+
return [];
|
|
676
|
+
const lines = [];
|
|
677
|
+
for (const [name, field] of Object.entries(root.shape)) {
|
|
678
|
+
let current = field;
|
|
679
|
+
let optional2 = false;
|
|
680
|
+
let defaultValue;
|
|
681
|
+
let description = current.description;
|
|
682
|
+
while (["optional", "default", "nullable"].includes(current.def.type)) {
|
|
683
|
+
optional2 = true;
|
|
684
|
+
if (current.def.type === "default") {
|
|
685
|
+
const dv = current.def.defaultValue;
|
|
686
|
+
defaultValue = typeof dv === "function" ? dv() : dv;
|
|
687
|
+
}
|
|
688
|
+
if (!current.def.innerType)
|
|
689
|
+
break;
|
|
690
|
+
current = current.def.innerType;
|
|
691
|
+
description ??= current.description;
|
|
692
|
+
}
|
|
693
|
+
const presence = defaultValue !== void 0 ? `(optional, default: ${String(defaultValue)})` : optional2 ? "(optional)" : "(required)";
|
|
694
|
+
const suffix = description ? ` \u2014 ${description}` : "";
|
|
695
|
+
lines.push(`- ${name}: ${typeLabel(current.def)} ${presence}${suffix}`);
|
|
696
|
+
}
|
|
697
|
+
return lines;
|
|
698
|
+
}
|
|
343
699
|
function generateAuthoringGuide(defs, opts = {}) {
|
|
344
700
|
const includeExamples = opts.includeExamples ?? true;
|
|
345
701
|
const includeAvoid = opts.includeAvoidRules ?? true;
|
|
@@ -349,6 +705,11 @@ function generateAuthoringGuide(defs, opts = {}) {
|
|
|
349
705
|
for (const def of defs) {
|
|
350
706
|
const lines = [`## ${def.name}`, ""];
|
|
351
707
|
lines.push(def.childOnly ? `${def.description} (child block \u2014 only inside a parent that allows it)` : def.description);
|
|
708
|
+
if (def.props) {
|
|
709
|
+
const props = describeProps(def.props);
|
|
710
|
+
if (props.length > 0)
|
|
711
|
+
lines.push("", "Props:", ...props);
|
|
712
|
+
}
|
|
352
713
|
lines.push("", `Content: ${def.content.describe()}`);
|
|
353
714
|
if (def.authoring.useWhen.length > 0) {
|
|
354
715
|
lines.push("", "Use when:", ...def.authoring.useWhen.map((u) => `- ${u}`));
|
|
@@ -742,6 +1103,8 @@ var init_dist = __esm({
|
|
|
742
1103
|
"use strict";
|
|
743
1104
|
init_diagnostics();
|
|
744
1105
|
init_parser();
|
|
1106
|
+
init_frontmatter();
|
|
1107
|
+
init_analyze();
|
|
745
1108
|
init_registry();
|
|
746
1109
|
init_content_models();
|
|
747
1110
|
init_validate();
|
|
@@ -6655,13 +7018,13 @@ var init_he = __esm({
|
|
|
6655
7018
|
// no unit
|
|
6656
7019
|
};
|
|
6657
7020
|
const typeEntry = (t) => t ? TypeNames[t] : void 0;
|
|
6658
|
-
const
|
|
7021
|
+
const typeLabel2 = (t) => {
|
|
6659
7022
|
const e = typeEntry(t);
|
|
6660
7023
|
if (e)
|
|
6661
7024
|
return e.label;
|
|
6662
7025
|
return t ?? TypeNames.unknown.label;
|
|
6663
7026
|
};
|
|
6664
|
-
const withDefinite = (t) => `\u05D4${
|
|
7027
|
+
const withDefinite = (t) => `\u05D4${typeLabel2(t)}`;
|
|
6665
7028
|
const verbFor = (t) => {
|
|
6666
7029
|
const e = typeEntry(t);
|
|
6667
7030
|
const gender = e?.gender ?? "m";
|
|
@@ -6711,7 +7074,7 @@ var init_he = __esm({
|
|
|
6711
7074
|
switch (issue2.code) {
|
|
6712
7075
|
case "invalid_type": {
|
|
6713
7076
|
const expectedKey = issue2.expected;
|
|
6714
|
-
const expected = TypeDictionary[expectedKey ?? ""] ??
|
|
7077
|
+
const expected = TypeDictionary[expectedKey ?? ""] ?? typeLabel2(expectedKey);
|
|
6715
7078
|
const receivedType = parsedType(issue2.input);
|
|
6716
7079
|
const received = TypeDictionary[receivedType] ?? TypeNames[receivedType]?.label ?? receivedType;
|
|
6717
7080
|
if (/^[A-Z]/.test(issue2.expected)) {
|
|
@@ -16154,7 +16517,17 @@ import { pathToFileURL } from "node:url";
|
|
|
16154
16517
|
async function loadRegistry(registryPath) {
|
|
16155
16518
|
const registry2 = createBlockRegistry().use(genericBlocks());
|
|
16156
16519
|
if (registryPath) {
|
|
16157
|
-
|
|
16520
|
+
let mod;
|
|
16521
|
+
try {
|
|
16522
|
+
mod = await import(pathToFileURL(registryPath).href);
|
|
16523
|
+
} catch (err) {
|
|
16524
|
+
if (err instanceof Error && "code" in err && err.code === "ERR_UNKNOWN_FILE_EXTENSION") {
|
|
16525
|
+
throw new Error(
|
|
16526
|
+
`Importing a TypeScript registry needs Node 22.18+ (native type stripping): ${registryPath}`
|
|
16527
|
+
);
|
|
16528
|
+
}
|
|
16529
|
+
throw err;
|
|
16530
|
+
}
|
|
16158
16531
|
if (!Array.isArray(mod.default)) {
|
|
16159
16532
|
throw new Error(
|
|
16160
16533
|
`--registry module must default-export an array of block definitions: ${registryPath}`
|
|
@@ -16172,16 +16545,372 @@ var init_load_registry = __esm({
|
|
|
16172
16545
|
}
|
|
16173
16546
|
});
|
|
16174
16547
|
|
|
16548
|
+
// src/commands/agents.ts
|
|
16549
|
+
var agents_exports = {};
|
|
16550
|
+
__export(agents_exports, {
|
|
16551
|
+
agentsCommand: () => agentsCommand,
|
|
16552
|
+
installAgentIntegration: () => installAgentIntegration
|
|
16553
|
+
});
|
|
16554
|
+
import { existsSync } from "node:fs";
|
|
16555
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
16556
|
+
import { join } from "node:path";
|
|
16557
|
+
import { parseArgs } from "node:util";
|
|
16558
|
+
function upsertBlock(existing) {
|
|
16559
|
+
const start = existing.indexOf(START);
|
|
16560
|
+
const end = existing.indexOf(END);
|
|
16561
|
+
if (start !== -1 && end !== -1) {
|
|
16562
|
+
return existing.slice(0, start) + AGENTS_MD_BLOCK + existing.slice(end + END.length);
|
|
16563
|
+
}
|
|
16564
|
+
if (existing.trim() === "") return `${AGENTS_MD_BLOCK}
|
|
16565
|
+
`;
|
|
16566
|
+
return `${existing.replace(/\n*$/, "\n\n")}${AGENTS_MD_BLOCK}
|
|
16567
|
+
`;
|
|
16568
|
+
}
|
|
16569
|
+
async function installAgentIntegration(cwd, options, io) {
|
|
16570
|
+
const claude = options.claude ?? existsSync(join(cwd, ".claude"));
|
|
16571
|
+
const agentsMd = options.agentsMd ?? true;
|
|
16572
|
+
if (agentsMd) {
|
|
16573
|
+
const path = join(cwd, "AGENTS.md");
|
|
16574
|
+
let existing = "";
|
|
16575
|
+
try {
|
|
16576
|
+
existing = await readFile(path, "utf8");
|
|
16577
|
+
} catch {
|
|
16578
|
+
}
|
|
16579
|
+
const created = existing === "";
|
|
16580
|
+
await writeFile(path, upsertBlock(existing), "utf8");
|
|
16581
|
+
io.stdout(`${created ? "created" : "updated"}: AGENTS.md (contentbit block)`);
|
|
16582
|
+
}
|
|
16583
|
+
if (claude) {
|
|
16584
|
+
const skills = [
|
|
16585
|
+
["contentbit-author", AUTHOR_SKILL],
|
|
16586
|
+
["contentbit-audit", AUDIT_SKILL]
|
|
16587
|
+
];
|
|
16588
|
+
for (const [name, content] of skills) {
|
|
16589
|
+
const dir = join(cwd, ".claude/skills", name);
|
|
16590
|
+
await mkdir(dir, { recursive: true });
|
|
16591
|
+
await writeFile(join(dir, "SKILL.md"), content, "utf8");
|
|
16592
|
+
io.stdout(`installed: .claude/skills/${name}/SKILL.md`);
|
|
16593
|
+
}
|
|
16594
|
+
}
|
|
16595
|
+
}
|
|
16596
|
+
async function agentsCommand(args, io) {
|
|
16597
|
+
const { values } = parseArgs({
|
|
16598
|
+
args,
|
|
16599
|
+
options: {
|
|
16600
|
+
claude: { type: "boolean", default: false },
|
|
16601
|
+
"no-agents-md": { type: "boolean", default: false },
|
|
16602
|
+
cwd: { type: "string", default: process.cwd() }
|
|
16603
|
+
}
|
|
16604
|
+
});
|
|
16605
|
+
await installAgentIntegration(
|
|
16606
|
+
values.cwd,
|
|
16607
|
+
{
|
|
16608
|
+
claude: values.claude || void 0,
|
|
16609
|
+
// false means "detect", not "skip"
|
|
16610
|
+
agentsMd: !values["no-agents-md"]
|
|
16611
|
+
},
|
|
16612
|
+
io
|
|
16613
|
+
);
|
|
16614
|
+
return 0;
|
|
16615
|
+
}
|
|
16616
|
+
var TEMPLATE_VERSION, AUTHOR_SKILL, AUDIT_SKILL, AGENTS_MD_BLOCK, START, END;
|
|
16617
|
+
var init_agents = __esm({
|
|
16618
|
+
"src/commands/agents.ts"() {
|
|
16619
|
+
"use strict";
|
|
16620
|
+
TEMPLATE_VERSION = 1;
|
|
16621
|
+
AUTHOR_SKILL = `---
|
|
16622
|
+
name: contentbit-author
|
|
16623
|
+
description: |
|
|
16624
|
+
Write or edit contentbit Markdown content (directive blocks like :::callout).
|
|
16625
|
+
Use when asked to create or modify content documents in a project that uses
|
|
16626
|
+
contentbit \u2014 blog posts, docs pages, changelogs, any Markdown covered by
|
|
16627
|
+
\`contentbit validate\`.
|
|
16628
|
+
version: ${TEMPLATE_VERSION}
|
|
16629
|
+
---
|
|
16630
|
+
|
|
16631
|
+
# Writing contentbit content
|
|
16632
|
+
|
|
16633
|
+
contentbit documents are plain Markdown plus directive blocks
|
|
16634
|
+
(\`:::name{props} ... :::\`). Every block has a schema. Never guess block names,
|
|
16635
|
+
props, or body shapes \u2014 fetch the live guide from the project's registry first.
|
|
16636
|
+
|
|
16637
|
+
## Find the project conventions
|
|
16638
|
+
|
|
16639
|
+
Check \`package.json\` for a \`content:check\` script. It holds the canonical
|
|
16640
|
+
validate invocation for this project: the content glob and, if present, the
|
|
16641
|
+
\`--registry <path>\` flag pointing at custom block definitions. Reuse both
|
|
16642
|
+
below. No script? Default to \`content/**/*.md\` with no \`--registry\` flag.
|
|
16643
|
+
|
|
16644
|
+
## The loop
|
|
16645
|
+
|
|
16646
|
+
1. **Fetch the authoring guide** (always \u2014 it covers this project's custom blocks):
|
|
16647
|
+
|
|
16648
|
+
\`\`\`sh
|
|
16649
|
+
contentbit instructions --audience llm [--registry <path from content:check>]
|
|
16650
|
+
\`\`\`
|
|
16651
|
+
|
|
16652
|
+
Read it before writing. It documents every available block: props, body
|
|
16653
|
+
shape, and when to use or avoid it.
|
|
16654
|
+
|
|
16655
|
+
2. **Write the document.** Plain Markdown everywhere; blocks only where the
|
|
16656
|
+
guide's use-when guidance fits. Keep frontmatter consistent with sibling
|
|
16657
|
+
documents in the same folder.
|
|
16658
|
+
|
|
16659
|
+
3. **Validate and fix until clean:**
|
|
16660
|
+
|
|
16661
|
+
\`\`\`sh
|
|
16662
|
+
contentbit validate <file> [--registry <path>]
|
|
16663
|
+
\`\`\`
|
|
16664
|
+
|
|
16665
|
+
Diagnostics print to stderr as \`file:line:col severity CODE message\`, often
|
|
16666
|
+
with a \`hint:\` line suggesting the fix. Exit 0 means clean; exit 1 means
|
|
16667
|
+
errors remain. Fix every diagnostic and re-run. Never finish with a failing
|
|
16668
|
+
validate.
|
|
16669
|
+
|
|
16670
|
+
## Failure modes
|
|
16671
|
+
|
|
16672
|
+
- \`contentbit\` not found or no registry resolvable: the project is not set up.
|
|
16673
|
+
Say so and suggest \`npx contentbit@latest init\` \u2014 do not invent block syntax.
|
|
16674
|
+
- A block you want does not exist: use plain Markdown, or ask whether to define
|
|
16675
|
+
a custom block in the registry. Never emit an unregistered block name.
|
|
16676
|
+
`;
|
|
16677
|
+
AUDIT_SKILL = `---
|
|
16678
|
+
name: contentbit-audit
|
|
16679
|
+
description: |
|
|
16680
|
+
Audit contentbit Markdown content health using document stats. Use when asked
|
|
16681
|
+
to audit, review, or find improvements across content \u2014 thin pages, missing
|
|
16682
|
+
structure, validation issues \u2014 in a project that uses contentbit.
|
|
16683
|
+
version: ${TEMPLATE_VERSION}
|
|
16684
|
+
---
|
|
16685
|
+
|
|
16686
|
+
# Auditing contentbit content
|
|
16687
|
+
|
|
16688
|
+
\`contentbit stats\` analyzes documents and prints JSON to stdout. It is a read
|
|
16689
|
+
tool: it always exits 0, even when documents have validation errors.
|
|
16690
|
+
|
|
16691
|
+
## Gather
|
|
16692
|
+
|
|
16693
|
+
Check \`package.json\` for the \`content:check\` script to find this project's
|
|
16694
|
+
content glob and \`--registry\` flag, then:
|
|
16695
|
+
|
|
16696
|
+
\`\`\`sh
|
|
16697
|
+
contentbit stats "content/**/*.md" [--registry <path>]
|
|
16698
|
+
\`\`\`
|
|
16699
|
+
|
|
16700
|
+
One matched file prints a single stats object; multiple files print an array.
|
|
16701
|
+
Each entry includes the file path, frontmatter data, a heading \`outline\` with
|
|
16702
|
+
per-section word counts, \`blocks.byName\` usage counts, \`links.domains\`, and
|
|
16703
|
+
a \`validation\` summary (\`errors\`/\`warnings\`).
|
|
16704
|
+
|
|
16705
|
+
## Interpret
|
|
16706
|
+
|
|
16707
|
+
Prioritize findings in this order:
|
|
16708
|
+
|
|
16709
|
+
1. **Validation errors and warnings** \u2014 broken content ships broken pages.
|
|
16710
|
+
2. **Thin documents** \u2014 outline sections with very low word counts.
|
|
16711
|
+
3. **Block-less documents** \u2014 \`blocks.byName\` empty where sibling documents
|
|
16712
|
+
use blocks; structure (steps, callouts, comparisons, faq) may be missing.
|
|
16713
|
+
4. **Missing or inconsistent frontmatter** compared to sibling documents.
|
|
16714
|
+
5. **Structural imbalance** \u2014 skipped heading levels, single-section walls of text.
|
|
16715
|
+
|
|
16716
|
+
## Report
|
|
16717
|
+
|
|
16718
|
+
Report findings per file with concrete suggestions, ordered by priority. Do not
|
|
16719
|
+
edit files during the audit. To fix a finding, follow the contentbit-author
|
|
16720
|
+
skill (fetch the guide, edit, validate until clean) \u2014 offer that as a follow-up.
|
|
16721
|
+
`;
|
|
16722
|
+
AGENTS_MD_BLOCK = `<!-- contentbit:start -->
|
|
16723
|
+
|
|
16724
|
+
## contentbit content (generated \u2014 edits inside this block are overwritten)
|
|
16725
|
+
|
|
16726
|
+
This project validates Markdown content with contentbit. Documents are plain
|
|
16727
|
+
Markdown plus directive blocks (\`:::name{props} ... :::\`), each with a schema.
|
|
16728
|
+
The \`content:check\` script in package.json holds the canonical validate
|
|
16729
|
+
command \u2014 the content glob and the \`--registry\` flag \u2014 reuse its arguments.
|
|
16730
|
+
|
|
16731
|
+
When writing or editing content:
|
|
16732
|
+
|
|
16733
|
+
1. Fetch the live authoring guide first \u2014 never guess block syntax:
|
|
16734
|
+
\`contentbit instructions --audience llm [--registry <path>]\`
|
|
16735
|
+
2. Write plain Markdown; use blocks where the guide's use-when guidance fits.
|
|
16736
|
+
3. Validate until clean (exit 0): \`contentbit validate <file> [--registry <path>]\`.
|
|
16737
|
+
Diagnostics print as \`file:line:col severity CODE message\` with fix hints.
|
|
16738
|
+
|
|
16739
|
+
When auditing content health:
|
|
16740
|
+
|
|
16741
|
+
- \`contentbit stats "content/**/*.md" [--registry <path>]\` prints JSON stats
|
|
16742
|
+
and always exits 0: outline word counts, block usage, link domains, and
|
|
16743
|
+
validation error/warning counts. Flag validation issues, thin documents, and
|
|
16744
|
+
block-less pages first.
|
|
16745
|
+
|
|
16746
|
+
If \`contentbit\` is unavailable, suggest \`npx contentbit@latest init\` instead
|
|
16747
|
+
of inventing block syntax.
|
|
16748
|
+
|
|
16749
|
+
<!-- contentbit:end -->`;
|
|
16750
|
+
START = "<!-- contentbit:start -->";
|
|
16751
|
+
END = "<!-- contentbit:end -->";
|
|
16752
|
+
}
|
|
16753
|
+
});
|
|
16754
|
+
|
|
16175
16755
|
// src/commands/init.ts
|
|
16176
16756
|
var init_exports = {};
|
|
16177
16757
|
__export(init_exports, {
|
|
16178
16758
|
initCommand: () => initCommand
|
|
16179
16759
|
});
|
|
16180
16760
|
import { spawn } from "node:child_process";
|
|
16181
|
-
import {
|
|
16182
|
-
import {
|
|
16183
|
-
import {
|
|
16184
|
-
|
|
16761
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
16762
|
+
import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
|
|
16763
|
+
import { join as join2 } from "node:path";
|
|
16764
|
+
import { parseArgs as parseArgs2 } from "node:util";
|
|
16765
|
+
function blockComponentsTemplate(styled) {
|
|
16766
|
+
const body = styled ? ` return (
|
|
16767
|
+
<figure className="my-6 border-s-2 ps-4">
|
|
16768
|
+
<blockquote className="text-lg italic">{ctx.renderMarkdown(data.markdown)}</blockquote>
|
|
16769
|
+
<figcaption className="text-muted-foreground mt-2 text-sm">
|
|
16770
|
+
\u2014 {String(node.props.author)}
|
|
16771
|
+
{node.props.role ? \`, \${String(node.props.role)}\` : null}
|
|
16772
|
+
</figcaption>
|
|
16773
|
+
</figure>
|
|
16774
|
+
)` : ` return (
|
|
16775
|
+
<figure style={{ margin: '1.5rem 0', borderLeft: '2px solid #d4d4d4', paddingLeft: '1rem' }}>
|
|
16776
|
+
<blockquote style={{ fontStyle: 'italic' }}>{ctx.renderMarkdown(data.markdown)}</blockquote>
|
|
16777
|
+
<figcaption style={{ marginTop: '0.5rem', fontSize: '0.875rem', opacity: 0.7 }}>
|
|
16778
|
+
\u2014 {String(node.props.author)}
|
|
16779
|
+
{node.props.role ? \`, \${String(node.props.role)}\` : null}
|
|
16780
|
+
</figcaption>
|
|
16781
|
+
</figure>
|
|
16782
|
+
)`;
|
|
16783
|
+
return `import type { BlockComponent, BlockComponentProps } from '@contentbit/react'
|
|
16784
|
+
|
|
16785
|
+
// One React component per custom block, keyed by block name. Definitions
|
|
16786
|
+
// live in ./registry.ts \u2014 add a block there, add its component here, and
|
|
16787
|
+
// the rest of the app never changes.
|
|
16788
|
+
function QuoteBlock({ node, ctx }: BlockComponentProps) {
|
|
16789
|
+
const data = node.data as { markdown: string }
|
|
16790
|
+
${body}
|
|
16791
|
+
}
|
|
16792
|
+
|
|
16793
|
+
export const blockComponents: Record<string, BlockComponent> = {
|
|
16794
|
+
quote: QuoteBlock,
|
|
16795
|
+
}
|
|
16796
|
+
`;
|
|
16797
|
+
}
|
|
16798
|
+
function reactComponent(styled, mdWired, blocksImport) {
|
|
16799
|
+
const mdImport = mdWired ? "import ReactMarkdown from 'react-markdown'\n" : "";
|
|
16800
|
+
const mdProp = mdWired ? "\n renderMarkdown={(md) => <ReactMarkdown>{md}</ReactMarkdown>}" : `
|
|
16801
|
+
// TODO: plug your Markdown library in here, e.g. react-markdown.
|
|
16802
|
+
// One function renders all prose: https://contentbit.dev/docs/guides/markdown
|
|
16803
|
+
// renderMarkdown={(md) => <Markdown source={md} />}`;
|
|
16804
|
+
const rendererImport = styled ? `
|
|
16805
|
+
// The styled pack installed by shadcn. Yours to edit.
|
|
16806
|
+
import { ContentRenderer } from '@/components/content-blocks/content-renderer'` : "";
|
|
16807
|
+
const renderer = styled ? "ContentRenderer" : "ContentBlocks";
|
|
16808
|
+
const reactImport = styled ? "" : "import { ContentBlocks } from '@contentbit/react'\n";
|
|
16809
|
+
return `'use client'
|
|
16810
|
+
|
|
16811
|
+
import { genericBlocks } from '@contentbit/blocks'
|
|
16812
|
+
import { createBlockRegistry, parseDocument, validateDocument } from '@contentbit/core'
|
|
16813
|
+
${reactImport}${mdImport}${rendererImport}
|
|
16814
|
+
// Everything block-related lives in the blocks/ folder: definitions in
|
|
16815
|
+
// registry.ts (shared with the validate CLI), components in components.tsx.
|
|
16816
|
+
import customBlocks from '${blocksImport}/registry'
|
|
16817
|
+
import { blockComponents } from '${blocksImport}/components'
|
|
16818
|
+
|
|
16819
|
+
const registry = createBlockRegistry().use(genericBlocks()).use(customBlocks)
|
|
16820
|
+
|
|
16821
|
+
export function Content({ source }: { source: string }) {
|
|
16822
|
+
const result = validateDocument(parseDocument(source), registry)
|
|
16823
|
+
return (
|
|
16824
|
+
<${renderer}
|
|
16825
|
+
document={result.document}
|
|
16826
|
+
components={blockComponents}${mdProp}
|
|
16827
|
+
/>
|
|
16828
|
+
)
|
|
16829
|
+
}
|
|
16830
|
+
`;
|
|
16831
|
+
}
|
|
16832
|
+
function htmlRenderScript(md) {
|
|
16833
|
+
const wiring = md === "marked" ? `import { marked } from 'marked'
|
|
16834
|
+
|
|
16835
|
+
const renderMarkdown = (md) => marked.parse(md, { async: false })` : md === "markdown-it" ? `import MarkdownIt from 'markdown-it'
|
|
16836
|
+
|
|
16837
|
+
const mdIt = new MarkdownIt() // html: false by default \u2014 raw HTML stays escaped
|
|
16838
|
+
const renderMarkdown = (md) => mdIt.render(md)` : `// TODO: plug a Markdown library in here (marked, markdown-it, remark).
|
|
16839
|
+
const renderMarkdown = undefined`;
|
|
16840
|
+
return `// Render content/example.md to example.html. Run: node scripts/render-example.mjs
|
|
16841
|
+
import { genericBlocks } from '@contentbit/blocks'
|
|
16842
|
+
import { createBlockRegistry, parseDocument, validateDocument } from '@contentbit/core'
|
|
16843
|
+
import { renderToHtml } from '@contentbit/html'
|
|
16844
|
+
import { readFile, writeFile } from 'node:fs/promises'
|
|
16845
|
+
${wiring}
|
|
16846
|
+
|
|
16847
|
+
const source = await readFile('content/example.md', 'utf8')
|
|
16848
|
+
const registry = createBlockRegistry().use(genericBlocks())
|
|
16849
|
+
const result = validateDocument(parseDocument(source), registry)
|
|
16850
|
+
const html = renderToHtml(result.document, { renderMarkdown })
|
|
16851
|
+
await writeFile('example.html', html, 'utf8')
|
|
16852
|
+
console.log('wrote example.html')
|
|
16853
|
+
`;
|
|
16854
|
+
}
|
|
16855
|
+
function detectFramework(cwd, deps) {
|
|
16856
|
+
if ((deps["@tanstack/react-start"] || deps["@tanstack/react-router"]) && existsSync2(join2(cwd, "src/routes"))) {
|
|
16857
|
+
return {
|
|
16858
|
+
framework: "tanstack",
|
|
16859
|
+
componentPath: "src/components/content-blocks.tsx",
|
|
16860
|
+
pagePath: "src/routes/example.tsx"
|
|
16861
|
+
};
|
|
16862
|
+
}
|
|
16863
|
+
if (deps.next) {
|
|
16864
|
+
const appDir = existsSync2(join2(cwd, "src/app")) ? "src/app" : "app";
|
|
16865
|
+
if (existsSync2(join2(cwd, appDir))) {
|
|
16866
|
+
return {
|
|
16867
|
+
framework: "next",
|
|
16868
|
+
componentPath: "components/content-blocks.tsx",
|
|
16869
|
+
pagePath: `${appDir}/example/page.tsx`
|
|
16870
|
+
};
|
|
16871
|
+
}
|
|
16872
|
+
}
|
|
16873
|
+
return { framework: null, componentPath: "components/content-blocks.tsx", pagePath: null };
|
|
16874
|
+
}
|
|
16875
|
+
function astroPage(styled) {
|
|
16876
|
+
const importLine = styled ? "import ContentRenderer from '../components/content-blocks/content-renderer.astro'" : "import { ContentBlocks } from '@contentbit/astro/components'";
|
|
16877
|
+
const renderer = styled ? "ContentRenderer" : "ContentBlocks";
|
|
16878
|
+
return `---
|
|
16879
|
+
import { genericBlocks } from '@contentbit/blocks'
|
|
16880
|
+
import { createBlockRegistry, parseDocument, validateDocument } from '@contentbit/core'
|
|
16881
|
+
import { getEntry } from 'astro:content'
|
|
16882
|
+
|
|
16883
|
+
${importLine}
|
|
16884
|
+
|
|
16885
|
+
// Definitions in blocks/registry.ts are shared with the validate CLI.
|
|
16886
|
+
import customBlocks from '../../blocks/registry'
|
|
16887
|
+
import QuoteBlock from '../../blocks/QuoteBlock.astro'
|
|
16888
|
+
|
|
16889
|
+
// Entry ids are the file path relative to the collection base, minus ".md".
|
|
16890
|
+
const entry = await getEntry('articles', 'example')
|
|
16891
|
+
if (!entry?.body) throw new Error('Entry "example" not found in the articles collection.')
|
|
16892
|
+
|
|
16893
|
+
const registry = createBlockRegistry().use(genericBlocks()).use(customBlocks)
|
|
16894
|
+
// Static pages render at build time, so invalid blocks fail the build here.
|
|
16895
|
+
const result = validateDocument(parseDocument(entry.body), registry)
|
|
16896
|
+
---
|
|
16897
|
+
|
|
16898
|
+
<main style="max-width: 42rem; margin: 0 auto; padding: 3rem 1.5rem;">
|
|
16899
|
+
<${renderer} document={result.document} components={{ quote: QuoteBlock }} />
|
|
16900
|
+
</main>
|
|
16901
|
+
`;
|
|
16902
|
+
}
|
|
16903
|
+
function detectPackageManager(cwd) {
|
|
16904
|
+
const locks = [
|
|
16905
|
+
["pnpm-lock.yaml", "pnpm"],
|
|
16906
|
+
["yarn.lock", "yarn"],
|
|
16907
|
+
["bun.lock", "bun"],
|
|
16908
|
+
["bun.lockb", "bun"],
|
|
16909
|
+
["package-lock.json", "npm"]
|
|
16910
|
+
];
|
|
16911
|
+
for (const [file2, pm] of locks) {
|
|
16912
|
+
if (existsSync2(join2(cwd, file2))) return pm;
|
|
16913
|
+
}
|
|
16185
16914
|
const agent = process.env.npm_config_user_agent ?? "";
|
|
16186
16915
|
for (const pm of ["pnpm", "yarn", "bun"]) {
|
|
16187
16916
|
if (agent.startsWith(pm)) return pm;
|
|
@@ -16192,6 +16921,12 @@ function installArgs(pm, dev, pkgs) {
|
|
|
16192
16921
|
const add = pm === "npm" ? "install" : "add";
|
|
16193
16922
|
return dev ? [add, "-D", ...pkgs] : [add, ...pkgs];
|
|
16194
16923
|
}
|
|
16924
|
+
function dlxCommand(pm) {
|
|
16925
|
+
if (pm === "pnpm") return ["pnpm", ["dlx"]];
|
|
16926
|
+
if (pm === "yarn") return ["yarn", ["dlx"]];
|
|
16927
|
+
if (pm === "bun") return ["bunx", []];
|
|
16928
|
+
return ["npx", ["--yes"]];
|
|
16929
|
+
}
|
|
16195
16930
|
function runInstall(pm, args, cwd) {
|
|
16196
16931
|
return new Promise((resolve) => {
|
|
16197
16932
|
const child = spawn(pm, args, { cwd, stdio: "inherit", shell: process.platform === "win32" });
|
|
@@ -16199,37 +16934,62 @@ function runInstall(pm, args, cwd) {
|
|
|
16199
16934
|
child.on("error", () => resolve(1));
|
|
16200
16935
|
});
|
|
16201
16936
|
}
|
|
16937
|
+
async function installStyledPack(cwd, pack, noInstall, io) {
|
|
16938
|
+
const componentsJsonPath = join2(cwd, "components.json");
|
|
16939
|
+
const componentsJson = JSON.parse(await readFile2(componentsJsonPath, "utf8"));
|
|
16940
|
+
componentsJson.registries ??= {};
|
|
16941
|
+
if (!componentsJson.registries["@contentbit"]) {
|
|
16942
|
+
componentsJson.registries["@contentbit"] = "https://contentbit.dev/r/{name}.json";
|
|
16943
|
+
await writeFile2(componentsJsonPath, `${JSON.stringify(componentsJson, null, 2)}
|
|
16944
|
+
`, "utf8");
|
|
16945
|
+
io.stdout("added @contentbit registry to components.json");
|
|
16946
|
+
}
|
|
16947
|
+
if (noInstall) {
|
|
16948
|
+
io.stdout(`skipped: shadcn add ${pack}`);
|
|
16949
|
+
return true;
|
|
16950
|
+
}
|
|
16951
|
+
const [bin, prefix] = dlxCommand(detectPackageManager(cwd));
|
|
16952
|
+
io.stdout(`installing the styled pack: shadcn add ${pack}`);
|
|
16953
|
+
const code2 = await runInstall(bin, [...prefix, "shadcn@latest", "add", pack, "--yes"], cwd);
|
|
16954
|
+
if (code2 !== 0) io.stderr("styled pack install failed; falling back to headless defaults");
|
|
16955
|
+
return code2 === 0;
|
|
16956
|
+
}
|
|
16202
16957
|
async function scaffold(path, content) {
|
|
16203
16958
|
try {
|
|
16204
|
-
await
|
|
16959
|
+
await readFile2(path, "utf8");
|
|
16205
16960
|
return "skipped";
|
|
16206
16961
|
} catch {
|
|
16207
|
-
await
|
|
16208
|
-
await
|
|
16962
|
+
await mkdir2(join2(path, ".."), { recursive: true });
|
|
16963
|
+
await writeFile2(path, content, "utf8");
|
|
16209
16964
|
return "created";
|
|
16210
16965
|
}
|
|
16211
16966
|
}
|
|
16212
16967
|
async function initCommand(args, io) {
|
|
16213
|
-
const { values } =
|
|
16968
|
+
const { values } = parseArgs2({
|
|
16214
16969
|
args,
|
|
16215
16970
|
options: {
|
|
16216
16971
|
target: { type: "string", short: "t" },
|
|
16972
|
+
md: { type: "string" },
|
|
16217
16973
|
yes: { type: "boolean", short: "y", default: false },
|
|
16218
16974
|
cwd: { type: "string", default: process.cwd() },
|
|
16219
|
-
"no-install": { type: "boolean", default: false }
|
|
16975
|
+
"no-install": { type: "boolean", default: false },
|
|
16976
|
+
"no-page": { type: "boolean", default: false },
|
|
16977
|
+
"no-styled": { type: "boolean", default: false },
|
|
16978
|
+
"no-agents": { type: "boolean", default: false }
|
|
16220
16979
|
}
|
|
16221
16980
|
});
|
|
16222
16981
|
const cwd = values.cwd;
|
|
16223
16982
|
let pkg;
|
|
16224
|
-
const pkgPath =
|
|
16983
|
+
const pkgPath = join2(cwd, "package.json");
|
|
16225
16984
|
try {
|
|
16226
|
-
pkg = JSON.parse(await
|
|
16985
|
+
pkg = JSON.parse(await readFile2(pkgPath, "utf8"));
|
|
16227
16986
|
} catch {
|
|
16228
16987
|
io.stderr("No package.json found. Run this inside a project (npm init first).");
|
|
16229
16988
|
return 1;
|
|
16230
16989
|
}
|
|
16231
16990
|
const hasReact = Boolean(pkg.dependencies?.react ?? pkg.devDependencies?.react);
|
|
16232
|
-
const
|
|
16991
|
+
const hasAstro = Boolean(pkg.dependencies?.astro ?? pkg.devDependencies?.astro);
|
|
16992
|
+
const detected = hasAstro ? "astro" : hasReact ? "react" : "html";
|
|
16233
16993
|
let target;
|
|
16234
16994
|
if (values.target) {
|
|
16235
16995
|
if (!TARGETS.includes(values.target)) {
|
|
@@ -16244,6 +17004,7 @@ async function initCommand(args, io) {
|
|
|
16244
17004
|
initialValue: detected,
|
|
16245
17005
|
options: [
|
|
16246
17006
|
{ value: "react", label: "React", hint: "ContentBlocks component" },
|
|
17007
|
+
{ value: "astro", label: "Astro", hint: "content collections + .astro components" },
|
|
16247
17008
|
{ value: "html", label: "Static HTML", hint: "renderToHtml, no framework" },
|
|
16248
17009
|
{ value: "markdown", label: "Plain Markdown", hint: "fallback rendering only" }
|
|
16249
17010
|
]
|
|
@@ -16253,13 +17014,39 @@ async function initCommand(args, io) {
|
|
|
16253
17014
|
} else {
|
|
16254
17015
|
target = detected;
|
|
16255
17016
|
}
|
|
16256
|
-
const
|
|
17017
|
+
const choices = MD_CHOICES[target];
|
|
17018
|
+
let md;
|
|
17019
|
+
if (values.md) {
|
|
17020
|
+
if (!choices.includes(values.md)) {
|
|
17021
|
+
io.stderr(`Unknown markdown library "${values.md}". Use one of: ${choices.join(", ")}`);
|
|
17022
|
+
return 2;
|
|
17023
|
+
}
|
|
17024
|
+
md = values.md;
|
|
17025
|
+
} else if (choices.length > 1 && !values.yes && process.stdin.isTTY && process.stdout.isTTY) {
|
|
17026
|
+
const { isCancel, select } = await import("@clack/prompts");
|
|
17027
|
+
const answer = await select({
|
|
17028
|
+
message: "Markdown library for prose rendering?",
|
|
17029
|
+
initialValue: choices[0],
|
|
17030
|
+
options: choices.map((c) => ({
|
|
17031
|
+
value: c,
|
|
17032
|
+
label: c,
|
|
17033
|
+
hint: c === "none" ? "wire one yourself later" : "installed and wired for you"
|
|
17034
|
+
}))
|
|
17035
|
+
});
|
|
17036
|
+
if (isCancel(answer)) return 1;
|
|
17037
|
+
md = answer;
|
|
17038
|
+
} else {
|
|
17039
|
+
md = choices[0];
|
|
17040
|
+
}
|
|
17041
|
+
const runtime = ["@contentbit/core", "@contentbit/blocks", "zod"];
|
|
16257
17042
|
if (target === "react") runtime.push("@contentbit/react");
|
|
16258
17043
|
if (target === "html") runtime.push("@contentbit/html");
|
|
17044
|
+
if (target === "astro") runtime.push("@contentbit/astro");
|
|
17045
|
+
if (md !== "none") runtime.push(md);
|
|
16259
17046
|
if (values["no-install"]) {
|
|
16260
17047
|
io.stdout(`skipped install: ${runtime.join(" ")} + contentbit (dev)`);
|
|
16261
17048
|
} else {
|
|
16262
|
-
const pm = detectPackageManager();
|
|
17049
|
+
const pm = detectPackageManager(cwd);
|
|
16263
17050
|
io.stdout(`installing with ${pm}: ${runtime.join(" ")}`);
|
|
16264
17051
|
if (await runInstall(pm, installArgs(pm, false, runtime), cwd) !== 0) {
|
|
16265
17052
|
io.stderr("install failed");
|
|
@@ -16271,68 +17058,142 @@ async function initCommand(args, io) {
|
|
|
16271
17058
|
}
|
|
16272
17059
|
}
|
|
16273
17060
|
const files = [
|
|
16274
|
-
["blocks/registry.
|
|
17061
|
+
["blocks/registry.ts", REGISTRY_TEMPLATE],
|
|
16275
17062
|
["content/example.md", EXAMPLE_CONTENT]
|
|
16276
17063
|
];
|
|
16277
|
-
|
|
17064
|
+
const layout = detectFramework(cwd, { ...pkg.dependencies, ...pkg.devDependencies });
|
|
17065
|
+
let styled = false;
|
|
17066
|
+
const componentsJsonPath = join2(cwd, "components.json");
|
|
17067
|
+
if (target === "react" && !values["no-styled"] && existsSync2(componentsJsonPath)) {
|
|
17068
|
+
styled = await installStyledPack(cwd, "@contentbit/generic-pack", values["no-install"], io);
|
|
17069
|
+
}
|
|
17070
|
+
if (target === "react") {
|
|
17071
|
+
const depth = layout.componentPath.split("/").length - 1;
|
|
17072
|
+
const blocksImport = `${"../".repeat(depth)}blocks`;
|
|
17073
|
+
files.push(["blocks/components.tsx", blockComponentsTemplate(styled)]);
|
|
17074
|
+
files.push([
|
|
17075
|
+
layout.componentPath,
|
|
17076
|
+
reactComponent(styled, md === "react-markdown", blocksImport)
|
|
17077
|
+
]);
|
|
17078
|
+
if (!values["no-page"] && layout.pagePath) {
|
|
17079
|
+
files.push([layout.pagePath, layout.framework === "tanstack" ? TANSTACK_PAGE : NEXT_PAGE]);
|
|
17080
|
+
}
|
|
17081
|
+
}
|
|
17082
|
+
if (target === "html") {
|
|
17083
|
+
files.push([
|
|
17084
|
+
"scripts/render-example.mjs",
|
|
17085
|
+
htmlRenderScript(md)
|
|
17086
|
+
]);
|
|
17087
|
+
}
|
|
17088
|
+
if (target === "astro") {
|
|
17089
|
+
let astroStyled = false;
|
|
17090
|
+
if (!values["no-styled"] && existsSync2(componentsJsonPath)) {
|
|
17091
|
+
astroStyled = await installStyledPack(cwd, "@contentbit/astro-pack", values["no-install"], io);
|
|
17092
|
+
}
|
|
17093
|
+
files.push(["blocks/QuoteBlock.astro", ASTRO_QUOTE_BLOCK]);
|
|
17094
|
+
const configCandidates = ["ts", "mts", "mjs", "js"].flatMap((ext) => [
|
|
17095
|
+
`src/content.config.${ext}`,
|
|
17096
|
+
`src/content/config.${ext}`
|
|
17097
|
+
]);
|
|
17098
|
+
const existingConfig = configCandidates.find((p) => existsSync2(join2(cwd, p)));
|
|
17099
|
+
if (existingConfig) {
|
|
17100
|
+
io.stdout(`content config exists (${existingConfig}); add this collection manually:`);
|
|
17101
|
+
io.stdout(ASTRO_CONTENT_CONFIG);
|
|
17102
|
+
io.stdout('the example page expects the "articles" collection above');
|
|
17103
|
+
} else {
|
|
17104
|
+
files.push(["src/content.config.ts", ASTRO_CONTENT_CONFIG]);
|
|
17105
|
+
}
|
|
17106
|
+
if (!values["no-page"]) files.push(["src/pages/example.astro", astroPage(astroStyled)]);
|
|
17107
|
+
}
|
|
16278
17108
|
for (const [rel, content] of files) {
|
|
16279
|
-
const result = await scaffold(
|
|
17109
|
+
const result = await scaffold(join2(cwd, rel), content);
|
|
16280
17110
|
io.stdout(`${result}: ${rel}`);
|
|
16281
17111
|
}
|
|
16282
|
-
const fresh = JSON.parse(await
|
|
17112
|
+
const fresh = JSON.parse(await readFile2(pkgPath, "utf8"));
|
|
16283
17113
|
fresh.scripts ??= {};
|
|
16284
17114
|
if (!fresh.scripts["content:check"]) {
|
|
16285
|
-
fresh.scripts["content:check"] = 'contentbit validate "content/**/*.md" --registry ./blocks/registry.
|
|
16286
|
-
await
|
|
17115
|
+
fresh.scripts["content:check"] = 'contentbit validate "content/**/*.md" --registry ./blocks/registry.ts';
|
|
17116
|
+
await writeFile2(pkgPath, `${JSON.stringify(fresh, null, 2)}
|
|
16287
17117
|
`, "utf8");
|
|
16288
17118
|
io.stdout("added script: content:check");
|
|
16289
17119
|
}
|
|
16290
|
-
|
|
17120
|
+
let registry2;
|
|
17121
|
+
try {
|
|
17122
|
+
registry2 = await loadRegistry(join2(cwd, "blocks/registry.ts"));
|
|
17123
|
+
} catch {
|
|
17124
|
+
registry2 = await loadRegistry();
|
|
17125
|
+
}
|
|
16291
17126
|
const guide = registry2.toAuthoringGuide({ audience: "llm", includeExamples: true });
|
|
16292
|
-
await
|
|
17127
|
+
await writeFile2(join2(cwd, "contentbit-guide.md"), guide, "utf8");
|
|
16293
17128
|
io.stdout("created: contentbit-guide.md (LLM authoring instructions)");
|
|
17129
|
+
if (!values["no-agents"]) {
|
|
17130
|
+
await installAgentIntegration(cwd, {}, io);
|
|
17131
|
+
io.stdout("Agent integration installed \u2014 try asking your agent:");
|
|
17132
|
+
io.stdout(' "write a blog post about X" or "audit my content"');
|
|
17133
|
+
}
|
|
16294
17134
|
io.stdout("");
|
|
16295
17135
|
io.stdout("Done. Next steps:");
|
|
16296
|
-
io.stdout(` 1. Validate the starter content: ${detectPackageManager()} run content:check`);
|
|
17136
|
+
io.stdout(` 1. Validate the starter content: ${detectPackageManager(cwd)} run content:check`);
|
|
16297
17137
|
if (target === "react") {
|
|
16298
|
-
|
|
17138
|
+
if (!values["no-page"] && layout.pagePath) {
|
|
17139
|
+
io.stdout(" 2. Start the dev server and open /example to see the article rendered.");
|
|
17140
|
+
} else {
|
|
17141
|
+
io.stdout(' 2. Render it: import { Content } from "./components/content-blocks"');
|
|
17142
|
+
io.stdout(" <Content source={...content/example.md as a string} />");
|
|
17143
|
+
}
|
|
16299
17144
|
io.stdout(" 3. Styled components: pnpm dlx shadcn@latest add @contentbit/generic-pack");
|
|
17145
|
+
} else if (target === "astro") {
|
|
17146
|
+
io.stdout(" 2. Start the dev server and open /example to see the article rendered.");
|
|
17147
|
+
io.stdout(" 3. Styled components: pnpm dlx shadcn@latest add @contentbit/astro-pack");
|
|
16300
17148
|
} else if (target === "html") {
|
|
16301
|
-
io.stdout(" 2. Render it:
|
|
17149
|
+
io.stdout(" 2. Render it: node scripts/render-example.mjs && open example.html");
|
|
16302
17150
|
} else {
|
|
16303
17151
|
io.stdout(" 2. Render it: contentbit render content/example.md --target markdown");
|
|
16304
17152
|
}
|
|
16305
17153
|
io.stdout(" Docs: https://contentbit.dev/docs");
|
|
16306
17154
|
return 0;
|
|
16307
17155
|
}
|
|
16308
|
-
var TARGETS, REGISTRY_TEMPLATE, EXAMPLE_CONTENT,
|
|
17156
|
+
var TARGETS, MD_CHOICES, REGISTRY_TEMPLATE, EXAMPLE_CONTENT, TANSTACK_PAGE, NEXT_PAGE, ASTRO_CONTENT_CONFIG, ASTRO_QUOTE_BLOCK;
|
|
16309
17157
|
var init_init = __esm({
|
|
16310
17158
|
"src/commands/init.ts"() {
|
|
16311
17159
|
"use strict";
|
|
16312
17160
|
init_load_registry();
|
|
16313
|
-
|
|
16314
|
-
|
|
17161
|
+
init_agents();
|
|
17162
|
+
TARGETS = ["react", "html", "markdown", "astro"];
|
|
17163
|
+
MD_CHOICES = {
|
|
17164
|
+
react: ["react-markdown", "none"],
|
|
17165
|
+
html: ["marked", "markdown-it", "none"],
|
|
17166
|
+
markdown: ["none"],
|
|
17167
|
+
// @contentbit/astro ships its own marked-based default; nothing to install.
|
|
17168
|
+
astro: ["none"]
|
|
17169
|
+
};
|
|
17170
|
+
REGISTRY_TEMPLATE = `// Custom block definitions for this project. The CLI and your app share
|
|
17171
|
+
// this module \u2014 Node 22.18+ imports TypeScript directly:
|
|
16315
17172
|
//
|
|
16316
|
-
// contentbit validate "content/**/*.md" --registry ./blocks/registry.
|
|
17173
|
+
// contentbit validate "content/**/*.md" --registry ./blocks/registry.ts
|
|
16317
17174
|
//
|
|
16318
|
-
//
|
|
17175
|
+
// Definitions stay framework-free (the CLI and every render target use
|
|
17176
|
+
// them); React components live next door in blocks/components.tsx.
|
|
16319
17177
|
// Docs: https://contentbit.dev/docs/guides/custom-blocks
|
|
16320
|
-
|
|
16321
|
-
|
|
16322
|
-
// import { z } from 'zod'
|
|
16323
|
-
//
|
|
16324
|
-
// const pricingTable = defineBlock({
|
|
16325
|
-
// name: 'pricing-table',
|
|
16326
|
-
// description: 'Compares product plans.',
|
|
16327
|
-
// props: z.object({ currency: z.enum(['usd', 'eur']).default('usd') }),
|
|
16328
|
-
// content: pipeRows({ columns: ['plan', 'price'], minRows: 2 }),
|
|
16329
|
-
// authoring: {
|
|
16330
|
-
// useWhen: ['Comparing pricing plans'],
|
|
16331
|
-
// example: ':::pricing-table\\n- Starter | $0\\n- Pro | $12/mo\\n:::',
|
|
16332
|
-
// },
|
|
16333
|
-
// })
|
|
17178
|
+
import { defineBlock, markdownBody, type BlockDefinition } from '@contentbit/core'
|
|
17179
|
+
import { z } from 'zod'
|
|
16334
17180
|
|
|
16335
|
-
export
|
|
17181
|
+
export const quote = defineBlock({
|
|
17182
|
+
name: 'quote',
|
|
17183
|
+
description: 'A pull quote with an author.',
|
|
17184
|
+
props: z.object({
|
|
17185
|
+
author: z.string().min(1),
|
|
17186
|
+
role: z.string().optional(),
|
|
17187
|
+
}),
|
|
17188
|
+
content: markdownBody({ minLength: 3 }),
|
|
17189
|
+
authoring: {
|
|
17190
|
+
useWhen: ['Quoting a person to support a point'],
|
|
17191
|
+
avoidWhen: ['Highlighting your own remark, use callout instead'],
|
|
17192
|
+
example: ':::quote{author="Ada Lovelace"}\\nThe Analytical Engine weaves algebraic patterns.\\n:::',
|
|
17193
|
+
},
|
|
17194
|
+
})
|
|
17195
|
+
|
|
17196
|
+
export default [quote] satisfies BlockDefinition<unknown>[]
|
|
16336
17197
|
`;
|
|
16337
17198
|
EXAMPLE_CONTENT = `# Hello, Content Blocks
|
|
16338
17199
|
|
|
@@ -16347,24 +17208,74 @@ Run the validate script and you will get file:line:col diagnostics.
|
|
|
16347
17208
|
2. Run \`contentbit validate "content/**/*.md"\`.
|
|
16348
17209
|
3. Render it with the target you picked at init.
|
|
16349
17210
|
:::
|
|
17211
|
+
|
|
17212
|
+
This one is a **custom block**, defined in \`blocks/registry.ts\` and rendered
|
|
17213
|
+
by the \`QuoteBlock\` component, in about twenty lines:
|
|
17214
|
+
|
|
17215
|
+
:::quote{author="Ada Lovelace" role="Notes on the Analytical Engine, 1843"}
|
|
17216
|
+
The Analytical Engine weaves algebraic patterns just as the Jacquard loom
|
|
17217
|
+
weaves flowers and leaves.
|
|
17218
|
+
:::
|
|
16350
17219
|
`;
|
|
16351
|
-
|
|
16352
|
-
import { createBlockRegistry, parseDocument, validateDocument } from '@contentbit/core'
|
|
16353
|
-
import { ContentBlocks } from '@contentbit/react'
|
|
17220
|
+
TANSTACK_PAGE = `import { createFileRoute } from '@tanstack/react-router'
|
|
16354
17221
|
|
|
16355
|
-
|
|
17222
|
+
import { Content } from '../components/content-blocks'
|
|
17223
|
+
// Vite's ?raw import inlines the Markdown as a string at build time.
|
|
17224
|
+
import source from '../../content/example.md?raw'
|
|
16356
17225
|
|
|
16357
|
-
export
|
|
16358
|
-
|
|
17226
|
+
export const Route = createFileRoute('/example')({ component: ExamplePage })
|
|
17227
|
+
|
|
17228
|
+
function ExamplePage() {
|
|
16359
17229
|
return (
|
|
16360
|
-
<
|
|
16361
|
-
|
|
16362
|
-
|
|
16363
|
-
// One function renders all prose: https://contentbit.dev/docs/guides/markdown
|
|
16364
|
-
// renderMarkdown={(md) => <Markdown source={md} />}
|
|
16365
|
-
/>
|
|
17230
|
+
<main style={{ maxWidth: '42rem', margin: '0 auto', padding: '3rem 1.5rem' }}>
|
|
17231
|
+
<Content source={source} />
|
|
17232
|
+
</main>
|
|
16366
17233
|
)
|
|
16367
17234
|
}
|
|
17235
|
+
`;
|
|
17236
|
+
NEXT_PAGE = `import { readFile } from 'node:fs/promises'
|
|
17237
|
+
|
|
17238
|
+
// If your project has no "@/" path alias, switch to a relative import.
|
|
17239
|
+
import { Content } from '@/components/content-blocks'
|
|
17240
|
+
|
|
17241
|
+
export default async function ExamplePage() {
|
|
17242
|
+
const source = await readFile('content/example.md', 'utf8')
|
|
17243
|
+
return (
|
|
17244
|
+
<main style={{ maxWidth: '42rem', margin: '0 auto', padding: '3rem 1.5rem' }}>
|
|
17245
|
+
<Content source={source} />
|
|
17246
|
+
</main>
|
|
17247
|
+
)
|
|
17248
|
+
}
|
|
17249
|
+
`;
|
|
17250
|
+
ASTRO_CONTENT_CONFIG = `import { defineCollection } from 'astro:content'
|
|
17251
|
+
import { glob } from 'astro/loaders'
|
|
17252
|
+
|
|
17253
|
+
export const collections = {
|
|
17254
|
+
articles: defineCollection({
|
|
17255
|
+
// Astro's builtin Markdown loader. Entry bodies are parsed and validated
|
|
17256
|
+
// where they render (see src/pages/example.astro); \`contentbit validate\`
|
|
17257
|
+
// covers the same files in CI.
|
|
17258
|
+
loader: glob({ pattern: '**/*.md', base: './content' }),
|
|
17259
|
+
}),
|
|
17260
|
+
}
|
|
17261
|
+
`;
|
|
17262
|
+
ASTRO_QUOTE_BLOCK = `---
|
|
17263
|
+
// The Astro component for the custom \`quote\` block defined in blocks/registry.ts.
|
|
17264
|
+
// Block props arrive as component props; nested content arrives via <slot />.
|
|
17265
|
+
interface Props {
|
|
17266
|
+
author: string
|
|
17267
|
+
role?: string
|
|
17268
|
+
}
|
|
17269
|
+
|
|
17270
|
+
const { author, role } = Astro.props
|
|
17271
|
+
---
|
|
17272
|
+
|
|
17273
|
+
<figure style="margin: 1.5rem 0; border-left: 2px solid #d4d4d4; padding-left: 1rem;">
|
|
17274
|
+
<blockquote style="font-style: italic;"><slot /></blockquote>
|
|
17275
|
+
<figcaption style="margin-top: 0.5rem; font-size: 0.875rem; opacity: 0.7;">
|
|
17276
|
+
\u2014 {author}{role ? \`, \${role}\` : null}
|
|
17277
|
+
</figcaption>
|
|
17278
|
+
</figure>
|
|
16368
17279
|
`;
|
|
16369
17280
|
}
|
|
16370
17281
|
});
|
|
@@ -16374,11 +17285,11 @@ var validate_exports = {};
|
|
|
16374
17285
|
__export(validate_exports, {
|
|
16375
17286
|
validateCommand: () => validateCommand
|
|
16376
17287
|
});
|
|
16377
|
-
import { readFile as
|
|
16378
|
-
import { parseArgs as
|
|
17288
|
+
import { readFile as readFile3 } from "node:fs/promises";
|
|
17289
|
+
import { parseArgs as parseArgs3 } from "node:util";
|
|
16379
17290
|
import { glob } from "tinyglobby";
|
|
16380
17291
|
async function validateCommand(args, io) {
|
|
16381
|
-
const { values, positionals } =
|
|
17292
|
+
const { values, positionals } = parseArgs3({
|
|
16382
17293
|
args,
|
|
16383
17294
|
allowPositionals: true,
|
|
16384
17295
|
options: {
|
|
@@ -16399,8 +17310,8 @@ async function validateCommand(args, io) {
|
|
|
16399
17310
|
let errors = 0;
|
|
16400
17311
|
let warnings = 0;
|
|
16401
17312
|
for (const file2 of files.sort()) {
|
|
16402
|
-
const source = await
|
|
16403
|
-
const result = validateDocument(parseDocument(source), registry2);
|
|
17313
|
+
const source = await readFile3(file2, "utf8");
|
|
17314
|
+
const result = validateDocument(parseDocument(stripFrontmatter(source)), registry2);
|
|
16404
17315
|
for (const d of result.diagnostics) {
|
|
16405
17316
|
io.stderr(formatDiagnostic(d, file2));
|
|
16406
17317
|
if (d.severity === "error") errors++;
|
|
@@ -16420,6 +17331,63 @@ var init_validate2 = __esm({
|
|
|
16420
17331
|
}
|
|
16421
17332
|
});
|
|
16422
17333
|
|
|
17334
|
+
// src/commands/stats.ts
|
|
17335
|
+
var stats_exports = {};
|
|
17336
|
+
__export(stats_exports, {
|
|
17337
|
+
statsCommand: () => statsCommand
|
|
17338
|
+
});
|
|
17339
|
+
import { readFile as readFile4 } from "node:fs/promises";
|
|
17340
|
+
import { parseArgs as parseArgs4 } from "node:util";
|
|
17341
|
+
import { glob as glob2 } from "tinyglobby";
|
|
17342
|
+
async function fileStats(file2, registry2) {
|
|
17343
|
+
const source = await readFile4(file2, "utf8");
|
|
17344
|
+
const stats = analyzeDocument(source, { path: file2 });
|
|
17345
|
+
if (registry2) {
|
|
17346
|
+
const result = validateDocument(parseDocument(stripFrontmatter(source)), registry2);
|
|
17347
|
+
let errors = 0;
|
|
17348
|
+
let warnings = 0;
|
|
17349
|
+
for (const d of result.diagnostics) {
|
|
17350
|
+
if (d.severity === "error") errors++;
|
|
17351
|
+
else if (d.severity === "warning") warnings++;
|
|
17352
|
+
}
|
|
17353
|
+
stats.validation = { errors, warnings };
|
|
17354
|
+
}
|
|
17355
|
+
return stats;
|
|
17356
|
+
}
|
|
17357
|
+
async function statsCommand(args, io) {
|
|
17358
|
+
const { values, positionals } = parseArgs4({
|
|
17359
|
+
args,
|
|
17360
|
+
allowPositionals: true,
|
|
17361
|
+
options: {
|
|
17362
|
+
registry: { type: "string" },
|
|
17363
|
+
"no-validate": { type: "boolean", default: false }
|
|
17364
|
+
}
|
|
17365
|
+
});
|
|
17366
|
+
if (positionals.length === 0) {
|
|
17367
|
+
io.stderr("stats: provide at least one file or glob.");
|
|
17368
|
+
return 2;
|
|
17369
|
+
}
|
|
17370
|
+
const files = await glob2(positionals, { absolute: true });
|
|
17371
|
+
if (files.length === 0) {
|
|
17372
|
+
io.stderr(`stats: no files matched ${positionals.join(" ")}`);
|
|
17373
|
+
return 2;
|
|
17374
|
+
}
|
|
17375
|
+
const registry2 = values["no-validate"] ? null : await loadRegistry(values.registry);
|
|
17376
|
+
const all = [];
|
|
17377
|
+
for (const file2 of files.sort()) {
|
|
17378
|
+
all.push(await fileStats(file2, registry2));
|
|
17379
|
+
}
|
|
17380
|
+
io.stdout(JSON.stringify(all.length === 1 ? all[0] : all, null, 2));
|
|
17381
|
+
return 0;
|
|
17382
|
+
}
|
|
17383
|
+
var init_stats = __esm({
|
|
17384
|
+
"src/commands/stats.ts"() {
|
|
17385
|
+
"use strict";
|
|
17386
|
+
init_dist();
|
|
17387
|
+
init_load_registry();
|
|
17388
|
+
}
|
|
17389
|
+
});
|
|
17390
|
+
|
|
16423
17391
|
// ../html/dist/escape.js
|
|
16424
17392
|
function escapeHtml(value) {
|
|
16425
17393
|
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
@@ -16500,12 +17468,18 @@ var init_blocks = __esm({
|
|
|
16500
17468
|
});
|
|
16501
17469
|
|
|
16502
17470
|
// ../html/dist/render.js
|
|
16503
|
-
function
|
|
17471
|
+
function fallbackMarkdown(md) {
|
|
16504
17472
|
return md.trim().split(/\n{2,}/).map((p) => `<p>${escapeHtml(p)}</p>`).join("\n");
|
|
16505
17473
|
}
|
|
17474
|
+
function invalidBlockHtml(node, prefix) {
|
|
17475
|
+
return `<div class="${prefix}invalid" data-cb-invalid="${escapeHtml(node.name)}"><pre>${escapeHtml(node.body)}</pre></div>`;
|
|
17476
|
+
}
|
|
17477
|
+
function unrenderableBlockError(name) {
|
|
17478
|
+
return new Error(`Cannot render block "${name}": not validated or no renderer registered.`);
|
|
17479
|
+
}
|
|
16506
17480
|
function renderToHtml(document, opts = {}) {
|
|
16507
17481
|
const prefix = opts.classPrefix ?? "cb-";
|
|
16508
|
-
const renderMarkdown = opts.renderMarkdown ??
|
|
17482
|
+
const renderMarkdown = opts.renderMarkdown ?? fallbackMarkdown;
|
|
16509
17483
|
const renderers = { ...genericHtmlRenderers, ...opts.renderers };
|
|
16510
17484
|
const onInvalid = opts.onInvalid ?? "fallback";
|
|
16511
17485
|
const ctx = {
|
|
@@ -16519,13 +17493,11 @@ function renderToHtml(document, opts = {}) {
|
|
|
16519
17493
|
const renderer = renderers[node.name];
|
|
16520
17494
|
if (renderer && isValidatedBlock(node))
|
|
16521
17495
|
return renderer(node, ctx);
|
|
16522
|
-
if (onInvalid === "strict")
|
|
16523
|
-
throw
|
|
16524
|
-
|
|
16525
|
-
|
|
16526
|
-
|
|
16527
|
-
}
|
|
16528
|
-
return defaultMarkdown(node.body);
|
|
17496
|
+
if (onInvalid === "strict")
|
|
17497
|
+
throw unrenderableBlockError(node.name);
|
|
17498
|
+
if (onInvalid === "annotated")
|
|
17499
|
+
return invalidBlockHtml(node, prefix);
|
|
17500
|
+
return fallbackMarkdown(node.body);
|
|
16529
17501
|
}).join("\n");
|
|
16530
17502
|
}
|
|
16531
17503
|
};
|
|
@@ -16553,10 +17525,10 @@ var render_exports = {};
|
|
|
16553
17525
|
__export(render_exports, {
|
|
16554
17526
|
renderCommand: () => renderCommand
|
|
16555
17527
|
});
|
|
16556
|
-
import { readFile as
|
|
16557
|
-
import { parseArgs as
|
|
17528
|
+
import { readFile as readFile5 } from "node:fs/promises";
|
|
17529
|
+
import { parseArgs as parseArgs5 } from "node:util";
|
|
16558
17530
|
async function renderCommand(args, io) {
|
|
16559
|
-
const { values, positionals } =
|
|
17531
|
+
const { values, positionals } = parseArgs5({
|
|
16560
17532
|
args,
|
|
16561
17533
|
allowPositionals: true,
|
|
16562
17534
|
options: {
|
|
@@ -16571,7 +17543,7 @@ async function renderCommand(args, io) {
|
|
|
16571
17543
|
return 2;
|
|
16572
17544
|
}
|
|
16573
17545
|
const registry2 = await loadRegistry(values.registry);
|
|
16574
|
-
const source = await
|
|
17546
|
+
const source = await readFile5(file2, "utf8");
|
|
16575
17547
|
const result = validateDocument(parseDocument(source), registry2);
|
|
16576
17548
|
if (!result.ok) {
|
|
16577
17549
|
for (const d of result.diagnostics) io.stderr(formatDiagnostic(d, file2));
|
|
@@ -16597,9 +17569,9 @@ var instructions_exports = {};
|
|
|
16597
17569
|
__export(instructions_exports, {
|
|
16598
17570
|
instructionsCommand: () => instructionsCommand
|
|
16599
17571
|
});
|
|
16600
|
-
import { parseArgs as
|
|
17572
|
+
import { parseArgs as parseArgs6 } from "node:util";
|
|
16601
17573
|
async function instructionsCommand(args, io) {
|
|
16602
|
-
const { values } =
|
|
17574
|
+
const { values } = parseArgs6({
|
|
16603
17575
|
args,
|
|
16604
17576
|
options: {
|
|
16605
17577
|
audience: { type: "string", default: "llm" },
|
|
@@ -16629,9 +17601,9 @@ var docs_exports = {};
|
|
|
16629
17601
|
__export(docs_exports, {
|
|
16630
17602
|
docsCommand: () => docsCommand
|
|
16631
17603
|
});
|
|
16632
|
-
import { parseArgs as
|
|
17604
|
+
import { parseArgs as parseArgs7 } from "node:util";
|
|
16633
17605
|
async function docsCommand(args, io) {
|
|
16634
|
-
const { values } =
|
|
17606
|
+
const { values } = parseArgs7({
|
|
16635
17607
|
args,
|
|
16636
17608
|
options: {
|
|
16637
17609
|
registry: { type: "string" },
|
|
@@ -16652,23 +17624,27 @@ var init_docs = __esm({
|
|
|
16652
17624
|
});
|
|
16653
17625
|
|
|
16654
17626
|
// src/bin.ts
|
|
16655
|
-
import { writeFile as
|
|
17627
|
+
import { writeFile as writeFile3 } from "node:fs/promises";
|
|
16656
17628
|
|
|
16657
17629
|
// src/run.ts
|
|
16658
|
-
var USAGE = `Usage: contentbit <init|validate|render|instructions|docs> [options]
|
|
17630
|
+
var USAGE = `Usage: contentbit <init|validate|stats|render|instructions|docs|agents> [options]
|
|
16659
17631
|
|
|
16660
|
-
init [-t react|html|markdown] [-y] [--no-install]
|
|
17632
|
+
init [-t react|html|markdown|astro] [--md ...] [-y] [--no-install] [--no-page] [--no-agents]
|
|
17633
|
+
agents [--claude] [--no-agents-md]
|
|
16661
17634
|
|
|
16662
17635
|
validate <globs...> [--registry <module.mjs>] [--strict-warnings]
|
|
17636
|
+
stats <globs...> [--registry <module.mjs>] [--no-validate]
|
|
16663
17637
|
render <file> --target html|markdown [--registry <module.mjs>] [--out <file>]
|
|
16664
17638
|
instructions [--audience llm|human] [--no-examples] [--registry <module.mjs>] [--out <file>]
|
|
16665
17639
|
docs [--registry <module.mjs>] [--out <file>]`;
|
|
16666
17640
|
var commands = {
|
|
16667
17641
|
init: async () => (await Promise.resolve().then(() => (init_init(), init_exports))).initCommand,
|
|
16668
17642
|
validate: async () => (await Promise.resolve().then(() => (init_validate2(), validate_exports))).validateCommand,
|
|
17643
|
+
stats: async () => (await Promise.resolve().then(() => (init_stats(), stats_exports))).statsCommand,
|
|
16669
17644
|
render: async () => (await Promise.resolve().then(() => (init_render2(), render_exports))).renderCommand,
|
|
16670
17645
|
instructions: async () => (await Promise.resolve().then(() => (init_instructions(), instructions_exports))).instructionsCommand,
|
|
16671
|
-
docs: async () => (await Promise.resolve().then(() => (init_docs(), docs_exports))).docsCommand
|
|
17646
|
+
docs: async () => (await Promise.resolve().then(() => (init_docs(), docs_exports))).docsCommand,
|
|
17647
|
+
agents: async () => (await Promise.resolve().then(() => (init_agents(), agents_exports))).agentsCommand
|
|
16672
17648
|
};
|
|
16673
17649
|
async function run(argv, io) {
|
|
16674
17650
|
const [name, ...rest] = argv;
|
|
@@ -16690,6 +17666,6 @@ async function run(argv, io) {
|
|
|
16690
17666
|
var code = await run(process.argv.slice(2), {
|
|
16691
17667
|
stdout: (s) => console.log(s),
|
|
16692
17668
|
stderr: (s) => console.error(s),
|
|
16693
|
-
writeFile: (path, content) =>
|
|
17669
|
+
writeFile: (path, content) => writeFile3(path, content, "utf8")
|
|
16694
17670
|
});
|
|
16695
17671
|
process.exit(code);
|