superlore-cli 0.2.0 → 0.3.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/config.d.ts CHANGED
@@ -11,8 +11,12 @@
11
11
  * imported from anywhere — including the browser or an edge runtime — without dragging in a
12
12
  * validator. Keep it that way.
13
13
  */
14
- /** The two kinds of superlore KB. Drives defaults (notably the auth warning for company KBs). */
15
- type SuperloreType = "company-kb" | "product-docs";
14
+ /**
15
+ * The kinds of superlore KB. Drives defaults — the auth warning for company KBs, and an
16
+ * auth-ON-by-default, MCP-ON private posture for a `personal-kb` (a digital replica of how one
17
+ * person thinks, works, and writes).
18
+ */
19
+ type SuperloreType = "company-kb" | "product-docs" | "personal-kb";
16
20
  /** Supported SSO providers. Only Google ships today (Auth.js v5 + Google SSO). */
17
21
  type SuperloreAuthProvider = "google";
18
22
  /** The human gate. Optional and per-deploy — public by default. */
@@ -35,7 +39,7 @@ interface SuperloreMcpConfig {
35
39
  interface SuperloreJson {
36
40
  /** Human-facing KB name. */
37
41
  name: string;
38
- /** Whether this is a private company KB or public product docs. */
42
+ /** Whether this is a private company KB, public product docs, or a private personal KB. */
39
43
  type: SuperloreType;
40
44
  /** Brand accent — any CSS colour. superlore derives the rest of the family (light + dark). */
41
45
  accent?: string;
package/dist/config.js CHANGED
@@ -1,7 +1,11 @@
1
1
  // src/config.ts
2
2
  var DEFAULT_MCP_PATH = "/api/mcp";
3
3
  var SUPERLORE_JSON_FILENAME = "superlore.json";
4
- var SUPERLORE_TYPES = ["company-kb", "product-docs"];
4
+ var SUPERLORE_TYPES = [
5
+ "company-kb",
6
+ "product-docs",
7
+ "personal-kb"
8
+ ];
5
9
  function isRecord(value) {
6
10
  return typeof value === "object" && value !== null && !Array.isArray(value);
7
11
  }
package/dist/index.d.ts CHANGED
@@ -13,8 +13,12 @@ import * as cac from 'cac';
13
13
  * imported from anywhere — including the browser or an edge runtime — without dragging in a
14
14
  * validator. Keep it that way.
15
15
  */
16
- /** The two kinds of superlore KB. Drives defaults (notably the auth warning for company KBs). */
17
- type SuperloreType = "company-kb" | "product-docs";
16
+ /**
17
+ * The kinds of superlore KB. Drives defaults — the auth warning for company KBs, and an
18
+ * auth-ON-by-default, MCP-ON private posture for a `personal-kb` (a digital replica of how one
19
+ * person thinks, works, and writes).
20
+ */
21
+ type SuperloreType = "company-kb" | "product-docs" | "personal-kb";
18
22
  /** Supported SSO providers. Only Google ships today (Auth.js v5 + Google SSO). */
19
23
  type SuperloreAuthProvider = "google";
20
24
  /** The human gate. Optional and per-deploy — public by default. */
@@ -37,7 +41,7 @@ interface SuperloreMcpConfig {
37
41
  interface SuperloreJson {
38
42
  /** Human-facing KB name. */
39
43
  name: string;
40
- /** Whether this is a private company KB or public product docs. */
44
+ /** Whether this is a private company KB, public product docs, or a private personal KB. */
41
45
  type: SuperloreType;
42
46
  /** Brand accent — any CSS colour. superlore derives the rest of the family (light + dark). */
43
47
  accent?: string;
@@ -83,7 +87,7 @@ declare function serializeSuperloreJson(config: SuperloreJson): string;
83
87
  declare function resolveMcpPath(config: SuperloreJson): string | undefined;
84
88
 
85
89
  /** The CLI version, kept in sync with package.json at build time. */
86
- declare const VERSION = "0.2.0";
90
+ declare const VERSION = "0.3.0";
87
91
  /** Build the argument parser. Exported for tests; `run()` wires it to argv. */
88
92
  declare function buildCli(argv?: readonly string[]): cac.CAC;
89
93
  /** Parse argv and dispatch. Reports unknown commands and unexpected errors cleanly. */
package/dist/index.js CHANGED
@@ -71,7 +71,11 @@ import { dirname, join, resolve } from "path";
71
71
  // src/config.ts
72
72
  var DEFAULT_MCP_PATH = "/api/mcp";
73
73
  var SUPERLORE_JSON_FILENAME = "superlore.json";
74
- var SUPERLORE_TYPES = ["company-kb", "product-docs"];
74
+ var SUPERLORE_TYPES = [
75
+ "company-kb",
76
+ "product-docs",
77
+ "personal-kb"
78
+ ];
75
79
  function isRecord(value) {
76
80
  return typeof value === "object" && value !== null && !Array.isArray(value);
77
81
  }
@@ -568,6 +572,7 @@ function scaffold(options) {
568
572
  function writeSkeleton(root, config) {
569
573
  const accent2 = config.accent ?? SUPERLORE_VIOLET;
570
574
  const mcpEnabled = config.mcp?.enabled ?? true;
575
+ const authEnabled = config.auth?.enabled ?? false;
571
576
  const slug = config.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "superlore-kb";
572
577
  const write = (rel, body) => {
573
578
  const file = join3(root, rel);
@@ -592,9 +597,14 @@ function writeSkeleton(root, config) {
592
597
  "fumadocs-core": "16.8.2",
593
598
  "fumadocs-mdx": "14.3.1",
594
599
  "fumadocs-ui": "16.8.2",
595
- superlore: "^0.1.0",
600
+ superlore: "^0.5.1",
596
601
  "lucide-react": "^1.21.0",
602
+ // superlore peers the rendered components pull in: Mermaid (Diagram), themes.
603
+ mermaid: "^11.15.0",
597
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" } : {},
598
608
  next: "16.2.4",
599
609
  "next-themes": "^0.4.6",
600
610
  react: "^19.2.5",
@@ -633,7 +643,7 @@ function writeSkeleton(root, config) {
633
643
  isolatedModules: true,
634
644
  skipLibCheck: true,
635
645
  incremental: true,
636
- paths: { "@/*": ["./*"] },
646
+ paths: { "@/*": ["./*"], "collections/*": ["./.source/*"] },
637
647
  plugins: [{ name: "next" }]
638
648
  },
639
649
  include: ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
@@ -653,8 +663,7 @@ const withMDX = createMDX();
653
663
  /** @type {import('next').NextConfig} */
654
664
  const config = {
655
665
  reactStrictMode: true,
656
- // \`superlore\` ships source (.ts/.tsx) for frictionless consumption \u2014 Next transpiles it.
657
- transpilePackages: ["superlore"],
666
+ // superlore ships compiled ESM \u2014 consume it as a normal package (no transpilePackages).
658
667
  };
659
668
 
660
669
  export default withMDX(config);
@@ -698,7 +707,7 @@ export default config;
698
707
  write(
699
708
  "app/layout.tsx",
700
709
  `import type { ReactNode } from "react";
701
- import { RootProvider } from "fumadocs-ui/provider";
710
+ import { RootProvider } from "superlore/ui";
702
711
  import "./global.css";
703
712
 
704
713
  export default function RootLayout({ children }: { children: ReactNode }) {
@@ -772,18 +781,22 @@ export function generateStaticParams() {
772
781
  );
773
782
  write(
774
783
  "lib/source.ts",
775
- `import { loader } from "fumadocs-core/source";
776
- import { docs } from "@/.source";
784
+ `import { docs } from "collections/server";
785
+ import { loader, lucideIconsPlugin } from "superlore/source";
777
786
 
778
787
  export const source = loader({
779
788
  baseUrl: "/docs",
780
789
  source: docs.toFumadocsSource(),
790
+ plugins: [lucideIconsPlugin()],
781
791
  });
782
792
  `
783
793
  );
784
- write(
785
- "content/docs/index.mdx",
786
- `---
794
+ if (config.type === "personal-kb") {
795
+ writePersonalContent(write, config);
796
+ } else {
797
+ write(
798
+ "content/docs/index.mdx",
799
+ `---
787
800
  title: Welcome
788
801
  description: The home of your superlore knowledge base.
789
802
  summary: Landing page for the ${config.name} knowledge base.
@@ -797,7 +810,11 @@ agents read the same structured content over MCP.
797
810
 
798
811
  Edit \`content/docs/index.mdx\` to make it yours, then run \`superlore dev\`.
799
812
  `
800
- );
813
+ );
814
+ }
815
+ if (authEnabled) {
816
+ writeAuth(write, config);
817
+ }
801
818
  if (mcpEnabled) {
802
819
  const mcpPath = config.mcp?.path ?? "/api/mcp";
803
820
  write(
@@ -805,10 +822,14 @@ Edit \`content/docs/index.mdx\` to make it yours, then run \`superlore dev\`.
805
822
  `import { createMcpHandler } from "mcp-handler";
806
823
  import { z } from "zod";
807
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";
808
828
 
809
829
  // Your KB's MCP endpoint. Served at ${mcpPath} \u2014 the same structured content the site renders,
810
- // exposed to agents. Build the index from your content source and pass it to each tool.
811
- // See the superlore docs (Agents & MCP) for wiring the index from \`source\`.
830
+ // exposed to agents. The index is built straight from your content \`source\`: author once, and
831
+ // humans read the pages while agents query this corpus. No scraping, no drift.
832
+ const index = buildIndexFromSource(source);
812
833
 
813
834
  const json = (data: unknown) => ({
814
835
  content: [{ type: "text" as const, text: JSON.stringify(data, null, 2) }],
@@ -820,31 +841,32 @@ const handler = createMcpHandler(
820
841
  "search",
821
842
  "Full-text search across the knowledge base.",
822
843
  { query: z.string(), limit: z.number().int().positive().optional() },
823
- async ({ query, limit }) => json(search(/* index */ {} as never, query, limit)),
844
+ async ({ query, limit }) => json(search(index, query, limit)),
824
845
  );
825
846
  server.tool(
826
847
  "get_page",
827
848
  "Get a page's full structured content by path.",
828
849
  { path: z.string() },
829
- async ({ path }) => json(getPage(/* index */ {} as never, path)),
850
+ async ({ path }) => json(getPage(index, path)),
830
851
  );
831
852
  server.tool(
832
853
  "list",
833
854
  "List knowledge nodes, filtered by kind / tag / entityType.",
834
855
  { kind: z.string().optional(), tag: z.string().optional(), entityType: z.string().optional() },
835
- async (args) => json(list(/* index */ {} as never, args)),
856
+ async ({ kind, tag, entityType }) =>
857
+ json(list(index, { kind: kind as KKind | undefined, tag, entityType })),
836
858
  );
837
859
  server.tool(
838
860
  "navigate",
839
861
  "Follow relations from a page path / node id / entity ref.",
840
862
  { target: z.string() },
841
- async ({ target }) => json(navigate(/* index */ {} as never, target)),
863
+ async ({ target }) => json(navigate(index, target)),
842
864
  );
843
865
  server.tool(
844
866
  "get_component_data",
845
867
  "Get the structured data behind a rendered component (its knowledge face).",
846
868
  { id: z.string() },
847
- async ({ id }) => json(getComponentData(/* index */ {} as never, id)),
869
+ async ({ id }) => json(getComponentData(index, id)),
848
870
  );
849
871
  },
850
872
  {},
@@ -865,6 +887,27 @@ out
865
887
  .env*.local
866
888
  `
867
889
  );
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.` : "";
868
911
  write(
869
912
  "README.md",
870
913
  `# ${config.name}
@@ -886,7 +929,417 @@ superlore build
886
929
 
887
930
  Config lives in \`superlore.json\`. Author content in \`content/docs/\`.${mcpEnabled ? `
888
931
 
889
- The MCP endpoint is served at \`${config.mcp?.path ?? "/api/mcp"}\`.` : ""}
932
+ The MCP endpoint is served at \`${config.mcp?.path ?? "/api/mcp"}\`.` : ""}${authReadme}
933
+ `
934
+ );
935
+ }
936
+ function writeAuth(write, config) {
937
+ const allowedDomain = config.auth?.allowedDomain;
938
+ write(
939
+ "auth.ts",
940
+ `import { createSuperloreAuth } from "superlore/auth";
941
+
942
+ // Auth.js v5 + Google SSO. Allowlists can come from env (AUTH_ALLOWED_DOMAIN / AUTH_ALLOWED_EMAILS)
943
+ // or be passed explicitly here. Off until AUTH_GOOGLE_ID is set, so local dev needs no config.
944
+ export const { handlers, auth, signIn, signOut } = createSuperloreAuth(${allowedDomain ? `{
945
+ allowedDomain: ${JSON.stringify(allowedDomain)},
946
+ }` : "{}"});
947
+ `
948
+ );
949
+ write(
950
+ "app/api/auth/[...nextauth]/route.ts",
951
+ `import { handlers } from "@/auth";
952
+
953
+ export const { GET, POST } = handlers;
954
+ `
955
+ );
956
+ write(
957
+ "proxy.ts",
958
+ `import { auth } from "@/auth";
959
+ import { createAuthProxy } from "superlore/auth";
960
+
961
+ // Next.js 16 middleware lives in proxy.ts. The gate is self-disabling: with no AUTH_GOOGLE_ID
962
+ // (or LOCAL=true) every request passes through, so local dev and public deploys stay open.
963
+ export default createAuthProxy(auth);
964
+
965
+ export const config = {
966
+ // Run on everything except static assets (the helper also skips the auth dance + icons).
967
+ matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"],
968
+ };
969
+ `
970
+ );
971
+ write(
972
+ "app/auth/signin/page.tsx",
973
+ `import { signIn } from "@/auth";
974
+
975
+ export default async function SignInPage(props: {
976
+ searchParams: Promise<{ callbackUrl?: string }>;
977
+ }) {
978
+ const { callbackUrl } = await props.searchParams;
979
+ return (
980
+ <main className="grid min-h-screen place-items-center p-6">
981
+ <form
982
+ action={async () => {
983
+ "use server";
984
+ await signIn("google", { redirectTo: callbackUrl ?? "/" });
985
+ }}
986
+ className="w-full max-w-sm rounded-lg border border-fd-border bg-fd-card p-6 text-center"
987
+ >
988
+ <h1 className="text-lg font-semibold text-fd-foreground">Sign in</h1>
989
+ <p className="mt-1 text-sm text-fd-muted-foreground">
990
+ This knowledge base is private. Continue with Google to read it.
991
+ </p>
992
+ <button
993
+ type="submit"
994
+ 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"
995
+ >
996
+ Continue with Google
997
+ </button>
998
+ </form>
999
+ </main>
1000
+ );
1001
+ }
1002
+ `
1003
+ );
1004
+ write(
1005
+ "app/auth/error/page.tsx",
1006
+ `export default async function AuthErrorPage(props: {
1007
+ searchParams: Promise<{ error?: string }>;
1008
+ }) {
1009
+ const { error } = await props.searchParams;
1010
+ return (
1011
+ <main className="grid min-h-screen place-items-center p-6">
1012
+ <div className="w-full max-w-sm rounded-lg border border-fd-border bg-fd-card p-6 text-center">
1013
+ <h1 className="text-lg font-semibold text-fd-foreground">Can't sign in</h1>
1014
+ <p className="mt-1 text-sm text-fd-muted-foreground">
1015
+ {error === "AccessDenied"
1016
+ ? "That account isn't allowed to access this knowledge base."
1017
+ : "Something went wrong signing in. Try again."}
1018
+ </p>
1019
+ <a
1020
+ href="/auth/signin"
1021
+ 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"
1022
+ >
1023
+ Back to sign in
1024
+ </a>
1025
+ </div>
1026
+ </main>
1027
+ );
1028
+ }
1029
+ `
1030
+ );
1031
+ }
1032
+ function writePersonalContent(write, config) {
1033
+ write(
1034
+ "content/docs/meta.json",
1035
+ `${JSON.stringify(
1036
+ {
1037
+ title: config.name,
1038
+ root: true,
1039
+ pages: ["index", "beliefs", "working-style", "pr-comments", "voice", "stories"]
1040
+ },
1041
+ null,
1042
+ 2
1043
+ )}
1044
+ `
1045
+ );
1046
+ write(
1047
+ "content/docs/index.mdx",
1048
+ `---
1049
+ title: About me
1050
+ description: Who I am, what I care about, and how to work with me.
1051
+ summary: Overview of this person \u2014 role, focus, what they optimize for, and how an agent should use this KB to act on their behalf.
1052
+ tags: [overview, about, profile]
1053
+ ---
1054
+
1055
+ <PageHero
1056
+ kicker="Personal KB"
1057
+ title="About me"
1058
+ description="A private, queryable replica of how I think, work, and write. Humans read this; my agents read the same content over MCP and act the way I would."
1059
+ />
1060
+
1061
+ I'm a placeholder. Replace this with a short, honest description of who you are \u2014 your role, what
1062
+ you build, and the through-line in your work. Keep it concrete: an agent should be able to read
1063
+ this and understand what you'd care about in a decision.
1064
+
1065
+ <KeyFacts
1066
+ items={[
1067
+ { label: "Role", value: "What you do, in five words or fewer" },
1068
+ { label: "Focus", value: "The problem you spend most days on" },
1069
+ { label: "Optimizes for", value: "Speed, correctness, clarity \u2014 pick yours" },
1070
+ { label: "Time zone", value: "UTC+0" },
1071
+ { label: "Best reached", value: "Async, in writing" },
1072
+ { label: "Decides by", value: "Evidence over opinion" },
1073
+ ]}
1074
+ />
1075
+
1076
+ <EntityCard
1077
+ type="person"
1078
+ slug="me"
1079
+ title="Your Name"
1080
+ summary="One line that captures how you'd want an agent to introduce you."
1081
+ icon="user"
1082
+ fields={[
1083
+ { key: "Discipline", value: "Engineering" },
1084
+ { key: "Superpower", value: "Turning ambiguity into a plan" },
1085
+ { key: "Allergic to", value: "Meetings that should have been a doc" },
1086
+ ]}
1087
+ refs={[
1088
+ { rel: "see", target: "/docs/working-style", label: "How I work" },
1089
+ { rel: "see", target: "/docs/voice", label: "How I write" },
1090
+ ]}
1091
+ />
1092
+
1093
+ ## How to use this KB
1094
+
1095
+ Ask it how I'd think about something before you ask me. The pages below are the source of truth
1096
+ for my beliefs, working style, review bar, voice, and the stories that shaped them.
1097
+ `
1098
+ );
1099
+ write(
1100
+ "content/docs/beliefs.mdx",
1101
+ `---
1102
+ title: Beliefs & takes
1103
+ description: The positions I hold and the principles I keep coming back to.
1104
+ summary: This person's strongly-held beliefs and operating principles, each phrased as a position an agent can apply when reasoning on their behalf.
1105
+ tags: [beliefs, principles, takes]
1106
+ ---
1107
+
1108
+ <SectionHead
1109
+ eyebrow="What I believe"
1110
+ title="Beliefs & takes"
1111
+ description="Strongly held, loosely coupled. Each is a position you can act on, not a vibe."
1112
+ />
1113
+
1114
+ These are placeholders \u2014 rewrite them as your own. Phrase each as a clear position so an agent can
1115
+ reason from it.
1116
+
1117
+ <KeyFacts
1118
+ items={[
1119
+ { label: "On shipping", value: "Small and reversible beats big and perfect." },
1120
+ { label: "On code", value: "Delete more than you add." },
1121
+ { label: "On process", value: "Process is scar tissue \u2014 keep only what earned its place." },
1122
+ { label: "On disagreement", value: "Disagree in the doc, commit in the room." },
1123
+ { label: "On estimates", value: "Confidence intervals, not single numbers." },
1124
+ { label: "On tools", value: "Boring tech for load-bearing things." },
1125
+ ]}
1126
+ />
1127
+
1128
+ ## A take I'll defend
1129
+
1130
+ <Decision
1131
+ title="Prefer clarity over cleverness"
1132
+ status="accepted"
1133
+ identifier="TAKE-01"
1134
+ context={<>Clever code feels good to write and is expensive to read. Most code is read far more than it is written.</>}
1135
+ decision={<>Optimize for the next person (often future me). If a reviewer has to ask what it does, it isn't done.</>}
1136
+ consequences={[
1137
+ "Fewer abstractions until they pay rent.",
1138
+ "Comments explain why, names explain what.",
1139
+ "I'll trade a few keystrokes for a faster read every time.",
1140
+ ]}
1141
+ />
1142
+ `
1143
+ );
1144
+ write(
1145
+ "content/docs/working-style.mdx",
1146
+ `---
1147
+ title: Working style
1148
+ description: How I plan, decide, focus, and collaborate.
1149
+ summary: How this person works day to day \u2014 how they plan, make decisions, manage focus, and collaborate. Use this to predict how they'd approach a task.
1150
+ tags: [working-style, how-to, collaboration]
1151
+ ---
1152
+
1153
+ <SectionHead
1154
+ eyebrow="How I work"
1155
+ title="Working style"
1156
+ description="If you handed me a task, this is the shape of what I'd do with it."
1157
+ />
1158
+
1159
+ Placeholder content \u2014 make it yours. Be specific enough that an agent could run a task the way you
1160
+ would.
1161
+
1162
+ <FeatureList
1163
+ items={[
1164
+ { icon: "target", title: "Start from the outcome", description: "I write the goal and the done-condition before touching the work." },
1165
+ { icon: "split", title: "Decompose, then sequence", description: "Break into reversible steps; do the riskiest cheap thing first." },
1166
+ { icon: "message-square", title: "Default to writing", description: "A short doc beats a meeting. Decisions live in text, not memory." },
1167
+ { icon: "gauge", title: "Protect deep work", description: "Mornings are for the hard thing. Coordination batches in the afternoon." },
1168
+ ]}
1169
+ />
1170
+
1171
+ ## How I decide
1172
+
1173
+ <Decision
1174
+ title="Two-way doors don't need a meeting"
1175
+ status="accepted"
1176
+ identifier="STYLE-01"
1177
+ context={<>Many decisions are easily reversible. Treating them as if they aren't is the real cost.</>}
1178
+ decision={<>For reversible calls, I pick quickly and move; for one-way doors, I slow down and write the trade-offs out.</>}
1179
+ consequences={[
1180
+ "Speed on the 80% that's reversible.",
1181
+ "Care on the 20% that isn't.",
1182
+ ]}
1183
+ />
1184
+
1185
+ ## A day, roughly
1186
+
1187
+ <Schedule
1188
+ label="Typical working day"
1189
+ events={[
1190
+ { date: "Weekday", time: "09:00", title: "Deep work", body: "The hardest task of the day, no notifications." },
1191
+ { date: "Weekday", time: "12:30", title: "Reviews & async", body: "PRs, comments, written replies." },
1192
+ { date: "Weekday", time: "15:00", title: "Collaboration", body: "Pairing, calls, unblock others." },
1193
+ { date: "Weekday", time: "17:00", title: "Wind-down", body: "Plan tomorrow's first task." },
1194
+ ]}
1195
+ />
1196
+ `
1197
+ );
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
+ ---
1206
+
1207
+ <SectionHead
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
+ />
1212
+
1213
+ ## My review bar
1214
+
1215
+ <Checklist
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
+ />
1226
+
1227
+ ## How I phrase comments
1228
+
1229
+ I prefix to signal weight: **blocking:** must change, **suggestion:** take it or leave it,
1230
+ **nit:** ignore freely, **question:** I genuinely don't know. Examples:
1231
+
1232
+ <Example title="A blocking comment">
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>
1236
+
1237
+ <Example title="A suggestion">
1238
+ **suggestion:** This loop reads cleanly, but \`items.flatMap\` would say the same thing in one line.
1239
+ Your call \u2014 not blocking.
1240
+ </Example>
1241
+
1242
+ <Example title="A nit">
1243
+ **nit:** tiny \u2014 can we name this \`pendingCount\` so it matches the others? Ignore if you disagree.
1244
+ </Example>
1245
+
1246
+ <Tip title="Tone">
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>
1250
+ `
1251
+ );
1252
+ write(
1253
+ "content/docs/voice.mdx",
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
1280
+
1281
+ <Comparison
1282
+ caption="My writing defaults"
1283
+ options={["Do", "Don't"]}
1284
+ rows={[
1285
+ { criterion: "Openers", cells: ["Get to the point in the first line", "Open with filler pleasantries"] },
1286
+ { criterion: "Hedging", cells: ["Say what I think", "It might possibly be worth considering"] },
1287
+ { criterion: "Structure", cells: ["Lead with the answer, then the why", "Bury the ask at the end"] },
1288
+ { criterion: "Emoji", cells: ["Rarely, and never in serious writing", "Decorate every line"] },
1289
+ ]}
1290
+ />
1291
+
1292
+ <Example title="A message in my voice">
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>
1297
+ `
1298
+ );
1299
+ write(
1300
+ "content/docs/stories.mdx",
1301
+ `---
1302
+ title: Stories
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
+ ---
1307
+
1308
+ <SectionHead
1309
+ eyebrow="Where it comes from"
1310
+ title="Stories"
1311
+ description="The moments behind the takes. Replace these with your own \u2014 dates can be approximate."
1312
+ />
1313
+
1314
+ An agent that knows *why* you believe something reasons better than one that only knows *what*.
1315
+ These are placeholders.
1316
+
1317
+ <Timeline
1318
+ label="Formative moments"
1319
+ items={[
1320
+ {
1321
+ date: "2016",
1322
+ title: "The outage that taught me to write things down",
1323
+ 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.",
1324
+ status: "done",
1325
+ tags: ["process", "writing"],
1326
+ },
1327
+ {
1328
+ date: "2019",
1329
+ title: "Shipped small for the first time",
1330
+ body: "Replaced a six-month rewrite with weekly reversible changes. It landed. I stopped believing in big-bang.",
1331
+ status: "done",
1332
+ tags: ["shipping"],
1333
+ },
1334
+ {
1335
+ date: "2022",
1336
+ title: "A review that changed how I review",
1337
+ body: "Someone critiqued my code without making me feel small. I've tried to give every review that way since.",
1338
+ status: "done",
1339
+ tags: ["code-review", "tone"],
1340
+ },
1341
+ ]}
1342
+ />
890
1343
  `
891
1344
  );
892
1345
  }
@@ -915,8 +1368,8 @@ async function initCommand(dir, flags) {
915
1368
  }
916
1369
  if (!name) bail("A name is required (pass --name, a [dir] argument, or run interactively).");
917
1370
  let type = flags.type;
918
- if (type && type !== "company-kb" && type !== "product-docs") {
919
- bail(`Invalid --type "${type}". Use "company-kb" or "product-docs".`);
1371
+ if (type && type !== "company-kb" && type !== "product-docs" && type !== "personal-kb") {
1372
+ bail(`Invalid --type "${type}". Use "company-kb", "product-docs", or "personal-kb".`);
920
1373
  }
921
1374
  if (!type && interactive) {
922
1375
  const answer = await select({
@@ -931,6 +1384,11 @@ async function initCommand(dir, flags) {
931
1384
  value: "product-docs",
932
1385
  label: "Product docs",
933
1386
  hint: "public-facing documentation"
1387
+ },
1388
+ {
1389
+ value: "personal-kb",
1390
+ label: "Personal KB",
1391
+ hint: "a private, queryable replica of how you think, work, and write"
934
1392
  }
935
1393
  ]
936
1394
  });
@@ -938,6 +1396,7 @@ async function initCommand(dir, flags) {
938
1396
  type = answer;
939
1397
  }
940
1398
  if (!type) bail("A type is required (pass --type or run interactively).");
1399
+ const wantsGate = type === "company-kb" || type === "personal-kb";
941
1400
  let authEnabled;
942
1401
  if (flags.auth !== void 0) {
943
1402
  authEnabled = flags.auth;
@@ -946,12 +1405,12 @@ async function initCommand(dir, flags) {
946
1405
  } else if (interactive) {
947
1406
  const answer = await confirm({
948
1407
  message: "Gate the site behind Google SSO (auth)?",
949
- initialValue: type === "company-kb"
1408
+ initialValue: wantsGate
950
1409
  });
951
1410
  if (isCancel(answer)) bail("Cancelled.");
952
1411
  authEnabled = answer;
953
1412
  } else {
954
- authEnabled = type === "company-kb";
1413
+ authEnabled = wantsGate;
955
1414
  }
956
1415
  let allowedDomain = flags.allowedDomain?.trim();
957
1416
  if (authEnabled && !allowedDomain && interactive) {
@@ -993,10 +1452,11 @@ ${result.issues.map((i) => ` - ${i.path} ${i.message}`).join("\n")}`
993
1452
  }
994
1453
  const { root, source } = scaffold({ dir: targetDir, config });
995
1454
  outro(`${bold("Scaffolded")} ${config.name} ${dim(`(${source})`)}`);
996
- if (config.type === "company-kb" && !config.auth?.enabled) {
1455
+ if ((config.type === "company-kb" || config.type === "personal-kb") && !config.auth?.enabled) {
1456
+ const kind = config.type === "personal-kb" ? "personal KB" : "company KB";
997
1457
  log.blank();
998
1458
  log.warn(
999
- `This is a ${bold("company KB")} but auth is ${bold("not enabled")}. A company KB should gate access before you deploy \u2014 re-run with ${cyan("--auth")} or set ${cyan('"auth": { "enabled": true }')} in superlore.json.`
1459
+ `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.`
1000
1460
  );
1001
1461
  }
1002
1462
  printNextSteps(root, config);
@@ -1007,13 +1467,17 @@ async function maybeConnectEditor(flags, interactive) {
1007
1467
  const editors = detectEditors();
1008
1468
  if (editors.length === 0) {
1009
1469
  log.blank();
1010
- log.info(`${dim("Editor preview:")} install VS Code, Cursor, or Windsurf, then ${cyan("superlore connect")}.`);
1470
+ log.info(
1471
+ `${dim("Editor preview:")} install VS Code, Cursor, or Windsurf, then ${cyan("superlore connect")}.`
1472
+ );
1011
1473
  return;
1012
1474
  }
1013
1475
  const names = editors.map((e) => e.label).join(", ");
1014
1476
  if (flags.connect !== true && !interactive) {
1015
1477
  log.blank();
1016
- log.info(`${dim(`Detected ${names}.`)} Run ${cyan("superlore connect")} to install the live-preview extension.`);
1478
+ log.info(
1479
+ `${dim(`Detected ${names}.`)} Run ${cyan("superlore connect")} to install the live-preview extension.`
1480
+ );
1017
1481
  return;
1018
1482
  }
1019
1483
  if (flags.connect !== true) {
@@ -1044,10 +1508,10 @@ function printNextSteps(root, config) {
1044
1508
  }
1045
1509
 
1046
1510
  // src/index.ts
1047
- var VERSION = "0.2.0";
1511
+ var VERSION = "0.3.0";
1048
1512
  function buildCli(argv = process3.argv) {
1049
1513
  const cli = cac("superlore");
1050
- cli.command("init [dir]", "Scaffold a new superlore knowledge base").option("--name <name>", "KB name").option("--type <type>", "KB type: company-kb | product-docs").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").action(
1514
+ 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(
1051
1515
  async (dir, flags) => {
1052
1516
  const authExplicit = argv.includes("--auth");
1053
1517
  const noAuthExplicit = argv.includes("--no-auth");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "superlore-cli",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "The superlore CLI — scaffold, run, and build an agent-native knowledge base. One corpus. Humans and agents.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Krishnan S G",