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/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)) {
|
|
@@ -16182,16 +16545,223 @@ var init_load_registry = __esm({
|
|
|
16182
16545
|
}
|
|
16183
16546
|
});
|
|
16184
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
|
+
|
|
16185
16755
|
// src/commands/init.ts
|
|
16186
16756
|
var init_exports = {};
|
|
16187
16757
|
__export(init_exports, {
|
|
16188
16758
|
initCommand: () => initCommand
|
|
16189
16759
|
});
|
|
16190
16760
|
import { spawn } from "node:child_process";
|
|
16191
|
-
import { existsSync } from "node:fs";
|
|
16192
|
-
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
16193
|
-
import { join } from "node:path";
|
|
16194
|
-
import { parseArgs } from "node:util";
|
|
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";
|
|
16195
16765
|
function blockComponentsTemplate(styled) {
|
|
16196
16766
|
const body = styled ? ` return (
|
|
16197
16767
|
<figure className="my-6 border-s-2 ps-4">
|
|
@@ -16283,7 +16853,7 @@ console.log('wrote example.html')
|
|
|
16283
16853
|
`;
|
|
16284
16854
|
}
|
|
16285
16855
|
function detectFramework(cwd, deps) {
|
|
16286
|
-
if ((deps["@tanstack/react-start"] || deps["@tanstack/react-router"]) &&
|
|
16856
|
+
if ((deps["@tanstack/react-start"] || deps["@tanstack/react-router"]) && existsSync2(join2(cwd, "src/routes"))) {
|
|
16287
16857
|
return {
|
|
16288
16858
|
framework: "tanstack",
|
|
16289
16859
|
componentPath: "src/components/content-blocks.tsx",
|
|
@@ -16291,8 +16861,8 @@ function detectFramework(cwd, deps) {
|
|
|
16291
16861
|
};
|
|
16292
16862
|
}
|
|
16293
16863
|
if (deps.next) {
|
|
16294
|
-
const appDir =
|
|
16295
|
-
if (
|
|
16864
|
+
const appDir = existsSync2(join2(cwd, "src/app")) ? "src/app" : "app";
|
|
16865
|
+
if (existsSync2(join2(cwd, appDir))) {
|
|
16296
16866
|
return {
|
|
16297
16867
|
framework: "next",
|
|
16298
16868
|
componentPath: "components/content-blocks.tsx",
|
|
@@ -16302,6 +16872,34 @@ function detectFramework(cwd, deps) {
|
|
|
16302
16872
|
}
|
|
16303
16873
|
return { framework: null, componentPath: "components/content-blocks.tsx", pagePath: null };
|
|
16304
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
|
+
}
|
|
16305
16903
|
function detectPackageManager(cwd) {
|
|
16306
16904
|
const locks = [
|
|
16307
16905
|
["pnpm-lock.yaml", "pnpm"],
|
|
@@ -16311,7 +16909,7 @@ function detectPackageManager(cwd) {
|
|
|
16311
16909
|
["package-lock.json", "npm"]
|
|
16312
16910
|
];
|
|
16313
16911
|
for (const [file2, pm] of locks) {
|
|
16314
|
-
if (
|
|
16912
|
+
if (existsSync2(join2(cwd, file2))) return pm;
|
|
16315
16913
|
}
|
|
16316
16914
|
const agent = process.env.npm_config_user_agent ?? "";
|
|
16317
16915
|
for (const pm of ["pnpm", "yarn", "bun"]) {
|
|
@@ -16336,18 +16934,38 @@ function runInstall(pm, args, cwd) {
|
|
|
16336
16934
|
child.on("error", () => resolve(1));
|
|
16337
16935
|
});
|
|
16338
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
|
+
}
|
|
16339
16957
|
async function scaffold(path, content) {
|
|
16340
16958
|
try {
|
|
16341
|
-
await
|
|
16959
|
+
await readFile2(path, "utf8");
|
|
16342
16960
|
return "skipped";
|
|
16343
16961
|
} catch {
|
|
16344
|
-
await
|
|
16345
|
-
await
|
|
16962
|
+
await mkdir2(join2(path, ".."), { recursive: true });
|
|
16963
|
+
await writeFile2(path, content, "utf8");
|
|
16346
16964
|
return "created";
|
|
16347
16965
|
}
|
|
16348
16966
|
}
|
|
16349
16967
|
async function initCommand(args, io) {
|
|
16350
|
-
const { values } =
|
|
16968
|
+
const { values } = parseArgs2({
|
|
16351
16969
|
args,
|
|
16352
16970
|
options: {
|
|
16353
16971
|
target: { type: "string", short: "t" },
|
|
@@ -16356,20 +16974,22 @@ async function initCommand(args, io) {
|
|
|
16356
16974
|
cwd: { type: "string", default: process.cwd() },
|
|
16357
16975
|
"no-install": { type: "boolean", default: false },
|
|
16358
16976
|
"no-page": { type: "boolean", default: false },
|
|
16359
|
-
"no-styled": { type: "boolean", default: false }
|
|
16977
|
+
"no-styled": { type: "boolean", default: false },
|
|
16978
|
+
"no-agents": { type: "boolean", default: false }
|
|
16360
16979
|
}
|
|
16361
16980
|
});
|
|
16362
16981
|
const cwd = values.cwd;
|
|
16363
16982
|
let pkg;
|
|
16364
|
-
const pkgPath =
|
|
16983
|
+
const pkgPath = join2(cwd, "package.json");
|
|
16365
16984
|
try {
|
|
16366
|
-
pkg = JSON.parse(await
|
|
16985
|
+
pkg = JSON.parse(await readFile2(pkgPath, "utf8"));
|
|
16367
16986
|
} catch {
|
|
16368
16987
|
io.stderr("No package.json found. Run this inside a project (npm init first).");
|
|
16369
16988
|
return 1;
|
|
16370
16989
|
}
|
|
16371
16990
|
const hasReact = Boolean(pkg.dependencies?.react ?? pkg.devDependencies?.react);
|
|
16372
|
-
const
|
|
16991
|
+
const hasAstro = Boolean(pkg.dependencies?.astro ?? pkg.devDependencies?.astro);
|
|
16992
|
+
const detected = hasAstro ? "astro" : hasReact ? "react" : "html";
|
|
16373
16993
|
let target;
|
|
16374
16994
|
if (values.target) {
|
|
16375
16995
|
if (!TARGETS.includes(values.target)) {
|
|
@@ -16384,6 +17004,7 @@ async function initCommand(args, io) {
|
|
|
16384
17004
|
initialValue: detected,
|
|
16385
17005
|
options: [
|
|
16386
17006
|
{ value: "react", label: "React", hint: "ContentBlocks component" },
|
|
17007
|
+
{ value: "astro", label: "Astro", hint: "content collections + .astro components" },
|
|
16387
17008
|
{ value: "html", label: "Static HTML", hint: "renderToHtml, no framework" },
|
|
16388
17009
|
{ value: "markdown", label: "Plain Markdown", hint: "fallback rendering only" }
|
|
16389
17010
|
]
|
|
@@ -16420,6 +17041,7 @@ async function initCommand(args, io) {
|
|
|
16420
17041
|
const runtime = ["@contentbit/core", "@contentbit/blocks", "zod"];
|
|
16421
17042
|
if (target === "react") runtime.push("@contentbit/react");
|
|
16422
17043
|
if (target === "html") runtime.push("@contentbit/html");
|
|
17044
|
+
if (target === "astro") runtime.push("@contentbit/astro");
|
|
16423
17045
|
if (md !== "none") runtime.push(md);
|
|
16424
17046
|
if (values["no-install"]) {
|
|
16425
17047
|
io.stdout(`skipped install: ${runtime.join(" ")} + contentbit (dev)`);
|
|
@@ -16441,30 +17063,9 @@ async function initCommand(args, io) {
|
|
|
16441
17063
|
];
|
|
16442
17064
|
const layout = detectFramework(cwd, { ...pkg.dependencies, ...pkg.devDependencies });
|
|
16443
17065
|
let styled = false;
|
|
16444
|
-
const componentsJsonPath =
|
|
16445
|
-
if (target === "react" && !values["no-styled"] &&
|
|
16446
|
-
|
|
16447
|
-
componentsJson.registries ??= {};
|
|
16448
|
-
if (!componentsJson.registries["@contentbit"]) {
|
|
16449
|
-
componentsJson.registries["@contentbit"] = "https://contentbit.dev/r/{name}.json";
|
|
16450
|
-
await writeFile(componentsJsonPath, `${JSON.stringify(componentsJson, null, 2)}
|
|
16451
|
-
`, "utf8");
|
|
16452
|
-
io.stdout("added @contentbit registry to components.json");
|
|
16453
|
-
}
|
|
16454
|
-
if (values["no-install"]) {
|
|
16455
|
-
io.stdout("skipped: shadcn add @contentbit/generic-pack");
|
|
16456
|
-
styled = true;
|
|
16457
|
-
} else {
|
|
16458
|
-
const [bin, prefix] = dlxCommand(detectPackageManager(cwd));
|
|
16459
|
-
io.stdout("installing the styled pack: shadcn add @contentbit/generic-pack");
|
|
16460
|
-
const code2 = await runInstall(
|
|
16461
|
-
bin,
|
|
16462
|
-
[...prefix, "shadcn@latest", "add", "@contentbit/generic-pack", "--yes"],
|
|
16463
|
-
cwd
|
|
16464
|
-
);
|
|
16465
|
-
if (code2 === 0) styled = true;
|
|
16466
|
-
else io.stderr("styled pack install failed; falling back to headless defaults");
|
|
16467
|
-
}
|
|
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);
|
|
16468
17069
|
}
|
|
16469
17070
|
if (target === "react") {
|
|
16470
17071
|
const depth = layout.componentPath.split("/").length - 1;
|
|
@@ -16484,27 +17085,52 @@ async function initCommand(args, io) {
|
|
|
16484
17085
|
htmlRenderScript(md)
|
|
16485
17086
|
]);
|
|
16486
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
|
+
}
|
|
16487
17108
|
for (const [rel, content] of files) {
|
|
16488
|
-
const result = await scaffold(
|
|
17109
|
+
const result = await scaffold(join2(cwd, rel), content);
|
|
16489
17110
|
io.stdout(`${result}: ${rel}`);
|
|
16490
17111
|
}
|
|
16491
|
-
const fresh = JSON.parse(await
|
|
17112
|
+
const fresh = JSON.parse(await readFile2(pkgPath, "utf8"));
|
|
16492
17113
|
fresh.scripts ??= {};
|
|
16493
17114
|
if (!fresh.scripts["content:check"]) {
|
|
16494
17115
|
fresh.scripts["content:check"] = 'contentbit validate "content/**/*.md" --registry ./blocks/registry.ts';
|
|
16495
|
-
await
|
|
17116
|
+
await writeFile2(pkgPath, `${JSON.stringify(fresh, null, 2)}
|
|
16496
17117
|
`, "utf8");
|
|
16497
17118
|
io.stdout("added script: content:check");
|
|
16498
17119
|
}
|
|
16499
17120
|
let registry2;
|
|
16500
17121
|
try {
|
|
16501
|
-
registry2 = await loadRegistry(
|
|
17122
|
+
registry2 = await loadRegistry(join2(cwd, "blocks/registry.ts"));
|
|
16502
17123
|
} catch {
|
|
16503
17124
|
registry2 = await loadRegistry();
|
|
16504
17125
|
}
|
|
16505
17126
|
const guide = registry2.toAuthoringGuide({ audience: "llm", includeExamples: true });
|
|
16506
|
-
await
|
|
17127
|
+
await writeFile2(join2(cwd, "contentbit-guide.md"), guide, "utf8");
|
|
16507
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
|
+
}
|
|
16508
17134
|
io.stdout("");
|
|
16509
17135
|
io.stdout("Done. Next steps:");
|
|
16510
17136
|
io.stdout(` 1. Validate the starter content: ${detectPackageManager(cwd)} run content:check`);
|
|
@@ -16516,6 +17142,9 @@ async function initCommand(args, io) {
|
|
|
16516
17142
|
io.stdout(" <Content source={...content/example.md as a string} />");
|
|
16517
17143
|
}
|
|
16518
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");
|
|
16519
17148
|
} else if (target === "html") {
|
|
16520
17149
|
io.stdout(" 2. Render it: node scripts/render-example.mjs && open example.html");
|
|
16521
17150
|
} else {
|
|
@@ -16524,16 +17153,19 @@ async function initCommand(args, io) {
|
|
|
16524
17153
|
io.stdout(" Docs: https://contentbit.dev/docs");
|
|
16525
17154
|
return 0;
|
|
16526
17155
|
}
|
|
16527
|
-
var TARGETS, MD_CHOICES, REGISTRY_TEMPLATE, EXAMPLE_CONTENT, TANSTACK_PAGE, NEXT_PAGE;
|
|
17156
|
+
var TARGETS, MD_CHOICES, REGISTRY_TEMPLATE, EXAMPLE_CONTENT, TANSTACK_PAGE, NEXT_PAGE, ASTRO_CONTENT_CONFIG, ASTRO_QUOTE_BLOCK;
|
|
16528
17157
|
var init_init = __esm({
|
|
16529
17158
|
"src/commands/init.ts"() {
|
|
16530
17159
|
"use strict";
|
|
16531
17160
|
init_load_registry();
|
|
16532
|
-
|
|
17161
|
+
init_agents();
|
|
17162
|
+
TARGETS = ["react", "html", "markdown", "astro"];
|
|
16533
17163
|
MD_CHOICES = {
|
|
16534
17164
|
react: ["react-markdown", "none"],
|
|
16535
17165
|
html: ["marked", "markdown-it", "none"],
|
|
16536
|
-
markdown: ["none"]
|
|
17166
|
+
markdown: ["none"],
|
|
17167
|
+
// @contentbit/astro ships its own marked-based default; nothing to install.
|
|
17168
|
+
astro: ["none"]
|
|
16537
17169
|
};
|
|
16538
17170
|
REGISTRY_TEMPLATE = `// Custom block definitions for this project. The CLI and your app share
|
|
16539
17171
|
// this module \u2014 Node 22.18+ imports TypeScript directly:
|
|
@@ -16614,6 +17246,36 @@ export default async function ExamplePage() {
|
|
|
16614
17246
|
</main>
|
|
16615
17247
|
)
|
|
16616
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>
|
|
16617
17279
|
`;
|
|
16618
17280
|
}
|
|
16619
17281
|
});
|
|
@@ -16623,11 +17285,11 @@ var validate_exports = {};
|
|
|
16623
17285
|
__export(validate_exports, {
|
|
16624
17286
|
validateCommand: () => validateCommand
|
|
16625
17287
|
});
|
|
16626
|
-
import { readFile as
|
|
16627
|
-
import { parseArgs as
|
|
17288
|
+
import { readFile as readFile3 } from "node:fs/promises";
|
|
17289
|
+
import { parseArgs as parseArgs3 } from "node:util";
|
|
16628
17290
|
import { glob } from "tinyglobby";
|
|
16629
17291
|
async function validateCommand(args, io) {
|
|
16630
|
-
const { values, positionals } =
|
|
17292
|
+
const { values, positionals } = parseArgs3({
|
|
16631
17293
|
args,
|
|
16632
17294
|
allowPositionals: true,
|
|
16633
17295
|
options: {
|
|
@@ -16648,8 +17310,8 @@ async function validateCommand(args, io) {
|
|
|
16648
17310
|
let errors = 0;
|
|
16649
17311
|
let warnings = 0;
|
|
16650
17312
|
for (const file2 of files.sort()) {
|
|
16651
|
-
const source = await
|
|
16652
|
-
const result = validateDocument(parseDocument(source), registry2);
|
|
17313
|
+
const source = await readFile3(file2, "utf8");
|
|
17314
|
+
const result = validateDocument(parseDocument(stripFrontmatter(source)), registry2);
|
|
16653
17315
|
for (const d of result.diagnostics) {
|
|
16654
17316
|
io.stderr(formatDiagnostic(d, file2));
|
|
16655
17317
|
if (d.severity === "error") errors++;
|
|
@@ -16669,6 +17331,63 @@ var init_validate2 = __esm({
|
|
|
16669
17331
|
}
|
|
16670
17332
|
});
|
|
16671
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
|
+
|
|
16672
17391
|
// ../html/dist/escape.js
|
|
16673
17392
|
function escapeHtml(value) {
|
|
16674
17393
|
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
@@ -16749,12 +17468,18 @@ var init_blocks = __esm({
|
|
|
16749
17468
|
});
|
|
16750
17469
|
|
|
16751
17470
|
// ../html/dist/render.js
|
|
16752
|
-
function
|
|
17471
|
+
function fallbackMarkdown(md) {
|
|
16753
17472
|
return md.trim().split(/\n{2,}/).map((p) => `<p>${escapeHtml(p)}</p>`).join("\n");
|
|
16754
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
|
+
}
|
|
16755
17480
|
function renderToHtml(document, opts = {}) {
|
|
16756
17481
|
const prefix = opts.classPrefix ?? "cb-";
|
|
16757
|
-
const renderMarkdown = opts.renderMarkdown ??
|
|
17482
|
+
const renderMarkdown = opts.renderMarkdown ?? fallbackMarkdown;
|
|
16758
17483
|
const renderers = { ...genericHtmlRenderers, ...opts.renderers };
|
|
16759
17484
|
const onInvalid = opts.onInvalid ?? "fallback";
|
|
16760
17485
|
const ctx = {
|
|
@@ -16768,13 +17493,11 @@ function renderToHtml(document, opts = {}) {
|
|
|
16768
17493
|
const renderer = renderers[node.name];
|
|
16769
17494
|
if (renderer && isValidatedBlock(node))
|
|
16770
17495
|
return renderer(node, ctx);
|
|
16771
|
-
if (onInvalid === "strict")
|
|
16772
|
-
throw
|
|
16773
|
-
|
|
16774
|
-
|
|
16775
|
-
|
|
16776
|
-
}
|
|
16777
|
-
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);
|
|
16778
17501
|
}).join("\n");
|
|
16779
17502
|
}
|
|
16780
17503
|
};
|
|
@@ -16802,10 +17525,10 @@ var render_exports = {};
|
|
|
16802
17525
|
__export(render_exports, {
|
|
16803
17526
|
renderCommand: () => renderCommand
|
|
16804
17527
|
});
|
|
16805
|
-
import { readFile as
|
|
16806
|
-
import { parseArgs as
|
|
17528
|
+
import { readFile as readFile5 } from "node:fs/promises";
|
|
17529
|
+
import { parseArgs as parseArgs5 } from "node:util";
|
|
16807
17530
|
async function renderCommand(args, io) {
|
|
16808
|
-
const { values, positionals } =
|
|
17531
|
+
const { values, positionals } = parseArgs5({
|
|
16809
17532
|
args,
|
|
16810
17533
|
allowPositionals: true,
|
|
16811
17534
|
options: {
|
|
@@ -16820,7 +17543,7 @@ async function renderCommand(args, io) {
|
|
|
16820
17543
|
return 2;
|
|
16821
17544
|
}
|
|
16822
17545
|
const registry2 = await loadRegistry(values.registry);
|
|
16823
|
-
const source = await
|
|
17546
|
+
const source = await readFile5(file2, "utf8");
|
|
16824
17547
|
const result = validateDocument(parseDocument(source), registry2);
|
|
16825
17548
|
if (!result.ok) {
|
|
16826
17549
|
for (const d of result.diagnostics) io.stderr(formatDiagnostic(d, file2));
|
|
@@ -16846,9 +17569,9 @@ var instructions_exports = {};
|
|
|
16846
17569
|
__export(instructions_exports, {
|
|
16847
17570
|
instructionsCommand: () => instructionsCommand
|
|
16848
17571
|
});
|
|
16849
|
-
import { parseArgs as
|
|
17572
|
+
import { parseArgs as parseArgs6 } from "node:util";
|
|
16850
17573
|
async function instructionsCommand(args, io) {
|
|
16851
|
-
const { values } =
|
|
17574
|
+
const { values } = parseArgs6({
|
|
16852
17575
|
args,
|
|
16853
17576
|
options: {
|
|
16854
17577
|
audience: { type: "string", default: "llm" },
|
|
@@ -16878,9 +17601,9 @@ var docs_exports = {};
|
|
|
16878
17601
|
__export(docs_exports, {
|
|
16879
17602
|
docsCommand: () => docsCommand
|
|
16880
17603
|
});
|
|
16881
|
-
import { parseArgs as
|
|
17604
|
+
import { parseArgs as parseArgs7 } from "node:util";
|
|
16882
17605
|
async function docsCommand(args, io) {
|
|
16883
|
-
const { values } =
|
|
17606
|
+
const { values } = parseArgs7({
|
|
16884
17607
|
args,
|
|
16885
17608
|
options: {
|
|
16886
17609
|
registry: { type: "string" },
|
|
@@ -16901,23 +17624,27 @@ var init_docs = __esm({
|
|
|
16901
17624
|
});
|
|
16902
17625
|
|
|
16903
17626
|
// src/bin.ts
|
|
16904
|
-
import { writeFile as
|
|
17627
|
+
import { writeFile as writeFile3 } from "node:fs/promises";
|
|
16905
17628
|
|
|
16906
17629
|
// src/run.ts
|
|
16907
|
-
var USAGE = `Usage: contentbit <init|validate|render|instructions|docs> [options]
|
|
17630
|
+
var USAGE = `Usage: contentbit <init|validate|stats|render|instructions|docs|agents> [options]
|
|
16908
17631
|
|
|
16909
|
-
init [-t react|html|markdown] [--md ...] [-y] [--no-install] [--no-page]
|
|
17632
|
+
init [-t react|html|markdown|astro] [--md ...] [-y] [--no-install] [--no-page] [--no-agents]
|
|
17633
|
+
agents [--claude] [--no-agents-md]
|
|
16910
17634
|
|
|
16911
17635
|
validate <globs...> [--registry <module.mjs>] [--strict-warnings]
|
|
17636
|
+
stats <globs...> [--registry <module.mjs>] [--no-validate]
|
|
16912
17637
|
render <file> --target html|markdown [--registry <module.mjs>] [--out <file>]
|
|
16913
17638
|
instructions [--audience llm|human] [--no-examples] [--registry <module.mjs>] [--out <file>]
|
|
16914
17639
|
docs [--registry <module.mjs>] [--out <file>]`;
|
|
16915
17640
|
var commands = {
|
|
16916
17641
|
init: async () => (await Promise.resolve().then(() => (init_init(), init_exports))).initCommand,
|
|
16917
17642
|
validate: async () => (await Promise.resolve().then(() => (init_validate2(), validate_exports))).validateCommand,
|
|
17643
|
+
stats: async () => (await Promise.resolve().then(() => (init_stats(), stats_exports))).statsCommand,
|
|
16918
17644
|
render: async () => (await Promise.resolve().then(() => (init_render2(), render_exports))).renderCommand,
|
|
16919
17645
|
instructions: async () => (await Promise.resolve().then(() => (init_instructions(), instructions_exports))).instructionsCommand,
|
|
16920
|
-
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
|
|
16921
17648
|
};
|
|
16922
17649
|
async function run(argv, io) {
|
|
16923
17650
|
const [name, ...rest] = argv;
|
|
@@ -16939,6 +17666,6 @@ async function run(argv, io) {
|
|
|
16939
17666
|
var code = await run(process.argv.slice(2), {
|
|
16940
17667
|
stdout: (s) => console.log(s),
|
|
16941
17668
|
stderr: (s) => console.error(s),
|
|
16942
|
-
writeFile: (path, content) =>
|
|
17669
|
+
writeFile: (path, content) => writeFile3(path, content, "utf8")
|
|
16943
17670
|
});
|
|
16944
17671
|
process.exit(code);
|