superlore-cli 0.3.1 → 0.4.1
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 +1447 -536
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -529,102 +529,25 @@ async function devCommand(flags) {
|
|
|
529
529
|
}
|
|
530
530
|
|
|
531
531
|
// src/commands/init.ts
|
|
532
|
-
import { existsSync as existsSync4 } from "fs";
|
|
533
|
-
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";
|
|
534
534
|
import { cancel, confirm, intro, isCancel, outro, select, text } from "@clack/prompts";
|
|
535
535
|
|
|
536
536
|
// src/lib/scaffold.ts
|
|
537
537
|
import { cpSync, existsSync as existsSync3, mkdirSync, readdirSync, writeFileSync as writeFileSync2 } from "fs";
|
|
538
538
|
import { fileURLToPath } from "url";
|
|
539
539
|
import { dirname as dirname2, join as join3, resolve as resolve2 } from "path";
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
for (; ; ) {
|
|
544
|
-
const candidate = join3(dir, "templates", "starter");
|
|
545
|
-
if (existsSync3(candidate)) return candidate;
|
|
546
|
-
const bundled = join3(dir, "template");
|
|
547
|
-
if (existsSync3(bundled)) return bundled;
|
|
548
|
-
const parent = dirname2(dir);
|
|
549
|
-
if (parent === dir) return void 0;
|
|
550
|
-
dir = parent;
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
function isUsableTemplate(dir) {
|
|
554
|
-
if (!existsSync3(dir)) return false;
|
|
555
|
-
const entries = readdirSync(dir).filter((name) => name !== "README.md" && !name.startsWith("."));
|
|
556
|
-
return entries.length > 0;
|
|
557
|
-
}
|
|
558
|
-
function isEmptyDir(dir) {
|
|
559
|
-
if (!existsSync3(dir)) return true;
|
|
560
|
-
return readdirSync(dir).filter((n) => !n.startsWith(".")).length === 0;
|
|
561
|
-
}
|
|
562
|
-
function scaffold(options) {
|
|
563
|
-
const root = resolve2(options.dir);
|
|
564
|
-
mkdirSync(root, { recursive: true });
|
|
565
|
-
const template = findStarterTemplate();
|
|
566
|
-
let source;
|
|
567
|
-
if (template && isUsableTemplate(template)) {
|
|
568
|
-
cpSync(template, root, { recursive: true });
|
|
569
|
-
source = "template";
|
|
570
|
-
} else {
|
|
571
|
-
writeSkeleton(root, options.config);
|
|
572
|
-
source = "skeleton";
|
|
573
|
-
}
|
|
574
|
-
writeFileSync2(join3(root, "superlore.json"), serializeSuperloreJson(options.config), "utf8");
|
|
575
|
-
return { root, source };
|
|
576
|
-
}
|
|
577
|
-
function writeSkeleton(root, config) {
|
|
578
|
-
const accent2 = config.accent ?? SUPERLORE_VIOLET;
|
|
579
|
-
const mcpEnabled = config.mcp?.enabled ?? true;
|
|
580
|
-
const authEnabled = config.auth?.enabled ?? false;
|
|
581
|
-
const slug = config.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "superlore-kb";
|
|
582
|
-
const write = (rel, body) => {
|
|
583
|
-
const file = join3(root, rel);
|
|
584
|
-
mkdirSync(dirname2(file), { recursive: true });
|
|
585
|
-
writeFileSync2(file, body, "utf8");
|
|
586
|
-
};
|
|
540
|
+
|
|
541
|
+
// src/lib/content/company.ts
|
|
542
|
+
function writeCompanyContent(write, config) {
|
|
587
543
|
write(
|
|
588
|
-
"
|
|
544
|
+
"content/docs/meta.json",
|
|
589
545
|
`${JSON.stringify(
|
|
590
546
|
{
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
scripts: {
|
|
596
|
-
dev: "next dev",
|
|
597
|
-
build: "next build",
|
|
598
|
-
start: "next start",
|
|
599
|
-
postinstall: "fumadocs-mdx"
|
|
600
|
-
},
|
|
601
|
-
dependencies: {
|
|
602
|
-
"fumadocs-core": "16.8.2",
|
|
603
|
-
"fumadocs-mdx": "14.3.1",
|
|
604
|
-
"fumadocs-ui": "16.8.2",
|
|
605
|
-
superlore: "^0.5.1",
|
|
606
|
-
"lucide-react": "^1.21.0",
|
|
607
|
-
// superlore peers the rendered components pull in: Mermaid (Diagram), themes.
|
|
608
|
-
mermaid: "^11.15.0",
|
|
609
|
-
...mcpEnabled ? { "@modelcontextprotocol/sdk": "^1.29.0", "mcp-handler": "^1.1.0" } : {},
|
|
610
|
-
// Auth.js v5 powers the optional Google SSO gate (superlore/auth). Self-disabling
|
|
611
|
-
// without AUTH_GOOGLE_ID, so it's harmless until the env is set.
|
|
612
|
-
...authEnabled ? { "next-auth": "^5.0.0-beta.25" } : {},
|
|
613
|
-
next: "16.2.4",
|
|
614
|
-
"next-themes": "^0.4.6",
|
|
615
|
-
react: "^19.2.5",
|
|
616
|
-
"react-dom": "^19.2.5",
|
|
617
|
-
zod: "^4.4.3"
|
|
618
|
-
},
|
|
619
|
-
devDependencies: {
|
|
620
|
-
"@tailwindcss/postcss": "^4.2.2",
|
|
621
|
-
"@types/node": "^25.6.0",
|
|
622
|
-
"@types/react": "^19.2.14",
|
|
623
|
-
"@types/react-dom": "^19.2.3",
|
|
624
|
-
postcss: "^8.5.10",
|
|
625
|
-
tailwindcss: "^4.2.2",
|
|
626
|
-
typescript: "6.0.3"
|
|
627
|
-
}
|
|
547
|
+
title: config.name,
|
|
548
|
+
root: true,
|
|
549
|
+
icon: "BookOpen",
|
|
550
|
+
pages: ["index", "engineering", "product", "team"]
|
|
628
551
|
},
|
|
629
552
|
null,
|
|
630
553
|
2
|
|
@@ -632,408 +555,830 @@ function writeSkeleton(root, config) {
|
|
|
632
555
|
`
|
|
633
556
|
);
|
|
634
557
|
write(
|
|
635
|
-
"
|
|
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",
|
|
636
641
|
`${JSON.stringify(
|
|
637
|
-
{
|
|
638
|
-
compilerOptions: {
|
|
639
|
-
target: "ES2022",
|
|
640
|
-
lib: ["dom", "dom.iterable", "esnext"],
|
|
641
|
-
module: "esnext",
|
|
642
|
-
moduleResolution: "bundler",
|
|
643
|
-
strict: true,
|
|
644
|
-
noEmit: true,
|
|
645
|
-
jsx: "preserve",
|
|
646
|
-
esModuleInterop: true,
|
|
647
|
-
resolveJsonModule: true,
|
|
648
|
-
isolatedModules: true,
|
|
649
|
-
skipLibCheck: true,
|
|
650
|
-
incremental: true,
|
|
651
|
-
paths: { "@/*": ["./*"], "collections/*": ["./.source/*"] },
|
|
652
|
-
plugins: [{ name: "next" }]
|
|
653
|
-
},
|
|
654
|
-
include: ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
|
655
|
-
exclude: ["node_modules", ".next", "out"]
|
|
656
|
-
},
|
|
642
|
+
{ title: "Engineering", icon: "Layers", pages: ["architecture", "decisions", "runbooks"] },
|
|
657
643
|
null,
|
|
658
644
|
2
|
|
659
645
|
)}
|
|
660
646
|
`
|
|
661
647
|
);
|
|
662
648
|
write(
|
|
663
|
-
"
|
|
664
|
-
|
|
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
|
+
---
|
|
665
656
|
|
|
666
|
-
|
|
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
|
+
/>
|
|
667
662
|
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
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
|
+
\`\`\`
|
|
673
703
|
|
|
674
|
-
|
|
675
|
-
`
|
|
676
|
-
);
|
|
677
|
-
write(
|
|
678
|
-
"source.config.ts",
|
|
679
|
-
`import { defineConfig, defineDocs } from "fumadocs-mdx/config";
|
|
680
|
-
import { superloreFrontmatterSchema } from "superlore/frontmatter";
|
|
704
|
+
## Services
|
|
681
705
|
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
}
|
|
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
|
+
/>
|
|
687
720
|
|
|
688
|
-
|
|
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
|
+
/>
|
|
689
729
|
`
|
|
690
730
|
);
|
|
691
731
|
write(
|
|
692
|
-
"
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
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
|
+
---
|
|
696
739
|
|
|
697
|
-
|
|
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
|
+
/>
|
|
762
|
+
|
|
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
|
+
/>
|
|
698
777
|
`
|
|
699
778
|
);
|
|
700
779
|
write(
|
|
701
|
-
"
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
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
|
+
---
|
|
705
787
|
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
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>
|
|
710
822
|
`
|
|
711
823
|
);
|
|
712
824
|
write(
|
|
713
|
-
"
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
// Light and dark are co-equal; default to the system preference.
|
|
720
|
-
return (
|
|
721
|
-
<html lang="en" suppressHydrationWarning>
|
|
722
|
-
<body>
|
|
723
|
-
<RootProvider>{children}</RootProvider>
|
|
724
|
-
</body>
|
|
725
|
-
</html>
|
|
726
|
-
);
|
|
727
|
-
}
|
|
825
|
+
"content/docs/product/meta.json",
|
|
826
|
+
`${JSON.stringify(
|
|
827
|
+
{ title: "Product", icon: "Rocket", pages: ["roadmap", "releases"] },
|
|
828
|
+
null,
|
|
829
|
+
2
|
|
830
|
+
)}
|
|
728
831
|
`
|
|
729
832
|
);
|
|
730
833
|
write(
|
|
731
|
-
"
|
|
732
|
-
|
|
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
|
+
---
|
|
733
841
|
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
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
|
+
/>
|
|
744
875
|
`
|
|
745
876
|
);
|
|
746
877
|
write(
|
|
747
|
-
"
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
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
|
+
---
|
|
751
885
|
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
);
|
|
758
|
-
}
|
|
759
|
-
`
|
|
760
|
-
);
|
|
761
|
-
write(
|
|
762
|
-
"app/docs/[[...slug]]/page.tsx",
|
|
763
|
-
`import { notFound } from "next/navigation";
|
|
764
|
-
import { DocsBody, DocsPage } from "fumadocs-ui/page";
|
|
765
|
-
import { getMDXComponents } from "superlore";
|
|
766
|
-
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
|
+
/>
|
|
767
891
|
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
}
|
|
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
|
+
/>
|
|
781
904
|
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
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
|
+
/>
|
|
785
915
|
`
|
|
786
916
|
);
|
|
787
917
|
write(
|
|
788
|
-
"
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
source: docs.toFumadocsSource(),
|
|
795
|
-
plugins: [lucideIconsPlugin()],
|
|
796
|
-
});
|
|
918
|
+
"content/docs/team/meta.json",
|
|
919
|
+
`${JSON.stringify(
|
|
920
|
+
{ title: "Team", icon: "Users", pages: ["onboarding", "people"] },
|
|
921
|
+
null,
|
|
922
|
+
2
|
|
923
|
+
)}
|
|
797
924
|
`
|
|
798
925
|
);
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
description: The home of your superlore knowledge base.
|
|
807
|
-
summary: Landing page for the ${config.name} knowledge base.
|
|
808
|
-
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]
|
|
809
933
|
---
|
|
810
934
|
|
|
811
|
-
|
|
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
|
+
/>
|
|
812
940
|
|
|
813
|
-
|
|
814
|
-
|
|
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
|
|
815
961
|
|
|
816
|
-
|
|
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
|
+
/>
|
|
817
973
|
`
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
`import { createMcpHandler } from "mcp-handler";
|
|
828
|
-
import { z } from "zod";
|
|
829
|
-
import { getComponentData, getPage, list, navigate, search } from "superlore/mcp";
|
|
830
|
-
import { buildIndexFromSource } from "superlore/source";
|
|
831
|
-
import type { KKind } from "superlore";
|
|
832
|
-
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
|
+
---
|
|
833
983
|
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
984
|
+
<SectionHead
|
|
985
|
+
eyebrow="Team"
|
|
986
|
+
title="People"
|
|
987
|
+
description="Who to ask about what. Replace these placeholders with your real team."
|
|
988
|
+
/>
|
|
838
989
|
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
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
|
+
/>
|
|
842
999
|
|
|
843
|
-
|
|
844
|
-
(server) => {
|
|
845
|
-
server.tool(
|
|
846
|
-
"search",
|
|
847
|
-
"Full-text search across the knowledge base.",
|
|
848
|
-
{ query: z.string(), limit: z.number().int().positive().optional() },
|
|
849
|
-
async ({ query, limit }) => json(search(index, query, limit)),
|
|
850
|
-
);
|
|
851
|
-
server.tool(
|
|
852
|
-
"get_page",
|
|
853
|
-
"Get a page's full structured content by path.",
|
|
854
|
-
{ path: z.string() },
|
|
855
|
-
async ({ path }) => json(getPage(index, path)),
|
|
856
|
-
);
|
|
857
|
-
server.tool(
|
|
858
|
-
"list",
|
|
859
|
-
"List knowledge nodes, filtered by kind / tag / entityType.",
|
|
860
|
-
{ kind: z.string().optional(), tag: z.string().optional(), entityType: z.string().optional() },
|
|
861
|
-
async ({ kind, tag, entityType }) =>
|
|
862
|
-
json(list(index, { kind: kind as KKind | undefined, tag, entityType })),
|
|
863
|
-
);
|
|
864
|
-
server.tool(
|
|
865
|
-
"navigate",
|
|
866
|
-
"Follow relations from a page path / node id / entity ref.",
|
|
867
|
-
{ target: z.string() },
|
|
868
|
-
async ({ target }) => json(navigate(index, target)),
|
|
869
|
-
);
|
|
870
|
-
server.tool(
|
|
871
|
-
"get_component_data",
|
|
872
|
-
"Get the structured data behind a rendered component (its knowledge face).",
|
|
873
|
-
{ id: z.string() },
|
|
874
|
-
async ({ id }) => json(getComponentData(index, id)),
|
|
875
|
-
);
|
|
876
|
-
},
|
|
877
|
-
{},
|
|
878
|
-
{ basePath: "/api" },
|
|
879
|
-
);
|
|
1000
|
+
## Example: a person, as structured data
|
|
880
1001
|
|
|
881
|
-
|
|
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
|
+
/>
|
|
882
1015
|
`
|
|
883
|
-
|
|
884
|
-
|
|
1016
|
+
);
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
// src/lib/content/product.ts
|
|
1020
|
+
function writeProductContent(write, config) {
|
|
885
1021
|
write(
|
|
886
|
-
".
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
.
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
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
|
+
)}
|
|
893
1033
|
`
|
|
894
1034
|
);
|
|
895
|
-
if (authEnabled) {
|
|
896
|
-
write(
|
|
897
|
-
".env.example",
|
|
898
|
-
`# Auth.js v5 + Google SSO. The gate is OFF until AUTH_GOOGLE_ID is set, so local dev works with
|
|
899
|
-
# this file empty. Copy to .env.local and fill in to enable it on a deploy.
|
|
900
|
-
AUTH_SECRET= # \`openssl rand -base64 32\`
|
|
901
|
-
AUTH_GOOGLE_ID= # presence of this turns the gate ON
|
|
902
|
-
AUTH_GOOGLE_SECRET=
|
|
903
|
-
AUTH_URL= # the deploy's canonical URL, e.g. https://your-kb.vercel.app
|
|
904
|
-
AUTH_TRUST_HOST=true
|
|
905
|
-
${config.auth?.allowedDomain ? `AUTH_ALLOWED_DOMAIN=${config.auth.allowedDomain}` : "# AUTH_ALLOWED_DOMAIN=example.com # restrict sign-in to one workspace domain (optional)"}
|
|
906
|
-
# AUTH_ALLOWED_EMAILS=you@example.com # comma-separated allowlist that bypasses the domain check
|
|
907
|
-
# LOCAL=true # force the gate OFF locally even when configured
|
|
908
|
-
`
|
|
909
|
-
);
|
|
910
|
-
}
|
|
911
|
-
const authReadme = authEnabled ? `
|
|
912
|
-
|
|
913
|
-
## Auth
|
|
914
|
-
|
|
915
|
-
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.` : "";
|
|
916
1035
|
write(
|
|
917
|
-
"
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
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
|
+
---
|
|
923
1043
|
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
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
|
+
/>
|
|
928
1050
|
|
|
929
|
-
|
|
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>
|
|
930
1055
|
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
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
|
+
/>
|
|
934
1063
|
|
|
935
|
-
|
|
1064
|
+
## Start here
|
|
936
1065
|
|
|
937
|
-
|
|
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
|
+
/>
|
|
938
1074
|
`
|
|
939
1075
|
);
|
|
940
|
-
}
|
|
941
|
-
function writeAuth(write, config) {
|
|
942
|
-
const allowedDomain = config.auth?.allowedDomain;
|
|
943
1076
|
write(
|
|
944
|
-
"
|
|
945
|
-
|
|
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
|
+
---
|
|
946
1084
|
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
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
|
+
/>
|
|
1090
|
+
|
|
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>
|
|
952
1125
|
`
|
|
953
1126
|
);
|
|
954
1127
|
write(
|
|
955
|
-
"
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
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
|
+
)}
|
|
959
1134
|
`
|
|
960
1135
|
);
|
|
961
1136
|
write(
|
|
962
|
-
"
|
|
963
|
-
|
|
964
|
-
|
|
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
|
+
---
|
|
965
1144
|
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
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
|
+
/>
|
|
969
1150
|
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
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
|
+
/>
|
|
974
1188
|
`
|
|
975
1189
|
);
|
|
976
1190
|
write(
|
|
977
|
-
"
|
|
978
|
-
|
|
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
|
+
---
|
|
979
1198
|
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
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
|
+
/>
|
|
1204
|
+
|
|
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
|
+
/>
|
|
1226
|
+
`
|
|
1005
1227
|
);
|
|
1006
|
-
|
|
1228
|
+
write(
|
|
1229
|
+
"content/docs/guides/meta.json",
|
|
1230
|
+
`${JSON.stringify(
|
|
1231
|
+
{ title: "Guides", icon: "BookOpen", pages: ["first-integration"] },
|
|
1232
|
+
null,
|
|
1233
|
+
2
|
|
1234
|
+
)}
|
|
1007
1235
|
`
|
|
1008
1236
|
);
|
|
1009
1237
|
write(
|
|
1010
|
-
"
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
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
|
+
---
|
|
1245
|
+
|
|
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
|
+
`
|
|
1032
1284
|
);
|
|
1033
|
-
|
|
1285
|
+
write(
|
|
1286
|
+
"content/docs/reference/meta.json",
|
|
1287
|
+
`${JSON.stringify(
|
|
1288
|
+
{ title: "Reference", icon: "Settings2", pages: ["configuration"] },
|
|
1289
|
+
null,
|
|
1290
|
+
2
|
|
1291
|
+
)}
|
|
1292
|
+
`
|
|
1293
|
+
);
|
|
1294
|
+
write(
|
|
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
|
+
`
|
|
1339
|
+
);
|
|
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
|
+
/>
|
|
1034
1377
|
`
|
|
1035
1378
|
);
|
|
1036
1379
|
}
|
|
1380
|
+
|
|
1381
|
+
// src/lib/content/personal.ts
|
|
1037
1382
|
function writePersonalContent(write, config) {
|
|
1038
1383
|
write(
|
|
1039
1384
|
"content/docs/meta.json",
|
|
@@ -1041,6 +1386,7 @@ function writePersonalContent(write, config) {
|
|
|
1041
1386
|
{
|
|
1042
1387
|
title: config.name,
|
|
1043
1388
|
root: true,
|
|
1389
|
+
icon: "User",
|
|
1044
1390
|
pages: ["index", "beliefs", "working-style", "pr-comments", "voice", "stories"]
|
|
1045
1391
|
},
|
|
1046
1392
|
null,
|
|
@@ -1173,6 +1519,34 @@ would.
|
|
|
1173
1519
|
]}
|
|
1174
1520
|
/>
|
|
1175
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
|
+
|
|
1176
1550
|
## How I decide
|
|
1177
1551
|
|
|
1178
1552
|
<Decision
|
|
@@ -1200,156 +1574,661 @@ would.
|
|
|
1200
1574
|
/>
|
|
1201
1575
|
`
|
|
1202
1576
|
);
|
|
1203
|
-
write(
|
|
1204
|
-
"content/docs/pr-comments.mdx",
|
|
1205
|
-
`---
|
|
1206
|
-
title: How I give PR comments
|
|
1207
|
-
description: My review bar, the tone I use, and concrete examples of comments I leave.
|
|
1208
|
-
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.
|
|
1209
|
-
tags: [code-review, feedback, how-to]
|
|
1210
|
-
---
|
|
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
|
+
// Relaxed ranges (not exact pins) — this lands in the user's own repo, so let them take
|
|
1810
|
+
// patches/minors freely. superlore's peerDependencies already fence the majors.
|
|
1811
|
+
"fumadocs-core": "^16.8.2",
|
|
1812
|
+
"fumadocs-mdx": "^14.3.1",
|
|
1813
|
+
"fumadocs-ui": "^16.8.2",
|
|
1814
|
+
superlore: "^0.5.3",
|
|
1815
|
+
"lucide-react": "^1.21.0",
|
|
1816
|
+
// superlore peers the rendered components pull in: Mermaid (Diagram), themes.
|
|
1817
|
+
mermaid: "^11.15.0",
|
|
1818
|
+
...mcpEnabled ? (
|
|
1819
|
+
// mcp-handler pins an EXACT sdk peer (1.26.0); npm errors on anything else (pnpm only
|
|
1820
|
+
// warns). Pin the sdk to match so `npm install` resolves cleanly for every package manager.
|
|
1821
|
+
{ "@modelcontextprotocol/sdk": "1.26.0", "mcp-handler": "^1.1.0" }
|
|
1822
|
+
) : {},
|
|
1823
|
+
// Auth.js v5 powers the optional Google SSO gate (superlore/auth). Self-disabling
|
|
1824
|
+
// without AUTH_GOOGLE_ID, so it's harmless until the env is set.
|
|
1825
|
+
...authEnabled ? { "next-auth": "^5.0.0-beta.25" } : {},
|
|
1826
|
+
next: "^16.2.4",
|
|
1827
|
+
"next-themes": "^0.4.6",
|
|
1828
|
+
react: "^19.2.5",
|
|
1829
|
+
"react-dom": "^19.2.5",
|
|
1830
|
+
zod: "^4.4.3"
|
|
1831
|
+
},
|
|
1832
|
+
devDependencies: {
|
|
1833
|
+
"@tailwindcss/postcss": "^4.2.2",
|
|
1834
|
+
"@types/node": "^25.6.0",
|
|
1835
|
+
"@types/react": "^19.2.14",
|
|
1836
|
+
"@types/react-dom": "^19.2.3",
|
|
1837
|
+
postcss: "^8.5.10",
|
|
1838
|
+
tailwindcss: "^4.2.2",
|
|
1839
|
+
typescript: "^6.0.3"
|
|
1840
|
+
}
|
|
1841
|
+
},
|
|
1842
|
+
null,
|
|
1843
|
+
2
|
|
1844
|
+
)}
|
|
1845
|
+
`
|
|
1846
|
+
);
|
|
1847
|
+
write(
|
|
1848
|
+
"tsconfig.json",
|
|
1849
|
+
`${JSON.stringify(
|
|
1850
|
+
{
|
|
1851
|
+
compilerOptions: {
|
|
1852
|
+
target: "ES2022",
|
|
1853
|
+
lib: ["dom", "dom.iterable", "esnext"],
|
|
1854
|
+
module: "esnext",
|
|
1855
|
+
moduleResolution: "bundler",
|
|
1856
|
+
strict: true,
|
|
1857
|
+
noEmit: true,
|
|
1858
|
+
jsx: "preserve",
|
|
1859
|
+
esModuleInterop: true,
|
|
1860
|
+
resolveJsonModule: true,
|
|
1861
|
+
isolatedModules: true,
|
|
1862
|
+
skipLibCheck: true,
|
|
1863
|
+
incremental: true,
|
|
1864
|
+
paths: { "@/*": ["./*"], "collections/*": ["./.source/*"] },
|
|
1865
|
+
plugins: [{ name: "next" }]
|
|
1866
|
+
},
|
|
1867
|
+
include: ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
|
1868
|
+
exclude: ["node_modules", ".next", "out"]
|
|
1869
|
+
},
|
|
1870
|
+
null,
|
|
1871
|
+
2
|
|
1872
|
+
)}
|
|
1873
|
+
`
|
|
1874
|
+
);
|
|
1875
|
+
write(
|
|
1876
|
+
"next.config.mjs",
|
|
1877
|
+
`import { createMDX } from "fumadocs-mdx/next";
|
|
1878
|
+
|
|
1879
|
+
const withMDX = createMDX();
|
|
1880
|
+
|
|
1881
|
+
/** @type {import('next').NextConfig} */
|
|
1882
|
+
const config = {
|
|
1883
|
+
reactStrictMode: true,
|
|
1884
|
+
// superlore ships compiled ESM \u2014 consume it as a normal package (no transpilePackages).
|
|
1885
|
+
};
|
|
1886
|
+
|
|
1887
|
+
export default withMDX(config);
|
|
1888
|
+
`
|
|
1889
|
+
);
|
|
1890
|
+
write(
|
|
1891
|
+
"source.config.ts",
|
|
1892
|
+
`import { defineConfig, defineDocs } from "fumadocs-mdx/config";
|
|
1893
|
+
import { superloreFrontmatterSchema } from "superlore/frontmatter";
|
|
1894
|
+
import { remarkSuperloreCanvas } from "superlore/mdx";
|
|
1895
|
+
|
|
1896
|
+
// Extend the superlore frontmatter schema in ONE place if you add custom fields.
|
|
1897
|
+
export const docs = defineDocs({
|
|
1898
|
+
dir: "content/docs",
|
|
1899
|
+
docs: { schema: superloreFrontmatterSchema },
|
|
1900
|
+
});
|
|
1901
|
+
|
|
1902
|
+
export default defineConfig({
|
|
1903
|
+
mdxOptions: {
|
|
1904
|
+
// Turn fenced \`\`\`superlore-canvas JSON blocks into <Canvas json="\u2026" /> \u2014 the headline
|
|
1905
|
+
// authoring path: write a whiteboard as a code block, get a rendered + serialized canvas.
|
|
1906
|
+
remarkPlugins: [remarkSuperloreCanvas],
|
|
1907
|
+
},
|
|
1908
|
+
});
|
|
1909
|
+
`
|
|
1910
|
+
);
|
|
1911
|
+
write(
|
|
1912
|
+
"postcss.config.mjs",
|
|
1913
|
+
`const config = {
|
|
1914
|
+
plugins: { "@tailwindcss/postcss": {} },
|
|
1915
|
+
};
|
|
1916
|
+
|
|
1917
|
+
export default config;
|
|
1918
|
+
`
|
|
1919
|
+
);
|
|
1920
|
+
write(
|
|
1921
|
+
"app/global.css",
|
|
1922
|
+
`@import "tailwindcss";
|
|
1923
|
+
/* superlore theme tokens (light + dark, co-equal). Re-skin the whole KB from one accent. */
|
|
1924
|
+
@import "superlore/css";
|
|
1925
|
+
|
|
1926
|
+
:root {
|
|
1927
|
+
/* Brand accent \u2014 superlore derives the hover/weak/border/text family for both themes. */
|
|
1928
|
+
--kp-accent: ${accent2};
|
|
1929
|
+
}
|
|
1930
|
+
`
|
|
1931
|
+
);
|
|
1932
|
+
write(
|
|
1933
|
+
"app/layout.tsx",
|
|
1934
|
+
`import type { ReactNode } from "react";
|
|
1935
|
+
import { RootProvider } from "superlore/ui";
|
|
1936
|
+
import "./global.css";
|
|
1937
|
+
|
|
1938
|
+
export default function RootLayout({ children }: { children: ReactNode }) {
|
|
1939
|
+
// Light and dark are co-equal; default to the system preference.
|
|
1940
|
+
return (
|
|
1941
|
+
<html lang="en" suppressHydrationWarning>
|
|
1942
|
+
<body>
|
|
1943
|
+
<RootProvider>{children}</RootProvider>
|
|
1944
|
+
</body>
|
|
1945
|
+
</html>
|
|
1946
|
+
);
|
|
1947
|
+
}
|
|
1948
|
+
`
|
|
1949
|
+
);
|
|
1950
|
+
write(
|
|
1951
|
+
"app/page.tsx",
|
|
1952
|
+
`import { redirect } from "next/navigation";
|
|
1953
|
+
|
|
1954
|
+
// A superlore KB *is* its docs \u2014 there's no separate marketing home to maintain. Land visitors
|
|
1955
|
+
// straight in the docs (full nav, header, sidebar, search). Replace this with a custom landing
|
|
1956
|
+
// only if you actually want one.
|
|
1957
|
+
export default function HomePage() {
|
|
1958
|
+
redirect("/docs");
|
|
1959
|
+
}
|
|
1960
|
+
`
|
|
1961
|
+
);
|
|
1962
|
+
write(
|
|
1963
|
+
"app/docs/layout.tsx",
|
|
1964
|
+
`import type { ReactNode } from "react";
|
|
1965
|
+
import { DocsLayout } from "fumadocs-ui/layouts/docs";
|
|
1966
|
+
import { source } from "@/lib/source";
|
|
1967
|
+
|
|
1968
|
+
export default function Layout({ children }: { children: ReactNode }) {
|
|
1969
|
+
return (
|
|
1970
|
+
<DocsLayout tree={source.pageTree} nav={{ title: "${escapeForJsx(config.name)}" }}>
|
|
1971
|
+
{children}
|
|
1972
|
+
</DocsLayout>
|
|
1973
|
+
);
|
|
1974
|
+
}
|
|
1975
|
+
`
|
|
1976
|
+
);
|
|
1977
|
+
write(
|
|
1978
|
+
"app/docs/[[...slug]]/page.tsx",
|
|
1979
|
+
`import { notFound } from "next/navigation";
|
|
1980
|
+
import { DocsBody, DocsPage } from "fumadocs-ui/page";
|
|
1981
|
+
import { getMDXComponents } from "superlore";
|
|
1982
|
+
import { source } from "@/lib/source";
|
|
1983
|
+
|
|
1984
|
+
export default async function Page(props: { params: Promise<{ slug?: string[] }> }) {
|
|
1985
|
+
const params = await props.params;
|
|
1986
|
+
const page = source.getPage(params.slug);
|
|
1987
|
+
if (!page) notFound();
|
|
1988
|
+
const MDX = page.data.body;
|
|
1989
|
+
return (
|
|
1990
|
+
<DocsPage toc={page.data.toc}>
|
|
1991
|
+
<DocsBody>
|
|
1992
|
+
<MDX components={getMDXComponents()} />
|
|
1993
|
+
</DocsBody>
|
|
1994
|
+
</DocsPage>
|
|
1995
|
+
);
|
|
1996
|
+
}
|
|
1997
|
+
|
|
1998
|
+
export function generateStaticParams() {
|
|
1999
|
+
return source.generateParams();
|
|
2000
|
+
}
|
|
2001
|
+
`
|
|
2002
|
+
);
|
|
2003
|
+
write(
|
|
2004
|
+
"lib/source.ts",
|
|
2005
|
+
`import { docs } from "collections/server";
|
|
2006
|
+
import { loader, lucideIconsPlugin } from "superlore/source";
|
|
2007
|
+
|
|
2008
|
+
export const source = loader({
|
|
2009
|
+
baseUrl: "/docs",
|
|
2010
|
+
source: docs.toFumadocsSource(),
|
|
2011
|
+
plugins: [lucideIconsPlugin()],
|
|
2012
|
+
});
|
|
2013
|
+
`
|
|
2014
|
+
);
|
|
2015
|
+
writeContent(write, config);
|
|
2016
|
+
if (authEnabled) {
|
|
2017
|
+
writeAuth(write, config);
|
|
2018
|
+
}
|
|
2019
|
+
if (mcpEnabled) {
|
|
2020
|
+
const mcpPath = config.mcp?.path ?? "/api/mcp";
|
|
2021
|
+
write(
|
|
2022
|
+
"app/api/[transport]/route.ts",
|
|
2023
|
+
`import { createMcpHandler } from "mcp-handler";
|
|
2024
|
+
import { z } from "zod";
|
|
2025
|
+
import { getComponentData, getPage, list, navigate, search } from "superlore/mcp";
|
|
2026
|
+
import { buildIndexFromSource } from "superlore/source";
|
|
2027
|
+
import type { KKind } from "superlore";
|
|
2028
|
+
import { source } from "@/lib/source";
|
|
2029
|
+
|
|
2030
|
+
// Your KB's MCP endpoint. Served at ${mcpPath} \u2014 the same structured content the site renders,
|
|
2031
|
+
// exposed to agents. The index is built straight from your content \`source\`: author once, and
|
|
2032
|
+
// humans read the pages while agents query this corpus. No scraping, no drift.
|
|
2033
|
+
const index = buildIndexFromSource(source);
|
|
2034
|
+
|
|
2035
|
+
const json = (data: unknown) => ({
|
|
2036
|
+
content: [{ type: "text" as const, text: JSON.stringify(data, null, 2) }],
|
|
2037
|
+
});
|
|
2038
|
+
|
|
2039
|
+
const handler = createMcpHandler(
|
|
2040
|
+
(server) => {
|
|
2041
|
+
server.tool(
|
|
2042
|
+
"search",
|
|
2043
|
+
"Full-text search across the knowledge base.",
|
|
2044
|
+
{ query: z.string(), limit: z.number().int().positive().optional() },
|
|
2045
|
+
async ({ query, limit }) => json(search(index, query, limit)),
|
|
2046
|
+
);
|
|
2047
|
+
server.tool(
|
|
2048
|
+
"get_page",
|
|
2049
|
+
"Get a page's full structured content by path.",
|
|
2050
|
+
{ path: z.string() },
|
|
2051
|
+
async ({ path }) => json(getPage(index, path)),
|
|
2052
|
+
);
|
|
2053
|
+
server.tool(
|
|
2054
|
+
"list",
|
|
2055
|
+
"List knowledge nodes, filtered by kind / tag / entityType.",
|
|
2056
|
+
{ kind: z.string().optional(), tag: z.string().optional(), entityType: z.string().optional() },
|
|
2057
|
+
async ({ kind, tag, entityType }) =>
|
|
2058
|
+
json(list(index, { kind: kind as KKind | undefined, tag, entityType })),
|
|
2059
|
+
);
|
|
2060
|
+
server.tool(
|
|
2061
|
+
"navigate",
|
|
2062
|
+
"Follow relations from a page path / node id / entity ref.",
|
|
2063
|
+
{ target: z.string() },
|
|
2064
|
+
async ({ target }) => json(navigate(index, target)),
|
|
2065
|
+
);
|
|
2066
|
+
server.tool(
|
|
2067
|
+
"get_component_data",
|
|
2068
|
+
"Get the structured data behind a rendered component (its knowledge face).",
|
|
2069
|
+
{ id: z.string() },
|
|
2070
|
+
async ({ id }) => json(getComponentData(index, id)),
|
|
2071
|
+
);
|
|
2072
|
+
},
|
|
2073
|
+
{},
|
|
2074
|
+
{ basePath: "/api" },
|
|
2075
|
+
);
|
|
2076
|
+
|
|
2077
|
+
export { handler as GET, handler as POST };
|
|
2078
|
+
`
|
|
2079
|
+
);
|
|
2080
|
+
}
|
|
2081
|
+
write(
|
|
2082
|
+
".gitignore",
|
|
2083
|
+
`node_modules
|
|
2084
|
+
.next
|
|
2085
|
+
.source
|
|
2086
|
+
out
|
|
2087
|
+
*.tsbuildinfo
|
|
2088
|
+
.env*.local
|
|
2089
|
+
`
|
|
2090
|
+
);
|
|
2091
|
+
if (authEnabled) {
|
|
2092
|
+
write(
|
|
2093
|
+
".env.example",
|
|
2094
|
+
`# Auth.js v5 + Google SSO. The gate is OFF until AUTH_GOOGLE_ID is set, so local dev works with
|
|
2095
|
+
# this file empty. Copy to .env.local and fill in to enable it on a deploy.
|
|
2096
|
+
AUTH_SECRET= # \`openssl rand -base64 32\`
|
|
2097
|
+
AUTH_GOOGLE_ID= # presence of this turns the gate ON
|
|
2098
|
+
AUTH_GOOGLE_SECRET=
|
|
2099
|
+
AUTH_URL= # the deploy's canonical URL, e.g. https://your-kb.vercel.app
|
|
2100
|
+
AUTH_TRUST_HOST=true
|
|
2101
|
+
${config.auth?.allowedDomain ? `AUTH_ALLOWED_DOMAIN=${config.auth.allowedDomain}` : "# AUTH_ALLOWED_DOMAIN=example.com # restrict sign-in to one workspace domain (optional)"}
|
|
2102
|
+
# AUTH_ALLOWED_EMAILS=you@example.com # comma-separated allowlist that bypasses the domain check
|
|
2103
|
+
# LOCAL=true # force the gate OFF locally even when configured
|
|
2104
|
+
`
|
|
2105
|
+
);
|
|
2106
|
+
}
|
|
2107
|
+
const authReadme = authEnabled ? `
|
|
1211
2108
|
|
|
1212
|
-
|
|
1213
|
-
eyebrow="Code review"
|
|
1214
|
-
title="How I give PR comments"
|
|
1215
|
-
description="What I block on, what I just nudge, and how I phrase it. Replace with your own."
|
|
1216
|
-
/>
|
|
2109
|
+
## Auth
|
|
1217
2110
|
|
|
1218
|
-
|
|
2111
|
+
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.` : "";
|
|
2112
|
+
write(
|
|
2113
|
+
"README.md",
|
|
2114
|
+
`# ${config.name}
|
|
1219
2115
|
|
|
1220
|
-
|
|
1221
|
-
label="What I look for, in order"
|
|
1222
|
-
items={[
|
|
1223
|
-
{ text: "Correctness \u2014 does it do the thing, including the edge cases?", group: "Block on" },
|
|
1224
|
-
{ text: "Tests that would fail without the change", group: "Block on" },
|
|
1225
|
-
{ text: "Names and structure I can read in one pass", group: "Block on" },
|
|
1226
|
-
{ text: "Unnecessary abstraction or dead code", group: "Nudge on" },
|
|
1227
|
-
{ text: "Comments that explain why, not what", group: "Nudge on" },
|
|
1228
|
-
{ text: "Nits \u2014 formatting, ordering, taste", group: "Optional" },
|
|
1229
|
-
]}
|
|
1230
|
-
/>
|
|
2116
|
+
A superlore knowledge base. One corpus. Humans and agents.
|
|
1231
2117
|
|
|
1232
|
-
##
|
|
2118
|
+
## Develop
|
|
1233
2119
|
|
|
1234
|
-
|
|
1235
|
-
|
|
2120
|
+
\`\`\`sh
|
|
2121
|
+
pnpm install # or npm / yarn / bun
|
|
2122
|
+
superlore dev # preview the site locally
|
|
2123
|
+
\`\`\`
|
|
1236
2124
|
|
|
1237
|
-
|
|
1238
|
-
**blocking:** This drops the error on the floor \u2014 if the fetch fails we return \`undefined\` and
|
|
1239
|
-
the caller renders an empty state as if it were success. Surface it, or handle it explicitly.
|
|
1240
|
-
</Example>
|
|
2125
|
+
## Build
|
|
1241
2126
|
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
</Example>
|
|
2127
|
+
\`\`\`sh
|
|
2128
|
+
superlore build
|
|
2129
|
+
\`\`\`
|
|
1246
2130
|
|
|
1247
|
-
|
|
1248
|
-
**nit:** tiny \u2014 can we name this \`pendingCount\` so it matches the others? Ignore if you disagree.
|
|
1249
|
-
</Example>
|
|
2131
|
+
Config lives in \`superlore.json\`. Author content in \`content/docs/\`.${mcpEnabled ? `
|
|
1250
2132
|
|
|
1251
|
-
|
|
1252
|
-
Critique the code, never the author. Lead with the why. If I'd want it softened when it lands on
|
|
1253
|
-
my own PR, I soften it.
|
|
1254
|
-
</Tip>
|
|
2133
|
+
The MCP endpoint is served at \`${config.mcp?.path ?? "/api/mcp"}\`.` : ""}${authReadme}
|
|
1255
2134
|
`
|
|
1256
2135
|
);
|
|
2136
|
+
}
|
|
2137
|
+
function writeAuth(write, config) {
|
|
2138
|
+
const allowedDomain = config.auth?.allowedDomain;
|
|
1257
2139
|
write(
|
|
1258
|
-
"
|
|
1259
|
-
|
|
1260
|
-
title: Voice & writing
|
|
1261
|
-
description: How I sound in writing \u2014 tone, defaults, and what I avoid.
|
|
1262
|
-
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.
|
|
1263
|
-
tags: [voice, writing, style]
|
|
1264
|
-
---
|
|
1265
|
-
|
|
1266
|
-
<SectionHead
|
|
1267
|
-
eyebrow="How I write"
|
|
1268
|
-
title="Voice & writing"
|
|
1269
|
-
description="So an agent drafting in my name sounds like me, not like a template."
|
|
1270
|
-
/>
|
|
1271
|
-
|
|
1272
|
-
Placeholder \u2014 capture your actual voice. The more specific the do/don't list, the better an agent
|
|
1273
|
-
can match you.
|
|
1274
|
-
|
|
1275
|
-
<KeyFacts
|
|
1276
|
-
items={[
|
|
1277
|
-
{ label: "Tone", value: "Direct, warm, low ceremony" },
|
|
1278
|
-
{ label: "Sentence length", value: "Short. Then one longer one to breathe." },
|
|
1279
|
-
{ label: "Person", value: "First person, active voice" },
|
|
1280
|
-
{ label: "Jargon", value: "Only when it's the precise word" },
|
|
1281
|
-
]}
|
|
1282
|
-
/>
|
|
1283
|
-
|
|
1284
|
-
## Do / don't
|
|
2140
|
+
"auth.ts",
|
|
2141
|
+
`import { createSuperloreAuth } from "superlore/auth";
|
|
1285
2142
|
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
2143
|
+
// Auth.js v5 + Google SSO. Allowlists can come from env (AUTH_ALLOWED_DOMAIN / AUTH_ALLOWED_EMAILS)
|
|
2144
|
+
// or be passed explicitly here. Off until AUTH_GOOGLE_ID is set, so local dev needs no config.
|
|
2145
|
+
export const { handlers, auth, signIn, signOut } = createSuperloreAuth(${allowedDomain ? `{
|
|
2146
|
+
allowedDomain: ${JSON.stringify(allowedDomain)},
|
|
2147
|
+
}` : "{}"});
|
|
2148
|
+
`
|
|
2149
|
+
);
|
|
2150
|
+
write(
|
|
2151
|
+
"app/api/auth/[...nextauth]/route.ts",
|
|
2152
|
+
`import { handlers } from "@/auth";
|
|
1296
2153
|
|
|
1297
|
-
|
|
1298
|
-
Shipping the import fix today. It was dropping rows when a column was empty \u2014 now we skip the row
|
|
1299
|
-
and log it. One follow-up: we should validate on upload so this can't happen again. Want me to take
|
|
1300
|
-
that next?
|
|
1301
|
-
</Example>
|
|
2154
|
+
export const { GET, POST } = handlers;
|
|
1302
2155
|
`
|
|
1303
2156
|
);
|
|
1304
2157
|
write(
|
|
1305
|
-
"
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
description: Formative moments that explain how I got my defaults.
|
|
1309
|
-
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.
|
|
1310
|
-
tags: [stories, background, timeline]
|
|
1311
|
-
---
|
|
2158
|
+
"proxy.ts",
|
|
2159
|
+
`import { auth } from "@/auth";
|
|
2160
|
+
import { createAuthProxy } from "superlore/auth";
|
|
1312
2161
|
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
description="The moments behind the takes. Replace these with your own \u2014 dates can be approximate."
|
|
1317
|
-
/>
|
|
2162
|
+
// Next.js 16 middleware lives in proxy.ts. The gate is self-disabling: with no AUTH_GOOGLE_ID
|
|
2163
|
+
// (or LOCAL=true) every request passes through, so local dev and public deploys stay open.
|
|
2164
|
+
export default createAuthProxy(auth);
|
|
1318
2165
|
|
|
1319
|
-
|
|
1320
|
-
|
|
2166
|
+
export const config = {
|
|
2167
|
+
// Run on everything except static assets (the helper also skips the auth dance + icons).
|
|
2168
|
+
matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"],
|
|
2169
|
+
};
|
|
2170
|
+
`
|
|
2171
|
+
);
|
|
2172
|
+
write(
|
|
2173
|
+
"app/auth/signin/page.tsx",
|
|
2174
|
+
`import { signIn } from "@/auth";
|
|
1321
2175
|
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
2176
|
+
export default async function SignInPage(props: {
|
|
2177
|
+
searchParams: Promise<{ callbackUrl?: string }>;
|
|
2178
|
+
}) {
|
|
2179
|
+
const { callbackUrl } = await props.searchParams;
|
|
2180
|
+
return (
|
|
2181
|
+
<main className="grid min-h-screen place-items-center p-6">
|
|
2182
|
+
<form
|
|
2183
|
+
action={async () => {
|
|
2184
|
+
"use server";
|
|
2185
|
+
await signIn("google", { redirectTo: callbackUrl ?? "/" });
|
|
2186
|
+
}}
|
|
2187
|
+
className="w-full max-w-sm rounded-lg border border-fd-border bg-fd-card p-6 text-center"
|
|
2188
|
+
>
|
|
2189
|
+
<h1 className="text-lg font-semibold text-fd-foreground">Sign in</h1>
|
|
2190
|
+
<p className="mt-1 text-sm text-fd-muted-foreground">
|
|
2191
|
+
This knowledge base is private. Continue with Google to read it.
|
|
2192
|
+
</p>
|
|
2193
|
+
<button
|
|
2194
|
+
type="submit"
|
|
2195
|
+
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"
|
|
2196
|
+
>
|
|
2197
|
+
Continue with Google
|
|
2198
|
+
</button>
|
|
2199
|
+
</form>
|
|
2200
|
+
</main>
|
|
2201
|
+
);
|
|
2202
|
+
}
|
|
1348
2203
|
`
|
|
1349
2204
|
);
|
|
2205
|
+
write(
|
|
2206
|
+
"app/auth/error/page.tsx",
|
|
2207
|
+
`export default async function AuthErrorPage(props: {
|
|
2208
|
+
searchParams: Promise<{ error?: string }>;
|
|
2209
|
+
}) {
|
|
2210
|
+
const { error } = await props.searchParams;
|
|
2211
|
+
return (
|
|
2212
|
+
<main className="grid min-h-screen place-items-center p-6">
|
|
2213
|
+
<div className="w-full max-w-sm rounded-lg border border-fd-border bg-fd-card p-6 text-center">
|
|
2214
|
+
<h1 className="text-lg font-semibold text-fd-foreground">Can't sign in</h1>
|
|
2215
|
+
<p className="mt-1 text-sm text-fd-muted-foreground">
|
|
2216
|
+
{error === "AccessDenied"
|
|
2217
|
+
? "That account isn't allowed to access this knowledge base."
|
|
2218
|
+
: "Something went wrong signing in. Try again."}
|
|
2219
|
+
</p>
|
|
2220
|
+
<a
|
|
2221
|
+
href="/auth/signin"
|
|
2222
|
+
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"
|
|
2223
|
+
>
|
|
2224
|
+
Back to sign in
|
|
2225
|
+
</a>
|
|
2226
|
+
</div>
|
|
2227
|
+
</main>
|
|
2228
|
+
);
|
|
1350
2229
|
}
|
|
1351
|
-
|
|
1352
|
-
|
|
2230
|
+
`
|
|
2231
|
+
);
|
|
1353
2232
|
}
|
|
1354
2233
|
|
|
1355
2234
|
// src/commands/init.ts
|
|
@@ -1464,9 +2343,41 @@ ${result.issues.map((i) => ` - ${i.path} ${i.message}`).join("\n")}`
|
|
|
1464
2343
|
`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.`
|
|
1465
2344
|
);
|
|
1466
2345
|
}
|
|
2346
|
+
printStructure(root);
|
|
1467
2347
|
printNextSteps(root, config);
|
|
1468
2348
|
await maybeConnectEditor(flags, interactive);
|
|
1469
2349
|
}
|
|
2350
|
+
function printStructure(root) {
|
|
2351
|
+
const docs = join4(root, "content", "docs");
|
|
2352
|
+
if (!existsSync4(docs)) return;
|
|
2353
|
+
const lines = [];
|
|
2354
|
+
const walk = (dir, depth) => {
|
|
2355
|
+
let names;
|
|
2356
|
+
try {
|
|
2357
|
+
names = readdirSync2(dir).sort((a, b) => a.localeCompare(b));
|
|
2358
|
+
} catch {
|
|
2359
|
+
return;
|
|
2360
|
+
}
|
|
2361
|
+
const dirs = names.filter((n) => !n.startsWith(".") && statSync(join4(dir, n)).isDirectory());
|
|
2362
|
+
const pages = names.filter((n) => n.endsWith(".mdx"));
|
|
2363
|
+
for (const name of dirs) {
|
|
2364
|
+
lines.push(`${" ".repeat(depth)}${cyan(`${name}/`)}`);
|
|
2365
|
+
walk(join4(dir, name), depth + 1);
|
|
2366
|
+
}
|
|
2367
|
+
for (const name of pages) {
|
|
2368
|
+
lines.push(`${" ".repeat(depth)}${name}`);
|
|
2369
|
+
}
|
|
2370
|
+
};
|
|
2371
|
+
walk(docs, 1);
|
|
2372
|
+
log.blank();
|
|
2373
|
+
log.info(bold("Your starter structure"));
|
|
2374
|
+
log.info(`${dim(" content/docs/")} ${dim("\u2014 a full KB, pre-filled with placeholder content:")}`);
|
|
2375
|
+
for (const line of lines) log.info(line);
|
|
2376
|
+
log.blank();
|
|
2377
|
+
log.info(
|
|
2378
|
+
`${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"')}.`
|
|
2379
|
+
);
|
|
2380
|
+
}
|
|
1470
2381
|
async function maybeConnectEditor(flags, interactive) {
|
|
1471
2382
|
if (flags.connect === false) return;
|
|
1472
2383
|
const editors = detectEditors();
|
|
@@ -1513,7 +2424,7 @@ function printNextSteps(root, config) {
|
|
|
1513
2424
|
}
|
|
1514
2425
|
|
|
1515
2426
|
// src/index.ts
|
|
1516
|
-
var VERSION = "0.
|
|
2427
|
+
var VERSION = "0.4.1";
|
|
1517
2428
|
function buildCli(argv = process3.argv) {
|
|
1518
2429
|
const cli = cac("superlore");
|
|
1519
2430
|
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(
|