superlore-cli 0.1.2 → 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/README.md CHANGED
@@ -40,11 +40,17 @@ curl -fsSL https://superlore.vercel.app/install.sh | sh # or: npm i -g superl
40
40
  ```
41
41
 
42
42
  ```bash
43
- superlore init my-kb # scaffold — 2 questions → superlore.json + starter pages
43
+ superlore init my-kb # scaffold — 2 questions → superlore.json + starter pages, then sets up your editor
44
+ superlore connect # detect VS Code / Cursor / Windsurf and install the live-preview extension
44
45
  superlore dev # live preview at localhost:3000
45
46
  superlore build # production build, deploy anywhere
46
47
  ```
47
48
 
49
+ `init` ends by offering to run `connect` for you, so one command takes you from nothing to a
50
+ scaffolded KB with the editor extension installed — then it points you at wiring the MCP to your
51
+ agent. `connect` detects each editor via its CLI or standard install path (macOS / Linux / Windows)
52
+ and is safe to re-run.
53
+
48
54
  `superlore deploy` is reserved for managed **superlore Cloud**
49
55
  ([waitlisted](https://superlore.vercel.app/cloud)) — self-host free with `superlore build`.
50
56
 
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.1.2";
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
@@ -2,7 +2,7 @@
2
2
 
3
3
  // src/index.ts
4
4
  import { fileURLToPath as fileURLToPath2 } from "url";
5
- import process2 from "process";
5
+ import process3 from "process";
6
6
  import { cac } from "cac";
7
7
 
8
8
  // src/lib/log.ts
@@ -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
  }
@@ -253,6 +257,194 @@ async function buildCommand() {
253
257
  process.exit(code);
254
258
  }
255
259
 
260
+ // src/lib/editors.ts
261
+ import { execFileSync } from "child_process";
262
+ import { existsSync as existsSync2, writeFileSync } from "fs";
263
+ import { homedir, tmpdir } from "os";
264
+ import { join as join2 } from "path";
265
+ import process2 from "process";
266
+ var EXTENSION_ID = "superlore.superlore-preview";
267
+ var DEFAULT_VSIX_URL = "https://superlore.vercel.app/superlore-preview.vsix";
268
+ async function downloadVsix(url = DEFAULT_VSIX_URL) {
269
+ const res = await fetch(url, { redirect: "follow" });
270
+ if (!res.ok) throw new Error(`couldn't fetch the extension (${res.status} ${res.statusText})`);
271
+ const bytes = Buffer.from(await res.arrayBuffer());
272
+ const path = join2(tmpdir(), "superlore-preview.vsix");
273
+ writeFileSync(path, bytes);
274
+ return path;
275
+ }
276
+ var EDITORS = [
277
+ { id: "vscode", label: "VS Code", bin: "code" },
278
+ { id: "cursor", label: "Cursor", bin: "cursor" },
279
+ { id: "windsurf", label: "Windsurf", bin: "windsurf" }
280
+ ];
281
+ function onPath(bin) {
282
+ const probe = process2.platform === "win32" ? "where" : "which";
283
+ try {
284
+ execFileSync(probe, [bin], { stdio: "ignore", shell: process2.platform === "win32" });
285
+ return true;
286
+ } catch {
287
+ return false;
288
+ }
289
+ }
290
+ function fallbackPaths(editor) {
291
+ const home = homedir();
292
+ if (process2.platform === "darwin") {
293
+ const app = {
294
+ vscode: "Visual Studio Code.app",
295
+ cursor: "Cursor.app",
296
+ windsurf: "Windsurf.app"
297
+ };
298
+ const rel = `Contents/Resources/app/bin/${editor.bin}`;
299
+ return [
300
+ join2("/Applications", app[editor.id], rel),
301
+ join2(home, "Applications", app[editor.id], rel)
302
+ ];
303
+ }
304
+ if (process2.platform === "win32") {
305
+ const local = process2.env.LOCALAPPDATA ?? join2(home, "AppData", "Local");
306
+ const programs = join2(local, "Programs");
307
+ const dir = {
308
+ vscode: "Microsoft VS Code",
309
+ cursor: "cursor",
310
+ windsurf: "Windsurf"
311
+ };
312
+ const exe = `bin\\${editor.bin}.cmd`;
313
+ return [
314
+ join2(programs, dir[editor.id], exe),
315
+ join2(process2.env.ProgramFiles ?? "C:\\Program Files", dir[editor.id], exe)
316
+ ];
317
+ }
318
+ return [
319
+ join2("/usr/share", editor.bin, "bin", editor.bin),
320
+ join2("/usr/bin", editor.bin),
321
+ join2("/snap/bin", editor.bin),
322
+ join2(home, ".local", "bin", editor.bin)
323
+ ];
324
+ }
325
+ function resolveEditorCommand(editor) {
326
+ if (onPath(editor.bin)) return editor.bin;
327
+ for (const candidate of fallbackPaths(editor)) {
328
+ if (existsSync2(candidate)) return candidate;
329
+ }
330
+ return void 0;
331
+ }
332
+ function detectEditors() {
333
+ const found = [];
334
+ for (const editor of EDITORS) {
335
+ const command = resolveEditorCommand(editor);
336
+ if (command) found.push({ ...editor, command });
337
+ }
338
+ return found;
339
+ }
340
+ var execRunner = (command, args) => execFileSync(command, [...args], {
341
+ encoding: "utf8",
342
+ stdio: ["ignore", "pipe", "pipe"],
343
+ // Editor CLIs on Windows are .cmd shims; a shell is needed to invoke them.
344
+ shell: process2.platform === "win32",
345
+ timeout: 6e4
346
+ }).trim();
347
+ function classifyInstallOutput(editor, output) {
348
+ return /already installed/i.test(output) ? { editor, status: "already-installed" } : { editor, status: "installed" };
349
+ }
350
+ function installInto(editor, options = {}) {
351
+ const run2 = options.run ?? execRunner;
352
+ const target = options.vsix ?? EXTENSION_ID;
353
+ try {
354
+ const output = run2(editor.command, ["--install-extension", target, "--force"]);
355
+ return classifyInstallOutput(editor, output);
356
+ } catch (error) {
357
+ return { editor, status: "failed", error: failureReason(error) };
358
+ }
359
+ }
360
+ function failureReason(error) {
361
+ if (error instanceof Error) {
362
+ const stderr = error.stderr;
363
+ const text2 = typeof stderr === "string" ? stderr : Buffer.isBuffer(stderr) ? stderr.toString() : "";
364
+ const lines = text2.split("\n").map((l) => l.trim()).filter((l) => l.length > 0 && !/^\(node:/.test(l) && !/^\(Use /.test(l));
365
+ const meaningful = lines.find((l) => /(not found|error|fail|denied)/i.test(l)) ?? lines[0];
366
+ if (meaningful) return meaningful;
367
+ return error.message.replace(/^Command failed:.*/s, "the editor CLI returned an error").trim();
368
+ }
369
+ return String(error);
370
+ }
371
+
372
+ // src/commands/connect.ts
373
+ async function connectCommand(flags = {}) {
374
+ log.blank();
375
+ log.info(`${accent("superlore connect")} ${dim("\u2014 set up your editor")}`);
376
+ log.blank();
377
+ const detected = detectEditors();
378
+ if (detected.length === 0) {
379
+ log.warn("No supported editor detected (looked for VS Code, Cursor, and Windsurf).");
380
+ log.blank();
381
+ log.info(`${dim("Install one, then re-run")} ${cyan("superlore connect")}${dim(".")}`);
382
+ log.info(
383
+ `${dim("If an editor is installed but its CLI isn't on PATH, open it and run")} ${cyan(`"Shell Command: Install '<editor>' command in PATH"`)}${dim(".")}`
384
+ );
385
+ printMcpNextStep();
386
+ process.exit(0);
387
+ }
388
+ const labels = detected.map((e) => bold(e.label)).join(", ");
389
+ log.step(`Found ${labels}. Installing the superlore Preview extension\u2026`);
390
+ log.blank();
391
+ let vsix;
392
+ try {
393
+ vsix = flags.vsix ?? await downloadVsix();
394
+ } catch (error) {
395
+ log.error(
396
+ `Couldn't fetch the extension: ${error instanceof Error ? error.message : String(error)}`
397
+ );
398
+ printManualInstall();
399
+ process.exit(flags.optional ? 0 : 1);
400
+ }
401
+ const results = detected.map((editor) => report(installInto(editor, { vsix })));
402
+ log.blank();
403
+ const failed = results.filter((r) => r.status === "failed");
404
+ if (failed.length > 0 && failed.length === results.length) {
405
+ log.error("Couldn't install the extension into any editor. See the errors above.");
406
+ printManualInstall();
407
+ process.exit(flags.optional ? 0 : 1);
408
+ }
409
+ log.success(
410
+ `superlore Preview is ready. Open a ${cyan(".mdx")} file and run ${cyan("superlore: Open Preview")} ${dim("(Cmd/Ctrl+K V)")}.`
411
+ );
412
+ printMcpNextStep();
413
+ }
414
+ function report(result) {
415
+ switch (result.status) {
416
+ case "installed":
417
+ log.success(`${bold(result.editor.label)} ${dim("\u2014 extension installed.")}`);
418
+ break;
419
+ case "already-installed":
420
+ log.info(`${accent("\u203A")} ${bold(result.editor.label)} ${dim("\u2014 already installed, up to date.")}`);
421
+ break;
422
+ case "failed":
423
+ log.error(`${bold(result.editor.label)} ${dim("\u2014 install failed:")} ${result.error}`);
424
+ break;
425
+ }
426
+ return result;
427
+ }
428
+ function printManualInstall() {
429
+ log.blank();
430
+ log.info(
431
+ `${dim("Install it by hand: download")} ${cyan("superlore.vercel.app/superlore-preview.vsix")}${dim(",")}`
432
+ );
433
+ log.info(
434
+ `${dim("then run")} ${cyan('"Extensions: Install from VSIX\u2026"')} ${dim("in your editor (or")} ${cyan("code --install-extension <file>.vsix")}${dim(").")}`
435
+ );
436
+ }
437
+ function printMcpNextStep() {
438
+ log.blank();
439
+ log.info(bold("Next: connect the MCP"));
440
+ log.info(
441
+ ` ${dim("Let your agent read the same corpus. Ask Claude")} ${cyan('"connect my superlore MCP"')}${dim(",")}`
442
+ );
443
+ log.info(
444
+ ` ${dim("or register it yourself:")} ${cyan("claude mcp add --transport http -s user superlore <url>/api/mcp")}`
445
+ );
446
+ }
447
+
256
448
  // src/commands/deploy.ts
257
449
  import { spawn as spawn2 } from "child_process";
258
450
 
@@ -332,34 +524,34 @@ async function devCommand(flags) {
332
524
  }
333
525
 
334
526
  // src/commands/init.ts
335
- import { existsSync as existsSync3 } from "fs";
527
+ import { existsSync as existsSync4 } from "fs";
336
528
  import { basename, resolve as resolve3 } from "path";
337
529
  import { cancel, confirm, intro, isCancel, outro, select, text } from "@clack/prompts";
338
530
 
339
531
  // src/lib/scaffold.ts
340
- import { cpSync, existsSync as existsSync2, mkdirSync, readdirSync, writeFileSync } from "fs";
532
+ import { cpSync, existsSync as existsSync3, mkdirSync, readdirSync, writeFileSync as writeFileSync2 } from "fs";
341
533
  import { fileURLToPath } from "url";
342
- import { dirname as dirname2, join as join2, resolve as resolve2 } from "path";
534
+ import { dirname as dirname2, join as join3, resolve as resolve2 } from "path";
343
535
  var here = dirname2(fileURLToPath(import.meta.url));
344
536
  function findStarterTemplate() {
345
537
  let dir = here;
346
538
  for (; ; ) {
347
- const candidate = join2(dir, "templates", "starter");
348
- if (existsSync2(candidate)) return candidate;
349
- const bundled = join2(dir, "template");
350
- if (existsSync2(bundled)) return bundled;
539
+ const candidate = join3(dir, "templates", "starter");
540
+ if (existsSync3(candidate)) return candidate;
541
+ const bundled = join3(dir, "template");
542
+ if (existsSync3(bundled)) return bundled;
351
543
  const parent = dirname2(dir);
352
544
  if (parent === dir) return void 0;
353
545
  dir = parent;
354
546
  }
355
547
  }
356
548
  function isUsableTemplate(dir) {
357
- if (!existsSync2(dir)) return false;
549
+ if (!existsSync3(dir)) return false;
358
550
  const entries = readdirSync(dir).filter((name) => name !== "README.md" && !name.startsWith("."));
359
551
  return entries.length > 0;
360
552
  }
361
553
  function isEmptyDir(dir) {
362
- if (!existsSync2(dir)) return true;
554
+ if (!existsSync3(dir)) return true;
363
555
  return readdirSync(dir).filter((n) => !n.startsWith(".")).length === 0;
364
556
  }
365
557
  function scaffold(options) {
@@ -374,17 +566,18 @@ function scaffold(options) {
374
566
  writeSkeleton(root, options.config);
375
567
  source = "skeleton";
376
568
  }
377
- writeFileSync(join2(root, "superlore.json"), serializeSuperloreJson(options.config), "utf8");
569
+ writeFileSync2(join3(root, "superlore.json"), serializeSuperloreJson(options.config), "utf8");
378
570
  return { root, source };
379
571
  }
380
572
  function writeSkeleton(root, config) {
381
573
  const accent2 = config.accent ?? SUPERLORE_VIOLET;
382
574
  const mcpEnabled = config.mcp?.enabled ?? true;
575
+ const authEnabled = config.auth?.enabled ?? false;
383
576
  const slug = config.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "superlore-kb";
384
577
  const write = (rel, body) => {
385
- const file = join2(root, rel);
578
+ const file = join3(root, rel);
386
579
  mkdirSync(dirname2(file), { recursive: true });
387
- writeFileSync(file, body, "utf8");
580
+ writeFileSync2(file, body, "utf8");
388
581
  };
389
582
  write(
390
583
  "package.json",
@@ -404,9 +597,14 @@ function writeSkeleton(root, config) {
404
597
  "fumadocs-core": "16.8.2",
405
598
  "fumadocs-mdx": "14.3.1",
406
599
  "fumadocs-ui": "16.8.2",
407
- superlore: "^0.1.0",
600
+ superlore: "^0.5.1",
408
601
  "lucide-react": "^1.21.0",
602
+ // superlore peers the rendered components pull in: Mermaid (Diagram), themes.
603
+ mermaid: "^11.15.0",
409
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" } : {},
410
608
  next: "16.2.4",
411
609
  "next-themes": "^0.4.6",
412
610
  react: "^19.2.5",
@@ -445,7 +643,7 @@ function writeSkeleton(root, config) {
445
643
  isolatedModules: true,
446
644
  skipLibCheck: true,
447
645
  incremental: true,
448
- paths: { "@/*": ["./*"] },
646
+ paths: { "@/*": ["./*"], "collections/*": ["./.source/*"] },
449
647
  plugins: [{ name: "next" }]
450
648
  },
451
649
  include: ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
@@ -465,8 +663,7 @@ const withMDX = createMDX();
465
663
  /** @type {import('next').NextConfig} */
466
664
  const config = {
467
665
  reactStrictMode: true,
468
- // \`superlore\` ships source (.ts/.tsx) for frictionless consumption \u2014 Next transpiles it.
469
- transpilePackages: ["superlore"],
666
+ // superlore ships compiled ESM \u2014 consume it as a normal package (no transpilePackages).
470
667
  };
471
668
 
472
669
  export default withMDX(config);
@@ -510,7 +707,7 @@ export default config;
510
707
  write(
511
708
  "app/layout.tsx",
512
709
  `import type { ReactNode } from "react";
513
- import { RootProvider } from "fumadocs-ui/provider";
710
+ import { RootProvider } from "superlore/ui";
514
711
  import "./global.css";
515
712
 
516
713
  export default function RootLayout({ children }: { children: ReactNode }) {
@@ -584,18 +781,22 @@ export function generateStaticParams() {
584
781
  );
585
782
  write(
586
783
  "lib/source.ts",
587
- `import { loader } from "fumadocs-core/source";
588
- import { docs } from "@/.source";
784
+ `import { docs } from "collections/server";
785
+ import { loader, lucideIconsPlugin } from "superlore/source";
589
786
 
590
787
  export const source = loader({
591
788
  baseUrl: "/docs",
592
789
  source: docs.toFumadocsSource(),
790
+ plugins: [lucideIconsPlugin()],
593
791
  });
594
792
  `
595
793
  );
596
- write(
597
- "content/docs/index.mdx",
598
- `---
794
+ if (config.type === "personal-kb") {
795
+ writePersonalContent(write, config);
796
+ } else {
797
+ write(
798
+ "content/docs/index.mdx",
799
+ `---
599
800
  title: Welcome
600
801
  description: The home of your superlore knowledge base.
601
802
  summary: Landing page for the ${config.name} knowledge base.
@@ -609,7 +810,11 @@ agents read the same structured content over MCP.
609
810
 
610
811
  Edit \`content/docs/index.mdx\` to make it yours, then run \`superlore dev\`.
611
812
  `
612
- );
813
+ );
814
+ }
815
+ if (authEnabled) {
816
+ writeAuth(write, config);
817
+ }
613
818
  if (mcpEnabled) {
614
819
  const mcpPath = config.mcp?.path ?? "/api/mcp";
615
820
  write(
@@ -617,10 +822,14 @@ Edit \`content/docs/index.mdx\` to make it yours, then run \`superlore dev\`.
617
822
  `import { createMcpHandler } from "mcp-handler";
618
823
  import { z } from "zod";
619
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";
620
828
 
621
829
  // Your KB's MCP endpoint. Served at ${mcpPath} \u2014 the same structured content the site renders,
622
- // exposed to agents. Build the index from your content source and pass it to each tool.
623
- // 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);
624
833
 
625
834
  const json = (data: unknown) => ({
626
835
  content: [{ type: "text" as const, text: JSON.stringify(data, null, 2) }],
@@ -632,31 +841,32 @@ const handler = createMcpHandler(
632
841
  "search",
633
842
  "Full-text search across the knowledge base.",
634
843
  { query: z.string(), limit: z.number().int().positive().optional() },
635
- async ({ query, limit }) => json(search(/* index */ {} as never, query, limit)),
844
+ async ({ query, limit }) => json(search(index, query, limit)),
636
845
  );
637
846
  server.tool(
638
847
  "get_page",
639
848
  "Get a page's full structured content by path.",
640
849
  { path: z.string() },
641
- async ({ path }) => json(getPage(/* index */ {} as never, path)),
850
+ async ({ path }) => json(getPage(index, path)),
642
851
  );
643
852
  server.tool(
644
853
  "list",
645
854
  "List knowledge nodes, filtered by kind / tag / entityType.",
646
855
  { kind: z.string().optional(), tag: z.string().optional(), entityType: z.string().optional() },
647
- async (args) => json(list(/* index */ {} as never, args)),
856
+ async ({ kind, tag, entityType }) =>
857
+ json(list(index, { kind: kind as KKind | undefined, tag, entityType })),
648
858
  );
649
859
  server.tool(
650
860
  "navigate",
651
861
  "Follow relations from a page path / node id / entity ref.",
652
862
  { target: z.string() },
653
- async ({ target }) => json(navigate(/* index */ {} as never, target)),
863
+ async ({ target }) => json(navigate(index, target)),
654
864
  );
655
865
  server.tool(
656
866
  "get_component_data",
657
867
  "Get the structured data behind a rendered component (its knowledge face).",
658
868
  { id: z.string() },
659
- async ({ id }) => json(getComponentData(/* index */ {} as never, id)),
869
+ async ({ id }) => json(getComponentData(index, id)),
660
870
  );
661
871
  },
662
872
  {},
@@ -677,6 +887,27 @@ out
677
887
  .env*.local
678
888
  `
679
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.` : "";
680
911
  write(
681
912
  "README.md",
682
913
  `# ${config.name}
@@ -698,7 +929,417 @@ superlore build
698
929
 
699
930
  Config lives in \`superlore.json\`. Author content in \`content/docs/\`.${mcpEnabled ? `
700
931
 
701
- 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
+ />
702
1343
  `
703
1344
  );
704
1345
  }
@@ -727,8 +1368,8 @@ async function initCommand(dir, flags) {
727
1368
  }
728
1369
  if (!name) bail("A name is required (pass --name, a [dir] argument, or run interactively).");
729
1370
  let type = flags.type;
730
- if (type && type !== "company-kb" && type !== "product-docs") {
731
- 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".`);
732
1373
  }
733
1374
  if (!type && interactive) {
734
1375
  const answer = await select({
@@ -743,6 +1384,11 @@ async function initCommand(dir, flags) {
743
1384
  value: "product-docs",
744
1385
  label: "Product docs",
745
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"
746
1392
  }
747
1393
  ]
748
1394
  });
@@ -750,6 +1396,7 @@ async function initCommand(dir, flags) {
750
1396
  type = answer;
751
1397
  }
752
1398
  if (!type) bail("A type is required (pass --type or run interactively).");
1399
+ const wantsGate = type === "company-kb" || type === "personal-kb";
753
1400
  let authEnabled;
754
1401
  if (flags.auth !== void 0) {
755
1402
  authEnabled = flags.auth;
@@ -758,12 +1405,12 @@ async function initCommand(dir, flags) {
758
1405
  } else if (interactive) {
759
1406
  const answer = await confirm({
760
1407
  message: "Gate the site behind Google SSO (auth)?",
761
- initialValue: type === "company-kb"
1408
+ initialValue: wantsGate
762
1409
  });
763
1410
  if (isCancel(answer)) bail("Cancelled.");
764
1411
  authEnabled = answer;
765
1412
  } else {
766
- authEnabled = type === "company-kb";
1413
+ authEnabled = wantsGate;
767
1414
  }
768
1415
  let allowedDomain = flags.allowedDomain?.trim();
769
1416
  if (authEnabled && !allowedDomain && interactive) {
@@ -792,7 +1439,7 @@ ${result.issues.map((i) => ` - ${i.path} ${i.message}`).join("\n")}`
792
1439
  const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
793
1440
  const targetName = dir ?? (slug || "superlore-kb");
794
1441
  const targetDir = resolve3(process.cwd(), targetName);
795
- if (existsSync3(targetDir) && !isEmptyDir(targetDir)) {
1442
+ if (existsSync4(targetDir) && !isEmptyDir(targetDir)) {
796
1443
  if (interactive) {
797
1444
  const proceed = await confirm({
798
1445
  message: `${cyan(targetDir)} is not empty. Scaffold into it anyway?`,
@@ -805,13 +1452,45 @@ ${result.issues.map((i) => ` - ${i.path} ${i.message}`).join("\n")}`
805
1452
  }
806
1453
  const { root, source } = scaffold({ dir: targetDir, config });
807
1454
  outro(`${bold("Scaffolded")} ${config.name} ${dim(`(${source})`)}`);
808
- 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";
809
1457
  log.blank();
810
1458
  log.warn(
811
- `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.`
812
1460
  );
813
1461
  }
814
1462
  printNextSteps(root, config);
1463
+ await maybeConnectEditor(flags, interactive);
1464
+ }
1465
+ async function maybeConnectEditor(flags, interactive) {
1466
+ if (flags.connect === false) return;
1467
+ const editors = detectEditors();
1468
+ if (editors.length === 0) {
1469
+ log.blank();
1470
+ log.info(
1471
+ `${dim("Editor preview:")} install VS Code, Cursor, or Windsurf, then ${cyan("superlore connect")}.`
1472
+ );
1473
+ return;
1474
+ }
1475
+ const names = editors.map((e) => e.label).join(", ");
1476
+ if (flags.connect !== true && !interactive) {
1477
+ log.blank();
1478
+ log.info(
1479
+ `${dim(`Detected ${names}.`)} Run ${cyan("superlore connect")} to install the live-preview extension.`
1480
+ );
1481
+ return;
1482
+ }
1483
+ if (flags.connect !== true) {
1484
+ const proceed = await confirm({
1485
+ message: `Install the superlore Preview extension into ${names}?`,
1486
+ initialValue: true
1487
+ });
1488
+ if (isCancel(proceed) || !proceed) {
1489
+ log.info(`${dim("Skipped \u2014 run")} ${cyan("superlore connect")} ${dim("any time.")}`);
1490
+ return;
1491
+ }
1492
+ }
1493
+ await connectCommand({ optional: true });
815
1494
  }
816
1495
  function printNextSteps(root, config) {
817
1496
  const rel = basename(root);
@@ -829,14 +1508,17 @@ function printNextSteps(root, config) {
829
1508
  }
830
1509
 
831
1510
  // src/index.ts
832
- var VERSION = "0.1.2";
833
- function buildCli(argv = process2.argv) {
1511
+ var VERSION = "0.3.0";
1512
+ function buildCli(argv = process3.argv) {
834
1513
  const cli = cac("superlore");
835
- 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("-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(
836
1515
  async (dir, flags) => {
837
1516
  const authExplicit = argv.includes("--auth");
838
1517
  const noAuthExplicit = argv.includes("--no-auth");
839
1518
  const auth = authExplicit ? true : noAuthExplicit ? false : void 0;
1519
+ const connectExplicit = argv.includes("--connect");
1520
+ const noConnectExplicit = argv.includes("--no-connect");
1521
+ const connect = connectExplicit ? true : noConnectExplicit ? false : void 0;
840
1522
  await initCommand(dir, {
841
1523
  name: flags.name,
842
1524
  type: flags.type,
@@ -845,6 +1527,7 @@ function buildCli(argv = process2.argv) {
845
1527
  accent: flags.accent,
846
1528
  // `--no-mcp` flips this to false; default true is the intended behaviour.
847
1529
  mcp: flags.mcp,
1530
+ connect,
848
1531
  yes: flags.yes
849
1532
  });
850
1533
  }
@@ -855,6 +1538,9 @@ function buildCli(argv = process2.argv) {
855
1538
  cli.command("build", "Production build of the KB").action(async () => {
856
1539
  await buildCommand();
857
1540
  });
1541
+ cli.command("connect", "Install the superlore editor extension (VS Code \xB7 Cursor \xB7 Windsurf)").option("--vsix <path>", "Install from a local .vsix instead of the Marketplace").example("superlore connect").action(async (flags) => {
1542
+ await connectCommand({ vsix: flags.vsix });
1543
+ });
858
1544
  cli.command("deploy", "Managed deploy (superlore Cloud) \u2014 private beta, joins the waitlist").option("--open", "Open the waitlist URL in your browser").action(async (flags) => {
859
1545
  await deployCommand({ open: flags.open });
860
1546
  });
@@ -862,7 +1548,7 @@ function buildCli(argv = process2.argv) {
862
1548
  cli.version(VERSION);
863
1549
  return cli;
864
1550
  }
865
- async function run(argv = process2.argv) {
1551
+ async function run(argv = process3.argv) {
866
1552
  const cli = buildCli(argv);
867
1553
  const tokens = argv.slice(2);
868
1554
  const hasCommand = tokens.some((t) => !t.startsWith("-"));
@@ -878,10 +1564,10 @@ async function run(argv = process2.argv) {
878
1564
  } catch (error) {
879
1565
  const message = error instanceof Error ? error.message : String(error);
880
1566
  log.error(message);
881
- process2.exit(1);
1567
+ process3.exit(1);
882
1568
  }
883
1569
  }
884
- var isEntrypoint = Boolean(process2.argv[1]) && fileURLToPath2(import.meta.url) === process2.argv[1];
1570
+ var isEntrypoint = Boolean(process3.argv[1]) && fileURLToPath2(import.meta.url) === process3.argv[1];
885
1571
  if (isEntrypoint) {
886
1572
  void run();
887
1573
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "superlore-cli",
3
- "version": "0.1.2",
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",