superlore-cli 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1456 -546
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -27,10 +27,10 @@ function banner() {
|
|
|
27
27
|
}
|
|
28
28
|
const seam = dim("\u258F");
|
|
29
29
|
const lines = [
|
|
30
|
-
` ${accent("\u2597\
|
|
31
|
-
` ${accent("\
|
|
32
|
-
` ${accent("\
|
|
33
|
-
` ${accent("\u259D\
|
|
30
|
+
` ${accent("\u2597\u2584")}${seam}${accentSoft("\u2584\u2596")}`,
|
|
31
|
+
` ${accent("\u2588\u2588")}${seam}${accentSoft("\u2588\u2588")} ${wordmark("superlore")}`,
|
|
32
|
+
` ${accent("\u2588\u2588")}${seam}${accentSoft("\u2588\u2588")} ${dim("the company knowledge base your agents run on")}`,
|
|
33
|
+
` ${accent("\u259D\u2580")}${seam}${accentSoft("\u2580\u2598")}`
|
|
34
34
|
];
|
|
35
35
|
process.stdout.write(`
|
|
36
36
|
${lines.join("\n")}
|
|
@@ -213,14 +213,15 @@ function detectPackageManager(root) {
|
|
|
213
213
|
function isInstalled(root) {
|
|
214
214
|
return existsSync(join(root, "node_modules"));
|
|
215
215
|
}
|
|
216
|
-
function runScript(root, script, extraArgs = []) {
|
|
216
|
+
function runScript(root, script, extraArgs = [], env) {
|
|
217
217
|
const pm = detectPackageManager(root);
|
|
218
218
|
const args = ["run", script, ...extraArgs.length ? ["--", ...extraArgs] : []];
|
|
219
219
|
return new Promise((resolvePromise, reject) => {
|
|
220
220
|
const child = spawn(pm, args, {
|
|
221
221
|
cwd: root,
|
|
222
222
|
stdio: "inherit",
|
|
223
|
-
shell: process.platform === "win32"
|
|
223
|
+
shell: process.platform === "win32",
|
|
224
|
+
env: env ? { ...process.env, ...env } : process.env
|
|
224
225
|
});
|
|
225
226
|
child.on("error", reject);
|
|
226
227
|
child.on("close", (code) => resolvePromise(code ?? 0));
|
|
@@ -417,7 +418,9 @@ function report(result) {
|
|
|
417
418
|
log.success(`${bold(result.editor.label)} ${dim("\u2014 extension installed.")}`);
|
|
418
419
|
break;
|
|
419
420
|
case "already-installed":
|
|
420
|
-
log.info(
|
|
421
|
+
log.info(
|
|
422
|
+
`${accent("\u203A")} ${bold(result.editor.label)} ${dim("\u2014 already installed, up to date.")}`
|
|
423
|
+
);
|
|
421
424
|
break;
|
|
422
425
|
case "failed":
|
|
423
426
|
log.error(`${bold(result.editor.label)} ${dim("\u2014 install failed:")} ${result.error}`);
|
|
@@ -437,12 +440,15 @@ function printManualInstall() {
|
|
|
437
440
|
function printMcpNextStep() {
|
|
438
441
|
log.blank();
|
|
439
442
|
log.info(bold("Next: connect the MCP"));
|
|
443
|
+
log.info(` ${dim("superlore's docs + help, in your agent \u2014 always current:")}`);
|
|
440
444
|
log.info(
|
|
441
|
-
` ${
|
|
445
|
+
` ${cyan("claude mcp add --transport http -s user superlore-docs https://superlore.vercel.app/api/mcp")}`
|
|
442
446
|
);
|
|
447
|
+
log.blank();
|
|
443
448
|
log.info(
|
|
444
|
-
` ${dim("
|
|
449
|
+
` ${dim("And your own KB once it's live (or ask Claude")} ${cyan('"connect my superlore MCP"')}${dim("):")}`
|
|
445
450
|
);
|
|
451
|
+
log.info(` ${cyan("claude mcp add --transport http -s user my-kb <your-kb-url>/api/mcp")}`);
|
|
446
452
|
}
|
|
447
453
|
|
|
448
454
|
// src/commands/deploy.ts
|
|
@@ -518,108 +524,30 @@ async function devCommand(flags) {
|
|
|
518
524
|
);
|
|
519
525
|
}
|
|
520
526
|
log.blank();
|
|
521
|
-
const
|
|
522
|
-
const code = await runScript(project.root, "dev", args);
|
|
527
|
+
const code = await runScript(project.root, "dev", [], { PORT: String(port) });
|
|
523
528
|
process.exit(code);
|
|
524
529
|
}
|
|
525
530
|
|
|
526
531
|
// src/commands/init.ts
|
|
527
|
-
import { existsSync as existsSync4 } from "fs";
|
|
528
|
-
import { basename, resolve as resolve3 } from "path";
|
|
532
|
+
import { existsSync as existsSync4, readdirSync as readdirSync2, statSync } from "fs";
|
|
533
|
+
import { basename, join as join4, resolve as resolve3 } from "path";
|
|
529
534
|
import { cancel, confirm, intro, isCancel, outro, select, text } from "@clack/prompts";
|
|
530
535
|
|
|
531
536
|
// src/lib/scaffold.ts
|
|
532
537
|
import { cpSync, existsSync as existsSync3, mkdirSync, readdirSync, writeFileSync as writeFileSync2 } from "fs";
|
|
533
538
|
import { fileURLToPath } from "url";
|
|
534
539
|
import { dirname as dirname2, join as join3, resolve as resolve2 } from "path";
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
for (; ; ) {
|
|
539
|
-
const candidate = join3(dir, "templates", "starter");
|
|
540
|
-
if (existsSync3(candidate)) return candidate;
|
|
541
|
-
const bundled = join3(dir, "template");
|
|
542
|
-
if (existsSync3(bundled)) return bundled;
|
|
543
|
-
const parent = dirname2(dir);
|
|
544
|
-
if (parent === dir) return void 0;
|
|
545
|
-
dir = parent;
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
function isUsableTemplate(dir) {
|
|
549
|
-
if (!existsSync3(dir)) return false;
|
|
550
|
-
const entries = readdirSync(dir).filter((name) => name !== "README.md" && !name.startsWith("."));
|
|
551
|
-
return entries.length > 0;
|
|
552
|
-
}
|
|
553
|
-
function isEmptyDir(dir) {
|
|
554
|
-
if (!existsSync3(dir)) return true;
|
|
555
|
-
return readdirSync(dir).filter((n) => !n.startsWith(".")).length === 0;
|
|
556
|
-
}
|
|
557
|
-
function scaffold(options) {
|
|
558
|
-
const root = resolve2(options.dir);
|
|
559
|
-
mkdirSync(root, { recursive: true });
|
|
560
|
-
const template = findStarterTemplate();
|
|
561
|
-
let source;
|
|
562
|
-
if (template && isUsableTemplate(template)) {
|
|
563
|
-
cpSync(template, root, { recursive: true });
|
|
564
|
-
source = "template";
|
|
565
|
-
} else {
|
|
566
|
-
writeSkeleton(root, options.config);
|
|
567
|
-
source = "skeleton";
|
|
568
|
-
}
|
|
569
|
-
writeFileSync2(join3(root, "superlore.json"), serializeSuperloreJson(options.config), "utf8");
|
|
570
|
-
return { root, source };
|
|
571
|
-
}
|
|
572
|
-
function writeSkeleton(root, config) {
|
|
573
|
-
const accent2 = config.accent ?? SUPERLORE_VIOLET;
|
|
574
|
-
const mcpEnabled = config.mcp?.enabled ?? true;
|
|
575
|
-
const authEnabled = config.auth?.enabled ?? false;
|
|
576
|
-
const slug = config.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "superlore-kb";
|
|
577
|
-
const write = (rel, body) => {
|
|
578
|
-
const file = join3(root, rel);
|
|
579
|
-
mkdirSync(dirname2(file), { recursive: true });
|
|
580
|
-
writeFileSync2(file, body, "utf8");
|
|
581
|
-
};
|
|
540
|
+
|
|
541
|
+
// src/lib/content/company.ts
|
|
542
|
+
function writeCompanyContent(write, config) {
|
|
582
543
|
write(
|
|
583
|
-
"
|
|
544
|
+
"content/docs/meta.json",
|
|
584
545
|
`${JSON.stringify(
|
|
585
546
|
{
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
scripts: {
|
|
591
|
-
dev: "next dev",
|
|
592
|
-
build: "next build",
|
|
593
|
-
start: "next start",
|
|
594
|
-
postinstall: "fumadocs-mdx"
|
|
595
|
-
},
|
|
596
|
-
dependencies: {
|
|
597
|
-
"fumadocs-core": "16.8.2",
|
|
598
|
-
"fumadocs-mdx": "14.3.1",
|
|
599
|
-
"fumadocs-ui": "16.8.2",
|
|
600
|
-
superlore: "^0.5.1",
|
|
601
|
-
"lucide-react": "^1.21.0",
|
|
602
|
-
// superlore peers the rendered components pull in: Mermaid (Diagram), themes.
|
|
603
|
-
mermaid: "^11.15.0",
|
|
604
|
-
...mcpEnabled ? { "@modelcontextprotocol/sdk": "^1.29.0", "mcp-handler": "^1.1.0" } : {},
|
|
605
|
-
// Auth.js v5 powers the optional Google SSO gate (superlore/auth). Self-disabling
|
|
606
|
-
// without AUTH_GOOGLE_ID, so it's harmless until the env is set.
|
|
607
|
-
...authEnabled ? { "next-auth": "^5.0.0-beta.25" } : {},
|
|
608
|
-
next: "16.2.4",
|
|
609
|
-
"next-themes": "^0.4.6",
|
|
610
|
-
react: "^19.2.5",
|
|
611
|
-
"react-dom": "^19.2.5",
|
|
612
|
-
zod: "^4.4.3"
|
|
613
|
-
},
|
|
614
|
-
devDependencies: {
|
|
615
|
-
"@tailwindcss/postcss": "^4.2.2",
|
|
616
|
-
"@types/node": "^25.6.0",
|
|
617
|
-
"@types/react": "^19.2.14",
|
|
618
|
-
"@types/react-dom": "^19.2.3",
|
|
619
|
-
postcss: "^8.5.10",
|
|
620
|
-
tailwindcss: "^4.2.2",
|
|
621
|
-
typescript: "6.0.3"
|
|
622
|
-
}
|
|
547
|
+
title: config.name,
|
|
548
|
+
root: true,
|
|
549
|
+
icon: "BookOpen",
|
|
550
|
+
pages: ["index", "engineering", "product", "team"]
|
|
623
551
|
},
|
|
624
552
|
null,
|
|
625
553
|
2
|
|
@@ -627,408 +555,830 @@ function writeSkeleton(root, config) {
|
|
|
627
555
|
`
|
|
628
556
|
);
|
|
629
557
|
write(
|
|
630
|
-
"
|
|
558
|
+
"content/docs/index.mdx",
|
|
559
|
+
`---
|
|
560
|
+
title: Home
|
|
561
|
+
description: The home of the ${config.name} knowledge base \u2014 for humans and the agents working beside them.
|
|
562
|
+
summary: Overview of the ${config.name} company knowledge base \u2014 what lives here, how it's organized, and how an agent should use it to answer questions and act on the team's behalf.
|
|
563
|
+
tags: [overview, home, getting-started]
|
|
564
|
+
---
|
|
565
|
+
|
|
566
|
+
<PageHero
|
|
567
|
+
kicker="Company knowledge base"
|
|
568
|
+
title="${config.name}"
|
|
569
|
+
description="One corpus, two readers. Humans get this clean, navigable site; agents read the same structured content over MCP and act the way the team would."
|
|
570
|
+
icon="book-open"
|
|
571
|
+
/>
|
|
572
|
+
|
|
573
|
+
<Note title="This is a starter structure">
|
|
574
|
+
Everything here is **placeholder content** that shows the shape of a good company KB. Keep the
|
|
575
|
+
structure, then replace each page with what's true for your team. Run \`superlore dev\` to preview as
|
|
576
|
+
you go.
|
|
577
|
+
</Note>
|
|
578
|
+
|
|
579
|
+
## What lives here
|
|
580
|
+
|
|
581
|
+
<FeatureList
|
|
582
|
+
items={[
|
|
583
|
+
{ icon: "layers", title: "Engineering", description: "System architecture, the decisions behind it, and the runbooks that keep it up.", href: "/docs/engineering/architecture" },
|
|
584
|
+
{ icon: "rocket", title: "Product", description: "The roadmap and what shipped \u2014 now, next, and later.", href: "/docs/product/roadmap" },
|
|
585
|
+
{ icon: "users", title: "Team", description: "How to onboard, and who owns what.", href: "/docs/team/onboarding" },
|
|
586
|
+
]}
|
|
587
|
+
/>
|
|
588
|
+
|
|
589
|
+
## How the company fits together
|
|
590
|
+
|
|
591
|
+
A map of the domains and how they depend on each other. Edit the canvas \u2014 the agent reads the same
|
|
592
|
+
graph the diagram is drawn from.
|
|
593
|
+
|
|
594
|
+
\`\`\`superlore-canvas
|
|
595
|
+
{
|
|
596
|
+
"title": "${config.name} \u2014 domains",
|
|
597
|
+
"layout": "flow",
|
|
598
|
+
"direction": "down",
|
|
599
|
+
"groups": [
|
|
600
|
+
{ "id": "surface", "label": "Customer surface", "frame": true, "intent": "blue" },
|
|
601
|
+
{ "id": "core", "label": "Core platform", "frame": true, "intent": "purple" },
|
|
602
|
+
{ "id": "data", "label": "Data & insight", "frame": true, "intent": "green" },
|
|
603
|
+
{ "id": "ops", "label": "Operations", "frame": true, "intent": "gray" }
|
|
604
|
+
],
|
|
605
|
+
"nodes": [
|
|
606
|
+
{ "id": "web", "kind": "rect", "intent": "blue", "group": "surface", "icon": "globe", "label": "Web app" },
|
|
607
|
+
{ "id": "api", "kind": "rect", "intent": "blue", "group": "surface", "icon": "plug", "label": "Public API" },
|
|
608
|
+
{ "id": "billing", "kind": "rect", "intent": "purple", "group": "core", "icon": "credit-card", "label": "Billing" },
|
|
609
|
+
{ "id": "accounts", "kind": "rect", "intent": "purple", "group": "core", "icon": "users", "label": "Accounts" },
|
|
610
|
+
{ "id": "workflow", "kind": "rect", "intent": "purple", "group": "core", "icon": "workflow", "label": "Workflow engine" },
|
|
611
|
+
{ "id": "warehouse", "kind": "cylinder", "intent": "green", "group": "data", "label": "Warehouse" },
|
|
612
|
+
{ "id": "insights", "kind": "rect", "intent": "green", "group": "data", "icon": "line-chart", "label": "Insights" },
|
|
613
|
+
{ "id": "oncall", "kind": "rect", "intent": "gray", "group": "ops", "icon": "siren", "label": "On-call" }
|
|
614
|
+
],
|
|
615
|
+
"edges": [
|
|
616
|
+
{ "from": "web", "to": "api", "intent": "blue" },
|
|
617
|
+
{ "from": "api", "to": "accounts", "rel": "depends-on" },
|
|
618
|
+
{ "from": "api", "to": "workflow", "rel": "depends-on" },
|
|
619
|
+
{ "from": "workflow", "to": "billing", "rel": "links" },
|
|
620
|
+
{ "from": "workflow", "to": "warehouse", "intent": "green", "rel": "depends-on" },
|
|
621
|
+
{ "from": "warehouse", "to": "insights", "intent": "green" },
|
|
622
|
+
{ "from": "oncall", "to": "workflow", "kind": "dashed", "intent": "gray", "label": "watches" }
|
|
623
|
+
]
|
|
624
|
+
}
|
|
625
|
+
\`\`\`
|
|
626
|
+
|
|
627
|
+
## How to use this KB
|
|
628
|
+
|
|
629
|
+
<KeyFacts
|
|
630
|
+
items={[
|
|
631
|
+
{ label: "For humans", value: "Browse the sections, or search. Every diagram links to the page behind it." },
|
|
632
|
+
{ label: "For agents", value: "Query the MCP \u2014 search, get a page, follow relations, read a component's data." },
|
|
633
|
+
{ label: "Source of truth", value: "If it isn't written here, it isn't decided. Author once; both readers stay in sync." },
|
|
634
|
+
{ label: "Keep it current", value: "Update the doc in the same PR as the change. Stale knowledge is worse than none." },
|
|
635
|
+
]}
|
|
636
|
+
/>
|
|
637
|
+
`
|
|
638
|
+
);
|
|
639
|
+
write(
|
|
640
|
+
"content/docs/engineering/meta.json",
|
|
631
641
|
`${JSON.stringify(
|
|
632
|
-
{
|
|
633
|
-
compilerOptions: {
|
|
634
|
-
target: "ES2022",
|
|
635
|
-
lib: ["dom", "dom.iterable", "esnext"],
|
|
636
|
-
module: "esnext",
|
|
637
|
-
moduleResolution: "bundler",
|
|
638
|
-
strict: true,
|
|
639
|
-
noEmit: true,
|
|
640
|
-
jsx: "preserve",
|
|
641
|
-
esModuleInterop: true,
|
|
642
|
-
resolveJsonModule: true,
|
|
643
|
-
isolatedModules: true,
|
|
644
|
-
skipLibCheck: true,
|
|
645
|
-
incremental: true,
|
|
646
|
-
paths: { "@/*": ["./*"], "collections/*": ["./.source/*"] },
|
|
647
|
-
plugins: [{ name: "next" }]
|
|
648
|
-
},
|
|
649
|
-
include: ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
|
650
|
-
exclude: ["node_modules", ".next", "out"]
|
|
651
|
-
},
|
|
642
|
+
{ title: "Engineering", icon: "Layers", pages: ["architecture", "decisions", "runbooks"] },
|
|
652
643
|
null,
|
|
653
644
|
2
|
|
654
645
|
)}
|
|
655
646
|
`
|
|
656
647
|
);
|
|
657
648
|
write(
|
|
658
|
-
"
|
|
659
|
-
|
|
649
|
+
"content/docs/engineering/architecture.mdx",
|
|
650
|
+
`---
|
|
651
|
+
title: Architecture
|
|
652
|
+
description: How the platform is built \u2014 the services, the request path, and where the data lives.
|
|
653
|
+
summary: The system architecture \u2014 services and their tiers, the synchronous request path, and the data stores. The canvas is the authoritative diagram; the agent reads the same nodes and edges.
|
|
654
|
+
tags: [engineering, architecture, system-design]
|
|
655
|
+
---
|
|
660
656
|
|
|
661
|
-
|
|
657
|
+
<SectionHead
|
|
658
|
+
eyebrow="Engineering"
|
|
659
|
+
title="Architecture"
|
|
660
|
+
description="The system, as a diagram the humans read and the agent queries. Replace the services with yours."
|
|
661
|
+
/>
|
|
662
662
|
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
663
|
+
This is placeholder architecture for a generic SaaS platform. Redraw the canvas to match your real
|
|
664
|
+
system \u2014 the agent answers "how does X work?" from this graph, so keep it honest.
|
|
665
|
+
|
|
666
|
+
\`\`\`superlore-canvas
|
|
667
|
+
{
|
|
668
|
+
"title": "Request path",
|
|
669
|
+
"direction": "right",
|
|
670
|
+
"groups": [
|
|
671
|
+
{ "id": "client", "label": "Client", "frame": true, "intent": "gray" },
|
|
672
|
+
{ "id": "edge", "label": "Edge", "frame": true, "intent": "blue" },
|
|
673
|
+
{ "id": "app", "label": "Services", "frame": true, "intent": "purple" },
|
|
674
|
+
{ "id": "data", "label": "Data", "frame": true, "intent": "green" },
|
|
675
|
+
{ "id": "async", "label": "Async", "frame": true, "intent": "orange" }
|
|
676
|
+
],
|
|
677
|
+
"nodes": [
|
|
678
|
+
{ "id": "user", "kind": "circle", "group": "client", "label": "User" },
|
|
679
|
+
{ "id": "cdn", "kind": "icon", "icon": "globe", "group": "edge", "label": "CDN + WAF" },
|
|
680
|
+
{ "id": "gw", "kind": "icon", "icon": "shield", "group": "edge", "label": "API Gateway" },
|
|
681
|
+
{ "id": "api", "kind": "rect", "intent": "purple", "group": "app", "label": "API service" },
|
|
682
|
+
{ "id": "workflow", "kind": "rect", "intent": "purple", "group": "app", "label": "Workflow engine" },
|
|
683
|
+
{ "id": "cachehit", "kind": "diamond", "group": "app", "label": "Cache hit?" },
|
|
684
|
+
{ "id": "worker", "kind": "rect", "intent": "orange", "group": "app", "label": "Background worker" },
|
|
685
|
+
{ "id": "cache", "kind": "cylinder", "intent": "red", "group": "data", "label": "Redis" },
|
|
686
|
+
{ "id": "db", "kind": "cylinder", "intent": "green", "group": "data", "label": "Postgres" },
|
|
687
|
+
{ "id": "queue", "kind": "parallelogram", "intent": "orange", "group": "async", "label": "Job queue" }
|
|
688
|
+
],
|
|
689
|
+
"edges": [
|
|
690
|
+
{ "from": "user", "to": "cdn", "label": "HTTPS", "intent": "blue" },
|
|
691
|
+
{ "from": "cdn", "to": "gw", "intent": "blue" },
|
|
692
|
+
{ "from": "gw", "to": "api", "label": "authenticated", "rel": "links" },
|
|
693
|
+
{ "from": "api", "to": "cachehit", "intent": "purple" },
|
|
694
|
+
{ "from": "cachehit", "to": "cache", "label": "lookup", "intent": "red", "rel": "depends-on" },
|
|
695
|
+
{ "from": "cachehit", "to": "db", "label": "miss", "kind": "dashed", "intent": "green" },
|
|
696
|
+
{ "from": "api", "to": "workflow", "rel": "depends-on" },
|
|
697
|
+
{ "from": "workflow", "to": "queue", "label": "enqueue", "intent": "orange" },
|
|
698
|
+
{ "from": "queue", "to": "worker", "intent": "orange" },
|
|
699
|
+
{ "from": "worker", "to": "db", "label": "write", "intent": "green", "rel": "depends-on" }
|
|
700
|
+
]
|
|
701
|
+
}
|
|
702
|
+
\`\`\`
|
|
668
703
|
|
|
669
|
-
|
|
704
|
+
## Services
|
|
705
|
+
|
|
706
|
+
<Table
|
|
707
|
+
caption="The services in the request path above"
|
|
708
|
+
columns={[
|
|
709
|
+
{ key: "service", label: "Service", type: "text" },
|
|
710
|
+
{ key: "owner", label: "Owner", type: "text" },
|
|
711
|
+
{ key: "store", label: "Data store", type: "text" },
|
|
712
|
+
{ key: "sla", label: "SLA", type: "text" }
|
|
713
|
+
]}
|
|
714
|
+
rows={[
|
|
715
|
+
{ service: "API service", owner: "Platform", store: "Postgres + Redis", sla: "99.95%" },
|
|
716
|
+
{ service: "Workflow engine", owner: "Platform", store: "Postgres", sla: "99.9%" },
|
|
717
|
+
{ service: "Background worker", owner: "Platform", store: "Job queue", sla: "best effort" }
|
|
718
|
+
]}
|
|
719
|
+
/>
|
|
720
|
+
|
|
721
|
+
<KeyFacts
|
|
722
|
+
items={[
|
|
723
|
+
{ label: "Language", value: "Replace with yours \u2014 e.g. TypeScript / Go" },
|
|
724
|
+
{ label: "Runtime", value: "Where it runs \u2014 e.g. containers on your cloud" },
|
|
725
|
+
{ label: "Primary store", value: "Postgres" },
|
|
726
|
+
{ label: "Cache", value: "Redis" },
|
|
727
|
+
]}
|
|
728
|
+
/>
|
|
670
729
|
`
|
|
671
730
|
);
|
|
672
731
|
write(
|
|
673
|
-
"
|
|
674
|
-
|
|
675
|
-
|
|
732
|
+
"content/docs/engineering/decisions.mdx",
|
|
733
|
+
`---
|
|
734
|
+
title: Decisions
|
|
735
|
+
description: The architectural decisions behind how things are built \u2014 and why.
|
|
736
|
+
summary: Architecture decision records (ADRs). Each captures the context, the call, and its consequences so an agent can reason about why the system is the way it is.
|
|
737
|
+
tags: [engineering, decisions, adr]
|
|
738
|
+
---
|
|
676
739
|
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
740
|
+
<SectionHead
|
|
741
|
+
eyebrow="Engineering"
|
|
742
|
+
title="Decisions"
|
|
743
|
+
description="Why the system looks the way it does. Add an ADR whenever a choice is hard to reverse."
|
|
744
|
+
/>
|
|
745
|
+
|
|
746
|
+
These are placeholder ADRs. Keep the format \u2014 context, decision, consequences \u2014 so both a new hire
|
|
747
|
+
and an agent can understand not just *what* was chosen but *why*.
|
|
748
|
+
|
|
749
|
+
<Decision
|
|
750
|
+
title="Postgres as the primary store"
|
|
751
|
+
status="accepted"
|
|
752
|
+
identifier="ADR-001"
|
|
753
|
+
date="2024-01-15"
|
|
754
|
+
context={<>We needed a primary store with strong consistency, relational queries, and an operational track record the team already had.</>}
|
|
755
|
+
decision={<>Use a single Postgres cluster as the system of record; reach for other stores only when a workload clearly outgrows it.</>}
|
|
756
|
+
consequences={[
|
|
757
|
+
"One backup, failover, and migration story to learn.",
|
|
758
|
+
"Relational integrity by default; JSONB where we need flexibility.",
|
|
759
|
+
"We accept vertical-scaling limits until a workload proves it needs more.",
|
|
760
|
+
]}
|
|
761
|
+
/>
|
|
682
762
|
|
|
683
|
-
|
|
763
|
+
<Decision
|
|
764
|
+
title="Redis cache in front of reads"
|
|
765
|
+
status="accepted"
|
|
766
|
+
identifier="ADR-002"
|
|
767
|
+
date="2024-03-02"
|
|
768
|
+
context={<>Hot read paths were hitting Postgres harder than necessary as traffic grew.</>}
|
|
769
|
+
decision={<>Add a Redis cache with a read-through pattern on the hottest endpoints; treat it as disposable.</>}
|
|
770
|
+
consequences={[
|
|
771
|
+
"Lower p99 on cached endpoints.",
|
|
772
|
+
"A cache-invalidation discipline we have to keep honest.",
|
|
773
|
+
"The system must stay correct when the cache is cold or down.",
|
|
774
|
+
]}
|
|
775
|
+
refs={[{ rel: "see", target: "/docs/engineering/architecture", label: "Where the cache sits" }]}
|
|
776
|
+
/>
|
|
684
777
|
`
|
|
685
778
|
);
|
|
686
779
|
write(
|
|
687
|
-
"
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
780
|
+
"content/docs/engineering/runbooks.mdx",
|
|
781
|
+
`---
|
|
782
|
+
title: Runbooks
|
|
783
|
+
description: What to do when things break \u2014 and who's on call.
|
|
784
|
+
summary: Operational runbooks for common incidents, as ordered checklists, plus the on-call rotation. An agent can walk these steps or hand them to the person on call.
|
|
785
|
+
tags: [engineering, operations, runbooks, on-call]
|
|
786
|
+
---
|
|
691
787
|
|
|
692
|
-
|
|
788
|
+
<SectionHead
|
|
789
|
+
eyebrow="Engineering"
|
|
790
|
+
title="Runbooks"
|
|
791
|
+
description="Calm, ordered steps for the bad days. Replace with your real incident procedures."
|
|
792
|
+
/>
|
|
793
|
+
|
|
794
|
+
## Runbook: elevated error rate
|
|
795
|
+
|
|
796
|
+
<Checklist
|
|
797
|
+
label="Elevated 5xx on the API"
|
|
798
|
+
items={[
|
|
799
|
+
{ text: "Confirm the alert against the dashboard \u2014 is it real and ongoing?", group: "Assess" },
|
|
800
|
+
{ text: "Check the most recent deploy; roll back if the timeline lines up", group: "Assess" },
|
|
801
|
+
{ text: "Check Redis and Postgres health and connection counts", group: "Assess" },
|
|
802
|
+
{ text: "If a dependency is down, shed load and post status", group: "Mitigate" },
|
|
803
|
+
{ text: "Once stable, capture a timeline for the post-incident review", group: "Recover" },
|
|
804
|
+
]}
|
|
805
|
+
/>
|
|
806
|
+
|
|
807
|
+
## On-call rotation
|
|
808
|
+
|
|
809
|
+
<Schedule
|
|
810
|
+
label="On-call this cycle"
|
|
811
|
+
events={[
|
|
812
|
+
{ date: "2024-06-03", title: "Primary \u2014 replace with a name", body: "Carries the pager; first responder." },
|
|
813
|
+
{ date: "2024-06-10", title: "Secondary \u2014 replace with a name", body: "Backup; escalation point." },
|
|
814
|
+
{ date: "2024-06-17", title: "Primary \u2014 replace with a name", body: "Carries the pager; first responder." },
|
|
815
|
+
]}
|
|
816
|
+
/>
|
|
817
|
+
|
|
818
|
+
<Tip title="Keep runbooks executable">
|
|
819
|
+
A good runbook is a list someone half-asleep can follow. If a step needs judgement, say what to
|
|
820
|
+
weigh. The agent can read these too \u2014 keep them concrete.
|
|
821
|
+
</Tip>
|
|
693
822
|
`
|
|
694
823
|
);
|
|
695
824
|
write(
|
|
696
|
-
"
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
/* Brand accent \u2014 superlore derives the hover/weak/border/text family for both themes. */
|
|
703
|
-
--kp-accent: ${accent2};
|
|
704
|
-
}
|
|
825
|
+
"content/docs/product/meta.json",
|
|
826
|
+
`${JSON.stringify(
|
|
827
|
+
{ title: "Product", icon: "Rocket", pages: ["roadmap", "releases"] },
|
|
828
|
+
null,
|
|
829
|
+
2
|
|
830
|
+
)}
|
|
705
831
|
`
|
|
706
832
|
);
|
|
707
833
|
write(
|
|
708
|
-
"
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
834
|
+
"content/docs/product/roadmap.mdx",
|
|
835
|
+
`---
|
|
836
|
+
title: Roadmap
|
|
837
|
+
description: What we're building \u2014 now, next, and later.
|
|
838
|
+
summary: The product roadmap as a board (now / next / later) and a timeline of milestones. An agent can answer "what's planned" and "what's in flight" from this structured data.
|
|
839
|
+
tags: [product, roadmap, planning]
|
|
840
|
+
---
|
|
712
841
|
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
842
|
+
<SectionHead
|
|
843
|
+
eyebrow="Product"
|
|
844
|
+
title="Roadmap"
|
|
845
|
+
description="Where the work is heading. Replace the cards and milestones with yours."
|
|
846
|
+
/>
|
|
847
|
+
|
|
848
|
+
<Board
|
|
849
|
+
label="Now / Next / Later"
|
|
850
|
+
columns={[
|
|
851
|
+
{ title: "Now", status: "in-progress", cards: [
|
|
852
|
+
{ title: "Faster onboarding", body: "Cut time-to-first-value for new accounts.", status: "in-progress", tags: ["growth"] },
|
|
853
|
+
{ title: "Audit log", body: "Per-account, exportable.", status: "in-progress", tags: ["enterprise"] }
|
|
854
|
+
] },
|
|
855
|
+
{ title: "Next", status: "planned", cards: [
|
|
856
|
+
{ title: "SSO for teams", body: "SAML + SCIM.", status: "planned", tags: ["enterprise"] },
|
|
857
|
+
{ title: "Usage-based billing", body: "Meter and invoice on usage.", status: "planned", tags: ["billing"] }
|
|
858
|
+
] },
|
|
859
|
+
{ title: "Later", status: "planned", cards: [
|
|
860
|
+
{ title: "Public API v2", body: "Cleaner resources, better pagination.", status: "planned", tags: ["platform"] }
|
|
861
|
+
] }
|
|
862
|
+
]}
|
|
863
|
+
/>
|
|
864
|
+
|
|
865
|
+
## Milestones
|
|
866
|
+
|
|
867
|
+
<Timeline
|
|
868
|
+
label="Product milestones"
|
|
869
|
+
items={[
|
|
870
|
+
{ date: "2024-Q1", title: "GA launch", body: "First generally-available release.", status: "done", tags: ["launch"] },
|
|
871
|
+
{ date: "2024-Q2", title: "Enterprise readiness", body: "Audit log, roles, SSO groundwork.", status: "in-progress", tags: ["enterprise"] },
|
|
872
|
+
{ date: "2024-Q3", title: "Self-serve growth", body: "Onboarding and usage-based billing.", status: "planned", tags: ["growth"] },
|
|
873
|
+
]}
|
|
874
|
+
/>
|
|
723
875
|
`
|
|
724
876
|
);
|
|
725
877
|
write(
|
|
726
|
-
"
|
|
727
|
-
|
|
878
|
+
"content/docs/product/releases.mdx",
|
|
879
|
+
`---
|
|
880
|
+
title: Releases
|
|
881
|
+
description: What shipped, version by version.
|
|
882
|
+
summary: The release history with versioned, categorized changes (added / changed / fixed). An agent can answer "what changed in 1.2?" from this structured changelog.
|
|
883
|
+
tags: [product, releases, changelog]
|
|
884
|
+
---
|
|
728
885
|
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
One corpus. Humans and agents. Start in <Link href="/docs">the docs</Link>.
|
|
735
|
-
</p>
|
|
736
|
-
</main>
|
|
737
|
-
);
|
|
738
|
-
}
|
|
739
|
-
`
|
|
740
|
-
);
|
|
741
|
-
write(
|
|
742
|
-
"app/docs/layout.tsx",
|
|
743
|
-
`import type { ReactNode } from "react";
|
|
744
|
-
import { DocsLayout } from "fumadocs-ui/layouts/docs";
|
|
745
|
-
import { source } from "@/lib/source";
|
|
746
|
-
|
|
747
|
-
export default function Layout({ children }: { children: ReactNode }) {
|
|
748
|
-
return (
|
|
749
|
-
<DocsLayout tree={source.pageTree} nav={{ title: "${escapeForJsx(config.name)}" }}>
|
|
750
|
-
{children}
|
|
751
|
-
</DocsLayout>
|
|
752
|
-
);
|
|
753
|
-
}
|
|
754
|
-
`
|
|
755
|
-
);
|
|
756
|
-
write(
|
|
757
|
-
"app/docs/[[...slug]]/page.tsx",
|
|
758
|
-
`import { notFound } from "next/navigation";
|
|
759
|
-
import { DocsBody, DocsPage } from "fumadocs-ui/page";
|
|
760
|
-
import { getMDXComponents } from "superlore";
|
|
761
|
-
import { source } from "@/lib/source";
|
|
886
|
+
<SectionHead
|
|
887
|
+
eyebrow="Product"
|
|
888
|
+
title="Releases"
|
|
889
|
+
description="The changelog, as structured data. Replace with your real release notes."
|
|
890
|
+
/>
|
|
762
891
|
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
}
|
|
892
|
+
<Releases
|
|
893
|
+
version="1.2.0"
|
|
894
|
+
date="2024-05-20"
|
|
895
|
+
status="done"
|
|
896
|
+
title="Audit log (beta)"
|
|
897
|
+
summary="Per-account audit logging, plus reliability fixes."
|
|
898
|
+
changes={[
|
|
899
|
+
{ type: "added", text: "Exportable audit log for account admins." },
|
|
900
|
+
{ type: "changed", text: "Faster account switching." },
|
|
901
|
+
{ type: "fixed", text: "Rare double-charge on retried payments." },
|
|
902
|
+
]}
|
|
903
|
+
/>
|
|
776
904
|
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
905
|
+
<Releases
|
|
906
|
+
version="1.1.0"
|
|
907
|
+
date="2024-04-02"
|
|
908
|
+
status="done"
|
|
909
|
+
title="Onboarding refresh"
|
|
910
|
+
changes={[
|
|
911
|
+
{ type: "added", text: "Guided setup for new accounts." },
|
|
912
|
+
{ type: "fixed", text: "Invite emails occasionally landing in spam." },
|
|
913
|
+
]}
|
|
914
|
+
/>
|
|
780
915
|
`
|
|
781
916
|
);
|
|
782
917
|
write(
|
|
783
|
-
"
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
source: docs.toFumadocsSource(),
|
|
790
|
-
plugins: [lucideIconsPlugin()],
|
|
791
|
-
});
|
|
918
|
+
"content/docs/team/meta.json",
|
|
919
|
+
`${JSON.stringify(
|
|
920
|
+
{ title: "Team", icon: "Users", pages: ["onboarding", "people"] },
|
|
921
|
+
null,
|
|
922
|
+
2
|
|
923
|
+
)}
|
|
792
924
|
`
|
|
793
925
|
);
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
description: The home of your superlore knowledge base.
|
|
802
|
-
summary: Landing page for the ${config.name} knowledge base.
|
|
803
|
-
tags: [getting-started]
|
|
926
|
+
write(
|
|
927
|
+
"content/docs/team/onboarding.mdx",
|
|
928
|
+
`---
|
|
929
|
+
title: Onboarding
|
|
930
|
+
description: Your first week \u2014 what to set up, who to meet, and where to start.
|
|
931
|
+
summary: The new-hire onboarding path as ordered steps and a first-week checklist. An agent can guide a new teammate through setup using these steps.
|
|
932
|
+
tags: [team, onboarding, getting-started]
|
|
804
933
|
---
|
|
805
934
|
|
|
806
|
-
|
|
935
|
+
<SectionHead
|
|
936
|
+
eyebrow="Team"
|
|
937
|
+
title="Onboarding"
|
|
938
|
+
description="Welcome. This is the path from day one to your first shipped change. Replace with yours."
|
|
939
|
+
/>
|
|
807
940
|
|
|
808
|
-
|
|
809
|
-
|
|
941
|
+
<Steps>
|
|
942
|
+
<Step>
|
|
943
|
+
### Get access
|
|
944
|
+
Accounts, repos, and the password manager. If something's missing, ask in the team channel.
|
|
945
|
+
</Step>
|
|
946
|
+
<Step>
|
|
947
|
+
### Set up your machine
|
|
948
|
+
Clone the repo, install dependencies, and get the app running locally.
|
|
949
|
+
</Step>
|
|
950
|
+
<Step>
|
|
951
|
+
### Read the architecture
|
|
952
|
+
Skim [Architecture](/docs/engineering/architecture) and the [Decisions](/docs/engineering/decisions) so the system isn't a black box.
|
|
953
|
+
</Step>
|
|
954
|
+
<Step>
|
|
955
|
+
### Ship something small
|
|
956
|
+
Pick a starter issue. The goal of week one is a merged PR, however small.
|
|
957
|
+
</Step>
|
|
958
|
+
</Steps>
|
|
959
|
+
|
|
960
|
+
## First-week checklist
|
|
810
961
|
|
|
811
|
-
|
|
962
|
+
<Checklist
|
|
963
|
+
label="Your first week"
|
|
964
|
+
items={[
|
|
965
|
+
{ text: "Access to repos, cloud, and the password manager", group: "Day 1" },
|
|
966
|
+
{ text: "App running locally", group: "Day 1" },
|
|
967
|
+
{ text: "Met your onboarding buddy", group: "Day 1" },
|
|
968
|
+
{ text: "Read the architecture and recent decisions", group: "Week 1" },
|
|
969
|
+
{ text: "Opened your first PR", group: "Week 1" },
|
|
970
|
+
{ text: "Added yourself to the team page", group: "Week 1" },
|
|
971
|
+
]}
|
|
972
|
+
/>
|
|
812
973
|
`
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
`import { createMcpHandler } from "mcp-handler";
|
|
823
|
-
import { z } from "zod";
|
|
824
|
-
import { getComponentData, getPage, list, navigate, search } from "superlore/mcp";
|
|
825
|
-
import { buildIndexFromSource } from "superlore/source";
|
|
826
|
-
import type { KKind } from "superlore";
|
|
827
|
-
import { source } from "@/lib/source";
|
|
974
|
+
);
|
|
975
|
+
write(
|
|
976
|
+
"content/docs/team/people.mdx",
|
|
977
|
+
`---
|
|
978
|
+
title: People
|
|
979
|
+
description: Who's on the team and what they own.
|
|
980
|
+
summary: The team roster \u2014 who's here, their role, and who they report to \u2014 plus an example entity card. An agent can answer "who owns X?" from this structured directory.
|
|
981
|
+
tags: [team, people, directory]
|
|
982
|
+
---
|
|
828
983
|
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
984
|
+
<SectionHead
|
|
985
|
+
eyebrow="Team"
|
|
986
|
+
title="People"
|
|
987
|
+
description="Who to ask about what. Replace these placeholders with your real team."
|
|
988
|
+
/>
|
|
833
989
|
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
990
|
+
<Roster
|
|
991
|
+
label="The team"
|
|
992
|
+
people={[
|
|
993
|
+
{ name: "Replace Me", role: "Engineering lead", tags: ["platform"] },
|
|
994
|
+
{ name: "Replace Me", role: "Product", reportsTo: "Replace Me", tags: ["roadmap"] },
|
|
995
|
+
{ name: "Replace Me", role: "Engineer", reportsTo: "Replace Me", tags: ["platform"] },
|
|
996
|
+
{ name: "Replace Me", role: "Design", reportsTo: "Replace Me", tags: ["product"] },
|
|
997
|
+
]}
|
|
998
|
+
/>
|
|
837
999
|
|
|
838
|
-
|
|
839
|
-
(server) => {
|
|
840
|
-
server.tool(
|
|
841
|
-
"search",
|
|
842
|
-
"Full-text search across the knowledge base.",
|
|
843
|
-
{ query: z.string(), limit: z.number().int().positive().optional() },
|
|
844
|
-
async ({ query, limit }) => json(search(index, query, limit)),
|
|
845
|
-
);
|
|
846
|
-
server.tool(
|
|
847
|
-
"get_page",
|
|
848
|
-
"Get a page's full structured content by path.",
|
|
849
|
-
{ path: z.string() },
|
|
850
|
-
async ({ path }) => json(getPage(index, path)),
|
|
851
|
-
);
|
|
852
|
-
server.tool(
|
|
853
|
-
"list",
|
|
854
|
-
"List knowledge nodes, filtered by kind / tag / entityType.",
|
|
855
|
-
{ kind: z.string().optional(), tag: z.string().optional(), entityType: z.string().optional() },
|
|
856
|
-
async ({ kind, tag, entityType }) =>
|
|
857
|
-
json(list(index, { kind: kind as KKind | undefined, tag, entityType })),
|
|
858
|
-
);
|
|
859
|
-
server.tool(
|
|
860
|
-
"navigate",
|
|
861
|
-
"Follow relations from a page path / node id / entity ref.",
|
|
862
|
-
{ target: z.string() },
|
|
863
|
-
async ({ target }) => json(navigate(index, target)),
|
|
864
|
-
);
|
|
865
|
-
server.tool(
|
|
866
|
-
"get_component_data",
|
|
867
|
-
"Get the structured data behind a rendered component (its knowledge face).",
|
|
868
|
-
{ id: z.string() },
|
|
869
|
-
async ({ id }) => json(getComponentData(index, id)),
|
|
870
|
-
);
|
|
871
|
-
},
|
|
872
|
-
{},
|
|
873
|
-
{ basePath: "/api" },
|
|
874
|
-
);
|
|
1000
|
+
## Example: a person, as structured data
|
|
875
1001
|
|
|
876
|
-
|
|
1002
|
+
<EntityCard
|
|
1003
|
+
type="person"
|
|
1004
|
+
slug="engineering-lead"
|
|
1005
|
+
title="Engineering lead"
|
|
1006
|
+
summary="One line on what they own and how to reach them."
|
|
1007
|
+
icon="user"
|
|
1008
|
+
fields={[
|
|
1009
|
+
{ key: "Owns", value: "Platform & architecture" },
|
|
1010
|
+
{ key: "Time zone", value: "UTC+0" },
|
|
1011
|
+
{ key: "Best reached", value: "Async, in the team channel" },
|
|
1012
|
+
]}
|
|
1013
|
+
refs={[{ rel: "see", target: "/docs/engineering/architecture", label: "What they own" }]}
|
|
1014
|
+
/>
|
|
877
1015
|
`
|
|
878
|
-
|
|
879
|
-
|
|
1016
|
+
);
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
// src/lib/content/product.ts
|
|
1020
|
+
function writeProductContent(write, config) {
|
|
880
1021
|
write(
|
|
881
|
-
".
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
.
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
1022
|
+
"content/docs/meta.json",
|
|
1023
|
+
`${JSON.stringify(
|
|
1024
|
+
{
|
|
1025
|
+
title: config.name,
|
|
1026
|
+
root: true,
|
|
1027
|
+
icon: "BookOpen",
|
|
1028
|
+
pages: ["index", "getting-started", "concepts", "guides", "reference", "changelog"]
|
|
1029
|
+
},
|
|
1030
|
+
null,
|
|
1031
|
+
2
|
|
1032
|
+
)}
|
|
888
1033
|
`
|
|
889
1034
|
);
|
|
890
|
-
if (authEnabled) {
|
|
891
|
-
write(
|
|
892
|
-
".env.example",
|
|
893
|
-
`# Auth.js v5 + Google SSO. The gate is OFF until AUTH_GOOGLE_ID is set, so local dev works with
|
|
894
|
-
# this file empty. Copy to .env.local and fill in to enable it on a deploy.
|
|
895
|
-
AUTH_SECRET= # \`openssl rand -base64 32\`
|
|
896
|
-
AUTH_GOOGLE_ID= # presence of this turns the gate ON
|
|
897
|
-
AUTH_GOOGLE_SECRET=
|
|
898
|
-
AUTH_URL= # the deploy's canonical URL, e.g. https://your-kb.vercel.app
|
|
899
|
-
AUTH_TRUST_HOST=true
|
|
900
|
-
${config.auth?.allowedDomain ? `AUTH_ALLOWED_DOMAIN=${config.auth.allowedDomain}` : "# AUTH_ALLOWED_DOMAIN=example.com # restrict sign-in to one workspace domain (optional)"}
|
|
901
|
-
# AUTH_ALLOWED_EMAILS=you@example.com # comma-separated allowlist that bypasses the domain check
|
|
902
|
-
# LOCAL=true # force the gate OFF locally even when configured
|
|
903
|
-
`
|
|
904
|
-
);
|
|
905
|
-
}
|
|
906
|
-
const authReadme = authEnabled ? `
|
|
907
|
-
|
|
908
|
-
## Auth
|
|
909
|
-
|
|
910
|
-
This KB ships a Google SSO gate (Auth.js v5). It is **off until you set \`AUTH_GOOGLE_ID\`**, so local dev runs open. Copy \`.env.example\` to \`.env.local\` and fill it in to enable it. The gate lives in \`proxy.ts\`, in front of every route \u2014 so the MCP inherits it too.` : "";
|
|
911
1035
|
write(
|
|
912
|
-
"
|
|
913
|
-
|
|
1036
|
+
"content/docs/index.mdx",
|
|
1037
|
+
`---
|
|
1038
|
+
title: Overview
|
|
1039
|
+
description: What ${config.name} is, who it's for, and where to start.
|
|
1040
|
+
summary: Overview of ${config.name} \u2014 what it does, the value it delivers, and the paths into the docs. The starting point for both a reader and an agent answering "what is this?".
|
|
1041
|
+
tags: [overview, getting-started]
|
|
1042
|
+
---
|
|
914
1043
|
|
|
915
|
-
|
|
1044
|
+
<PageHero
|
|
1045
|
+
kicker="Documentation"
|
|
1046
|
+
title="${config.name}"
|
|
1047
|
+
description="What it is, in one line you'll replace with your own. Author once in MDX \u2014 readers get this site, agents get the same content over MCP."
|
|
1048
|
+
icon="book-open"
|
|
1049
|
+
/>
|
|
916
1050
|
|
|
917
|
-
|
|
1051
|
+
<Note title="This is a starter structure">
|
|
1052
|
+
Placeholder docs that show the shape of a good product site. Keep the structure, replace the
|
|
1053
|
+
content. Preview with \`superlore dev\`.
|
|
1054
|
+
</Note>
|
|
918
1055
|
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
1056
|
+
<StatGrid
|
|
1057
|
+
stats={[
|
|
1058
|
+
{ label: "Set up in", value: "5 min", hint: "From install to first call" },
|
|
1059
|
+
{ label: "Works with", value: "Any stack", hint: "Replace with your real surface" },
|
|
1060
|
+
{ label: "Agent-ready", value: "MCP", hint: "The docs, queryable" },
|
|
1061
|
+
]}
|
|
1062
|
+
/>
|
|
923
1063
|
|
|
924
|
-
##
|
|
1064
|
+
## Start here
|
|
925
1065
|
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
1066
|
+
<FeatureList
|
|
1067
|
+
items={[
|
|
1068
|
+
{ icon: "rocket", title: "Getting started", description: "Install, configure, and make your first call.", href: "/docs/getting-started" },
|
|
1069
|
+
{ icon: "lightbulb", title: "Concepts", description: "How it works and the core ideas.", href: "/docs/concepts/how-it-works" },
|
|
1070
|
+
{ icon: "book-open", title: "Guides", description: "Step-by-step for common tasks.", href: "/docs/guides/first-integration" },
|
|
1071
|
+
{ icon: "settings", title: "Reference", description: "Every option, in one place.", href: "/docs/reference/configuration" },
|
|
1072
|
+
]}
|
|
1073
|
+
/>
|
|
1074
|
+
`
|
|
1075
|
+
);
|
|
1076
|
+
write(
|
|
1077
|
+
"content/docs/getting-started.mdx",
|
|
1078
|
+
`---
|
|
1079
|
+
title: Getting started
|
|
1080
|
+
description: Install ${config.name}, configure it, and make your first call.
|
|
1081
|
+
summary: The quickstart for ${config.name} \u2014 install, minimal configuration, and a first working call, as ordered steps an agent can also follow.
|
|
1082
|
+
tags: [getting-started, quickstart, install]
|
|
1083
|
+
---
|
|
929
1084
|
|
|
930
|
-
|
|
1085
|
+
<SectionHead
|
|
1086
|
+
eyebrow="Getting started"
|
|
1087
|
+
title="Quickstart"
|
|
1088
|
+
description="From nothing to a working call. Replace the commands and snippet with your real ones."
|
|
1089
|
+
/>
|
|
931
1090
|
|
|
932
|
-
|
|
1091
|
+
<Steps>
|
|
1092
|
+
<Step>
|
|
1093
|
+
### Install
|
|
1094
|
+
|
|
1095
|
+
\`\`\`bash
|
|
1096
|
+
npm install your-package
|
|
1097
|
+
\`\`\`
|
|
1098
|
+
</Step>
|
|
1099
|
+
<Step>
|
|
1100
|
+
### Configure
|
|
1101
|
+
|
|
1102
|
+
Set your API key in the environment:
|
|
1103
|
+
|
|
1104
|
+
\`\`\`bash
|
|
1105
|
+
export YOUR_API_KEY=sk_...
|
|
1106
|
+
\`\`\`
|
|
1107
|
+
</Step>
|
|
1108
|
+
<Step>
|
|
1109
|
+
### Make your first call
|
|
1110
|
+
|
|
1111
|
+
\`\`\`ts
|
|
1112
|
+
import { Client } from "your-package";
|
|
1113
|
+
|
|
1114
|
+
const client = new Client({ apiKey: process.env.YOUR_API_KEY });
|
|
1115
|
+
const result = await client.ping();
|
|
1116
|
+
console.log(result);
|
|
1117
|
+
\`\`\`
|
|
1118
|
+
</Step>
|
|
1119
|
+
</Steps>
|
|
1120
|
+
|
|
1121
|
+
<Tip title="That's it">
|
|
1122
|
+
You've made your first call. Next, read [how it works](/docs/concepts/how-it-works) or jump into a
|
|
1123
|
+
[guide](/docs/guides/first-integration).
|
|
1124
|
+
</Tip>
|
|
933
1125
|
`
|
|
934
1126
|
);
|
|
935
|
-
}
|
|
936
|
-
function writeAuth(write, config) {
|
|
937
|
-
const allowedDomain = config.auth?.allowedDomain;
|
|
938
1127
|
write(
|
|
939
|
-
"
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
allowedDomain: ${JSON.stringify(allowedDomain)},
|
|
946
|
-
}` : "{}"});
|
|
1128
|
+
"content/docs/concepts/meta.json",
|
|
1129
|
+
`${JSON.stringify(
|
|
1130
|
+
{ title: "Concepts", icon: "Lightbulb", pages: ["how-it-works", "core-concepts"] },
|
|
1131
|
+
null,
|
|
1132
|
+
2
|
|
1133
|
+
)}
|
|
947
1134
|
`
|
|
948
1135
|
);
|
|
949
1136
|
write(
|
|
950
|
-
"
|
|
951
|
-
|
|
1137
|
+
"content/docs/concepts/how-it-works.mdx",
|
|
1138
|
+
`---
|
|
1139
|
+
title: How it works
|
|
1140
|
+
description: The path a request takes through ${config.name}, end to end.
|
|
1141
|
+
summary: How ${config.name} works under the hood \u2014 the request path from the caller's app through the API to the result. The canvas is the authoritative diagram; the agent reads the same graph.
|
|
1142
|
+
tags: [concepts, architecture, how-it-works]
|
|
1143
|
+
---
|
|
952
1144
|
|
|
953
|
-
|
|
1145
|
+
<SectionHead
|
|
1146
|
+
eyebrow="Concepts"
|
|
1147
|
+
title="How it works"
|
|
1148
|
+
description="The journey of a request. Redraw the canvas to match your real flow."
|
|
1149
|
+
/>
|
|
1150
|
+
|
|
1151
|
+
\`\`\`superlore-canvas
|
|
1152
|
+
{
|
|
1153
|
+
"title": "Request lifecycle",
|
|
1154
|
+
"direction": "right",
|
|
1155
|
+
"groups": [
|
|
1156
|
+
{ "id": "you", "label": "Your app", "frame": true, "intent": "gray" },
|
|
1157
|
+
{ "id": "svc", "label": "${config.name}", "frame": true, "intent": "accent" },
|
|
1158
|
+
{ "id": "out", "label": "Result", "frame": true, "intent": "green" }
|
|
1159
|
+
],
|
|
1160
|
+
"nodes": [
|
|
1161
|
+
{ "id": "sdk", "kind": "rect", "intent": "blue", "group": "you", "icon": "code", "label": "SDK / HTTP call" },
|
|
1162
|
+
{ "id": "auth", "kind": "icon", "icon": "shield", "group": "svc", "label": "Auth" },
|
|
1163
|
+
{ "id": "validate", "kind": "diamond", "group": "svc", "label": "Valid request?" },
|
|
1164
|
+
{ "id": "process", "kind": "rect", "intent": "purple", "group": "svc", "label": "Process" },
|
|
1165
|
+
{ "id": "store", "kind": "cylinder", "intent": "green", "group": "svc", "label": "Store" },
|
|
1166
|
+
{ "id": "result", "kind": "rounded", "intent": "green", "group": "out", "label": "Response" },
|
|
1167
|
+
{ "id": "error", "kind": "callout", "intent": "red", "group": "out", "label": "Typed error" }
|
|
1168
|
+
],
|
|
1169
|
+
"edges": [
|
|
1170
|
+
{ "from": "sdk", "to": "auth", "label": "request", "intent": "blue" },
|
|
1171
|
+
{ "from": "auth", "to": "validate", "rel": "links" },
|
|
1172
|
+
{ "from": "validate", "to": "process", "label": "yes", "intent": "purple" },
|
|
1173
|
+
{ "from": "validate", "to": "error", "label": "no", "kind": "dashed", "intent": "red" },
|
|
1174
|
+
{ "from": "process", "to": "store", "intent": "green", "rel": "depends-on" },
|
|
1175
|
+
{ "from": "process", "to": "result", "intent": "green" }
|
|
1176
|
+
]
|
|
1177
|
+
}
|
|
1178
|
+
\`\`\`
|
|
1179
|
+
|
|
1180
|
+
<KeyFacts
|
|
1181
|
+
items={[
|
|
1182
|
+
{ label: "Transport", value: "HTTPS / your SDK" },
|
|
1183
|
+
{ label: "Auth", value: "API key \u2014 replace with your real scheme" },
|
|
1184
|
+
{ label: "Errors", value: "Typed and predictable, never a bare 500" },
|
|
1185
|
+
{ label: "Idempotency", value: "Safe to retry \u2014 say how, here" },
|
|
1186
|
+
]}
|
|
1187
|
+
/>
|
|
954
1188
|
`
|
|
955
1189
|
);
|
|
956
1190
|
write(
|
|
957
|
-
"
|
|
958
|
-
|
|
959
|
-
|
|
1191
|
+
"content/docs/concepts/core-concepts.mdx",
|
|
1192
|
+
`---
|
|
1193
|
+
title: Core concepts
|
|
1194
|
+
description: The handful of ideas that explain everything else.
|
|
1195
|
+
summary: The core concepts of ${config.name} \u2014 the vocabulary a reader (or agent) needs, plus how the approaches compare.
|
|
1196
|
+
tags: [concepts, glossary]
|
|
1197
|
+
---
|
|
960
1198
|
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
1199
|
+
<SectionHead
|
|
1200
|
+
eyebrow="Concepts"
|
|
1201
|
+
title="Core concepts"
|
|
1202
|
+
description="Learn these five words and the rest of the docs make sense. Replace with yours."
|
|
1203
|
+
/>
|
|
964
1204
|
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
}
|
|
1205
|
+
<FeatureList
|
|
1206
|
+
items={[
|
|
1207
|
+
{ icon: "box", title: "Resource", description: "The main thing you create and operate on. Define yours." },
|
|
1208
|
+
{ icon: "key", title: "API key", description: "How a request is authenticated." },
|
|
1209
|
+
{ icon: "webhook", title: "Webhook", description: "How you hear about events asynchronously." },
|
|
1210
|
+
{ icon: "repeat", title: "Idempotency key", description: "How a retry stays safe." },
|
|
1211
|
+
]}
|
|
1212
|
+
/>
|
|
1213
|
+
|
|
1214
|
+
## Which approach fits?
|
|
1215
|
+
|
|
1216
|
+
<Comparison
|
|
1217
|
+
caption="Choosing how to integrate"
|
|
1218
|
+
options={["SDK", "Raw HTTP"]}
|
|
1219
|
+
rows={[
|
|
1220
|
+
{ criterion: "Setup time", cells: ["Minutes", "A bit more"] },
|
|
1221
|
+
{ criterion: "Type safety", cells: [true, false] },
|
|
1222
|
+
{ criterion: "Control", cells: ["partial", true] },
|
|
1223
|
+
{ criterion: "Best for", cells: ["Most apps", "Exotic runtimes"] },
|
|
1224
|
+
]}
|
|
1225
|
+
/>
|
|
969
1226
|
`
|
|
970
1227
|
);
|
|
971
1228
|
write(
|
|
972
|
-
"
|
|
973
|
-
|
|
1229
|
+
"content/docs/guides/meta.json",
|
|
1230
|
+
`${JSON.stringify(
|
|
1231
|
+
{ title: "Guides", icon: "BookOpen", pages: ["first-integration"] },
|
|
1232
|
+
null,
|
|
1233
|
+
2
|
|
1234
|
+
)}
|
|
1235
|
+
`
|
|
1236
|
+
);
|
|
1237
|
+
write(
|
|
1238
|
+
"content/docs/guides/first-integration.mdx",
|
|
1239
|
+
`---
|
|
1240
|
+
title: Your first integration
|
|
1241
|
+
description: A worked, end-to-end example of integrating ${config.name}.
|
|
1242
|
+
summary: A step-by-step guide to a first real integration with ${config.name}, with a verification checklist an agent can run through.
|
|
1243
|
+
tags: [guides, integration, tutorial]
|
|
1244
|
+
---
|
|
974
1245
|
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1246
|
+
<SectionHead
|
|
1247
|
+
eyebrow="Guides"
|
|
1248
|
+
title="Your first integration"
|
|
1249
|
+
description="A realistic, end-to-end task. Replace with a real flow your users care about."
|
|
1250
|
+
/>
|
|
1251
|
+
|
|
1252
|
+
<Steps>
|
|
1253
|
+
<Step>
|
|
1254
|
+
### Create a resource
|
|
1255
|
+
|
|
1256
|
+
\`\`\`ts
|
|
1257
|
+
const item = await client.items.create({ name: "Example" });
|
|
1258
|
+
\`\`\`
|
|
1259
|
+
</Step>
|
|
1260
|
+
<Step>
|
|
1261
|
+
### Handle the response
|
|
1262
|
+
|
|
1263
|
+
Check for the typed error before using the result. Never assume success.
|
|
1264
|
+
</Step>
|
|
1265
|
+
<Step>
|
|
1266
|
+
### Listen for events
|
|
1267
|
+
|
|
1268
|
+
Register a webhook so you hear about changes you didn't initiate.
|
|
1269
|
+
</Step>
|
|
1270
|
+
</Steps>
|
|
1271
|
+
|
|
1272
|
+
## Before you ship
|
|
1273
|
+
|
|
1274
|
+
<Checklist
|
|
1275
|
+
label="Integration checklist"
|
|
1276
|
+
items={[
|
|
1277
|
+
{ text: "API key stored as a secret, never in code", group: "Security" },
|
|
1278
|
+
{ text: "Errors handled, not swallowed", group: "Correctness" },
|
|
1279
|
+
{ text: "Retries use an idempotency key", group: "Correctness" },
|
|
1280
|
+
{ text: "Webhook signature verified", group: "Security" },
|
|
1281
|
+
]}
|
|
1282
|
+
/>
|
|
1283
|
+
`
|
|
1000
1284
|
);
|
|
1001
|
-
|
|
1285
|
+
write(
|
|
1286
|
+
"content/docs/reference/meta.json",
|
|
1287
|
+
`${JSON.stringify(
|
|
1288
|
+
{ title: "Reference", icon: "Settings2", pages: ["configuration"] },
|
|
1289
|
+
null,
|
|
1290
|
+
2
|
|
1291
|
+
)}
|
|
1002
1292
|
`
|
|
1003
1293
|
);
|
|
1004
1294
|
write(
|
|
1005
|
-
"
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
}
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1295
|
+
"content/docs/reference/configuration.mdx",
|
|
1296
|
+
`---
|
|
1297
|
+
title: Configuration
|
|
1298
|
+
description: Every configuration option for ${config.name}, in one table.
|
|
1299
|
+
summary: The full configuration reference for ${config.name} \u2014 each option, its type, default, and what it does. An agent can answer "what does option X do?" from this table.
|
|
1300
|
+
tags: [reference, configuration, options]
|
|
1301
|
+
---
|
|
1302
|
+
|
|
1303
|
+
<SectionHead
|
|
1304
|
+
eyebrow="Reference"
|
|
1305
|
+
title="Configuration"
|
|
1306
|
+
description="The complete list of options. Replace these placeholders with your real surface."
|
|
1307
|
+
/>
|
|
1308
|
+
|
|
1309
|
+
<Table
|
|
1310
|
+
caption="Client options"
|
|
1311
|
+
columns={[
|
|
1312
|
+
{ key: "option", label: "Option", type: "code" },
|
|
1313
|
+
{ key: "type", label: "Type", type: "text" },
|
|
1314
|
+
{ key: "default", label: "Default", type: "code" },
|
|
1315
|
+
{ key: "desc", label: "Description", type: "text" }
|
|
1316
|
+
]}
|
|
1317
|
+
rows={[
|
|
1318
|
+
{ option: "apiKey", type: "string", default: "\u2014", desc: "Required. Your secret API key." },
|
|
1319
|
+
{ option: "baseUrl", type: "string", default: "https://api\u2026", desc: "Override for self-hosted or staging." },
|
|
1320
|
+
{ option: "timeout", type: "number", default: "30000", desc: "Request timeout in milliseconds." },
|
|
1321
|
+
{ option: "maxRetries", type: "number", default: "2", desc: "Automatic retries on transient errors." }
|
|
1322
|
+
]}
|
|
1323
|
+
/>
|
|
1324
|
+
|
|
1325
|
+
## A design note
|
|
1326
|
+
|
|
1327
|
+
<Decision
|
|
1328
|
+
title="Sensible defaults over required config"
|
|
1329
|
+
status="accepted"
|
|
1330
|
+
identifier="DESIGN-01"
|
|
1331
|
+
context={<>Every required option is friction between a developer and their first success.</>}
|
|
1332
|
+
decision={<>Only \`apiKey\` is required; everything else has a default that works for most apps.</>}
|
|
1333
|
+
consequences={[
|
|
1334
|
+
"A working call needs one line of config.",
|
|
1335
|
+
"Power users override exactly what they need.",
|
|
1336
|
+
]}
|
|
1337
|
+
/>
|
|
1338
|
+
`
|
|
1027
1339
|
);
|
|
1028
|
-
|
|
1340
|
+
write(
|
|
1341
|
+
"content/docs/changelog.mdx",
|
|
1342
|
+
`---
|
|
1343
|
+
title: Changelog
|
|
1344
|
+
description: What changed in ${config.name}, version by version.
|
|
1345
|
+
summary: The release history for ${config.name} with versioned, categorized changes. An agent can answer "what changed in version X?" from this structured changelog.
|
|
1346
|
+
tags: [changelog, releases]
|
|
1347
|
+
---
|
|
1348
|
+
|
|
1349
|
+
<SectionHead
|
|
1350
|
+
eyebrow="Changelog"
|
|
1351
|
+
title="What's new"
|
|
1352
|
+
description="Versioned, categorized changes. Replace with your real release notes."
|
|
1353
|
+
/>
|
|
1354
|
+
|
|
1355
|
+
<Releases
|
|
1356
|
+
version="1.1.0"
|
|
1357
|
+
date="2024-05-20"
|
|
1358
|
+
status="done"
|
|
1359
|
+
title="Webhooks"
|
|
1360
|
+
summary="Asynchronous events, plus reliability fixes."
|
|
1361
|
+
changes={[
|
|
1362
|
+
{ type: "added", text: "Webhooks for resource events." },
|
|
1363
|
+
{ type: "changed", text: "Lower default timeout to 30s." },
|
|
1364
|
+
{ type: "fixed", text: "Retries no longer double-create on timeout." },
|
|
1365
|
+
]}
|
|
1366
|
+
/>
|
|
1367
|
+
|
|
1368
|
+
<Releases
|
|
1369
|
+
version="1.0.0"
|
|
1370
|
+
date="2024-04-01"
|
|
1371
|
+
status="done"
|
|
1372
|
+
title="General availability"
|
|
1373
|
+
changes={[
|
|
1374
|
+
{ type: "added", text: "Stable v1 API and the official SDK." },
|
|
1375
|
+
]}
|
|
1376
|
+
/>
|
|
1029
1377
|
`
|
|
1030
1378
|
);
|
|
1031
1379
|
}
|
|
1380
|
+
|
|
1381
|
+
// src/lib/content/personal.ts
|
|
1032
1382
|
function writePersonalContent(write, config) {
|
|
1033
1383
|
write(
|
|
1034
1384
|
"content/docs/meta.json",
|
|
@@ -1036,6 +1386,7 @@ function writePersonalContent(write, config) {
|
|
|
1036
1386
|
{
|
|
1037
1387
|
title: config.name,
|
|
1038
1388
|
root: true,
|
|
1389
|
+
icon: "User",
|
|
1039
1390
|
pages: ["index", "beliefs", "working-style", "pr-comments", "voice", "stories"]
|
|
1040
1391
|
},
|
|
1041
1392
|
null,
|
|
@@ -1168,6 +1519,34 @@ would.
|
|
|
1168
1519
|
]}
|
|
1169
1520
|
/>
|
|
1170
1521
|
|
|
1522
|
+
## How I approach a task
|
|
1523
|
+
|
|
1524
|
+
This is the loop I run on anything non-trivial. An agent working for me should follow the same path.
|
|
1525
|
+
|
|
1526
|
+
\`\`\`superlore-canvas
|
|
1527
|
+
{
|
|
1528
|
+
"title": "How I approach a task",
|
|
1529
|
+
"layout": "row",
|
|
1530
|
+
"nodes": [
|
|
1531
|
+
{ "id": "outcome", "kind": "rounded", "intent": "accent", "icon": "target", "label": "Name the outcome", "body": "Goal + done-condition, in writing." },
|
|
1532
|
+
{ "id": "decompose", "kind": "rounded", "intent": "blue", "icon": "split", "label": "Decompose", "body": "Reversible steps, sequenced." },
|
|
1533
|
+
{ "id": "risk", "kind": "diamond", "intent": "orange", "label": "Riskiest part?" },
|
|
1534
|
+
{ "id": "spike", "kind": "rounded", "intent": "orange", "icon": "flask-conical", "label": "Spike it cheap", "body": "Prove the unknown first." },
|
|
1535
|
+
{ "id": "ship", "kind": "rounded", "intent": "green", "icon": "rocket", "label": "Ship small", "body": "Smallest reversible change." },
|
|
1536
|
+
{ "id": "reflect", "kind": "rounded", "intent": "purple", "icon": "repeat", "label": "Reflect", "body": "What did I learn? Write it down." }
|
|
1537
|
+
],
|
|
1538
|
+
"edges": [
|
|
1539
|
+
{ "from": "outcome", "to": "decompose", "intent": "accent" },
|
|
1540
|
+
{ "from": "decompose", "to": "risk", "intent": "blue" },
|
|
1541
|
+
{ "from": "risk", "to": "spike", "label": "unknown", "intent": "orange" },
|
|
1542
|
+
{ "from": "risk", "to": "ship", "label": "clear", "intent": "green" },
|
|
1543
|
+
{ "from": "spike", "to": "ship", "intent": "green" },
|
|
1544
|
+
{ "from": "ship", "to": "reflect", "intent": "purple" },
|
|
1545
|
+
{ "from": "reflect", "to": "outcome", "kind": "dashed", "intent": "muted", "label": "next slice" }
|
|
1546
|
+
]
|
|
1547
|
+
}
|
|
1548
|
+
\`\`\`
|
|
1549
|
+
|
|
1171
1550
|
## How I decide
|
|
1172
1551
|
|
|
1173
1552
|
<Decision
|
|
@@ -1195,156 +1574,655 @@ would.
|
|
|
1195
1574
|
/>
|
|
1196
1575
|
`
|
|
1197
1576
|
);
|
|
1198
|
-
write(
|
|
1199
|
-
"content/docs/pr-comments.mdx",
|
|
1200
|
-
`---
|
|
1201
|
-
title: How I give PR comments
|
|
1202
|
-
description: My review bar, the tone I use, and concrete examples of comments I leave.
|
|
1203
|
-
summary: How this person reviews code \u2014 what they block on versus nudge, the tone of their comments, and worked examples an agent can imitate when reviewing on their behalf.
|
|
1204
|
-
tags: [code-review, feedback, how-to]
|
|
1205
|
-
---
|
|
1577
|
+
write(
|
|
1578
|
+
"content/docs/pr-comments.mdx",
|
|
1579
|
+
`---
|
|
1580
|
+
title: How I give PR comments
|
|
1581
|
+
description: My review bar, the tone I use, and concrete examples of comments I leave.
|
|
1582
|
+
summary: How this person reviews code \u2014 what they block on versus nudge, the tone of their comments, and worked examples an agent can imitate when reviewing on their behalf.
|
|
1583
|
+
tags: [code-review, feedback, how-to]
|
|
1584
|
+
---
|
|
1585
|
+
|
|
1586
|
+
<SectionHead
|
|
1587
|
+
eyebrow="Code review"
|
|
1588
|
+
title="How I give PR comments"
|
|
1589
|
+
description="What I block on, what I just nudge, and how I phrase it. Replace with your own."
|
|
1590
|
+
/>
|
|
1591
|
+
|
|
1592
|
+
## My review bar
|
|
1593
|
+
|
|
1594
|
+
<Checklist
|
|
1595
|
+
label="What I look for, in order"
|
|
1596
|
+
items={[
|
|
1597
|
+
{ text: "Correctness \u2014 does it do the thing, including the edge cases?", group: "Block on" },
|
|
1598
|
+
{ text: "Tests that would fail without the change", group: "Block on" },
|
|
1599
|
+
{ text: "Names and structure I can read in one pass", group: "Block on" },
|
|
1600
|
+
{ text: "Unnecessary abstraction or dead code", group: "Nudge on" },
|
|
1601
|
+
{ text: "Comments that explain why, not what", group: "Nudge on" },
|
|
1602
|
+
{ text: "Nits \u2014 formatting, ordering, taste", group: "Optional" },
|
|
1603
|
+
]}
|
|
1604
|
+
/>
|
|
1605
|
+
|
|
1606
|
+
## How I phrase comments
|
|
1607
|
+
|
|
1608
|
+
I prefix to signal weight: **blocking:** must change, **suggestion:** take it or leave it,
|
|
1609
|
+
**nit:** ignore freely, **question:** I genuinely don't know. Examples:
|
|
1610
|
+
|
|
1611
|
+
<Example title="A blocking comment">
|
|
1612
|
+
**blocking:** This drops the error on the floor \u2014 if the fetch fails we return \`undefined\` and
|
|
1613
|
+
the caller renders an empty state as if it were success. Surface it, or handle it explicitly.
|
|
1614
|
+
</Example>
|
|
1615
|
+
|
|
1616
|
+
<Example title="A suggestion">
|
|
1617
|
+
**suggestion:** This loop reads cleanly, but \`items.flatMap\` would say the same thing in one line.
|
|
1618
|
+
Your call \u2014 not blocking.
|
|
1619
|
+
</Example>
|
|
1620
|
+
|
|
1621
|
+
<Example title="A nit">
|
|
1622
|
+
**nit:** tiny \u2014 can we name this \`pendingCount\` so it matches the others? Ignore if you disagree.
|
|
1623
|
+
</Example>
|
|
1624
|
+
|
|
1625
|
+
<Tip title="Tone">
|
|
1626
|
+
Critique the code, never the author. Lead with the why. If I'd want it softened when it lands on
|
|
1627
|
+
my own PR, I soften it.
|
|
1628
|
+
</Tip>
|
|
1629
|
+
`
|
|
1630
|
+
);
|
|
1631
|
+
write(
|
|
1632
|
+
"content/docs/voice.mdx",
|
|
1633
|
+
`---
|
|
1634
|
+
title: Voice & writing
|
|
1635
|
+
description: How I sound in writing \u2014 tone, defaults, and what I avoid.
|
|
1636
|
+
summary: This person's writing voice \u2014 tone, structure defaults, and explicit do/don't rules an agent should follow when drafting in their name.
|
|
1637
|
+
tags: [voice, writing, style]
|
|
1638
|
+
---
|
|
1639
|
+
|
|
1640
|
+
<SectionHead
|
|
1641
|
+
eyebrow="How I write"
|
|
1642
|
+
title="Voice & writing"
|
|
1643
|
+
description="So an agent drafting in my name sounds like me, not like a template."
|
|
1644
|
+
/>
|
|
1645
|
+
|
|
1646
|
+
Placeholder \u2014 capture your actual voice. The more specific the do/don't list, the better an agent
|
|
1647
|
+
can match you.
|
|
1648
|
+
|
|
1649
|
+
<KeyFacts
|
|
1650
|
+
items={[
|
|
1651
|
+
{ label: "Tone", value: "Direct, warm, low ceremony" },
|
|
1652
|
+
{ label: "Sentence length", value: "Short. Then one longer one to breathe." },
|
|
1653
|
+
{ label: "Person", value: "First person, active voice" },
|
|
1654
|
+
{ label: "Jargon", value: "Only when it's the precise word" },
|
|
1655
|
+
]}
|
|
1656
|
+
/>
|
|
1657
|
+
|
|
1658
|
+
## Do / don't
|
|
1659
|
+
|
|
1660
|
+
<Comparison
|
|
1661
|
+
caption="My writing defaults"
|
|
1662
|
+
options={["Do", "Don't"]}
|
|
1663
|
+
rows={[
|
|
1664
|
+
{ criterion: "Openers", cells: ["Get to the point in the first line", "Open with filler pleasantries"] },
|
|
1665
|
+
{ criterion: "Hedging", cells: ["Say what I think", "It might possibly be worth considering"] },
|
|
1666
|
+
{ criterion: "Structure", cells: ["Lead with the answer, then the why", "Bury the ask at the end"] },
|
|
1667
|
+
{ criterion: "Emoji", cells: ["Rarely, and never in serious writing", "Decorate every line"] },
|
|
1668
|
+
]}
|
|
1669
|
+
/>
|
|
1670
|
+
|
|
1671
|
+
<Example title="A message in my voice">
|
|
1672
|
+
Shipping the import fix today. It was dropping rows when a column was empty \u2014 now we skip the row
|
|
1673
|
+
and log it. One follow-up: we should validate on upload so this can't happen again. Want me to take
|
|
1674
|
+
that next?
|
|
1675
|
+
</Example>
|
|
1676
|
+
`
|
|
1677
|
+
);
|
|
1678
|
+
write(
|
|
1679
|
+
"content/docs/stories.mdx",
|
|
1680
|
+
`---
|
|
1681
|
+
title: Stories
|
|
1682
|
+
description: Formative moments that explain how I got my defaults.
|
|
1683
|
+
summary: Formative experiences that shaped this person's beliefs and working style, as a dated timeline an agent can reference for context on why they think the way they do.
|
|
1684
|
+
tags: [stories, background, timeline]
|
|
1685
|
+
---
|
|
1686
|
+
|
|
1687
|
+
<SectionHead
|
|
1688
|
+
eyebrow="Where it comes from"
|
|
1689
|
+
title="Stories"
|
|
1690
|
+
description="The moments behind the takes. Replace these with your own \u2014 dates can be approximate."
|
|
1691
|
+
/>
|
|
1692
|
+
|
|
1693
|
+
An agent that knows *why* you believe something reasons better than one that only knows *what*.
|
|
1694
|
+
These are placeholders.
|
|
1695
|
+
|
|
1696
|
+
<Timeline
|
|
1697
|
+
label="Formative moments"
|
|
1698
|
+
items={[
|
|
1699
|
+
{
|
|
1700
|
+
date: "2016",
|
|
1701
|
+
title: "The outage that taught me to write things down",
|
|
1702
|
+
body: "A fix lived only in one person's head. When they were out, we relearned it the hard way. I've defaulted to docs ever since.",
|
|
1703
|
+
status: "done",
|
|
1704
|
+
tags: ["process", "writing"],
|
|
1705
|
+
},
|
|
1706
|
+
{
|
|
1707
|
+
date: "2019",
|
|
1708
|
+
title: "Shipped small for the first time",
|
|
1709
|
+
body: "Replaced a six-month rewrite with weekly reversible changes. It landed. I stopped believing in big-bang.",
|
|
1710
|
+
status: "done",
|
|
1711
|
+
tags: ["shipping"],
|
|
1712
|
+
},
|
|
1713
|
+
{
|
|
1714
|
+
date: "2022",
|
|
1715
|
+
title: "A review that changed how I review",
|
|
1716
|
+
body: "Someone critiqued my code without making me feel small. I've tried to give every review that way since.",
|
|
1717
|
+
status: "done",
|
|
1718
|
+
tags: ["code-review", "tone"],
|
|
1719
|
+
},
|
|
1720
|
+
]}
|
|
1721
|
+
/>
|
|
1722
|
+
`
|
|
1723
|
+
);
|
|
1724
|
+
}
|
|
1725
|
+
|
|
1726
|
+
// src/lib/content/util.ts
|
|
1727
|
+
function escapeForJsx(value) {
|
|
1728
|
+
return value.replace(/[\\"]/g, "\\$&").replace(/[{}]/g, "");
|
|
1729
|
+
}
|
|
1730
|
+
|
|
1731
|
+
// src/lib/content/index.ts
|
|
1732
|
+
function writeContent(write, config) {
|
|
1733
|
+
switch (config.type) {
|
|
1734
|
+
case "company-kb":
|
|
1735
|
+
writeCompanyContent(write, config);
|
|
1736
|
+
return;
|
|
1737
|
+
case "product-docs":
|
|
1738
|
+
writeProductContent(write, config);
|
|
1739
|
+
return;
|
|
1740
|
+
case "personal-kb":
|
|
1741
|
+
writePersonalContent(write, config);
|
|
1742
|
+
return;
|
|
1743
|
+
}
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1746
|
+
// src/lib/scaffold.ts
|
|
1747
|
+
var here = dirname2(fileURLToPath(import.meta.url));
|
|
1748
|
+
function findStarterTemplate() {
|
|
1749
|
+
let dir = here;
|
|
1750
|
+
for (; ; ) {
|
|
1751
|
+
const candidate = join3(dir, "templates", "starter");
|
|
1752
|
+
if (existsSync3(candidate)) return candidate;
|
|
1753
|
+
const bundled = join3(dir, "template");
|
|
1754
|
+
if (existsSync3(bundled)) return bundled;
|
|
1755
|
+
const parent = dirname2(dir);
|
|
1756
|
+
if (parent === dir) return void 0;
|
|
1757
|
+
dir = parent;
|
|
1758
|
+
}
|
|
1759
|
+
}
|
|
1760
|
+
function isUsableTemplate(dir) {
|
|
1761
|
+
if (!existsSync3(dir)) return false;
|
|
1762
|
+
const entries = readdirSync(dir).filter((name) => name !== "README.md" && !name.startsWith("."));
|
|
1763
|
+
return entries.length > 0;
|
|
1764
|
+
}
|
|
1765
|
+
function isEmptyDir(dir) {
|
|
1766
|
+
if (!existsSync3(dir)) return true;
|
|
1767
|
+
return readdirSync(dir).filter((n) => !n.startsWith(".")).length === 0;
|
|
1768
|
+
}
|
|
1769
|
+
function scaffold(options) {
|
|
1770
|
+
const root = resolve2(options.dir);
|
|
1771
|
+
mkdirSync(root, { recursive: true });
|
|
1772
|
+
const template = findStarterTemplate();
|
|
1773
|
+
let source;
|
|
1774
|
+
if (template && isUsableTemplate(template)) {
|
|
1775
|
+
cpSync(template, root, { recursive: true });
|
|
1776
|
+
source = "template";
|
|
1777
|
+
} else {
|
|
1778
|
+
writeSkeleton(root, options.config);
|
|
1779
|
+
source = "skeleton";
|
|
1780
|
+
}
|
|
1781
|
+
writeFileSync2(join3(root, "superlore.json"), serializeSuperloreJson(options.config), "utf8");
|
|
1782
|
+
return { root, source };
|
|
1783
|
+
}
|
|
1784
|
+
function writeSkeleton(root, config) {
|
|
1785
|
+
const accent2 = config.accent ?? SUPERLORE_VIOLET;
|
|
1786
|
+
const mcpEnabled = config.mcp?.enabled ?? true;
|
|
1787
|
+
const authEnabled = config.auth?.enabled ?? false;
|
|
1788
|
+
const slug = config.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "superlore-kb";
|
|
1789
|
+
const write = (rel, body) => {
|
|
1790
|
+
const file = join3(root, rel);
|
|
1791
|
+
mkdirSync(dirname2(file), { recursive: true });
|
|
1792
|
+
writeFileSync2(file, body, "utf8");
|
|
1793
|
+
};
|
|
1794
|
+
write(
|
|
1795
|
+
"package.json",
|
|
1796
|
+
`${JSON.stringify(
|
|
1797
|
+
{
|
|
1798
|
+
name: slug,
|
|
1799
|
+
version: "0.1.0",
|
|
1800
|
+
private: true,
|
|
1801
|
+
type: "module",
|
|
1802
|
+
scripts: {
|
|
1803
|
+
dev: "next dev",
|
|
1804
|
+
build: "next build",
|
|
1805
|
+
start: "next start",
|
|
1806
|
+
postinstall: "fumadocs-mdx"
|
|
1807
|
+
},
|
|
1808
|
+
dependencies: {
|
|
1809
|
+
"fumadocs-core": "16.8.2",
|
|
1810
|
+
"fumadocs-mdx": "14.3.1",
|
|
1811
|
+
"fumadocs-ui": "16.8.2",
|
|
1812
|
+
superlore: "^0.5.3",
|
|
1813
|
+
"lucide-react": "^1.21.0",
|
|
1814
|
+
// superlore peers the rendered components pull in: Mermaid (Diagram), themes.
|
|
1815
|
+
mermaid: "^11.15.0",
|
|
1816
|
+
...mcpEnabled ? { "@modelcontextprotocol/sdk": "^1.29.0", "mcp-handler": "^1.1.0" } : {},
|
|
1817
|
+
// Auth.js v5 powers the optional Google SSO gate (superlore/auth). Self-disabling
|
|
1818
|
+
// without AUTH_GOOGLE_ID, so it's harmless until the env is set.
|
|
1819
|
+
...authEnabled ? { "next-auth": "^5.0.0-beta.25" } : {},
|
|
1820
|
+
next: "16.2.4",
|
|
1821
|
+
"next-themes": "^0.4.6",
|
|
1822
|
+
react: "^19.2.5",
|
|
1823
|
+
"react-dom": "^19.2.5",
|
|
1824
|
+
zod: "^4.4.3"
|
|
1825
|
+
},
|
|
1826
|
+
devDependencies: {
|
|
1827
|
+
"@tailwindcss/postcss": "^4.2.2",
|
|
1828
|
+
"@types/node": "^25.6.0",
|
|
1829
|
+
"@types/react": "^19.2.14",
|
|
1830
|
+
"@types/react-dom": "^19.2.3",
|
|
1831
|
+
postcss: "^8.5.10",
|
|
1832
|
+
tailwindcss: "^4.2.2",
|
|
1833
|
+
typescript: "6.0.3"
|
|
1834
|
+
}
|
|
1835
|
+
},
|
|
1836
|
+
null,
|
|
1837
|
+
2
|
|
1838
|
+
)}
|
|
1839
|
+
`
|
|
1840
|
+
);
|
|
1841
|
+
write(
|
|
1842
|
+
"tsconfig.json",
|
|
1843
|
+
`${JSON.stringify(
|
|
1844
|
+
{
|
|
1845
|
+
compilerOptions: {
|
|
1846
|
+
target: "ES2022",
|
|
1847
|
+
lib: ["dom", "dom.iterable", "esnext"],
|
|
1848
|
+
module: "esnext",
|
|
1849
|
+
moduleResolution: "bundler",
|
|
1850
|
+
strict: true,
|
|
1851
|
+
noEmit: true,
|
|
1852
|
+
jsx: "preserve",
|
|
1853
|
+
esModuleInterop: true,
|
|
1854
|
+
resolveJsonModule: true,
|
|
1855
|
+
isolatedModules: true,
|
|
1856
|
+
skipLibCheck: true,
|
|
1857
|
+
incremental: true,
|
|
1858
|
+
paths: { "@/*": ["./*"], "collections/*": ["./.source/*"] },
|
|
1859
|
+
plugins: [{ name: "next" }]
|
|
1860
|
+
},
|
|
1861
|
+
include: ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
|
1862
|
+
exclude: ["node_modules", ".next", "out"]
|
|
1863
|
+
},
|
|
1864
|
+
null,
|
|
1865
|
+
2
|
|
1866
|
+
)}
|
|
1867
|
+
`
|
|
1868
|
+
);
|
|
1869
|
+
write(
|
|
1870
|
+
"next.config.mjs",
|
|
1871
|
+
`import { createMDX } from "fumadocs-mdx/next";
|
|
1872
|
+
|
|
1873
|
+
const withMDX = createMDX();
|
|
1874
|
+
|
|
1875
|
+
/** @type {import('next').NextConfig} */
|
|
1876
|
+
const config = {
|
|
1877
|
+
reactStrictMode: true,
|
|
1878
|
+
// superlore ships compiled ESM \u2014 consume it as a normal package (no transpilePackages).
|
|
1879
|
+
};
|
|
1880
|
+
|
|
1881
|
+
export default withMDX(config);
|
|
1882
|
+
`
|
|
1883
|
+
);
|
|
1884
|
+
write(
|
|
1885
|
+
"source.config.ts",
|
|
1886
|
+
`import { defineConfig, defineDocs } from "fumadocs-mdx/config";
|
|
1887
|
+
import { superloreFrontmatterSchema } from "superlore/frontmatter";
|
|
1888
|
+
import { remarkSuperloreCanvas } from "superlore/mdx";
|
|
1889
|
+
|
|
1890
|
+
// Extend the superlore frontmatter schema in ONE place if you add custom fields.
|
|
1891
|
+
export const docs = defineDocs({
|
|
1892
|
+
dir: "content/docs",
|
|
1893
|
+
docs: { schema: superloreFrontmatterSchema },
|
|
1894
|
+
});
|
|
1895
|
+
|
|
1896
|
+
export default defineConfig({
|
|
1897
|
+
mdxOptions: {
|
|
1898
|
+
// Turn fenced \`\`\`superlore-canvas JSON blocks into <Canvas json="\u2026" /> \u2014 the headline
|
|
1899
|
+
// authoring path: write a whiteboard as a code block, get a rendered + serialized canvas.
|
|
1900
|
+
remarkPlugins: [remarkSuperloreCanvas],
|
|
1901
|
+
},
|
|
1902
|
+
});
|
|
1903
|
+
`
|
|
1904
|
+
);
|
|
1905
|
+
write(
|
|
1906
|
+
"postcss.config.mjs",
|
|
1907
|
+
`const config = {
|
|
1908
|
+
plugins: { "@tailwindcss/postcss": {} },
|
|
1909
|
+
};
|
|
1910
|
+
|
|
1911
|
+
export default config;
|
|
1912
|
+
`
|
|
1913
|
+
);
|
|
1914
|
+
write(
|
|
1915
|
+
"app/global.css",
|
|
1916
|
+
`@import "tailwindcss";
|
|
1917
|
+
/* superlore theme tokens (light + dark, co-equal). Re-skin the whole KB from one accent. */
|
|
1918
|
+
@import "superlore/css";
|
|
1919
|
+
|
|
1920
|
+
:root {
|
|
1921
|
+
/* Brand accent \u2014 superlore derives the hover/weak/border/text family for both themes. */
|
|
1922
|
+
--kp-accent: ${accent2};
|
|
1923
|
+
}
|
|
1924
|
+
`
|
|
1925
|
+
);
|
|
1926
|
+
write(
|
|
1927
|
+
"app/layout.tsx",
|
|
1928
|
+
`import type { ReactNode } from "react";
|
|
1929
|
+
import { RootProvider } from "superlore/ui";
|
|
1930
|
+
import "./global.css";
|
|
1931
|
+
|
|
1932
|
+
export default function RootLayout({ children }: { children: ReactNode }) {
|
|
1933
|
+
// Light and dark are co-equal; default to the system preference.
|
|
1934
|
+
return (
|
|
1935
|
+
<html lang="en" suppressHydrationWarning>
|
|
1936
|
+
<body>
|
|
1937
|
+
<RootProvider>{children}</RootProvider>
|
|
1938
|
+
</body>
|
|
1939
|
+
</html>
|
|
1940
|
+
);
|
|
1941
|
+
}
|
|
1942
|
+
`
|
|
1943
|
+
);
|
|
1944
|
+
write(
|
|
1945
|
+
"app/page.tsx",
|
|
1946
|
+
`import { redirect } from "next/navigation";
|
|
1947
|
+
|
|
1948
|
+
// A superlore KB *is* its docs \u2014 there's no separate marketing home to maintain. Land visitors
|
|
1949
|
+
// straight in the docs (full nav, header, sidebar, search). Replace this with a custom landing
|
|
1950
|
+
// only if you actually want one.
|
|
1951
|
+
export default function HomePage() {
|
|
1952
|
+
redirect("/docs");
|
|
1953
|
+
}
|
|
1954
|
+
`
|
|
1955
|
+
);
|
|
1956
|
+
write(
|
|
1957
|
+
"app/docs/layout.tsx",
|
|
1958
|
+
`import type { ReactNode } from "react";
|
|
1959
|
+
import { DocsLayout } from "fumadocs-ui/layouts/docs";
|
|
1960
|
+
import { source } from "@/lib/source";
|
|
1961
|
+
|
|
1962
|
+
export default function Layout({ children }: { children: ReactNode }) {
|
|
1963
|
+
return (
|
|
1964
|
+
<DocsLayout tree={source.pageTree} nav={{ title: "${escapeForJsx(config.name)}" }}>
|
|
1965
|
+
{children}
|
|
1966
|
+
</DocsLayout>
|
|
1967
|
+
);
|
|
1968
|
+
}
|
|
1969
|
+
`
|
|
1970
|
+
);
|
|
1971
|
+
write(
|
|
1972
|
+
"app/docs/[[...slug]]/page.tsx",
|
|
1973
|
+
`import { notFound } from "next/navigation";
|
|
1974
|
+
import { DocsBody, DocsPage } from "fumadocs-ui/page";
|
|
1975
|
+
import { getMDXComponents } from "superlore";
|
|
1976
|
+
import { source } from "@/lib/source";
|
|
1977
|
+
|
|
1978
|
+
export default async function Page(props: { params: Promise<{ slug?: string[] }> }) {
|
|
1979
|
+
const params = await props.params;
|
|
1980
|
+
const page = source.getPage(params.slug);
|
|
1981
|
+
if (!page) notFound();
|
|
1982
|
+
const MDX = page.data.body;
|
|
1983
|
+
return (
|
|
1984
|
+
<DocsPage toc={page.data.toc}>
|
|
1985
|
+
<DocsBody>
|
|
1986
|
+
<MDX components={getMDXComponents()} />
|
|
1987
|
+
</DocsBody>
|
|
1988
|
+
</DocsPage>
|
|
1989
|
+
);
|
|
1990
|
+
}
|
|
1991
|
+
|
|
1992
|
+
export function generateStaticParams() {
|
|
1993
|
+
return source.generateParams();
|
|
1994
|
+
}
|
|
1995
|
+
`
|
|
1996
|
+
);
|
|
1997
|
+
write(
|
|
1998
|
+
"lib/source.ts",
|
|
1999
|
+
`import { docs } from "collections/server";
|
|
2000
|
+
import { loader, lucideIconsPlugin } from "superlore/source";
|
|
2001
|
+
|
|
2002
|
+
export const source = loader({
|
|
2003
|
+
baseUrl: "/docs",
|
|
2004
|
+
source: docs.toFumadocsSource(),
|
|
2005
|
+
plugins: [lucideIconsPlugin()],
|
|
2006
|
+
});
|
|
2007
|
+
`
|
|
2008
|
+
);
|
|
2009
|
+
writeContent(write, config);
|
|
2010
|
+
if (authEnabled) {
|
|
2011
|
+
writeAuth(write, config);
|
|
2012
|
+
}
|
|
2013
|
+
if (mcpEnabled) {
|
|
2014
|
+
const mcpPath = config.mcp?.path ?? "/api/mcp";
|
|
2015
|
+
write(
|
|
2016
|
+
"app/api/[transport]/route.ts",
|
|
2017
|
+
`import { createMcpHandler } from "mcp-handler";
|
|
2018
|
+
import { z } from "zod";
|
|
2019
|
+
import { getComponentData, getPage, list, navigate, search } from "superlore/mcp";
|
|
2020
|
+
import { buildIndexFromSource } from "superlore/source";
|
|
2021
|
+
import type { KKind } from "superlore";
|
|
2022
|
+
import { source } from "@/lib/source";
|
|
2023
|
+
|
|
2024
|
+
// Your KB's MCP endpoint. Served at ${mcpPath} \u2014 the same structured content the site renders,
|
|
2025
|
+
// exposed to agents. The index is built straight from your content \`source\`: author once, and
|
|
2026
|
+
// humans read the pages while agents query this corpus. No scraping, no drift.
|
|
2027
|
+
const index = buildIndexFromSource(source);
|
|
2028
|
+
|
|
2029
|
+
const json = (data: unknown) => ({
|
|
2030
|
+
content: [{ type: "text" as const, text: JSON.stringify(data, null, 2) }],
|
|
2031
|
+
});
|
|
2032
|
+
|
|
2033
|
+
const handler = createMcpHandler(
|
|
2034
|
+
(server) => {
|
|
2035
|
+
server.tool(
|
|
2036
|
+
"search",
|
|
2037
|
+
"Full-text search across the knowledge base.",
|
|
2038
|
+
{ query: z.string(), limit: z.number().int().positive().optional() },
|
|
2039
|
+
async ({ query, limit }) => json(search(index, query, limit)),
|
|
2040
|
+
);
|
|
2041
|
+
server.tool(
|
|
2042
|
+
"get_page",
|
|
2043
|
+
"Get a page's full structured content by path.",
|
|
2044
|
+
{ path: z.string() },
|
|
2045
|
+
async ({ path }) => json(getPage(index, path)),
|
|
2046
|
+
);
|
|
2047
|
+
server.tool(
|
|
2048
|
+
"list",
|
|
2049
|
+
"List knowledge nodes, filtered by kind / tag / entityType.",
|
|
2050
|
+
{ kind: z.string().optional(), tag: z.string().optional(), entityType: z.string().optional() },
|
|
2051
|
+
async ({ kind, tag, entityType }) =>
|
|
2052
|
+
json(list(index, { kind: kind as KKind | undefined, tag, entityType })),
|
|
2053
|
+
);
|
|
2054
|
+
server.tool(
|
|
2055
|
+
"navigate",
|
|
2056
|
+
"Follow relations from a page path / node id / entity ref.",
|
|
2057
|
+
{ target: z.string() },
|
|
2058
|
+
async ({ target }) => json(navigate(index, target)),
|
|
2059
|
+
);
|
|
2060
|
+
server.tool(
|
|
2061
|
+
"get_component_data",
|
|
2062
|
+
"Get the structured data behind a rendered component (its knowledge face).",
|
|
2063
|
+
{ id: z.string() },
|
|
2064
|
+
async ({ id }) => json(getComponentData(index, id)),
|
|
2065
|
+
);
|
|
2066
|
+
},
|
|
2067
|
+
{},
|
|
2068
|
+
{ basePath: "/api" },
|
|
2069
|
+
);
|
|
2070
|
+
|
|
2071
|
+
export { handler as GET, handler as POST };
|
|
2072
|
+
`
|
|
2073
|
+
);
|
|
2074
|
+
}
|
|
2075
|
+
write(
|
|
2076
|
+
".gitignore",
|
|
2077
|
+
`node_modules
|
|
2078
|
+
.next
|
|
2079
|
+
.source
|
|
2080
|
+
out
|
|
2081
|
+
*.tsbuildinfo
|
|
2082
|
+
.env*.local
|
|
2083
|
+
`
|
|
2084
|
+
);
|
|
2085
|
+
if (authEnabled) {
|
|
2086
|
+
write(
|
|
2087
|
+
".env.example",
|
|
2088
|
+
`# Auth.js v5 + Google SSO. The gate is OFF until AUTH_GOOGLE_ID is set, so local dev works with
|
|
2089
|
+
# this file empty. Copy to .env.local and fill in to enable it on a deploy.
|
|
2090
|
+
AUTH_SECRET= # \`openssl rand -base64 32\`
|
|
2091
|
+
AUTH_GOOGLE_ID= # presence of this turns the gate ON
|
|
2092
|
+
AUTH_GOOGLE_SECRET=
|
|
2093
|
+
AUTH_URL= # the deploy's canonical URL, e.g. https://your-kb.vercel.app
|
|
2094
|
+
AUTH_TRUST_HOST=true
|
|
2095
|
+
${config.auth?.allowedDomain ? `AUTH_ALLOWED_DOMAIN=${config.auth.allowedDomain}` : "# AUTH_ALLOWED_DOMAIN=example.com # restrict sign-in to one workspace domain (optional)"}
|
|
2096
|
+
# AUTH_ALLOWED_EMAILS=you@example.com # comma-separated allowlist that bypasses the domain check
|
|
2097
|
+
# LOCAL=true # force the gate OFF locally even when configured
|
|
2098
|
+
`
|
|
2099
|
+
);
|
|
2100
|
+
}
|
|
2101
|
+
const authReadme = authEnabled ? `
|
|
1206
2102
|
|
|
1207
|
-
|
|
1208
|
-
eyebrow="Code review"
|
|
1209
|
-
title="How I give PR comments"
|
|
1210
|
-
description="What I block on, what I just nudge, and how I phrase it. Replace with your own."
|
|
1211
|
-
/>
|
|
2103
|
+
## Auth
|
|
1212
2104
|
|
|
1213
|
-
|
|
2105
|
+
This KB ships a Google SSO gate (Auth.js v5). It is **off until you set \`AUTH_GOOGLE_ID\`**, so local dev runs open. Copy \`.env.example\` to \`.env.local\` and fill it in to enable it. The gate lives in \`proxy.ts\`, in front of every route \u2014 so the MCP inherits it too.` : "";
|
|
2106
|
+
write(
|
|
2107
|
+
"README.md",
|
|
2108
|
+
`# ${config.name}
|
|
1214
2109
|
|
|
1215
|
-
|
|
1216
|
-
label="What I look for, in order"
|
|
1217
|
-
items={[
|
|
1218
|
-
{ text: "Correctness \u2014 does it do the thing, including the edge cases?", group: "Block on" },
|
|
1219
|
-
{ text: "Tests that would fail without the change", group: "Block on" },
|
|
1220
|
-
{ text: "Names and structure I can read in one pass", group: "Block on" },
|
|
1221
|
-
{ text: "Unnecessary abstraction or dead code", group: "Nudge on" },
|
|
1222
|
-
{ text: "Comments that explain why, not what", group: "Nudge on" },
|
|
1223
|
-
{ text: "Nits \u2014 formatting, ordering, taste", group: "Optional" },
|
|
1224
|
-
]}
|
|
1225
|
-
/>
|
|
2110
|
+
A superlore knowledge base. One corpus. Humans and agents.
|
|
1226
2111
|
|
|
1227
|
-
##
|
|
2112
|
+
## Develop
|
|
1228
2113
|
|
|
1229
|
-
|
|
1230
|
-
|
|
2114
|
+
\`\`\`sh
|
|
2115
|
+
pnpm install # or npm / yarn / bun
|
|
2116
|
+
superlore dev # preview the site locally
|
|
2117
|
+
\`\`\`
|
|
1231
2118
|
|
|
1232
|
-
|
|
1233
|
-
**blocking:** This drops the error on the floor \u2014 if the fetch fails we return \`undefined\` and
|
|
1234
|
-
the caller renders an empty state as if it were success. Surface it, or handle it explicitly.
|
|
1235
|
-
</Example>
|
|
2119
|
+
## Build
|
|
1236
2120
|
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
</Example>
|
|
2121
|
+
\`\`\`sh
|
|
2122
|
+
superlore build
|
|
2123
|
+
\`\`\`
|
|
1241
2124
|
|
|
1242
|
-
|
|
1243
|
-
**nit:** tiny \u2014 can we name this \`pendingCount\` so it matches the others? Ignore if you disagree.
|
|
1244
|
-
</Example>
|
|
2125
|
+
Config lives in \`superlore.json\`. Author content in \`content/docs/\`.${mcpEnabled ? `
|
|
1245
2126
|
|
|
1246
|
-
|
|
1247
|
-
Critique the code, never the author. Lead with the why. If I'd want it softened when it lands on
|
|
1248
|
-
my own PR, I soften it.
|
|
1249
|
-
</Tip>
|
|
2127
|
+
The MCP endpoint is served at \`${config.mcp?.path ?? "/api/mcp"}\`.` : ""}${authReadme}
|
|
1250
2128
|
`
|
|
1251
2129
|
);
|
|
2130
|
+
}
|
|
2131
|
+
function writeAuth(write, config) {
|
|
2132
|
+
const allowedDomain = config.auth?.allowedDomain;
|
|
1252
2133
|
write(
|
|
1253
|
-
"
|
|
1254
|
-
|
|
1255
|
-
title: Voice & writing
|
|
1256
|
-
description: How I sound in writing \u2014 tone, defaults, and what I avoid.
|
|
1257
|
-
summary: This person's writing voice \u2014 tone, structure defaults, and explicit do/don't rules an agent should follow when drafting in their name.
|
|
1258
|
-
tags: [voice, writing, style]
|
|
1259
|
-
---
|
|
1260
|
-
|
|
1261
|
-
<SectionHead
|
|
1262
|
-
eyebrow="How I write"
|
|
1263
|
-
title="Voice & writing"
|
|
1264
|
-
description="So an agent drafting in my name sounds like me, not like a template."
|
|
1265
|
-
/>
|
|
1266
|
-
|
|
1267
|
-
Placeholder \u2014 capture your actual voice. The more specific the do/don't list, the better an agent
|
|
1268
|
-
can match you.
|
|
1269
|
-
|
|
1270
|
-
<KeyFacts
|
|
1271
|
-
items={[
|
|
1272
|
-
{ label: "Tone", value: "Direct, warm, low ceremony" },
|
|
1273
|
-
{ label: "Sentence length", value: "Short. Then one longer one to breathe." },
|
|
1274
|
-
{ label: "Person", value: "First person, active voice" },
|
|
1275
|
-
{ label: "Jargon", value: "Only when it's the precise word" },
|
|
1276
|
-
]}
|
|
1277
|
-
/>
|
|
1278
|
-
|
|
1279
|
-
## Do / don't
|
|
2134
|
+
"auth.ts",
|
|
2135
|
+
`import { createSuperloreAuth } from "superlore/auth";
|
|
1280
2136
|
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
2137
|
+
// Auth.js v5 + Google SSO. Allowlists can come from env (AUTH_ALLOWED_DOMAIN / AUTH_ALLOWED_EMAILS)
|
|
2138
|
+
// or be passed explicitly here. Off until AUTH_GOOGLE_ID is set, so local dev needs no config.
|
|
2139
|
+
export const { handlers, auth, signIn, signOut } = createSuperloreAuth(${allowedDomain ? `{
|
|
2140
|
+
allowedDomain: ${JSON.stringify(allowedDomain)},
|
|
2141
|
+
}` : "{}"});
|
|
2142
|
+
`
|
|
2143
|
+
);
|
|
2144
|
+
write(
|
|
2145
|
+
"app/api/auth/[...nextauth]/route.ts",
|
|
2146
|
+
`import { handlers } from "@/auth";
|
|
1291
2147
|
|
|
1292
|
-
|
|
1293
|
-
Shipping the import fix today. It was dropping rows when a column was empty \u2014 now we skip the row
|
|
1294
|
-
and log it. One follow-up: we should validate on upload so this can't happen again. Want me to take
|
|
1295
|
-
that next?
|
|
1296
|
-
</Example>
|
|
2148
|
+
export const { GET, POST } = handlers;
|
|
1297
2149
|
`
|
|
1298
2150
|
);
|
|
1299
2151
|
write(
|
|
1300
|
-
"
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
description: Formative moments that explain how I got my defaults.
|
|
1304
|
-
summary: Formative experiences that shaped this person's beliefs and working style, as a dated timeline an agent can reference for context on why they think the way they do.
|
|
1305
|
-
tags: [stories, background, timeline]
|
|
1306
|
-
---
|
|
2152
|
+
"proxy.ts",
|
|
2153
|
+
`import { auth } from "@/auth";
|
|
2154
|
+
import { createAuthProxy } from "superlore/auth";
|
|
1307
2155
|
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
description="The moments behind the takes. Replace these with your own \u2014 dates can be approximate."
|
|
1312
|
-
/>
|
|
2156
|
+
// Next.js 16 middleware lives in proxy.ts. The gate is self-disabling: with no AUTH_GOOGLE_ID
|
|
2157
|
+
// (or LOCAL=true) every request passes through, so local dev and public deploys stay open.
|
|
2158
|
+
export default createAuthProxy(auth);
|
|
1313
2159
|
|
|
1314
|
-
|
|
1315
|
-
|
|
2160
|
+
export const config = {
|
|
2161
|
+
// Run on everything except static assets (the helper also skips the auth dance + icons).
|
|
2162
|
+
matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"],
|
|
2163
|
+
};
|
|
2164
|
+
`
|
|
2165
|
+
);
|
|
2166
|
+
write(
|
|
2167
|
+
"app/auth/signin/page.tsx",
|
|
2168
|
+
`import { signIn } from "@/auth";
|
|
1316
2169
|
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
2170
|
+
export default async function SignInPage(props: {
|
|
2171
|
+
searchParams: Promise<{ callbackUrl?: string }>;
|
|
2172
|
+
}) {
|
|
2173
|
+
const { callbackUrl } = await props.searchParams;
|
|
2174
|
+
return (
|
|
2175
|
+
<main className="grid min-h-screen place-items-center p-6">
|
|
2176
|
+
<form
|
|
2177
|
+
action={async () => {
|
|
2178
|
+
"use server";
|
|
2179
|
+
await signIn("google", { redirectTo: callbackUrl ?? "/" });
|
|
2180
|
+
}}
|
|
2181
|
+
className="w-full max-w-sm rounded-lg border border-fd-border bg-fd-card p-6 text-center"
|
|
2182
|
+
>
|
|
2183
|
+
<h1 className="text-lg font-semibold text-fd-foreground">Sign in</h1>
|
|
2184
|
+
<p className="mt-1 text-sm text-fd-muted-foreground">
|
|
2185
|
+
This knowledge base is private. Continue with Google to read it.
|
|
2186
|
+
</p>
|
|
2187
|
+
<button
|
|
2188
|
+
type="submit"
|
|
2189
|
+
className="mt-5 inline-flex w-full items-center justify-center rounded-md border border-kp-accent-border bg-kp-accent px-4 py-2 text-sm font-medium text-white"
|
|
2190
|
+
>
|
|
2191
|
+
Continue with Google
|
|
2192
|
+
</button>
|
|
2193
|
+
</form>
|
|
2194
|
+
</main>
|
|
2195
|
+
);
|
|
2196
|
+
}
|
|
1343
2197
|
`
|
|
1344
2198
|
);
|
|
2199
|
+
write(
|
|
2200
|
+
"app/auth/error/page.tsx",
|
|
2201
|
+
`export default async function AuthErrorPage(props: {
|
|
2202
|
+
searchParams: Promise<{ error?: string }>;
|
|
2203
|
+
}) {
|
|
2204
|
+
const { error } = await props.searchParams;
|
|
2205
|
+
return (
|
|
2206
|
+
<main className="grid min-h-screen place-items-center p-6">
|
|
2207
|
+
<div className="w-full max-w-sm rounded-lg border border-fd-border bg-fd-card p-6 text-center">
|
|
2208
|
+
<h1 className="text-lg font-semibold text-fd-foreground">Can't sign in</h1>
|
|
2209
|
+
<p className="mt-1 text-sm text-fd-muted-foreground">
|
|
2210
|
+
{error === "AccessDenied"
|
|
2211
|
+
? "That account isn't allowed to access this knowledge base."
|
|
2212
|
+
: "Something went wrong signing in. Try again."}
|
|
2213
|
+
</p>
|
|
2214
|
+
<a
|
|
2215
|
+
href="/auth/signin"
|
|
2216
|
+
className="mt-5 inline-flex w-full items-center justify-center rounded-md border border-fd-border px-4 py-2 text-sm font-medium text-fd-foreground no-underline"
|
|
2217
|
+
>
|
|
2218
|
+
Back to sign in
|
|
2219
|
+
</a>
|
|
2220
|
+
</div>
|
|
2221
|
+
</main>
|
|
2222
|
+
);
|
|
1345
2223
|
}
|
|
1346
|
-
|
|
1347
|
-
|
|
2224
|
+
`
|
|
2225
|
+
);
|
|
1348
2226
|
}
|
|
1349
2227
|
|
|
1350
2228
|
// src/commands/init.ts
|
|
@@ -1459,9 +2337,41 @@ ${result.issues.map((i) => ` - ${i.path} ${i.message}`).join("\n")}`
|
|
|
1459
2337
|
`This is a ${bold(kind)} but auth is ${bold("not enabled")}. A ${kind} should gate access before you deploy \u2014 re-run with ${cyan("--auth")} or set ${cyan('"auth": { "enabled": true }')} in superlore.json.`
|
|
1460
2338
|
);
|
|
1461
2339
|
}
|
|
2340
|
+
printStructure(root);
|
|
1462
2341
|
printNextSteps(root, config);
|
|
1463
2342
|
await maybeConnectEditor(flags, interactive);
|
|
1464
2343
|
}
|
|
2344
|
+
function printStructure(root) {
|
|
2345
|
+
const docs = join4(root, "content", "docs");
|
|
2346
|
+
if (!existsSync4(docs)) return;
|
|
2347
|
+
const lines = [];
|
|
2348
|
+
const walk = (dir, depth) => {
|
|
2349
|
+
let names;
|
|
2350
|
+
try {
|
|
2351
|
+
names = readdirSync2(dir).sort((a, b) => a.localeCompare(b));
|
|
2352
|
+
} catch {
|
|
2353
|
+
return;
|
|
2354
|
+
}
|
|
2355
|
+
const dirs = names.filter((n) => !n.startsWith(".") && statSync(join4(dir, n)).isDirectory());
|
|
2356
|
+
const pages = names.filter((n) => n.endsWith(".mdx"));
|
|
2357
|
+
for (const name of dirs) {
|
|
2358
|
+
lines.push(`${" ".repeat(depth)}${cyan(`${name}/`)}`);
|
|
2359
|
+
walk(join4(dir, name), depth + 1);
|
|
2360
|
+
}
|
|
2361
|
+
for (const name of pages) {
|
|
2362
|
+
lines.push(`${" ".repeat(depth)}${name}`);
|
|
2363
|
+
}
|
|
2364
|
+
};
|
|
2365
|
+
walk(docs, 1);
|
|
2366
|
+
log.blank();
|
|
2367
|
+
log.info(bold("Your starter structure"));
|
|
2368
|
+
log.info(`${dim(" content/docs/")} ${dim("\u2014 a full KB, pre-filled with placeholder content:")}`);
|
|
2369
|
+
for (const line of lines) log.info(line);
|
|
2370
|
+
log.blank();
|
|
2371
|
+
log.info(
|
|
2372
|
+
`${bold("Now bring your content in.")} Keep the structure and replace the placeholder pages with what's true for you \u2014 page by page, or ask your agent: ${cyan('"fill in my superlore KB"')}.`
|
|
2373
|
+
);
|
|
2374
|
+
}
|
|
1465
2375
|
async function maybeConnectEditor(flags, interactive) {
|
|
1466
2376
|
if (flags.connect === false) return;
|
|
1467
2377
|
const editors = detectEditors();
|
|
@@ -1508,7 +2418,7 @@ function printNextSteps(root, config) {
|
|
|
1508
2418
|
}
|
|
1509
2419
|
|
|
1510
2420
|
// src/index.ts
|
|
1511
|
-
var VERSION = "0.
|
|
2421
|
+
var VERSION = "0.4.0";
|
|
1512
2422
|
function buildCli(argv = process3.argv) {
|
|
1513
2423
|
const cli = cac("superlore");
|
|
1514
2424
|
cli.command("init [dir]", "Scaffold a new superlore knowledge base").option("--name <name>", "KB name").option("--type <type>", "KB type: company-kb | product-docs | personal-kb").option("--auth", "Enable the Google SSO auth gate").option("--no-auth", "Disable the auth gate").option("--allowed-domain <domain>", "Restrict SSO to one email domain (implies --auth)").option("--accent <color>", "Brand accent colour (any CSS colour)").option("--no-mcp", "Disable the MCP endpoint (on by default)").option("--connect", "Install the editor extension after scaffolding (skip the prompt)").option("--no-connect", "Don't set up the editor extension").option("-y, --yes", "Skip prompts; use flags + defaults").example("superlore init my-kb --type product-docs").example("superlore init acme --type company-kb --auth --allowed-domain acme.com").example("superlore init me --type personal-kb").action(
|