superlore-cli 0.1.2 → 0.2.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/index.d.ts CHANGED
@@ -83,7 +83,7 @@ declare function serializeSuperloreJson(config: SuperloreJson): string;
83
83
  declare function resolveMcpPath(config: SuperloreJson): string | undefined;
84
84
 
85
85
  /** The CLI version, kept in sync with package.json at build time. */
86
- declare const VERSION = "0.1.2";
86
+ declare const VERSION = "0.2.0";
87
87
  /** Build the argument parser. Exported for tests; `run()` wires it to argv. */
88
88
  declare function buildCli(argv?: readonly string[]): cac.CAC;
89
89
  /** 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
@@ -253,6 +253,194 @@ async function buildCommand() {
253
253
  process.exit(code);
254
254
  }
255
255
 
256
+ // src/lib/editors.ts
257
+ import { execFileSync } from "child_process";
258
+ import { existsSync as existsSync2, writeFileSync } from "fs";
259
+ import { homedir, tmpdir } from "os";
260
+ import { join as join2 } from "path";
261
+ import process2 from "process";
262
+ var EXTENSION_ID = "superlore.superlore-preview";
263
+ var DEFAULT_VSIX_URL = "https://superlore.vercel.app/superlore-preview.vsix";
264
+ async function downloadVsix(url = DEFAULT_VSIX_URL) {
265
+ const res = await fetch(url, { redirect: "follow" });
266
+ if (!res.ok) throw new Error(`couldn't fetch the extension (${res.status} ${res.statusText})`);
267
+ const bytes = Buffer.from(await res.arrayBuffer());
268
+ const path = join2(tmpdir(), "superlore-preview.vsix");
269
+ writeFileSync(path, bytes);
270
+ return path;
271
+ }
272
+ var EDITORS = [
273
+ { id: "vscode", label: "VS Code", bin: "code" },
274
+ { id: "cursor", label: "Cursor", bin: "cursor" },
275
+ { id: "windsurf", label: "Windsurf", bin: "windsurf" }
276
+ ];
277
+ function onPath(bin) {
278
+ const probe = process2.platform === "win32" ? "where" : "which";
279
+ try {
280
+ execFileSync(probe, [bin], { stdio: "ignore", shell: process2.platform === "win32" });
281
+ return true;
282
+ } catch {
283
+ return false;
284
+ }
285
+ }
286
+ function fallbackPaths(editor) {
287
+ const home = homedir();
288
+ if (process2.platform === "darwin") {
289
+ const app = {
290
+ vscode: "Visual Studio Code.app",
291
+ cursor: "Cursor.app",
292
+ windsurf: "Windsurf.app"
293
+ };
294
+ const rel = `Contents/Resources/app/bin/${editor.bin}`;
295
+ return [
296
+ join2("/Applications", app[editor.id], rel),
297
+ join2(home, "Applications", app[editor.id], rel)
298
+ ];
299
+ }
300
+ if (process2.platform === "win32") {
301
+ const local = process2.env.LOCALAPPDATA ?? join2(home, "AppData", "Local");
302
+ const programs = join2(local, "Programs");
303
+ const dir = {
304
+ vscode: "Microsoft VS Code",
305
+ cursor: "cursor",
306
+ windsurf: "Windsurf"
307
+ };
308
+ const exe = `bin\\${editor.bin}.cmd`;
309
+ return [
310
+ join2(programs, dir[editor.id], exe),
311
+ join2(process2.env.ProgramFiles ?? "C:\\Program Files", dir[editor.id], exe)
312
+ ];
313
+ }
314
+ return [
315
+ join2("/usr/share", editor.bin, "bin", editor.bin),
316
+ join2("/usr/bin", editor.bin),
317
+ join2("/snap/bin", editor.bin),
318
+ join2(home, ".local", "bin", editor.bin)
319
+ ];
320
+ }
321
+ function resolveEditorCommand(editor) {
322
+ if (onPath(editor.bin)) return editor.bin;
323
+ for (const candidate of fallbackPaths(editor)) {
324
+ if (existsSync2(candidate)) return candidate;
325
+ }
326
+ return void 0;
327
+ }
328
+ function detectEditors() {
329
+ const found = [];
330
+ for (const editor of EDITORS) {
331
+ const command = resolveEditorCommand(editor);
332
+ if (command) found.push({ ...editor, command });
333
+ }
334
+ return found;
335
+ }
336
+ var execRunner = (command, args) => execFileSync(command, [...args], {
337
+ encoding: "utf8",
338
+ stdio: ["ignore", "pipe", "pipe"],
339
+ // Editor CLIs on Windows are .cmd shims; a shell is needed to invoke them.
340
+ shell: process2.platform === "win32",
341
+ timeout: 6e4
342
+ }).trim();
343
+ function classifyInstallOutput(editor, output) {
344
+ return /already installed/i.test(output) ? { editor, status: "already-installed" } : { editor, status: "installed" };
345
+ }
346
+ function installInto(editor, options = {}) {
347
+ const run2 = options.run ?? execRunner;
348
+ const target = options.vsix ?? EXTENSION_ID;
349
+ try {
350
+ const output = run2(editor.command, ["--install-extension", target, "--force"]);
351
+ return classifyInstallOutput(editor, output);
352
+ } catch (error) {
353
+ return { editor, status: "failed", error: failureReason(error) };
354
+ }
355
+ }
356
+ function failureReason(error) {
357
+ if (error instanceof Error) {
358
+ const stderr = error.stderr;
359
+ const text2 = typeof stderr === "string" ? stderr : Buffer.isBuffer(stderr) ? stderr.toString() : "";
360
+ const lines = text2.split("\n").map((l) => l.trim()).filter((l) => l.length > 0 && !/^\(node:/.test(l) && !/^\(Use /.test(l));
361
+ const meaningful = lines.find((l) => /(not found|error|fail|denied)/i.test(l)) ?? lines[0];
362
+ if (meaningful) return meaningful;
363
+ return error.message.replace(/^Command failed:.*/s, "the editor CLI returned an error").trim();
364
+ }
365
+ return String(error);
366
+ }
367
+
368
+ // src/commands/connect.ts
369
+ async function connectCommand(flags = {}) {
370
+ log.blank();
371
+ log.info(`${accent("superlore connect")} ${dim("\u2014 set up your editor")}`);
372
+ log.blank();
373
+ const detected = detectEditors();
374
+ if (detected.length === 0) {
375
+ log.warn("No supported editor detected (looked for VS Code, Cursor, and Windsurf).");
376
+ log.blank();
377
+ log.info(`${dim("Install one, then re-run")} ${cyan("superlore connect")}${dim(".")}`);
378
+ log.info(
379
+ `${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(".")}`
380
+ );
381
+ printMcpNextStep();
382
+ process.exit(0);
383
+ }
384
+ const labels = detected.map((e) => bold(e.label)).join(", ");
385
+ log.step(`Found ${labels}. Installing the superlore Preview extension\u2026`);
386
+ log.blank();
387
+ let vsix;
388
+ try {
389
+ vsix = flags.vsix ?? await downloadVsix();
390
+ } catch (error) {
391
+ log.error(
392
+ `Couldn't fetch the extension: ${error instanceof Error ? error.message : String(error)}`
393
+ );
394
+ printManualInstall();
395
+ process.exit(flags.optional ? 0 : 1);
396
+ }
397
+ const results = detected.map((editor) => report(installInto(editor, { vsix })));
398
+ log.blank();
399
+ const failed = results.filter((r) => r.status === "failed");
400
+ if (failed.length > 0 && failed.length === results.length) {
401
+ log.error("Couldn't install the extension into any editor. See the errors above.");
402
+ printManualInstall();
403
+ process.exit(flags.optional ? 0 : 1);
404
+ }
405
+ log.success(
406
+ `superlore Preview is ready. Open a ${cyan(".mdx")} file and run ${cyan("superlore: Open Preview")} ${dim("(Cmd/Ctrl+K V)")}.`
407
+ );
408
+ printMcpNextStep();
409
+ }
410
+ function report(result) {
411
+ switch (result.status) {
412
+ case "installed":
413
+ log.success(`${bold(result.editor.label)} ${dim("\u2014 extension installed.")}`);
414
+ break;
415
+ case "already-installed":
416
+ log.info(`${accent("\u203A")} ${bold(result.editor.label)} ${dim("\u2014 already installed, up to date.")}`);
417
+ break;
418
+ case "failed":
419
+ log.error(`${bold(result.editor.label)} ${dim("\u2014 install failed:")} ${result.error}`);
420
+ break;
421
+ }
422
+ return result;
423
+ }
424
+ function printManualInstall() {
425
+ log.blank();
426
+ log.info(
427
+ `${dim("Install it by hand: download")} ${cyan("superlore.vercel.app/superlore-preview.vsix")}${dim(",")}`
428
+ );
429
+ log.info(
430
+ `${dim("then run")} ${cyan('"Extensions: Install from VSIX\u2026"')} ${dim("in your editor (or")} ${cyan("code --install-extension <file>.vsix")}${dim(").")}`
431
+ );
432
+ }
433
+ function printMcpNextStep() {
434
+ log.blank();
435
+ log.info(bold("Next: connect the MCP"));
436
+ log.info(
437
+ ` ${dim("Let your agent read the same corpus. Ask Claude")} ${cyan('"connect my superlore MCP"')}${dim(",")}`
438
+ );
439
+ log.info(
440
+ ` ${dim("or register it yourself:")} ${cyan("claude mcp add --transport http -s user superlore <url>/api/mcp")}`
441
+ );
442
+ }
443
+
256
444
  // src/commands/deploy.ts
257
445
  import { spawn as spawn2 } from "child_process";
258
446
 
@@ -332,34 +520,34 @@ async function devCommand(flags) {
332
520
  }
333
521
 
334
522
  // src/commands/init.ts
335
- import { existsSync as existsSync3 } from "fs";
523
+ import { existsSync as existsSync4 } from "fs";
336
524
  import { basename, resolve as resolve3 } from "path";
337
525
  import { cancel, confirm, intro, isCancel, outro, select, text } from "@clack/prompts";
338
526
 
339
527
  // src/lib/scaffold.ts
340
- import { cpSync, existsSync as existsSync2, mkdirSync, readdirSync, writeFileSync } from "fs";
528
+ import { cpSync, existsSync as existsSync3, mkdirSync, readdirSync, writeFileSync as writeFileSync2 } from "fs";
341
529
  import { fileURLToPath } from "url";
342
- import { dirname as dirname2, join as join2, resolve as resolve2 } from "path";
530
+ import { dirname as dirname2, join as join3, resolve as resolve2 } from "path";
343
531
  var here = dirname2(fileURLToPath(import.meta.url));
344
532
  function findStarterTemplate() {
345
533
  let dir = here;
346
534
  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;
535
+ const candidate = join3(dir, "templates", "starter");
536
+ if (existsSync3(candidate)) return candidate;
537
+ const bundled = join3(dir, "template");
538
+ if (existsSync3(bundled)) return bundled;
351
539
  const parent = dirname2(dir);
352
540
  if (parent === dir) return void 0;
353
541
  dir = parent;
354
542
  }
355
543
  }
356
544
  function isUsableTemplate(dir) {
357
- if (!existsSync2(dir)) return false;
545
+ if (!existsSync3(dir)) return false;
358
546
  const entries = readdirSync(dir).filter((name) => name !== "README.md" && !name.startsWith("."));
359
547
  return entries.length > 0;
360
548
  }
361
549
  function isEmptyDir(dir) {
362
- if (!existsSync2(dir)) return true;
550
+ if (!existsSync3(dir)) return true;
363
551
  return readdirSync(dir).filter((n) => !n.startsWith(".")).length === 0;
364
552
  }
365
553
  function scaffold(options) {
@@ -374,7 +562,7 @@ function scaffold(options) {
374
562
  writeSkeleton(root, options.config);
375
563
  source = "skeleton";
376
564
  }
377
- writeFileSync(join2(root, "superlore.json"), serializeSuperloreJson(options.config), "utf8");
565
+ writeFileSync2(join3(root, "superlore.json"), serializeSuperloreJson(options.config), "utf8");
378
566
  return { root, source };
379
567
  }
380
568
  function writeSkeleton(root, config) {
@@ -382,9 +570,9 @@ function writeSkeleton(root, config) {
382
570
  const mcpEnabled = config.mcp?.enabled ?? true;
383
571
  const slug = config.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "superlore-kb";
384
572
  const write = (rel, body) => {
385
- const file = join2(root, rel);
573
+ const file = join3(root, rel);
386
574
  mkdirSync(dirname2(file), { recursive: true });
387
- writeFileSync(file, body, "utf8");
575
+ writeFileSync2(file, body, "utf8");
388
576
  };
389
577
  write(
390
578
  "package.json",
@@ -792,7 +980,7 @@ ${result.issues.map((i) => ` - ${i.path} ${i.message}`).join("\n")}`
792
980
  const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
793
981
  const targetName = dir ?? (slug || "superlore-kb");
794
982
  const targetDir = resolve3(process.cwd(), targetName);
795
- if (existsSync3(targetDir) && !isEmptyDir(targetDir)) {
983
+ if (existsSync4(targetDir) && !isEmptyDir(targetDir)) {
796
984
  if (interactive) {
797
985
  const proceed = await confirm({
798
986
  message: `${cyan(targetDir)} is not empty. Scaffold into it anyway?`,
@@ -812,6 +1000,33 @@ ${result.issues.map((i) => ` - ${i.path} ${i.message}`).join("\n")}`
812
1000
  );
813
1001
  }
814
1002
  printNextSteps(root, config);
1003
+ await maybeConnectEditor(flags, interactive);
1004
+ }
1005
+ async function maybeConnectEditor(flags, interactive) {
1006
+ if (flags.connect === false) return;
1007
+ const editors = detectEditors();
1008
+ if (editors.length === 0) {
1009
+ log.blank();
1010
+ log.info(`${dim("Editor preview:")} install VS Code, Cursor, or Windsurf, then ${cyan("superlore connect")}.`);
1011
+ return;
1012
+ }
1013
+ const names = editors.map((e) => e.label).join(", ");
1014
+ if (flags.connect !== true && !interactive) {
1015
+ log.blank();
1016
+ log.info(`${dim(`Detected ${names}.`)} Run ${cyan("superlore connect")} to install the live-preview extension.`);
1017
+ return;
1018
+ }
1019
+ if (flags.connect !== true) {
1020
+ const proceed = await confirm({
1021
+ message: `Install the superlore Preview extension into ${names}?`,
1022
+ initialValue: true
1023
+ });
1024
+ if (isCancel(proceed) || !proceed) {
1025
+ log.info(`${dim("Skipped \u2014 run")} ${cyan("superlore connect")} ${dim("any time.")}`);
1026
+ return;
1027
+ }
1028
+ }
1029
+ await connectCommand({ optional: true });
815
1030
  }
816
1031
  function printNextSteps(root, config) {
817
1032
  const rel = basename(root);
@@ -829,14 +1044,17 @@ function printNextSteps(root, config) {
829
1044
  }
830
1045
 
831
1046
  // src/index.ts
832
- var VERSION = "0.1.2";
833
- function buildCli(argv = process2.argv) {
1047
+ var VERSION = "0.2.0";
1048
+ function buildCli(argv = process3.argv) {
834
1049
  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(
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(
836
1051
  async (dir, flags) => {
837
1052
  const authExplicit = argv.includes("--auth");
838
1053
  const noAuthExplicit = argv.includes("--no-auth");
839
1054
  const auth = authExplicit ? true : noAuthExplicit ? false : void 0;
1055
+ const connectExplicit = argv.includes("--connect");
1056
+ const noConnectExplicit = argv.includes("--no-connect");
1057
+ const connect = connectExplicit ? true : noConnectExplicit ? false : void 0;
840
1058
  await initCommand(dir, {
841
1059
  name: flags.name,
842
1060
  type: flags.type,
@@ -845,6 +1063,7 @@ function buildCli(argv = process2.argv) {
845
1063
  accent: flags.accent,
846
1064
  // `--no-mcp` flips this to false; default true is the intended behaviour.
847
1065
  mcp: flags.mcp,
1066
+ connect,
848
1067
  yes: flags.yes
849
1068
  });
850
1069
  }
@@ -855,6 +1074,9 @@ function buildCli(argv = process2.argv) {
855
1074
  cli.command("build", "Production build of the KB").action(async () => {
856
1075
  await buildCommand();
857
1076
  });
1077
+ 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) => {
1078
+ await connectCommand({ vsix: flags.vsix });
1079
+ });
858
1080
  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
1081
  await deployCommand({ open: flags.open });
860
1082
  });
@@ -862,7 +1084,7 @@ function buildCli(argv = process2.argv) {
862
1084
  cli.version(VERSION);
863
1085
  return cli;
864
1086
  }
865
- async function run(argv = process2.argv) {
1087
+ async function run(argv = process3.argv) {
866
1088
  const cli = buildCli(argv);
867
1089
  const tokens = argv.slice(2);
868
1090
  const hasCommand = tokens.some((t) => !t.startsWith("-"));
@@ -878,10 +1100,10 @@ async function run(argv = process2.argv) {
878
1100
  } catch (error) {
879
1101
  const message = error instanceof Error ? error.message : String(error);
880
1102
  log.error(message);
881
- process2.exit(1);
1103
+ process3.exit(1);
882
1104
  }
883
1105
  }
884
- var isEntrypoint = Boolean(process2.argv[1]) && fileURLToPath2(import.meta.url) === process2.argv[1];
1106
+ var isEntrypoint = Boolean(process3.argv[1]) && fileURLToPath2(import.meta.url) === process3.argv[1];
885
1107
  if (isEntrypoint) {
886
1108
  void run();
887
1109
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "superlore-cli",
3
- "version": "0.1.2",
3
+ "version": "0.2.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",