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 +7 -3
- package/dist/config.js +5 -1
- package/dist/index.d.ts +8 -4
- package/dist/index.js +494 -30
- package/package.json +1 -1
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
|
-
/**
|
|
15
|
-
|
|
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
|
|
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 = [
|
|
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
|
-
/**
|
|
17
|
-
|
|
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
|
|
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.
|
|
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 = [
|
|
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
|
|
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
|
-
//
|
|
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 "
|
|
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 {
|
|
776
|
-
import {
|
|
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
|
-
|
|
785
|
-
|
|
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.
|
|
811
|
-
//
|
|
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(
|
|
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(
|
|
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 (
|
|
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(
|
|
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(
|
|
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"
|
|
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:
|
|
1408
|
+
initialValue: wantsGate
|
|
950
1409
|
});
|
|
951
1410
|
if (isCancel(answer)) bail("Cancelled.");
|
|
952
1411
|
authEnabled = answer;
|
|
953
1412
|
} else {
|
|
954
|
-
authEnabled =
|
|
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(
|
|
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(
|
|
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(
|
|
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.
|
|
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