superlore-cli 0.2.0 → 0.3.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/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.1";
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
@@ -27,10 +27,10 @@ function banner() {
27
27
  }
28
28
  const seam = dim("\u258F");
29
29
  const lines = [
30
- ` ${accent("\u2597\u259F\u2588")}${accentSoft("\u2599\u2596")}`,
31
- ` ${accent("\u2590\u2588")}${seam}${accentSoft("\u2592\u258C")} ${wordmark("superlore")}`,
32
- ` ${accent("\u2590\u2588")}${seam}${accentSoft("\u2592\u258C")} ${dim("the company knowledge base your agents run on")}`,
33
- ` ${accent("\u259D\u259C\u2588")}${accentSoft("\u259B\u2598")}`
30
+ ` ${accent("\u2597\u2584")}${seam}${accentSoft("\u2584\u2596")}`,
31
+ ` ${accent("\u2588\u2588")}${seam}${accentSoft("\u2588\u2588")} ${wordmark("superlore")}`,
32
+ ` ${accent("\u2588\u2588")}${seam}${accentSoft("\u2588\u2588")} ${dim("the company knowledge base your agents run on")}`,
33
+ ` ${accent("\u259D\u2580")}${seam}${accentSoft("\u2580\u2598")}`
34
34
  ];
35
35
  process.stdout.write(`
36
36
  ${lines.join("\n")}
@@ -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
  }
@@ -209,14 +213,15 @@ function detectPackageManager(root) {
209
213
  function isInstalled(root) {
210
214
  return existsSync(join(root, "node_modules"));
211
215
  }
212
- function runScript(root, script, extraArgs = []) {
216
+ function runScript(root, script, extraArgs = [], env) {
213
217
  const pm = detectPackageManager(root);
214
218
  const args = ["run", script, ...extraArgs.length ? ["--", ...extraArgs] : []];
215
219
  return new Promise((resolvePromise, reject) => {
216
220
  const child = spawn(pm, args, {
217
221
  cwd: root,
218
222
  stdio: "inherit",
219
- shell: process.platform === "win32"
223
+ shell: process.platform === "win32",
224
+ env: env ? { ...process.env, ...env } : process.env
220
225
  });
221
226
  child.on("error", reject);
222
227
  child.on("close", (code) => resolvePromise(code ?? 0));
@@ -413,7 +418,9 @@ function report(result) {
413
418
  log.success(`${bold(result.editor.label)} ${dim("\u2014 extension installed.")}`);
414
419
  break;
415
420
  case "already-installed":
416
- log.info(`${accent("\u203A")} ${bold(result.editor.label)} ${dim("\u2014 already installed, up to date.")}`);
421
+ log.info(
422
+ `${accent("\u203A")} ${bold(result.editor.label)} ${dim("\u2014 already installed, up to date.")}`
423
+ );
417
424
  break;
418
425
  case "failed":
419
426
  log.error(`${bold(result.editor.label)} ${dim("\u2014 install failed:")} ${result.error}`);
@@ -433,12 +440,15 @@ function printManualInstall() {
433
440
  function printMcpNextStep() {
434
441
  log.blank();
435
442
  log.info(bold("Next: connect the MCP"));
443
+ log.info(` ${dim("superlore's docs + help, in your agent \u2014 always current:")}`);
436
444
  log.info(
437
- ` ${dim("Let your agent read the same corpus. Ask Claude")} ${cyan('"connect my superlore MCP"')}${dim(",")}`
445
+ ` ${cyan("claude mcp add --transport http -s user superlore-docs https://superlore.vercel.app/api/mcp")}`
438
446
  );
447
+ log.blank();
439
448
  log.info(
440
- ` ${dim("or register it yourself:")} ${cyan("claude mcp add --transport http -s user superlore <url>/api/mcp")}`
449
+ ` ${dim("And your own KB once it's live (or ask Claude")} ${cyan('"connect my superlore MCP"')}${dim("):")}`
441
450
  );
451
+ log.info(` ${cyan("claude mcp add --transport http -s user my-kb <your-kb-url>/api/mcp")}`);
442
452
  }
443
453
 
444
454
  // src/commands/deploy.ts
@@ -514,8 +524,7 @@ async function devCommand(flags) {
514
524
  );
515
525
  }
516
526
  log.blank();
517
- const args = flags.port ? ["--port", String(flags.port)] : [];
518
- const code = await runScript(project.root, "dev", args);
527
+ const code = await runScript(project.root, "dev", [], { PORT: String(port) });
519
528
  process.exit(code);
520
529
  }
521
530
 
@@ -568,6 +577,7 @@ function scaffold(options) {
568
577
  function writeSkeleton(root, config) {
569
578
  const accent2 = config.accent ?? SUPERLORE_VIOLET;
570
579
  const mcpEnabled = config.mcp?.enabled ?? true;
580
+ const authEnabled = config.auth?.enabled ?? false;
571
581
  const slug = config.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "superlore-kb";
572
582
  const write = (rel, body) => {
573
583
  const file = join3(root, rel);
@@ -592,9 +602,14 @@ function writeSkeleton(root, config) {
592
602
  "fumadocs-core": "16.8.2",
593
603
  "fumadocs-mdx": "14.3.1",
594
604
  "fumadocs-ui": "16.8.2",
595
- superlore: "^0.1.0",
605
+ superlore: "^0.5.1",
596
606
  "lucide-react": "^1.21.0",
607
+ // superlore peers the rendered components pull in: Mermaid (Diagram), themes.
608
+ mermaid: "^11.15.0",
597
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" } : {},
598
613
  next: "16.2.4",
599
614
  "next-themes": "^0.4.6",
600
615
  react: "^19.2.5",
@@ -633,7 +648,7 @@ function writeSkeleton(root, config) {
633
648
  isolatedModules: true,
634
649
  skipLibCheck: true,
635
650
  incremental: true,
636
- paths: { "@/*": ["./*"] },
651
+ paths: { "@/*": ["./*"], "collections/*": ["./.source/*"] },
637
652
  plugins: [{ name: "next" }]
638
653
  },
639
654
  include: ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
@@ -653,8 +668,7 @@ const withMDX = createMDX();
653
668
  /** @type {import('next').NextConfig} */
654
669
  const config = {
655
670
  reactStrictMode: true,
656
- // \`superlore\` ships source (.ts/.tsx) for frictionless consumption \u2014 Next transpiles it.
657
- transpilePackages: ["superlore"],
671
+ // superlore ships compiled ESM \u2014 consume it as a normal package (no transpilePackages).
658
672
  };
659
673
 
660
674
  export default withMDX(config);
@@ -698,7 +712,7 @@ export default config;
698
712
  write(
699
713
  "app/layout.tsx",
700
714
  `import type { ReactNode } from "react";
701
- import { RootProvider } from "fumadocs-ui/provider";
715
+ import { RootProvider } from "superlore/ui";
702
716
  import "./global.css";
703
717
 
704
718
  export default function RootLayout({ children }: { children: ReactNode }) {
@@ -772,18 +786,22 @@ export function generateStaticParams() {
772
786
  );
773
787
  write(
774
788
  "lib/source.ts",
775
- `import { loader } from "fumadocs-core/source";
776
- import { docs } from "@/.source";
789
+ `import { docs } from "collections/server";
790
+ import { loader, lucideIconsPlugin } from "superlore/source";
777
791
 
778
792
  export const source = loader({
779
793
  baseUrl: "/docs",
780
794
  source: docs.toFumadocsSource(),
795
+ plugins: [lucideIconsPlugin()],
781
796
  });
782
797
  `
783
798
  );
784
- write(
785
- "content/docs/index.mdx",
786
- `---
799
+ if (config.type === "personal-kb") {
800
+ writePersonalContent(write, config);
801
+ } else {
802
+ write(
803
+ "content/docs/index.mdx",
804
+ `---
787
805
  title: Welcome
788
806
  description: The home of your superlore knowledge base.
789
807
  summary: Landing page for the ${config.name} knowledge base.
@@ -797,7 +815,11 @@ agents read the same structured content over MCP.
797
815
 
798
816
  Edit \`content/docs/index.mdx\` to make it yours, then run \`superlore dev\`.
799
817
  `
800
- );
818
+ );
819
+ }
820
+ if (authEnabled) {
821
+ writeAuth(write, config);
822
+ }
801
823
  if (mcpEnabled) {
802
824
  const mcpPath = config.mcp?.path ?? "/api/mcp";
803
825
  write(
@@ -805,10 +827,14 @@ Edit \`content/docs/index.mdx\` to make it yours, then run \`superlore dev\`.
805
827
  `import { createMcpHandler } from "mcp-handler";
806
828
  import { z } from "zod";
807
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";
808
833
 
809
834
  // 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\`.
835
+ // exposed to agents. The index is built straight from your content \`source\`: author once, and
836
+ // humans read the pages while agents query this corpus. No scraping, no drift.
837
+ const index = buildIndexFromSource(source);
812
838
 
813
839
  const json = (data: unknown) => ({
814
840
  content: [{ type: "text" as const, text: JSON.stringify(data, null, 2) }],
@@ -820,31 +846,32 @@ const handler = createMcpHandler(
820
846
  "search",
821
847
  "Full-text search across the knowledge base.",
822
848
  { query: z.string(), limit: z.number().int().positive().optional() },
823
- async ({ query, limit }) => json(search(/* index */ {} as never, query, limit)),
849
+ async ({ query, limit }) => json(search(index, query, limit)),
824
850
  );
825
851
  server.tool(
826
852
  "get_page",
827
853
  "Get a page's full structured content by path.",
828
854
  { path: z.string() },
829
- async ({ path }) => json(getPage(/* index */ {} as never, path)),
855
+ async ({ path }) => json(getPage(index, path)),
830
856
  );
831
857
  server.tool(
832
858
  "list",
833
859
  "List knowledge nodes, filtered by kind / tag / entityType.",
834
860
  { kind: z.string().optional(), tag: z.string().optional(), entityType: z.string().optional() },
835
- async (args) => json(list(/* index */ {} as never, args)),
861
+ async ({ kind, tag, entityType }) =>
862
+ json(list(index, { kind: kind as KKind | undefined, tag, entityType })),
836
863
  );
837
864
  server.tool(
838
865
  "navigate",
839
866
  "Follow relations from a page path / node id / entity ref.",
840
867
  { target: z.string() },
841
- async ({ target }) => json(navigate(/* index */ {} as never, target)),
868
+ async ({ target }) => json(navigate(index, target)),
842
869
  );
843
870
  server.tool(
844
871
  "get_component_data",
845
872
  "Get the structured data behind a rendered component (its knowledge face).",
846
873
  { id: z.string() },
847
- async ({ id }) => json(getComponentData(/* index */ {} as never, id)),
874
+ async ({ id }) => json(getComponentData(index, id)),
848
875
  );
849
876
  },
850
877
  {},
@@ -865,6 +892,27 @@ out
865
892
  .env*.local
866
893
  `
867
894
  );
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.` : "";
868
916
  write(
869
917
  "README.md",
870
918
  `# ${config.name}
@@ -886,7 +934,417 @@ superlore build
886
934
 
887
935
  Config lives in \`superlore.json\`. Author content in \`content/docs/\`.${mcpEnabled ? `
888
936
 
889
- The MCP endpoint is served at \`${config.mcp?.path ?? "/api/mcp"}\`.` : ""}
937
+ The MCP endpoint is served at \`${config.mcp?.path ?? "/api/mcp"}\`.` : ""}${authReadme}
938
+ `
939
+ );
940
+ }
941
+ function writeAuth(write, config) {
942
+ const allowedDomain = config.auth?.allowedDomain;
943
+ write(
944
+ "auth.ts",
945
+ `import { createSuperloreAuth } from "superlore/auth";
946
+
947
+ // Auth.js v5 + Google SSO. Allowlists can come from env (AUTH_ALLOWED_DOMAIN / AUTH_ALLOWED_EMAILS)
948
+ // or be passed explicitly here. Off until AUTH_GOOGLE_ID is set, so local dev needs no config.
949
+ export const { handlers, auth, signIn, signOut } = createSuperloreAuth(${allowedDomain ? `{
950
+ allowedDomain: ${JSON.stringify(allowedDomain)},
951
+ }` : "{}"});
952
+ `
953
+ );
954
+ write(
955
+ "app/api/auth/[...nextauth]/route.ts",
956
+ `import { handlers } from "@/auth";
957
+
958
+ export const { GET, POST } = handlers;
959
+ `
960
+ );
961
+ write(
962
+ "proxy.ts",
963
+ `import { auth } from "@/auth";
964
+ import { createAuthProxy } from "superlore/auth";
965
+
966
+ // Next.js 16 middleware lives in proxy.ts. The gate is self-disabling: with no AUTH_GOOGLE_ID
967
+ // (or LOCAL=true) every request passes through, so local dev and public deploys stay open.
968
+ export default createAuthProxy(auth);
969
+
970
+ export const config = {
971
+ // Run on everything except static assets (the helper also skips the auth dance + icons).
972
+ matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"],
973
+ };
974
+ `
975
+ );
976
+ write(
977
+ "app/auth/signin/page.tsx",
978
+ `import { signIn } from "@/auth";
979
+
980
+ export default async function SignInPage(props: {
981
+ searchParams: Promise<{ callbackUrl?: string }>;
982
+ }) {
983
+ const { callbackUrl } = await props.searchParams;
984
+ return (
985
+ <main className="grid min-h-screen place-items-center p-6">
986
+ <form
987
+ action={async () => {
988
+ "use server";
989
+ await signIn("google", { redirectTo: callbackUrl ?? "/" });
990
+ }}
991
+ className="w-full max-w-sm rounded-lg border border-fd-border bg-fd-card p-6 text-center"
992
+ >
993
+ <h1 className="text-lg font-semibold text-fd-foreground">Sign in</h1>
994
+ <p className="mt-1 text-sm text-fd-muted-foreground">
995
+ This knowledge base is private. Continue with Google to read it.
996
+ </p>
997
+ <button
998
+ type="submit"
999
+ 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"
1000
+ >
1001
+ Continue with Google
1002
+ </button>
1003
+ </form>
1004
+ </main>
1005
+ );
1006
+ }
1007
+ `
1008
+ );
1009
+ write(
1010
+ "app/auth/error/page.tsx",
1011
+ `export default async function AuthErrorPage(props: {
1012
+ searchParams: Promise<{ error?: string }>;
1013
+ }) {
1014
+ const { error } = await props.searchParams;
1015
+ return (
1016
+ <main className="grid min-h-screen place-items-center p-6">
1017
+ <div className="w-full max-w-sm rounded-lg border border-fd-border bg-fd-card p-6 text-center">
1018
+ <h1 className="text-lg font-semibold text-fd-foreground">Can't sign in</h1>
1019
+ <p className="mt-1 text-sm text-fd-muted-foreground">
1020
+ {error === "AccessDenied"
1021
+ ? "That account isn't allowed to access this knowledge base."
1022
+ : "Something went wrong signing in. Try again."}
1023
+ </p>
1024
+ <a
1025
+ href="/auth/signin"
1026
+ 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"
1027
+ >
1028
+ Back to sign in
1029
+ </a>
1030
+ </div>
1031
+ </main>
1032
+ );
1033
+ }
1034
+ `
1035
+ );
1036
+ }
1037
+ function writePersonalContent(write, config) {
1038
+ write(
1039
+ "content/docs/meta.json",
1040
+ `${JSON.stringify(
1041
+ {
1042
+ title: config.name,
1043
+ root: true,
1044
+ pages: ["index", "beliefs", "working-style", "pr-comments", "voice", "stories"]
1045
+ },
1046
+ null,
1047
+ 2
1048
+ )}
1049
+ `
1050
+ );
1051
+ write(
1052
+ "content/docs/index.mdx",
1053
+ `---
1054
+ title: About me
1055
+ description: Who I am, what I care about, and how to work with me.
1056
+ 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.
1057
+ tags: [overview, about, profile]
1058
+ ---
1059
+
1060
+ <PageHero
1061
+ kicker="Personal KB"
1062
+ title="About me"
1063
+ 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."
1064
+ />
1065
+
1066
+ I'm a placeholder. Replace this with a short, honest description of who you are \u2014 your role, what
1067
+ you build, and the through-line in your work. Keep it concrete: an agent should be able to read
1068
+ this and understand what you'd care about in a decision.
1069
+
1070
+ <KeyFacts
1071
+ items={[
1072
+ { label: "Role", value: "What you do, in five words or fewer" },
1073
+ { label: "Focus", value: "The problem you spend most days on" },
1074
+ { label: "Optimizes for", value: "Speed, correctness, clarity \u2014 pick yours" },
1075
+ { label: "Time zone", value: "UTC+0" },
1076
+ { label: "Best reached", value: "Async, in writing" },
1077
+ { label: "Decides by", value: "Evidence over opinion" },
1078
+ ]}
1079
+ />
1080
+
1081
+ <EntityCard
1082
+ type="person"
1083
+ slug="me"
1084
+ title="Your Name"
1085
+ summary="One line that captures how you'd want an agent to introduce you."
1086
+ icon="user"
1087
+ fields={[
1088
+ { key: "Discipline", value: "Engineering" },
1089
+ { key: "Superpower", value: "Turning ambiguity into a plan" },
1090
+ { key: "Allergic to", value: "Meetings that should have been a doc" },
1091
+ ]}
1092
+ refs={[
1093
+ { rel: "see", target: "/docs/working-style", label: "How I work" },
1094
+ { rel: "see", target: "/docs/voice", label: "How I write" },
1095
+ ]}
1096
+ />
1097
+
1098
+ ## How to use this KB
1099
+
1100
+ Ask it how I'd think about something before you ask me. The pages below are the source of truth
1101
+ for my beliefs, working style, review bar, voice, and the stories that shaped them.
1102
+ `
1103
+ );
1104
+ write(
1105
+ "content/docs/beliefs.mdx",
1106
+ `---
1107
+ title: Beliefs & takes
1108
+ description: The positions I hold and the principles I keep coming back to.
1109
+ summary: This person's strongly-held beliefs and operating principles, each phrased as a position an agent can apply when reasoning on their behalf.
1110
+ tags: [beliefs, principles, takes]
1111
+ ---
1112
+
1113
+ <SectionHead
1114
+ eyebrow="What I believe"
1115
+ title="Beliefs & takes"
1116
+ description="Strongly held, loosely coupled. Each is a position you can act on, not a vibe."
1117
+ />
1118
+
1119
+ These are placeholders \u2014 rewrite them as your own. Phrase each as a clear position so an agent can
1120
+ reason from it.
1121
+
1122
+ <KeyFacts
1123
+ items={[
1124
+ { label: "On shipping", value: "Small and reversible beats big and perfect." },
1125
+ { label: "On code", value: "Delete more than you add." },
1126
+ { label: "On process", value: "Process is scar tissue \u2014 keep only what earned its place." },
1127
+ { label: "On disagreement", value: "Disagree in the doc, commit in the room." },
1128
+ { label: "On estimates", value: "Confidence intervals, not single numbers." },
1129
+ { label: "On tools", value: "Boring tech for load-bearing things." },
1130
+ ]}
1131
+ />
1132
+
1133
+ ## A take I'll defend
1134
+
1135
+ <Decision
1136
+ title="Prefer clarity over cleverness"
1137
+ status="accepted"
1138
+ identifier="TAKE-01"
1139
+ context={<>Clever code feels good to write and is expensive to read. Most code is read far more than it is written.</>}
1140
+ decision={<>Optimize for the next person (often future me). If a reviewer has to ask what it does, it isn't done.</>}
1141
+ consequences={[
1142
+ "Fewer abstractions until they pay rent.",
1143
+ "Comments explain why, names explain what.",
1144
+ "I'll trade a few keystrokes for a faster read every time.",
1145
+ ]}
1146
+ />
1147
+ `
1148
+ );
1149
+ write(
1150
+ "content/docs/working-style.mdx",
1151
+ `---
1152
+ title: Working style
1153
+ description: How I plan, decide, focus, and collaborate.
1154
+ 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.
1155
+ tags: [working-style, how-to, collaboration]
1156
+ ---
1157
+
1158
+ <SectionHead
1159
+ eyebrow="How I work"
1160
+ title="Working style"
1161
+ description="If you handed me a task, this is the shape of what I'd do with it."
1162
+ />
1163
+
1164
+ Placeholder content \u2014 make it yours. Be specific enough that an agent could run a task the way you
1165
+ would.
1166
+
1167
+ <FeatureList
1168
+ items={[
1169
+ { icon: "target", title: "Start from the outcome", description: "I write the goal and the done-condition before touching the work." },
1170
+ { icon: "split", title: "Decompose, then sequence", description: "Break into reversible steps; do the riskiest cheap thing first." },
1171
+ { icon: "message-square", title: "Default to writing", description: "A short doc beats a meeting. Decisions live in text, not memory." },
1172
+ { icon: "gauge", title: "Protect deep work", description: "Mornings are for the hard thing. Coordination batches in the afternoon." },
1173
+ ]}
1174
+ />
1175
+
1176
+ ## How I decide
1177
+
1178
+ <Decision
1179
+ title="Two-way doors don't need a meeting"
1180
+ status="accepted"
1181
+ identifier="STYLE-01"
1182
+ context={<>Many decisions are easily reversible. Treating them as if they aren't is the real cost.</>}
1183
+ decision={<>For reversible calls, I pick quickly and move; for one-way doors, I slow down and write the trade-offs out.</>}
1184
+ consequences={[
1185
+ "Speed on the 80% that's reversible.",
1186
+ "Care on the 20% that isn't.",
1187
+ ]}
1188
+ />
1189
+
1190
+ ## A day, roughly
1191
+
1192
+ <Schedule
1193
+ label="Typical working day"
1194
+ events={[
1195
+ { date: "Weekday", time: "09:00", title: "Deep work", body: "The hardest task of the day, no notifications." },
1196
+ { date: "Weekday", time: "12:30", title: "Reviews & async", body: "PRs, comments, written replies." },
1197
+ { date: "Weekday", time: "15:00", title: "Collaboration", body: "Pairing, calls, unblock others." },
1198
+ { date: "Weekday", time: "17:00", title: "Wind-down", body: "Plan tomorrow's first task." },
1199
+ ]}
1200
+ />
1201
+ `
1202
+ );
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
+ ---
1211
+
1212
+ <SectionHead
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
+ />
1217
+
1218
+ ## My review bar
1219
+
1220
+ <Checklist
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
+ />
1231
+
1232
+ ## How I phrase comments
1233
+
1234
+ I prefix to signal weight: **blocking:** must change, **suggestion:** take it or leave it,
1235
+ **nit:** ignore freely, **question:** I genuinely don't know. Examples:
1236
+
1237
+ <Example title="A blocking comment">
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>
1241
+
1242
+ <Example title="A suggestion">
1243
+ **suggestion:** This loop reads cleanly, but \`items.flatMap\` would say the same thing in one line.
1244
+ Your call \u2014 not blocking.
1245
+ </Example>
1246
+
1247
+ <Example title="A nit">
1248
+ **nit:** tiny \u2014 can we name this \`pendingCount\` so it matches the others? Ignore if you disagree.
1249
+ </Example>
1250
+
1251
+ <Tip title="Tone">
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>
1255
+ `
1256
+ );
1257
+ write(
1258
+ "content/docs/voice.mdx",
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
1285
+
1286
+ <Comparison
1287
+ caption="My writing defaults"
1288
+ options={["Do", "Don't"]}
1289
+ rows={[
1290
+ { criterion: "Openers", cells: ["Get to the point in the first line", "Open with filler pleasantries"] },
1291
+ { criterion: "Hedging", cells: ["Say what I think", "It might possibly be worth considering"] },
1292
+ { criterion: "Structure", cells: ["Lead with the answer, then the why", "Bury the ask at the end"] },
1293
+ { criterion: "Emoji", cells: ["Rarely, and never in serious writing", "Decorate every line"] },
1294
+ ]}
1295
+ />
1296
+
1297
+ <Example title="A message in my voice">
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>
1302
+ `
1303
+ );
1304
+ write(
1305
+ "content/docs/stories.mdx",
1306
+ `---
1307
+ title: Stories
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
+ ---
1312
+
1313
+ <SectionHead
1314
+ eyebrow="Where it comes from"
1315
+ title="Stories"
1316
+ description="The moments behind the takes. Replace these with your own \u2014 dates can be approximate."
1317
+ />
1318
+
1319
+ An agent that knows *why* you believe something reasons better than one that only knows *what*.
1320
+ These are placeholders.
1321
+
1322
+ <Timeline
1323
+ label="Formative moments"
1324
+ items={[
1325
+ {
1326
+ date: "2016",
1327
+ title: "The outage that taught me to write things down",
1328
+ 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.",
1329
+ status: "done",
1330
+ tags: ["process", "writing"],
1331
+ },
1332
+ {
1333
+ date: "2019",
1334
+ title: "Shipped small for the first time",
1335
+ body: "Replaced a six-month rewrite with weekly reversible changes. It landed. I stopped believing in big-bang.",
1336
+ status: "done",
1337
+ tags: ["shipping"],
1338
+ },
1339
+ {
1340
+ date: "2022",
1341
+ title: "A review that changed how I review",
1342
+ body: "Someone critiqued my code without making me feel small. I've tried to give every review that way since.",
1343
+ status: "done",
1344
+ tags: ["code-review", "tone"],
1345
+ },
1346
+ ]}
1347
+ />
890
1348
  `
891
1349
  );
892
1350
  }
@@ -915,8 +1373,8 @@ async function initCommand(dir, flags) {
915
1373
  }
916
1374
  if (!name) bail("A name is required (pass --name, a [dir] argument, or run interactively).");
917
1375
  let type = flags.type;
918
- if (type && type !== "company-kb" && type !== "product-docs") {
919
- bail(`Invalid --type "${type}". Use "company-kb" or "product-docs".`);
1376
+ if (type && type !== "company-kb" && type !== "product-docs" && type !== "personal-kb") {
1377
+ bail(`Invalid --type "${type}". Use "company-kb", "product-docs", or "personal-kb".`);
920
1378
  }
921
1379
  if (!type && interactive) {
922
1380
  const answer = await select({
@@ -931,6 +1389,11 @@ async function initCommand(dir, flags) {
931
1389
  value: "product-docs",
932
1390
  label: "Product docs",
933
1391
  hint: "public-facing documentation"
1392
+ },
1393
+ {
1394
+ value: "personal-kb",
1395
+ label: "Personal KB",
1396
+ hint: "a private, queryable replica of how you think, work, and write"
934
1397
  }
935
1398
  ]
936
1399
  });
@@ -938,6 +1401,7 @@ async function initCommand(dir, flags) {
938
1401
  type = answer;
939
1402
  }
940
1403
  if (!type) bail("A type is required (pass --type or run interactively).");
1404
+ const wantsGate = type === "company-kb" || type === "personal-kb";
941
1405
  let authEnabled;
942
1406
  if (flags.auth !== void 0) {
943
1407
  authEnabled = flags.auth;
@@ -946,12 +1410,12 @@ async function initCommand(dir, flags) {
946
1410
  } else if (interactive) {
947
1411
  const answer = await confirm({
948
1412
  message: "Gate the site behind Google SSO (auth)?",
949
- initialValue: type === "company-kb"
1413
+ initialValue: wantsGate
950
1414
  });
951
1415
  if (isCancel(answer)) bail("Cancelled.");
952
1416
  authEnabled = answer;
953
1417
  } else {
954
- authEnabled = type === "company-kb";
1418
+ authEnabled = wantsGate;
955
1419
  }
956
1420
  let allowedDomain = flags.allowedDomain?.trim();
957
1421
  if (authEnabled && !allowedDomain && interactive) {
@@ -993,10 +1457,11 @@ ${result.issues.map((i) => ` - ${i.path} ${i.message}`).join("\n")}`
993
1457
  }
994
1458
  const { root, source } = scaffold({ dir: targetDir, config });
995
1459
  outro(`${bold("Scaffolded")} ${config.name} ${dim(`(${source})`)}`);
996
- if (config.type === "company-kb" && !config.auth?.enabled) {
1460
+ if ((config.type === "company-kb" || config.type === "personal-kb") && !config.auth?.enabled) {
1461
+ const kind = config.type === "personal-kb" ? "personal KB" : "company KB";
997
1462
  log.blank();
998
1463
  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.`
1464
+ `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
1465
  );
1001
1466
  }
1002
1467
  printNextSteps(root, config);
@@ -1007,13 +1472,17 @@ async function maybeConnectEditor(flags, interactive) {
1007
1472
  const editors = detectEditors();
1008
1473
  if (editors.length === 0) {
1009
1474
  log.blank();
1010
- log.info(`${dim("Editor preview:")} install VS Code, Cursor, or Windsurf, then ${cyan("superlore connect")}.`);
1475
+ log.info(
1476
+ `${dim("Editor preview:")} install VS Code, Cursor, or Windsurf, then ${cyan("superlore connect")}.`
1477
+ );
1011
1478
  return;
1012
1479
  }
1013
1480
  const names = editors.map((e) => e.label).join(", ");
1014
1481
  if (flags.connect !== true && !interactive) {
1015
1482
  log.blank();
1016
- log.info(`${dim(`Detected ${names}.`)} Run ${cyan("superlore connect")} to install the live-preview extension.`);
1483
+ log.info(
1484
+ `${dim(`Detected ${names}.`)} Run ${cyan("superlore connect")} to install the live-preview extension.`
1485
+ );
1017
1486
  return;
1018
1487
  }
1019
1488
  if (flags.connect !== true) {
@@ -1044,10 +1513,10 @@ function printNextSteps(root, config) {
1044
1513
  }
1045
1514
 
1046
1515
  // src/index.ts
1047
- var VERSION = "0.2.0";
1516
+ var VERSION = "0.3.1";
1048
1517
  function buildCli(argv = process3.argv) {
1049
1518
  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(
1519
+ 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
1520
  async (dir, flags) => {
1052
1521
  const authExplicit = argv.includes("--auth");
1053
1522
  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.1",
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",