contentbit 0.1.1 → 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 +805 -78
- 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 +129 -22
- 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/run.d.ts +1 -1
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +803 -76
- package/package.json +4 -4
package/dist/run.js
CHANGED
|
@@ -319,6 +319,323 @@ var init_parser = __esm({
|
|
|
319
319
|
}
|
|
320
320
|
});
|
|
321
321
|
|
|
322
|
+
// ../core/dist/frontmatter.js
|
|
323
|
+
function stripFrontmatter(source) {
|
|
324
|
+
const match = FM_RE.exec(source);
|
|
325
|
+
if (!match)
|
|
326
|
+
return source;
|
|
327
|
+
return match[0].replace(/[^\n]+/g, "") + source.slice(match[0].length);
|
|
328
|
+
}
|
|
329
|
+
function extractFrontmatter(source) {
|
|
330
|
+
const match = source.match(FM_RE);
|
|
331
|
+
if (!match)
|
|
332
|
+
return null;
|
|
333
|
+
const blockLines = match[0].replace(/\r?\n$/, "").split(/\r?\n/);
|
|
334
|
+
const inner = blockLines.slice(1, -1);
|
|
335
|
+
const { data, keys } = parseYamlSubset(inner);
|
|
336
|
+
return { raw: inner.join("\n"), data, keys, lines: { start: 1, end: blockLines.length } };
|
|
337
|
+
}
|
|
338
|
+
function parseYamlSubset(lines) {
|
|
339
|
+
const data = {};
|
|
340
|
+
const keys = [];
|
|
341
|
+
let i = 0;
|
|
342
|
+
while (i < lines.length) {
|
|
343
|
+
const line = lines[i];
|
|
344
|
+
const trimmed = line.trim();
|
|
345
|
+
if (trimmed === "" || trimmed.startsWith("#") || /^[ \t]/.test(line)) {
|
|
346
|
+
i++;
|
|
347
|
+
continue;
|
|
348
|
+
}
|
|
349
|
+
const m = line.match(KEY_RE2);
|
|
350
|
+
if (!m) {
|
|
351
|
+
i++;
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
const [, key, rawValue] = m;
|
|
355
|
+
const value = rawValue.trim();
|
|
356
|
+
i++;
|
|
357
|
+
const indented = [];
|
|
358
|
+
while (i < lines.length && /^[ \t]/.test(lines[i]) && lines[i].trim() !== "") {
|
|
359
|
+
indented.push(lines[i]);
|
|
360
|
+
i++;
|
|
361
|
+
}
|
|
362
|
+
keys.push(key);
|
|
363
|
+
data[key] = parseValue(value, indented);
|
|
364
|
+
}
|
|
365
|
+
return { data, keys };
|
|
366
|
+
}
|
|
367
|
+
function parseValue(value, indented) {
|
|
368
|
+
if (/^[|>][+-]?$/.test(value))
|
|
369
|
+
return dedent(indented).join("\n");
|
|
370
|
+
if (value === "") {
|
|
371
|
+
if (indented.length === 0)
|
|
372
|
+
return null;
|
|
373
|
+
const items = dedent(indented);
|
|
374
|
+
if (items.every((l) => l.startsWith("- ")))
|
|
375
|
+
return items.map((l) => parseScalar(l.slice(2).trim()));
|
|
376
|
+
return items.join("\n");
|
|
377
|
+
}
|
|
378
|
+
return parseScalar(value);
|
|
379
|
+
}
|
|
380
|
+
function dedent(lines) {
|
|
381
|
+
const indent = Math.min(...lines.map((l) => l.match(/^[ \t]*/)[0].length));
|
|
382
|
+
return lines.map((l) => l.slice(indent));
|
|
383
|
+
}
|
|
384
|
+
function parseScalar(value) {
|
|
385
|
+
if (value === "" || value === "null" || value === "~")
|
|
386
|
+
return null;
|
|
387
|
+
if (value === "true")
|
|
388
|
+
return true;
|
|
389
|
+
if (value === "false")
|
|
390
|
+
return false;
|
|
391
|
+
if (/^[+-]?\d+$/.test(value))
|
|
392
|
+
return Number.parseInt(value, 10);
|
|
393
|
+
if (/^[+-]?\d*\.\d+$/.test(value))
|
|
394
|
+
return Number.parseFloat(value);
|
|
395
|
+
if (value.startsWith('"') && value.endsWith('"') && value.length >= 2) {
|
|
396
|
+
try {
|
|
397
|
+
return JSON.parse(value);
|
|
398
|
+
} catch {
|
|
399
|
+
return value.slice(1, -1);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
if (value.startsWith("'") && value.endsWith("'") && value.length >= 2) {
|
|
403
|
+
return value.slice(1, -1).replace(/''/g, "'");
|
|
404
|
+
}
|
|
405
|
+
if (value.startsWith("[") && value.endsWith("]")) {
|
|
406
|
+
const inner = value.slice(1, -1).trim();
|
|
407
|
+
if (inner === "")
|
|
408
|
+
return [];
|
|
409
|
+
return splitInlineItems(inner).map((item) => parseScalar(item.trim()));
|
|
410
|
+
}
|
|
411
|
+
return value;
|
|
412
|
+
}
|
|
413
|
+
function splitInlineItems(inner) {
|
|
414
|
+
const items = [];
|
|
415
|
+
let current = "";
|
|
416
|
+
let quote = null;
|
|
417
|
+
for (const ch of inner) {
|
|
418
|
+
if (quote) {
|
|
419
|
+
current += ch;
|
|
420
|
+
if (ch === quote)
|
|
421
|
+
quote = null;
|
|
422
|
+
} else if (ch === '"' || ch === "'") {
|
|
423
|
+
current += ch;
|
|
424
|
+
quote = ch;
|
|
425
|
+
} else if (ch === ",") {
|
|
426
|
+
items.push(current);
|
|
427
|
+
current = "";
|
|
428
|
+
} else {
|
|
429
|
+
current += ch;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
items.push(current);
|
|
433
|
+
return items;
|
|
434
|
+
}
|
|
435
|
+
var FM_RE, KEY_RE2;
|
|
436
|
+
var init_frontmatter = __esm({
|
|
437
|
+
"../core/dist/frontmatter.js"() {
|
|
438
|
+
"use strict";
|
|
439
|
+
FM_RE = /^---[ \t]*\r?\n(?:[\s\S]*?\r?\n)?---[ \t]*(?:\r?\n|$)/;
|
|
440
|
+
KEY_RE2 = /^([A-Za-z0-9_.-]+):(.*)$/;
|
|
441
|
+
}
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
// ../core/dist/analyze.js
|
|
445
|
+
function utf8Length(source) {
|
|
446
|
+
let bytes = 0;
|
|
447
|
+
for (const ch of source) {
|
|
448
|
+
const cp = ch.codePointAt(0);
|
|
449
|
+
bytes += cp <= 127 ? 1 : cp <= 2047 ? 2 : cp <= 65535 ? 3 : 4;
|
|
450
|
+
}
|
|
451
|
+
return bytes;
|
|
452
|
+
}
|
|
453
|
+
function analyzeDocument(source, options = {}) {
|
|
454
|
+
const frontmatter = extractFrontmatter(source);
|
|
455
|
+
const { document } = parseDocument(stripFrontmatter(source));
|
|
456
|
+
const outline = [];
|
|
457
|
+
const blocks = { total: 0, byName: {}, maxDepth: 0, instances: [] };
|
|
458
|
+
const links = {
|
|
459
|
+
total: 0,
|
|
460
|
+
external: 0,
|
|
461
|
+
internal: 0,
|
|
462
|
+
domains: [],
|
|
463
|
+
items: []
|
|
464
|
+
};
|
|
465
|
+
const images = { total: 0, missingAlt: 0 };
|
|
466
|
+
const code = { fences: 0, languages: [], inlineSpans: 0 };
|
|
467
|
+
const structure = { listItems: 0, tables: 0, blockquotes: 0 };
|
|
468
|
+
const languages = /* @__PURE__ */ new Set();
|
|
469
|
+
const domains = /* @__PURE__ */ new Set();
|
|
470
|
+
let words = 0;
|
|
471
|
+
function addWords(count) {
|
|
472
|
+
words += count;
|
|
473
|
+
const section = outline[outline.length - 1];
|
|
474
|
+
if (section)
|
|
475
|
+
section.words += count;
|
|
476
|
+
}
|
|
477
|
+
function recordLink(url2, text, line) {
|
|
478
|
+
const external = EXTERNAL_URL_RE.test(url2);
|
|
479
|
+
links.total++;
|
|
480
|
+
if (external) {
|
|
481
|
+
links.external++;
|
|
482
|
+
const host = url2.match(/^(?:https?:)?\/\/(?:[^/?#@]*@)?([^/?#:]+)/i);
|
|
483
|
+
if (host)
|
|
484
|
+
domains.add(host[1].toLowerCase());
|
|
485
|
+
} else {
|
|
486
|
+
links.internal++;
|
|
487
|
+
}
|
|
488
|
+
links.items.push({ url: url2, text, line, external });
|
|
489
|
+
}
|
|
490
|
+
function inlineToProse(text, line) {
|
|
491
|
+
return text.replace(CODE_SPAN_RE, () => {
|
|
492
|
+
code.inlineSpans++;
|
|
493
|
+
return " ";
|
|
494
|
+
}).replace(IMAGE_RE, (_, alt) => {
|
|
495
|
+
images.total++;
|
|
496
|
+
if (alt.trim() === "")
|
|
497
|
+
images.missingAlt++;
|
|
498
|
+
return " ";
|
|
499
|
+
}).replace(LINK_RE, (_, label, url2) => {
|
|
500
|
+
recordLink(url2, label, line);
|
|
501
|
+
return label;
|
|
502
|
+
}).replace(AUTOLINK_RE, (_, url2) => {
|
|
503
|
+
recordLink(url2, url2, line);
|
|
504
|
+
return " ";
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
function countWords(text) {
|
|
508
|
+
return text.split(/\s+/).filter((token) => /[\p{L}\p{N}]/u.test(token)).length;
|
|
509
|
+
}
|
|
510
|
+
function scanMarkdown(value, startLine) {
|
|
511
|
+
let fence = null;
|
|
512
|
+
let inBlockquote = false;
|
|
513
|
+
let prevLineHasPipe = false;
|
|
514
|
+
const lines = value.split("\n");
|
|
515
|
+
for (let i = 0; i < lines.length; i++) {
|
|
516
|
+
const line = lines[i];
|
|
517
|
+
const lineNo = startLine + i;
|
|
518
|
+
const trimmed = line.trim();
|
|
519
|
+
const fenceMatch = trimmed.match(CODE_FENCE_RE2);
|
|
520
|
+
if (fence !== null) {
|
|
521
|
+
if (fenceMatch && fenceMatch[1][0] === fence[0] && fenceMatch[1].length >= fence.length) {
|
|
522
|
+
fence = null;
|
|
523
|
+
}
|
|
524
|
+
continue;
|
|
525
|
+
}
|
|
526
|
+
if (fenceMatch) {
|
|
527
|
+
fence = fenceMatch[1];
|
|
528
|
+
code.fences++;
|
|
529
|
+
const lang = fenceMatch[2].trim().split(/\s+/)[0];
|
|
530
|
+
if (lang)
|
|
531
|
+
languages.add(lang);
|
|
532
|
+
inBlockquote = false;
|
|
533
|
+
prevLineHasPipe = false;
|
|
534
|
+
continue;
|
|
535
|
+
}
|
|
536
|
+
if (trimmed === "") {
|
|
537
|
+
inBlockquote = false;
|
|
538
|
+
prevLineHasPipe = false;
|
|
539
|
+
continue;
|
|
540
|
+
}
|
|
541
|
+
const heading = trimmed.match(HEADING_RE);
|
|
542
|
+
if (heading) {
|
|
543
|
+
const raw = heading[2].replace(/\s+#+\s*$/, "");
|
|
544
|
+
const text = inlineToProse(raw, lineNo).replace(/\s+/g, " ").trim();
|
|
545
|
+
outline.push({ level: heading[1].length, text, line: lineNo, words: 0 });
|
|
546
|
+
addWords(countWords(text));
|
|
547
|
+
inBlockquote = false;
|
|
548
|
+
prevLineHasPipe = false;
|
|
549
|
+
continue;
|
|
550
|
+
}
|
|
551
|
+
let content = trimmed;
|
|
552
|
+
if (content.startsWith(">")) {
|
|
553
|
+
if (!inBlockquote) {
|
|
554
|
+
structure.blockquotes++;
|
|
555
|
+
inBlockquote = true;
|
|
556
|
+
}
|
|
557
|
+
content = content.replace(/^(>\s*)+/, "");
|
|
558
|
+
} else {
|
|
559
|
+
inBlockquote = false;
|
|
560
|
+
}
|
|
561
|
+
if (LIST_ITEM_RE.test(content)) {
|
|
562
|
+
structure.listItems++;
|
|
563
|
+
content = content.replace(LIST_ITEM_RE, "");
|
|
564
|
+
}
|
|
565
|
+
if (content.includes("|")) {
|
|
566
|
+
if (TABLE_SEPARATOR_RE.test(content) && content.includes("-") && prevLineHasPipe) {
|
|
567
|
+
structure.tables++;
|
|
568
|
+
}
|
|
569
|
+
prevLineHasPipe = true;
|
|
570
|
+
content = inlineToProse(content, lineNo).replaceAll("|", " ");
|
|
571
|
+
} else {
|
|
572
|
+
prevLineHasPipe = false;
|
|
573
|
+
content = inlineToProse(content, lineNo);
|
|
574
|
+
}
|
|
575
|
+
addWords(countWords(content));
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
function walk(nodes, depth) {
|
|
579
|
+
for (const node of nodes) {
|
|
580
|
+
if (node.type === "block") {
|
|
581
|
+
blocks.total++;
|
|
582
|
+
blocks.byName[node.name] = (blocks.byName[node.name] ?? 0) + 1;
|
|
583
|
+
blocks.maxDepth = Math.max(blocks.maxDepth, depth);
|
|
584
|
+
blocks.instances.push({ name: node.name, line: node.openPosition.start.line, depth });
|
|
585
|
+
walk(node.children, depth + 1);
|
|
586
|
+
} else {
|
|
587
|
+
scanMarkdown(node.value, node.position.start.line);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
walk(document.children, 1);
|
|
592
|
+
code.languages = [...languages];
|
|
593
|
+
links.domains = [...domains].sort();
|
|
594
|
+
const sourceLines = source === "" ? 0 : source.split("\n").length - (source.endsWith("\n") ? 1 : 0);
|
|
595
|
+
return {
|
|
596
|
+
file: {
|
|
597
|
+
path: options.path ?? null,
|
|
598
|
+
bytes: utf8Length(source),
|
|
599
|
+
lines: sourceLines
|
|
600
|
+
},
|
|
601
|
+
frontmatter: frontmatter ? {
|
|
602
|
+
present: true,
|
|
603
|
+
keys: frontmatter.keys,
|
|
604
|
+
data: frontmatter.data,
|
|
605
|
+
lines: frontmatter.lines
|
|
606
|
+
} : { present: false, keys: [], data: {}, lines: null },
|
|
607
|
+
length: {
|
|
608
|
+
words,
|
|
609
|
+
characters: source.length,
|
|
610
|
+
readingMinutes: Math.ceil(words / 200),
|
|
611
|
+
approxTokens: Math.ceil(source.length / 4)
|
|
612
|
+
},
|
|
613
|
+
outline,
|
|
614
|
+
blocks,
|
|
615
|
+
links,
|
|
616
|
+
images,
|
|
617
|
+
code,
|
|
618
|
+
structure
|
|
619
|
+
};
|
|
620
|
+
}
|
|
621
|
+
var CODE_FENCE_RE2, HEADING_RE, LIST_ITEM_RE, TABLE_SEPARATOR_RE, CODE_SPAN_RE, IMAGE_RE, LINK_RE, AUTOLINK_RE, EXTERNAL_URL_RE;
|
|
622
|
+
var init_analyze = __esm({
|
|
623
|
+
"../core/dist/analyze.js"() {
|
|
624
|
+
"use strict";
|
|
625
|
+
init_frontmatter();
|
|
626
|
+
init_parser();
|
|
627
|
+
CODE_FENCE_RE2 = /^(`{3,}|~{3,})(.*)$/;
|
|
628
|
+
HEADING_RE = /^(#{1,6})\s+(.*)$/;
|
|
629
|
+
LIST_ITEM_RE = /^\s*(?:[-*+]|\d+[.)])\s+/;
|
|
630
|
+
TABLE_SEPARATOR_RE = /^\|?[\s:|-]+$/;
|
|
631
|
+
CODE_SPAN_RE = /`[^`]+`/g;
|
|
632
|
+
IMAGE_RE = /!\[([^\]]*)\]\(([^)]*)\)/g;
|
|
633
|
+
LINK_RE = /\[([^\]]+)\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g;
|
|
634
|
+
AUTOLINK_RE = /<(https?:\/\/[^>\s]+)>/g;
|
|
635
|
+
EXTERNAL_URL_RE = /^(?:https?:)?\/\//i;
|
|
636
|
+
}
|
|
637
|
+
});
|
|
638
|
+
|
|
322
639
|
// ../core/dist/position.js
|
|
323
640
|
function bodyLineRange(node, bodyLineIndex) {
|
|
324
641
|
const bodyLines = node.body.split("\n");
|
|
@@ -339,6 +656,45 @@ var init_position = __esm({
|
|
|
339
656
|
});
|
|
340
657
|
|
|
341
658
|
// ../core/dist/authoring.js
|
|
659
|
+
function typeLabel(def) {
|
|
660
|
+
switch (def.type) {
|
|
661
|
+
case "enum":
|
|
662
|
+
return `one of ${Object.values(def.entries ?? {}).join("|")}`;
|
|
663
|
+
case "literal":
|
|
664
|
+
return `one of ${(def.values ?? []).map(String).join("|")}`;
|
|
665
|
+
case "union":
|
|
666
|
+
return `one of ${(def.options ?? []).map((o) => typeLabel(o.def).replace(/^one of /, "")).join("|")}`;
|
|
667
|
+
default:
|
|
668
|
+
return def.type;
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
function describeProps(schema) {
|
|
672
|
+
const root = schema.def;
|
|
673
|
+
if (root.type !== "object" || !root.shape)
|
|
674
|
+
return [];
|
|
675
|
+
const lines = [];
|
|
676
|
+
for (const [name, field] of Object.entries(root.shape)) {
|
|
677
|
+
let current = field;
|
|
678
|
+
let optional2 = false;
|
|
679
|
+
let defaultValue;
|
|
680
|
+
let description = current.description;
|
|
681
|
+
while (["optional", "default", "nullable"].includes(current.def.type)) {
|
|
682
|
+
optional2 = true;
|
|
683
|
+
if (current.def.type === "default") {
|
|
684
|
+
const dv = current.def.defaultValue;
|
|
685
|
+
defaultValue = typeof dv === "function" ? dv() : dv;
|
|
686
|
+
}
|
|
687
|
+
if (!current.def.innerType)
|
|
688
|
+
break;
|
|
689
|
+
current = current.def.innerType;
|
|
690
|
+
description ??= current.description;
|
|
691
|
+
}
|
|
692
|
+
const presence = defaultValue !== void 0 ? `(optional, default: ${String(defaultValue)})` : optional2 ? "(optional)" : "(required)";
|
|
693
|
+
const suffix = description ? ` \u2014 ${description}` : "";
|
|
694
|
+
lines.push(`- ${name}: ${typeLabel(current.def)} ${presence}${suffix}`);
|
|
695
|
+
}
|
|
696
|
+
return lines;
|
|
697
|
+
}
|
|
342
698
|
function generateAuthoringGuide(defs, opts = {}) {
|
|
343
699
|
const includeExamples = opts.includeExamples ?? true;
|
|
344
700
|
const includeAvoid = opts.includeAvoidRules ?? true;
|
|
@@ -348,6 +704,11 @@ function generateAuthoringGuide(defs, opts = {}) {
|
|
|
348
704
|
for (const def of defs) {
|
|
349
705
|
const lines = [`## ${def.name}`, ""];
|
|
350
706
|
lines.push(def.childOnly ? `${def.description} (child block \u2014 only inside a parent that allows it)` : def.description);
|
|
707
|
+
if (def.props) {
|
|
708
|
+
const props = describeProps(def.props);
|
|
709
|
+
if (props.length > 0)
|
|
710
|
+
lines.push("", "Props:", ...props);
|
|
711
|
+
}
|
|
351
712
|
lines.push("", `Content: ${def.content.describe()}`);
|
|
352
713
|
if (def.authoring.useWhen.length > 0) {
|
|
353
714
|
lines.push("", "Use when:", ...def.authoring.useWhen.map((u) => `- ${u}`));
|
|
@@ -741,6 +1102,8 @@ var init_dist = __esm({
|
|
|
741
1102
|
"use strict";
|
|
742
1103
|
init_diagnostics();
|
|
743
1104
|
init_parser();
|
|
1105
|
+
init_frontmatter();
|
|
1106
|
+
init_analyze();
|
|
744
1107
|
init_registry();
|
|
745
1108
|
init_content_models();
|
|
746
1109
|
init_validate();
|
|
@@ -6654,13 +7017,13 @@ var init_he = __esm({
|
|
|
6654
7017
|
// no unit
|
|
6655
7018
|
};
|
|
6656
7019
|
const typeEntry = (t) => t ? TypeNames[t] : void 0;
|
|
6657
|
-
const
|
|
7020
|
+
const typeLabel2 = (t) => {
|
|
6658
7021
|
const e = typeEntry(t);
|
|
6659
7022
|
if (e)
|
|
6660
7023
|
return e.label;
|
|
6661
7024
|
return t ?? TypeNames.unknown.label;
|
|
6662
7025
|
};
|
|
6663
|
-
const withDefinite = (t) => `\u05D4${
|
|
7026
|
+
const withDefinite = (t) => `\u05D4${typeLabel2(t)}`;
|
|
6664
7027
|
const verbFor = (t) => {
|
|
6665
7028
|
const e = typeEntry(t);
|
|
6666
7029
|
const gender = e?.gender ?? "m";
|
|
@@ -6710,7 +7073,7 @@ var init_he = __esm({
|
|
|
6710
7073
|
switch (issue2.code) {
|
|
6711
7074
|
case "invalid_type": {
|
|
6712
7075
|
const expectedKey = issue2.expected;
|
|
6713
|
-
const expected = TypeDictionary[expectedKey ?? ""] ??
|
|
7076
|
+
const expected = TypeDictionary[expectedKey ?? ""] ?? typeLabel2(expectedKey);
|
|
6714
7077
|
const receivedType = parsedType(issue2.input);
|
|
6715
7078
|
const received = TypeDictionary[receivedType] ?? TypeNames[receivedType]?.label ?? receivedType;
|
|
6716
7079
|
if (/^[A-Z]/.test(issue2.expected)) {
|
|
@@ -16181,16 +16544,223 @@ var init_load_registry = __esm({
|
|
|
16181
16544
|
}
|
|
16182
16545
|
});
|
|
16183
16546
|
|
|
16547
|
+
// src/commands/agents.ts
|
|
16548
|
+
var agents_exports = {};
|
|
16549
|
+
__export(agents_exports, {
|
|
16550
|
+
agentsCommand: () => agentsCommand,
|
|
16551
|
+
installAgentIntegration: () => installAgentIntegration
|
|
16552
|
+
});
|
|
16553
|
+
import { existsSync } from "node:fs";
|
|
16554
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
16555
|
+
import { join } from "node:path";
|
|
16556
|
+
import { parseArgs } from "node:util";
|
|
16557
|
+
function upsertBlock(existing) {
|
|
16558
|
+
const start = existing.indexOf(START);
|
|
16559
|
+
const end = existing.indexOf(END);
|
|
16560
|
+
if (start !== -1 && end !== -1) {
|
|
16561
|
+
return existing.slice(0, start) + AGENTS_MD_BLOCK + existing.slice(end + END.length);
|
|
16562
|
+
}
|
|
16563
|
+
if (existing.trim() === "") return `${AGENTS_MD_BLOCK}
|
|
16564
|
+
`;
|
|
16565
|
+
return `${existing.replace(/\n*$/, "\n\n")}${AGENTS_MD_BLOCK}
|
|
16566
|
+
`;
|
|
16567
|
+
}
|
|
16568
|
+
async function installAgentIntegration(cwd, options, io) {
|
|
16569
|
+
const claude = options.claude ?? existsSync(join(cwd, ".claude"));
|
|
16570
|
+
const agentsMd = options.agentsMd ?? true;
|
|
16571
|
+
if (agentsMd) {
|
|
16572
|
+
const path = join(cwd, "AGENTS.md");
|
|
16573
|
+
let existing = "";
|
|
16574
|
+
try {
|
|
16575
|
+
existing = await readFile(path, "utf8");
|
|
16576
|
+
} catch {
|
|
16577
|
+
}
|
|
16578
|
+
const created = existing === "";
|
|
16579
|
+
await writeFile(path, upsertBlock(existing), "utf8");
|
|
16580
|
+
io.stdout(`${created ? "created" : "updated"}: AGENTS.md (contentbit block)`);
|
|
16581
|
+
}
|
|
16582
|
+
if (claude) {
|
|
16583
|
+
const skills = [
|
|
16584
|
+
["contentbit-author", AUTHOR_SKILL],
|
|
16585
|
+
["contentbit-audit", AUDIT_SKILL]
|
|
16586
|
+
];
|
|
16587
|
+
for (const [name, content] of skills) {
|
|
16588
|
+
const dir = join(cwd, ".claude/skills", name);
|
|
16589
|
+
await mkdir(dir, { recursive: true });
|
|
16590
|
+
await writeFile(join(dir, "SKILL.md"), content, "utf8");
|
|
16591
|
+
io.stdout(`installed: .claude/skills/${name}/SKILL.md`);
|
|
16592
|
+
}
|
|
16593
|
+
}
|
|
16594
|
+
}
|
|
16595
|
+
async function agentsCommand(args, io) {
|
|
16596
|
+
const { values } = parseArgs({
|
|
16597
|
+
args,
|
|
16598
|
+
options: {
|
|
16599
|
+
claude: { type: "boolean", default: false },
|
|
16600
|
+
"no-agents-md": { type: "boolean", default: false },
|
|
16601
|
+
cwd: { type: "string", default: process.cwd() }
|
|
16602
|
+
}
|
|
16603
|
+
});
|
|
16604
|
+
await installAgentIntegration(
|
|
16605
|
+
values.cwd,
|
|
16606
|
+
{
|
|
16607
|
+
claude: values.claude || void 0,
|
|
16608
|
+
// false means "detect", not "skip"
|
|
16609
|
+
agentsMd: !values["no-agents-md"]
|
|
16610
|
+
},
|
|
16611
|
+
io
|
|
16612
|
+
);
|
|
16613
|
+
return 0;
|
|
16614
|
+
}
|
|
16615
|
+
var TEMPLATE_VERSION, AUTHOR_SKILL, AUDIT_SKILL, AGENTS_MD_BLOCK, START, END;
|
|
16616
|
+
var init_agents = __esm({
|
|
16617
|
+
"src/commands/agents.ts"() {
|
|
16618
|
+
"use strict";
|
|
16619
|
+
TEMPLATE_VERSION = 1;
|
|
16620
|
+
AUTHOR_SKILL = `---
|
|
16621
|
+
name: contentbit-author
|
|
16622
|
+
description: |
|
|
16623
|
+
Write or edit contentbit Markdown content (directive blocks like :::callout).
|
|
16624
|
+
Use when asked to create or modify content documents in a project that uses
|
|
16625
|
+
contentbit \u2014 blog posts, docs pages, changelogs, any Markdown covered by
|
|
16626
|
+
\`contentbit validate\`.
|
|
16627
|
+
version: ${TEMPLATE_VERSION}
|
|
16628
|
+
---
|
|
16629
|
+
|
|
16630
|
+
# Writing contentbit content
|
|
16631
|
+
|
|
16632
|
+
contentbit documents are plain Markdown plus directive blocks
|
|
16633
|
+
(\`:::name{props} ... :::\`). Every block has a schema. Never guess block names,
|
|
16634
|
+
props, or body shapes \u2014 fetch the live guide from the project's registry first.
|
|
16635
|
+
|
|
16636
|
+
## Find the project conventions
|
|
16637
|
+
|
|
16638
|
+
Check \`package.json\` for a \`content:check\` script. It holds the canonical
|
|
16639
|
+
validate invocation for this project: the content glob and, if present, the
|
|
16640
|
+
\`--registry <path>\` flag pointing at custom block definitions. Reuse both
|
|
16641
|
+
below. No script? Default to \`content/**/*.md\` with no \`--registry\` flag.
|
|
16642
|
+
|
|
16643
|
+
## The loop
|
|
16644
|
+
|
|
16645
|
+
1. **Fetch the authoring guide** (always \u2014 it covers this project's custom blocks):
|
|
16646
|
+
|
|
16647
|
+
\`\`\`sh
|
|
16648
|
+
contentbit instructions --audience llm [--registry <path from content:check>]
|
|
16649
|
+
\`\`\`
|
|
16650
|
+
|
|
16651
|
+
Read it before writing. It documents every available block: props, body
|
|
16652
|
+
shape, and when to use or avoid it.
|
|
16653
|
+
|
|
16654
|
+
2. **Write the document.** Plain Markdown everywhere; blocks only where the
|
|
16655
|
+
guide's use-when guidance fits. Keep frontmatter consistent with sibling
|
|
16656
|
+
documents in the same folder.
|
|
16657
|
+
|
|
16658
|
+
3. **Validate and fix until clean:**
|
|
16659
|
+
|
|
16660
|
+
\`\`\`sh
|
|
16661
|
+
contentbit validate <file> [--registry <path>]
|
|
16662
|
+
\`\`\`
|
|
16663
|
+
|
|
16664
|
+
Diagnostics print to stderr as \`file:line:col severity CODE message\`, often
|
|
16665
|
+
with a \`hint:\` line suggesting the fix. Exit 0 means clean; exit 1 means
|
|
16666
|
+
errors remain. Fix every diagnostic and re-run. Never finish with a failing
|
|
16667
|
+
validate.
|
|
16668
|
+
|
|
16669
|
+
## Failure modes
|
|
16670
|
+
|
|
16671
|
+
- \`contentbit\` not found or no registry resolvable: the project is not set up.
|
|
16672
|
+
Say so and suggest \`npx contentbit@latest init\` \u2014 do not invent block syntax.
|
|
16673
|
+
- A block you want does not exist: use plain Markdown, or ask whether to define
|
|
16674
|
+
a custom block in the registry. Never emit an unregistered block name.
|
|
16675
|
+
`;
|
|
16676
|
+
AUDIT_SKILL = `---
|
|
16677
|
+
name: contentbit-audit
|
|
16678
|
+
description: |
|
|
16679
|
+
Audit contentbit Markdown content health using document stats. Use when asked
|
|
16680
|
+
to audit, review, or find improvements across content \u2014 thin pages, missing
|
|
16681
|
+
structure, validation issues \u2014 in a project that uses contentbit.
|
|
16682
|
+
version: ${TEMPLATE_VERSION}
|
|
16683
|
+
---
|
|
16684
|
+
|
|
16685
|
+
# Auditing contentbit content
|
|
16686
|
+
|
|
16687
|
+
\`contentbit stats\` analyzes documents and prints JSON to stdout. It is a read
|
|
16688
|
+
tool: it always exits 0, even when documents have validation errors.
|
|
16689
|
+
|
|
16690
|
+
## Gather
|
|
16691
|
+
|
|
16692
|
+
Check \`package.json\` for the \`content:check\` script to find this project's
|
|
16693
|
+
content glob and \`--registry\` flag, then:
|
|
16694
|
+
|
|
16695
|
+
\`\`\`sh
|
|
16696
|
+
contentbit stats "content/**/*.md" [--registry <path>]
|
|
16697
|
+
\`\`\`
|
|
16698
|
+
|
|
16699
|
+
One matched file prints a single stats object; multiple files print an array.
|
|
16700
|
+
Each entry includes the file path, frontmatter data, a heading \`outline\` with
|
|
16701
|
+
per-section word counts, \`blocks.byName\` usage counts, \`links.domains\`, and
|
|
16702
|
+
a \`validation\` summary (\`errors\`/\`warnings\`).
|
|
16703
|
+
|
|
16704
|
+
## Interpret
|
|
16705
|
+
|
|
16706
|
+
Prioritize findings in this order:
|
|
16707
|
+
|
|
16708
|
+
1. **Validation errors and warnings** \u2014 broken content ships broken pages.
|
|
16709
|
+
2. **Thin documents** \u2014 outline sections with very low word counts.
|
|
16710
|
+
3. **Block-less documents** \u2014 \`blocks.byName\` empty where sibling documents
|
|
16711
|
+
use blocks; structure (steps, callouts, comparisons, faq) may be missing.
|
|
16712
|
+
4. **Missing or inconsistent frontmatter** compared to sibling documents.
|
|
16713
|
+
5. **Structural imbalance** \u2014 skipped heading levels, single-section walls of text.
|
|
16714
|
+
|
|
16715
|
+
## Report
|
|
16716
|
+
|
|
16717
|
+
Report findings per file with concrete suggestions, ordered by priority. Do not
|
|
16718
|
+
edit files during the audit. To fix a finding, follow the contentbit-author
|
|
16719
|
+
skill (fetch the guide, edit, validate until clean) \u2014 offer that as a follow-up.
|
|
16720
|
+
`;
|
|
16721
|
+
AGENTS_MD_BLOCK = `<!-- contentbit:start -->
|
|
16722
|
+
|
|
16723
|
+
## contentbit content (generated \u2014 edits inside this block are overwritten)
|
|
16724
|
+
|
|
16725
|
+
This project validates Markdown content with contentbit. Documents are plain
|
|
16726
|
+
Markdown plus directive blocks (\`:::name{props} ... :::\`), each with a schema.
|
|
16727
|
+
The \`content:check\` script in package.json holds the canonical validate
|
|
16728
|
+
command \u2014 the content glob and the \`--registry\` flag \u2014 reuse its arguments.
|
|
16729
|
+
|
|
16730
|
+
When writing or editing content:
|
|
16731
|
+
|
|
16732
|
+
1. Fetch the live authoring guide first \u2014 never guess block syntax:
|
|
16733
|
+
\`contentbit instructions --audience llm [--registry <path>]\`
|
|
16734
|
+
2. Write plain Markdown; use blocks where the guide's use-when guidance fits.
|
|
16735
|
+
3. Validate until clean (exit 0): \`contentbit validate <file> [--registry <path>]\`.
|
|
16736
|
+
Diagnostics print as \`file:line:col severity CODE message\` with fix hints.
|
|
16737
|
+
|
|
16738
|
+
When auditing content health:
|
|
16739
|
+
|
|
16740
|
+
- \`contentbit stats "content/**/*.md" [--registry <path>]\` prints JSON stats
|
|
16741
|
+
and always exits 0: outline word counts, block usage, link domains, and
|
|
16742
|
+
validation error/warning counts. Flag validation issues, thin documents, and
|
|
16743
|
+
block-less pages first.
|
|
16744
|
+
|
|
16745
|
+
If \`contentbit\` is unavailable, suggest \`npx contentbit@latest init\` instead
|
|
16746
|
+
of inventing block syntax.
|
|
16747
|
+
|
|
16748
|
+
<!-- contentbit:end -->`;
|
|
16749
|
+
START = "<!-- contentbit:start -->";
|
|
16750
|
+
END = "<!-- contentbit:end -->";
|
|
16751
|
+
}
|
|
16752
|
+
});
|
|
16753
|
+
|
|
16184
16754
|
// src/commands/init.ts
|
|
16185
16755
|
var init_exports = {};
|
|
16186
16756
|
__export(init_exports, {
|
|
16187
16757
|
initCommand: () => initCommand
|
|
16188
16758
|
});
|
|
16189
16759
|
import { spawn } from "node:child_process";
|
|
16190
|
-
import { existsSync } from "node:fs";
|
|
16191
|
-
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
16192
|
-
import { join } from "node:path";
|
|
16193
|
-
import { parseArgs } from "node:util";
|
|
16760
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
16761
|
+
import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
|
|
16762
|
+
import { join as join2 } from "node:path";
|
|
16763
|
+
import { parseArgs as parseArgs2 } from "node:util";
|
|
16194
16764
|
function blockComponentsTemplate(styled) {
|
|
16195
16765
|
const body = styled ? ` return (
|
|
16196
16766
|
<figure className="my-6 border-s-2 ps-4">
|
|
@@ -16282,7 +16852,7 @@ console.log('wrote example.html')
|
|
|
16282
16852
|
`;
|
|
16283
16853
|
}
|
|
16284
16854
|
function detectFramework(cwd, deps) {
|
|
16285
|
-
if ((deps["@tanstack/react-start"] || deps["@tanstack/react-router"]) &&
|
|
16855
|
+
if ((deps["@tanstack/react-start"] || deps["@tanstack/react-router"]) && existsSync2(join2(cwd, "src/routes"))) {
|
|
16286
16856
|
return {
|
|
16287
16857
|
framework: "tanstack",
|
|
16288
16858
|
componentPath: "src/components/content-blocks.tsx",
|
|
@@ -16290,8 +16860,8 @@ function detectFramework(cwd, deps) {
|
|
|
16290
16860
|
};
|
|
16291
16861
|
}
|
|
16292
16862
|
if (deps.next) {
|
|
16293
|
-
const appDir =
|
|
16294
|
-
if (
|
|
16863
|
+
const appDir = existsSync2(join2(cwd, "src/app")) ? "src/app" : "app";
|
|
16864
|
+
if (existsSync2(join2(cwd, appDir))) {
|
|
16295
16865
|
return {
|
|
16296
16866
|
framework: "next",
|
|
16297
16867
|
componentPath: "components/content-blocks.tsx",
|
|
@@ -16301,6 +16871,34 @@ function detectFramework(cwd, deps) {
|
|
|
16301
16871
|
}
|
|
16302
16872
|
return { framework: null, componentPath: "components/content-blocks.tsx", pagePath: null };
|
|
16303
16873
|
}
|
|
16874
|
+
function astroPage(styled) {
|
|
16875
|
+
const importLine = styled ? "import ContentRenderer from '../components/content-blocks/content-renderer.astro'" : "import { ContentBlocks } from '@contentbit/astro/components'";
|
|
16876
|
+
const renderer = styled ? "ContentRenderer" : "ContentBlocks";
|
|
16877
|
+
return `---
|
|
16878
|
+
import { genericBlocks } from '@contentbit/blocks'
|
|
16879
|
+
import { createBlockRegistry, parseDocument, validateDocument } from '@contentbit/core'
|
|
16880
|
+
import { getEntry } from 'astro:content'
|
|
16881
|
+
|
|
16882
|
+
${importLine}
|
|
16883
|
+
|
|
16884
|
+
// Definitions in blocks/registry.ts are shared with the validate CLI.
|
|
16885
|
+
import customBlocks from '../../blocks/registry'
|
|
16886
|
+
import QuoteBlock from '../../blocks/QuoteBlock.astro'
|
|
16887
|
+
|
|
16888
|
+
// Entry ids are the file path relative to the collection base, minus ".md".
|
|
16889
|
+
const entry = await getEntry('articles', 'example')
|
|
16890
|
+
if (!entry?.body) throw new Error('Entry "example" not found in the articles collection.')
|
|
16891
|
+
|
|
16892
|
+
const registry = createBlockRegistry().use(genericBlocks()).use(customBlocks)
|
|
16893
|
+
// Static pages render at build time, so invalid blocks fail the build here.
|
|
16894
|
+
const result = validateDocument(parseDocument(entry.body), registry)
|
|
16895
|
+
---
|
|
16896
|
+
|
|
16897
|
+
<main style="max-width: 42rem; margin: 0 auto; padding: 3rem 1.5rem;">
|
|
16898
|
+
<${renderer} document={result.document} components={{ quote: QuoteBlock }} />
|
|
16899
|
+
</main>
|
|
16900
|
+
`;
|
|
16901
|
+
}
|
|
16304
16902
|
function detectPackageManager(cwd) {
|
|
16305
16903
|
const locks = [
|
|
16306
16904
|
["pnpm-lock.yaml", "pnpm"],
|
|
@@ -16310,7 +16908,7 @@ function detectPackageManager(cwd) {
|
|
|
16310
16908
|
["package-lock.json", "npm"]
|
|
16311
16909
|
];
|
|
16312
16910
|
for (const [file2, pm] of locks) {
|
|
16313
|
-
if (
|
|
16911
|
+
if (existsSync2(join2(cwd, file2))) return pm;
|
|
16314
16912
|
}
|
|
16315
16913
|
const agent = process.env.npm_config_user_agent ?? "";
|
|
16316
16914
|
for (const pm of ["pnpm", "yarn", "bun"]) {
|
|
@@ -16335,18 +16933,38 @@ function runInstall(pm, args, cwd) {
|
|
|
16335
16933
|
child.on("error", () => resolve(1));
|
|
16336
16934
|
});
|
|
16337
16935
|
}
|
|
16936
|
+
async function installStyledPack(cwd, pack, noInstall, io) {
|
|
16937
|
+
const componentsJsonPath = join2(cwd, "components.json");
|
|
16938
|
+
const componentsJson = JSON.parse(await readFile2(componentsJsonPath, "utf8"));
|
|
16939
|
+
componentsJson.registries ??= {};
|
|
16940
|
+
if (!componentsJson.registries["@contentbit"]) {
|
|
16941
|
+
componentsJson.registries["@contentbit"] = "https://contentbit.dev/r/{name}.json";
|
|
16942
|
+
await writeFile2(componentsJsonPath, `${JSON.stringify(componentsJson, null, 2)}
|
|
16943
|
+
`, "utf8");
|
|
16944
|
+
io.stdout("added @contentbit registry to components.json");
|
|
16945
|
+
}
|
|
16946
|
+
if (noInstall) {
|
|
16947
|
+
io.stdout(`skipped: shadcn add ${pack}`);
|
|
16948
|
+
return true;
|
|
16949
|
+
}
|
|
16950
|
+
const [bin, prefix] = dlxCommand(detectPackageManager(cwd));
|
|
16951
|
+
io.stdout(`installing the styled pack: shadcn add ${pack}`);
|
|
16952
|
+
const code = await runInstall(bin, [...prefix, "shadcn@latest", "add", pack, "--yes"], cwd);
|
|
16953
|
+
if (code !== 0) io.stderr("styled pack install failed; falling back to headless defaults");
|
|
16954
|
+
return code === 0;
|
|
16955
|
+
}
|
|
16338
16956
|
async function scaffold(path, content) {
|
|
16339
16957
|
try {
|
|
16340
|
-
await
|
|
16958
|
+
await readFile2(path, "utf8");
|
|
16341
16959
|
return "skipped";
|
|
16342
16960
|
} catch {
|
|
16343
|
-
await
|
|
16344
|
-
await
|
|
16961
|
+
await mkdir2(join2(path, ".."), { recursive: true });
|
|
16962
|
+
await writeFile2(path, content, "utf8");
|
|
16345
16963
|
return "created";
|
|
16346
16964
|
}
|
|
16347
16965
|
}
|
|
16348
16966
|
async function initCommand(args, io) {
|
|
16349
|
-
const { values } =
|
|
16967
|
+
const { values } = parseArgs2({
|
|
16350
16968
|
args,
|
|
16351
16969
|
options: {
|
|
16352
16970
|
target: { type: "string", short: "t" },
|
|
@@ -16355,20 +16973,22 @@ async function initCommand(args, io) {
|
|
|
16355
16973
|
cwd: { type: "string", default: process.cwd() },
|
|
16356
16974
|
"no-install": { type: "boolean", default: false },
|
|
16357
16975
|
"no-page": { type: "boolean", default: false },
|
|
16358
|
-
"no-styled": { type: "boolean", default: false }
|
|
16976
|
+
"no-styled": { type: "boolean", default: false },
|
|
16977
|
+
"no-agents": { type: "boolean", default: false }
|
|
16359
16978
|
}
|
|
16360
16979
|
});
|
|
16361
16980
|
const cwd = values.cwd;
|
|
16362
16981
|
let pkg;
|
|
16363
|
-
const pkgPath =
|
|
16982
|
+
const pkgPath = join2(cwd, "package.json");
|
|
16364
16983
|
try {
|
|
16365
|
-
pkg = JSON.parse(await
|
|
16984
|
+
pkg = JSON.parse(await readFile2(pkgPath, "utf8"));
|
|
16366
16985
|
} catch {
|
|
16367
16986
|
io.stderr("No package.json found. Run this inside a project (npm init first).");
|
|
16368
16987
|
return 1;
|
|
16369
16988
|
}
|
|
16370
16989
|
const hasReact = Boolean(pkg.dependencies?.react ?? pkg.devDependencies?.react);
|
|
16371
|
-
const
|
|
16990
|
+
const hasAstro = Boolean(pkg.dependencies?.astro ?? pkg.devDependencies?.astro);
|
|
16991
|
+
const detected = hasAstro ? "astro" : hasReact ? "react" : "html";
|
|
16372
16992
|
let target;
|
|
16373
16993
|
if (values.target) {
|
|
16374
16994
|
if (!TARGETS.includes(values.target)) {
|
|
@@ -16383,6 +17003,7 @@ async function initCommand(args, io) {
|
|
|
16383
17003
|
initialValue: detected,
|
|
16384
17004
|
options: [
|
|
16385
17005
|
{ value: "react", label: "React", hint: "ContentBlocks component" },
|
|
17006
|
+
{ value: "astro", label: "Astro", hint: "content collections + .astro components" },
|
|
16386
17007
|
{ value: "html", label: "Static HTML", hint: "renderToHtml, no framework" },
|
|
16387
17008
|
{ value: "markdown", label: "Plain Markdown", hint: "fallback rendering only" }
|
|
16388
17009
|
]
|
|
@@ -16419,6 +17040,7 @@ async function initCommand(args, io) {
|
|
|
16419
17040
|
const runtime = ["@contentbit/core", "@contentbit/blocks", "zod"];
|
|
16420
17041
|
if (target === "react") runtime.push("@contentbit/react");
|
|
16421
17042
|
if (target === "html") runtime.push("@contentbit/html");
|
|
17043
|
+
if (target === "astro") runtime.push("@contentbit/astro");
|
|
16422
17044
|
if (md !== "none") runtime.push(md);
|
|
16423
17045
|
if (values["no-install"]) {
|
|
16424
17046
|
io.stdout(`skipped install: ${runtime.join(" ")} + contentbit (dev)`);
|
|
@@ -16440,30 +17062,9 @@ async function initCommand(args, io) {
|
|
|
16440
17062
|
];
|
|
16441
17063
|
const layout = detectFramework(cwd, { ...pkg.dependencies, ...pkg.devDependencies });
|
|
16442
17064
|
let styled = false;
|
|
16443
|
-
const componentsJsonPath =
|
|
16444
|
-
if (target === "react" && !values["no-styled"] &&
|
|
16445
|
-
|
|
16446
|
-
componentsJson.registries ??= {};
|
|
16447
|
-
if (!componentsJson.registries["@contentbit"]) {
|
|
16448
|
-
componentsJson.registries["@contentbit"] = "https://contentbit.dev/r/{name}.json";
|
|
16449
|
-
await writeFile(componentsJsonPath, `${JSON.stringify(componentsJson, null, 2)}
|
|
16450
|
-
`, "utf8");
|
|
16451
|
-
io.stdout("added @contentbit registry to components.json");
|
|
16452
|
-
}
|
|
16453
|
-
if (values["no-install"]) {
|
|
16454
|
-
io.stdout("skipped: shadcn add @contentbit/generic-pack");
|
|
16455
|
-
styled = true;
|
|
16456
|
-
} else {
|
|
16457
|
-
const [bin, prefix] = dlxCommand(detectPackageManager(cwd));
|
|
16458
|
-
io.stdout("installing the styled pack: shadcn add @contentbit/generic-pack");
|
|
16459
|
-
const code = await runInstall(
|
|
16460
|
-
bin,
|
|
16461
|
-
[...prefix, "shadcn@latest", "add", "@contentbit/generic-pack", "--yes"],
|
|
16462
|
-
cwd
|
|
16463
|
-
);
|
|
16464
|
-
if (code === 0) styled = true;
|
|
16465
|
-
else io.stderr("styled pack install failed; falling back to headless defaults");
|
|
16466
|
-
}
|
|
17065
|
+
const componentsJsonPath = join2(cwd, "components.json");
|
|
17066
|
+
if (target === "react" && !values["no-styled"] && existsSync2(componentsJsonPath)) {
|
|
17067
|
+
styled = await installStyledPack(cwd, "@contentbit/generic-pack", values["no-install"], io);
|
|
16467
17068
|
}
|
|
16468
17069
|
if (target === "react") {
|
|
16469
17070
|
const depth = layout.componentPath.split("/").length - 1;
|
|
@@ -16483,27 +17084,52 @@ async function initCommand(args, io) {
|
|
|
16483
17084
|
htmlRenderScript(md)
|
|
16484
17085
|
]);
|
|
16485
17086
|
}
|
|
17087
|
+
if (target === "astro") {
|
|
17088
|
+
let astroStyled = false;
|
|
17089
|
+
if (!values["no-styled"] && existsSync2(componentsJsonPath)) {
|
|
17090
|
+
astroStyled = await installStyledPack(cwd, "@contentbit/astro-pack", values["no-install"], io);
|
|
17091
|
+
}
|
|
17092
|
+
files.push(["blocks/QuoteBlock.astro", ASTRO_QUOTE_BLOCK]);
|
|
17093
|
+
const configCandidates = ["ts", "mts", "mjs", "js"].flatMap((ext) => [
|
|
17094
|
+
`src/content.config.${ext}`,
|
|
17095
|
+
`src/content/config.${ext}`
|
|
17096
|
+
]);
|
|
17097
|
+
const existingConfig = configCandidates.find((p) => existsSync2(join2(cwd, p)));
|
|
17098
|
+
if (existingConfig) {
|
|
17099
|
+
io.stdout(`content config exists (${existingConfig}); add this collection manually:`);
|
|
17100
|
+
io.stdout(ASTRO_CONTENT_CONFIG);
|
|
17101
|
+
io.stdout('the example page expects the "articles" collection above');
|
|
17102
|
+
} else {
|
|
17103
|
+
files.push(["src/content.config.ts", ASTRO_CONTENT_CONFIG]);
|
|
17104
|
+
}
|
|
17105
|
+
if (!values["no-page"]) files.push(["src/pages/example.astro", astroPage(astroStyled)]);
|
|
17106
|
+
}
|
|
16486
17107
|
for (const [rel, content] of files) {
|
|
16487
|
-
const result = await scaffold(
|
|
17108
|
+
const result = await scaffold(join2(cwd, rel), content);
|
|
16488
17109
|
io.stdout(`${result}: ${rel}`);
|
|
16489
17110
|
}
|
|
16490
|
-
const fresh = JSON.parse(await
|
|
17111
|
+
const fresh = JSON.parse(await readFile2(pkgPath, "utf8"));
|
|
16491
17112
|
fresh.scripts ??= {};
|
|
16492
17113
|
if (!fresh.scripts["content:check"]) {
|
|
16493
17114
|
fresh.scripts["content:check"] = 'contentbit validate "content/**/*.md" --registry ./blocks/registry.ts';
|
|
16494
|
-
await
|
|
17115
|
+
await writeFile2(pkgPath, `${JSON.stringify(fresh, null, 2)}
|
|
16495
17116
|
`, "utf8");
|
|
16496
17117
|
io.stdout("added script: content:check");
|
|
16497
17118
|
}
|
|
16498
17119
|
let registry2;
|
|
16499
17120
|
try {
|
|
16500
|
-
registry2 = await loadRegistry(
|
|
17121
|
+
registry2 = await loadRegistry(join2(cwd, "blocks/registry.ts"));
|
|
16501
17122
|
} catch {
|
|
16502
17123
|
registry2 = await loadRegistry();
|
|
16503
17124
|
}
|
|
16504
17125
|
const guide = registry2.toAuthoringGuide({ audience: "llm", includeExamples: true });
|
|
16505
|
-
await
|
|
17126
|
+
await writeFile2(join2(cwd, "contentbit-guide.md"), guide, "utf8");
|
|
16506
17127
|
io.stdout("created: contentbit-guide.md (LLM authoring instructions)");
|
|
17128
|
+
if (!values["no-agents"]) {
|
|
17129
|
+
await installAgentIntegration(cwd, {}, io);
|
|
17130
|
+
io.stdout("Agent integration installed \u2014 try asking your agent:");
|
|
17131
|
+
io.stdout(' "write a blog post about X" or "audit my content"');
|
|
17132
|
+
}
|
|
16507
17133
|
io.stdout("");
|
|
16508
17134
|
io.stdout("Done. Next steps:");
|
|
16509
17135
|
io.stdout(` 1. Validate the starter content: ${detectPackageManager(cwd)} run content:check`);
|
|
@@ -16515,6 +17141,9 @@ async function initCommand(args, io) {
|
|
|
16515
17141
|
io.stdout(" <Content source={...content/example.md as a string} />");
|
|
16516
17142
|
}
|
|
16517
17143
|
io.stdout(" 3. Styled components: pnpm dlx shadcn@latest add @contentbit/generic-pack");
|
|
17144
|
+
} else if (target === "astro") {
|
|
17145
|
+
io.stdout(" 2. Start the dev server and open /example to see the article rendered.");
|
|
17146
|
+
io.stdout(" 3. Styled components: pnpm dlx shadcn@latest add @contentbit/astro-pack");
|
|
16518
17147
|
} else if (target === "html") {
|
|
16519
17148
|
io.stdout(" 2. Render it: node scripts/render-example.mjs && open example.html");
|
|
16520
17149
|
} else {
|
|
@@ -16523,16 +17152,19 @@ async function initCommand(args, io) {
|
|
|
16523
17152
|
io.stdout(" Docs: https://contentbit.dev/docs");
|
|
16524
17153
|
return 0;
|
|
16525
17154
|
}
|
|
16526
|
-
var TARGETS, MD_CHOICES, REGISTRY_TEMPLATE, EXAMPLE_CONTENT, TANSTACK_PAGE, NEXT_PAGE;
|
|
17155
|
+
var TARGETS, MD_CHOICES, REGISTRY_TEMPLATE, EXAMPLE_CONTENT, TANSTACK_PAGE, NEXT_PAGE, ASTRO_CONTENT_CONFIG, ASTRO_QUOTE_BLOCK;
|
|
16527
17156
|
var init_init = __esm({
|
|
16528
17157
|
"src/commands/init.ts"() {
|
|
16529
17158
|
"use strict";
|
|
16530
17159
|
init_load_registry();
|
|
16531
|
-
|
|
17160
|
+
init_agents();
|
|
17161
|
+
TARGETS = ["react", "html", "markdown", "astro"];
|
|
16532
17162
|
MD_CHOICES = {
|
|
16533
17163
|
react: ["react-markdown", "none"],
|
|
16534
17164
|
html: ["marked", "markdown-it", "none"],
|
|
16535
|
-
markdown: ["none"]
|
|
17165
|
+
markdown: ["none"],
|
|
17166
|
+
// @contentbit/astro ships its own marked-based default; nothing to install.
|
|
17167
|
+
astro: ["none"]
|
|
16536
17168
|
};
|
|
16537
17169
|
REGISTRY_TEMPLATE = `// Custom block definitions for this project. The CLI and your app share
|
|
16538
17170
|
// this module \u2014 Node 22.18+ imports TypeScript directly:
|
|
@@ -16613,6 +17245,36 @@ export default async function ExamplePage() {
|
|
|
16613
17245
|
</main>
|
|
16614
17246
|
)
|
|
16615
17247
|
}
|
|
17248
|
+
`;
|
|
17249
|
+
ASTRO_CONTENT_CONFIG = `import { defineCollection } from 'astro:content'
|
|
17250
|
+
import { glob } from 'astro/loaders'
|
|
17251
|
+
|
|
17252
|
+
export const collections = {
|
|
17253
|
+
articles: defineCollection({
|
|
17254
|
+
// Astro's builtin Markdown loader. Entry bodies are parsed and validated
|
|
17255
|
+
// where they render (see src/pages/example.astro); \`contentbit validate\`
|
|
17256
|
+
// covers the same files in CI.
|
|
17257
|
+
loader: glob({ pattern: '**/*.md', base: './content' }),
|
|
17258
|
+
}),
|
|
17259
|
+
}
|
|
17260
|
+
`;
|
|
17261
|
+
ASTRO_QUOTE_BLOCK = `---
|
|
17262
|
+
// The Astro component for the custom \`quote\` block defined in blocks/registry.ts.
|
|
17263
|
+
// Block props arrive as component props; nested content arrives via <slot />.
|
|
17264
|
+
interface Props {
|
|
17265
|
+
author: string
|
|
17266
|
+
role?: string
|
|
17267
|
+
}
|
|
17268
|
+
|
|
17269
|
+
const { author, role } = Astro.props
|
|
17270
|
+
---
|
|
17271
|
+
|
|
17272
|
+
<figure style="margin: 1.5rem 0; border-left: 2px solid #d4d4d4; padding-left: 1rem;">
|
|
17273
|
+
<blockquote style="font-style: italic;"><slot /></blockquote>
|
|
17274
|
+
<figcaption style="margin-top: 0.5rem; font-size: 0.875rem; opacity: 0.7;">
|
|
17275
|
+
\u2014 {author}{role ? \`, \${role}\` : null}
|
|
17276
|
+
</figcaption>
|
|
17277
|
+
</figure>
|
|
16616
17278
|
`;
|
|
16617
17279
|
}
|
|
16618
17280
|
});
|
|
@@ -16622,11 +17284,11 @@ var validate_exports = {};
|
|
|
16622
17284
|
__export(validate_exports, {
|
|
16623
17285
|
validateCommand: () => validateCommand
|
|
16624
17286
|
});
|
|
16625
|
-
import { readFile as
|
|
16626
|
-
import { parseArgs as
|
|
17287
|
+
import { readFile as readFile3 } from "node:fs/promises";
|
|
17288
|
+
import { parseArgs as parseArgs3 } from "node:util";
|
|
16627
17289
|
import { glob } from "tinyglobby";
|
|
16628
17290
|
async function validateCommand(args, io) {
|
|
16629
|
-
const { values, positionals } =
|
|
17291
|
+
const { values, positionals } = parseArgs3({
|
|
16630
17292
|
args,
|
|
16631
17293
|
allowPositionals: true,
|
|
16632
17294
|
options: {
|
|
@@ -16647,8 +17309,8 @@ async function validateCommand(args, io) {
|
|
|
16647
17309
|
let errors = 0;
|
|
16648
17310
|
let warnings = 0;
|
|
16649
17311
|
for (const file2 of files.sort()) {
|
|
16650
|
-
const source = await
|
|
16651
|
-
const result = validateDocument(parseDocument(source), registry2);
|
|
17312
|
+
const source = await readFile3(file2, "utf8");
|
|
17313
|
+
const result = validateDocument(parseDocument(stripFrontmatter(source)), registry2);
|
|
16652
17314
|
for (const d of result.diagnostics) {
|
|
16653
17315
|
io.stderr(formatDiagnostic(d, file2));
|
|
16654
17316
|
if (d.severity === "error") errors++;
|
|
@@ -16668,6 +17330,63 @@ var init_validate2 = __esm({
|
|
|
16668
17330
|
}
|
|
16669
17331
|
});
|
|
16670
17332
|
|
|
17333
|
+
// src/commands/stats.ts
|
|
17334
|
+
var stats_exports = {};
|
|
17335
|
+
__export(stats_exports, {
|
|
17336
|
+
statsCommand: () => statsCommand
|
|
17337
|
+
});
|
|
17338
|
+
import { readFile as readFile4 } from "node:fs/promises";
|
|
17339
|
+
import { parseArgs as parseArgs4 } from "node:util";
|
|
17340
|
+
import { glob as glob2 } from "tinyglobby";
|
|
17341
|
+
async function fileStats(file2, registry2) {
|
|
17342
|
+
const source = await readFile4(file2, "utf8");
|
|
17343
|
+
const stats = analyzeDocument(source, { path: file2 });
|
|
17344
|
+
if (registry2) {
|
|
17345
|
+
const result = validateDocument(parseDocument(stripFrontmatter(source)), registry2);
|
|
17346
|
+
let errors = 0;
|
|
17347
|
+
let warnings = 0;
|
|
17348
|
+
for (const d of result.diagnostics) {
|
|
17349
|
+
if (d.severity === "error") errors++;
|
|
17350
|
+
else if (d.severity === "warning") warnings++;
|
|
17351
|
+
}
|
|
17352
|
+
stats.validation = { errors, warnings };
|
|
17353
|
+
}
|
|
17354
|
+
return stats;
|
|
17355
|
+
}
|
|
17356
|
+
async function statsCommand(args, io) {
|
|
17357
|
+
const { values, positionals } = parseArgs4({
|
|
17358
|
+
args,
|
|
17359
|
+
allowPositionals: true,
|
|
17360
|
+
options: {
|
|
17361
|
+
registry: { type: "string" },
|
|
17362
|
+
"no-validate": { type: "boolean", default: false }
|
|
17363
|
+
}
|
|
17364
|
+
});
|
|
17365
|
+
if (positionals.length === 0) {
|
|
17366
|
+
io.stderr("stats: provide at least one file or glob.");
|
|
17367
|
+
return 2;
|
|
17368
|
+
}
|
|
17369
|
+
const files = await glob2(positionals, { absolute: true });
|
|
17370
|
+
if (files.length === 0) {
|
|
17371
|
+
io.stderr(`stats: no files matched ${positionals.join(" ")}`);
|
|
17372
|
+
return 2;
|
|
17373
|
+
}
|
|
17374
|
+
const registry2 = values["no-validate"] ? null : await loadRegistry(values.registry);
|
|
17375
|
+
const all = [];
|
|
17376
|
+
for (const file2 of files.sort()) {
|
|
17377
|
+
all.push(await fileStats(file2, registry2));
|
|
17378
|
+
}
|
|
17379
|
+
io.stdout(JSON.stringify(all.length === 1 ? all[0] : all, null, 2));
|
|
17380
|
+
return 0;
|
|
17381
|
+
}
|
|
17382
|
+
var init_stats = __esm({
|
|
17383
|
+
"src/commands/stats.ts"() {
|
|
17384
|
+
"use strict";
|
|
17385
|
+
init_dist();
|
|
17386
|
+
init_load_registry();
|
|
17387
|
+
}
|
|
17388
|
+
});
|
|
17389
|
+
|
|
16671
17390
|
// ../html/dist/escape.js
|
|
16672
17391
|
function escapeHtml(value) {
|
|
16673
17392
|
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
@@ -16748,12 +17467,18 @@ var init_blocks = __esm({
|
|
|
16748
17467
|
});
|
|
16749
17468
|
|
|
16750
17469
|
// ../html/dist/render.js
|
|
16751
|
-
function
|
|
17470
|
+
function fallbackMarkdown(md) {
|
|
16752
17471
|
return md.trim().split(/\n{2,}/).map((p) => `<p>${escapeHtml(p)}</p>`).join("\n");
|
|
16753
17472
|
}
|
|
17473
|
+
function invalidBlockHtml(node, prefix) {
|
|
17474
|
+
return `<div class="${prefix}invalid" data-cb-invalid="${escapeHtml(node.name)}"><pre>${escapeHtml(node.body)}</pre></div>`;
|
|
17475
|
+
}
|
|
17476
|
+
function unrenderableBlockError(name) {
|
|
17477
|
+
return new Error(`Cannot render block "${name}": not validated or no renderer registered.`);
|
|
17478
|
+
}
|
|
16754
17479
|
function renderToHtml(document, opts = {}) {
|
|
16755
17480
|
const prefix = opts.classPrefix ?? "cb-";
|
|
16756
|
-
const renderMarkdown = opts.renderMarkdown ??
|
|
17481
|
+
const renderMarkdown = opts.renderMarkdown ?? fallbackMarkdown;
|
|
16757
17482
|
const renderers = { ...genericHtmlRenderers, ...opts.renderers };
|
|
16758
17483
|
const onInvalid = opts.onInvalid ?? "fallback";
|
|
16759
17484
|
const ctx = {
|
|
@@ -16767,13 +17492,11 @@ function renderToHtml(document, opts = {}) {
|
|
|
16767
17492
|
const renderer = renderers[node.name];
|
|
16768
17493
|
if (renderer && isValidatedBlock(node))
|
|
16769
17494
|
return renderer(node, ctx);
|
|
16770
|
-
if (onInvalid === "strict")
|
|
16771
|
-
throw
|
|
16772
|
-
|
|
16773
|
-
|
|
16774
|
-
|
|
16775
|
-
}
|
|
16776
|
-
return defaultMarkdown(node.body);
|
|
17495
|
+
if (onInvalid === "strict")
|
|
17496
|
+
throw unrenderableBlockError(node.name);
|
|
17497
|
+
if (onInvalid === "annotated")
|
|
17498
|
+
return invalidBlockHtml(node, prefix);
|
|
17499
|
+
return fallbackMarkdown(node.body);
|
|
16777
17500
|
}).join("\n");
|
|
16778
17501
|
}
|
|
16779
17502
|
};
|
|
@@ -16801,10 +17524,10 @@ var render_exports = {};
|
|
|
16801
17524
|
__export(render_exports, {
|
|
16802
17525
|
renderCommand: () => renderCommand
|
|
16803
17526
|
});
|
|
16804
|
-
import { readFile as
|
|
16805
|
-
import { parseArgs as
|
|
17527
|
+
import { readFile as readFile5 } from "node:fs/promises";
|
|
17528
|
+
import { parseArgs as parseArgs5 } from "node:util";
|
|
16806
17529
|
async function renderCommand(args, io) {
|
|
16807
|
-
const { values, positionals } =
|
|
17530
|
+
const { values, positionals } = parseArgs5({
|
|
16808
17531
|
args,
|
|
16809
17532
|
allowPositionals: true,
|
|
16810
17533
|
options: {
|
|
@@ -16819,7 +17542,7 @@ async function renderCommand(args, io) {
|
|
|
16819
17542
|
return 2;
|
|
16820
17543
|
}
|
|
16821
17544
|
const registry2 = await loadRegistry(values.registry);
|
|
16822
|
-
const source = await
|
|
17545
|
+
const source = await readFile5(file2, "utf8");
|
|
16823
17546
|
const result = validateDocument(parseDocument(source), registry2);
|
|
16824
17547
|
if (!result.ok) {
|
|
16825
17548
|
for (const d of result.diagnostics) io.stderr(formatDiagnostic(d, file2));
|
|
@@ -16845,9 +17568,9 @@ var instructions_exports = {};
|
|
|
16845
17568
|
__export(instructions_exports, {
|
|
16846
17569
|
instructionsCommand: () => instructionsCommand
|
|
16847
17570
|
});
|
|
16848
|
-
import { parseArgs as
|
|
17571
|
+
import { parseArgs as parseArgs6 } from "node:util";
|
|
16849
17572
|
async function instructionsCommand(args, io) {
|
|
16850
|
-
const { values } =
|
|
17573
|
+
const { values } = parseArgs6({
|
|
16851
17574
|
args,
|
|
16852
17575
|
options: {
|
|
16853
17576
|
audience: { type: "string", default: "llm" },
|
|
@@ -16877,9 +17600,9 @@ var docs_exports = {};
|
|
|
16877
17600
|
__export(docs_exports, {
|
|
16878
17601
|
docsCommand: () => docsCommand
|
|
16879
17602
|
});
|
|
16880
|
-
import { parseArgs as
|
|
17603
|
+
import { parseArgs as parseArgs7 } from "node:util";
|
|
16881
17604
|
async function docsCommand(args, io) {
|
|
16882
|
-
const { values } =
|
|
17605
|
+
const { values } = parseArgs7({
|
|
16883
17606
|
args,
|
|
16884
17607
|
options: {
|
|
16885
17608
|
registry: { type: "string" },
|
|
@@ -16900,20 +17623,24 @@ var init_docs = __esm({
|
|
|
16900
17623
|
});
|
|
16901
17624
|
|
|
16902
17625
|
// src/run.ts
|
|
16903
|
-
var USAGE = `Usage: contentbit <init|validate|render|instructions|docs> [options]
|
|
17626
|
+
var USAGE = `Usage: contentbit <init|validate|stats|render|instructions|docs|agents> [options]
|
|
16904
17627
|
|
|
16905
|
-
init [-t react|html|markdown] [--md ...] [-y] [--no-install] [--no-page]
|
|
17628
|
+
init [-t react|html|markdown|astro] [--md ...] [-y] [--no-install] [--no-page] [--no-agents]
|
|
17629
|
+
agents [--claude] [--no-agents-md]
|
|
16906
17630
|
|
|
16907
17631
|
validate <globs...> [--registry <module.mjs>] [--strict-warnings]
|
|
17632
|
+
stats <globs...> [--registry <module.mjs>] [--no-validate]
|
|
16908
17633
|
render <file> --target html|markdown [--registry <module.mjs>] [--out <file>]
|
|
16909
17634
|
instructions [--audience llm|human] [--no-examples] [--registry <module.mjs>] [--out <file>]
|
|
16910
17635
|
docs [--registry <module.mjs>] [--out <file>]`;
|
|
16911
17636
|
var commands = {
|
|
16912
17637
|
init: async () => (await Promise.resolve().then(() => (init_init(), init_exports))).initCommand,
|
|
16913
17638
|
validate: async () => (await Promise.resolve().then(() => (init_validate2(), validate_exports))).validateCommand,
|
|
17639
|
+
stats: async () => (await Promise.resolve().then(() => (init_stats(), stats_exports))).statsCommand,
|
|
16914
17640
|
render: async () => (await Promise.resolve().then(() => (init_render2(), render_exports))).renderCommand,
|
|
16915
17641
|
instructions: async () => (await Promise.resolve().then(() => (init_instructions(), instructions_exports))).instructionsCommand,
|
|
16916
|
-
docs: async () => (await Promise.resolve().then(() => (init_docs(), docs_exports))).docsCommand
|
|
17642
|
+
docs: async () => (await Promise.resolve().then(() => (init_docs(), docs_exports))).docsCommand,
|
|
17643
|
+
agents: async () => (await Promise.resolve().then(() => (init_agents(), agents_exports))).agentsCommand
|
|
16917
17644
|
};
|
|
16918
17645
|
async function run(argv, io) {
|
|
16919
17646
|
const [name, ...rest] = argv;
|