@piut/cli 3.4.0 → 3.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli.js +186 -65
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -141,6 +141,16 @@ async function publishServer(key) {
141
141
  }
142
142
  return res.json();
143
143
  }
144
+ async function getBrain(key) {
145
+ const res = await fetch(`${API_BASE}/api/cli/brain`, {
146
+ headers: { Authorization: `Bearer ${key}` }
147
+ });
148
+ if (!res.ok) {
149
+ const body = await res.json().catch(() => ({ error: "Unknown error" }));
150
+ throw new Error(body.error || `Failed to fetch brain (HTTP ${res.status})`);
151
+ }
152
+ return res.json();
153
+ }
144
154
  function getMachineId() {
145
155
  const hostname = os.hostname();
146
156
  return crypto.createHash("sha256").update(hostname).digest("hex").slice(0, 16);
@@ -603,24 +613,36 @@ var Spinner = class {
603
613
  interval = null;
604
614
  startTime = Date.now();
605
615
  message = "";
606
- tokens = 0;
607
616
  sections = [];
617
+ currentSection = null;
608
618
  start(message) {
609
619
  this.message = message;
610
620
  this.startTime = Date.now();
611
- this.tokens = 0;
612
621
  this.sections = [];
622
+ this.currentSection = null;
613
623
  this.render();
614
624
  this.interval = setInterval(() => this.render(), 80);
615
625
  }
616
- updateTokens(count) {
617
- this.tokens = count;
618
- }
619
626
  addSection(name) {
620
- this.clearLine();
621
- const elapsed = this.elapsed();
622
- console.log(` ${success("\u2713")} ${name.padEnd(12)} ${dim(elapsed)}`);
627
+ if (this.currentSection) {
628
+ this.clearLine();
629
+ const elapsed = this.elapsed();
630
+ const label = this.capitalize(this.currentSection);
631
+ console.log(` ${success("\u2713")} ${label.padEnd(14)} ${dim(elapsed)}`);
632
+ }
633
+ this.currentSection = name;
623
634
  this.sections.push(name);
635
+ this.message = `Building ${this.capitalize(name)}...`;
636
+ }
637
+ completeAll() {
638
+ if (this.currentSection) {
639
+ this.clearLine();
640
+ const elapsed = this.elapsed();
641
+ const label = this.capitalize(this.currentSection);
642
+ console.log(` ${success("\u2713")} ${label.padEnd(14)} ${dim(elapsed)}`);
643
+ this.currentSection = null;
644
+ }
645
+ this.message = "Finalizing...";
624
646
  }
625
647
  updateMessage(message) {
626
648
  this.message = message;
@@ -639,9 +661,8 @@ var Spinner = class {
639
661
  this.frame = (this.frame + 1) % SPINNER_FRAMES.length;
640
662
  const spinner = brand(SPINNER_FRAMES[this.frame]);
641
663
  const elapsed = dim(this.elapsed());
642
- const tokenStr = this.tokens > 0 ? dim(` ${this.tokens.toLocaleString()} tokens`) : "";
643
664
  this.clearLine();
644
- process.stdout.write(` ${spinner} ${this.message} ${elapsed}${tokenStr}`);
665
+ process.stdout.write(` ${spinner} ${this.message} ${elapsed}`);
645
666
  }
646
667
  elapsed() {
647
668
  const ms = Date.now() - this.startTime;
@@ -651,6 +672,9 @@ var Spinner = class {
651
672
  clearLine() {
652
673
  process.stdout.write("\r\x1B[K");
653
674
  }
675
+ capitalize(s) {
676
+ return s.charAt(0).toUpperCase() + s.slice(1);
677
+ }
654
678
  };
655
679
 
656
680
  // src/types.ts
@@ -1430,37 +1454,13 @@ async function removeCommand() {
1430
1454
  }
1431
1455
 
1432
1456
  // src/commands/build.ts
1433
- import { select, checkbox as checkbox3, input } from "@inquirer/prompts";
1457
+ import { select, checkbox as checkbox3, input, confirm as confirm3 } from "@inquirer/prompts";
1434
1458
  import chalk5 from "chalk";
1435
1459
  import os6 from "os";
1436
1460
 
1437
1461
  // src/lib/auth.ts
1438
1462
  import { password as password2 } from "@inquirer/prompts";
1439
1463
  import chalk4 from "chalk";
1440
- async function resolveApiKey(keyOption) {
1441
- const config = readStore();
1442
- let apiKey = keyOption || config.apiKey;
1443
- if (!apiKey) {
1444
- apiKey = await password2({
1445
- message: "Enter your p\u0131ut API key:",
1446
- mask: "*",
1447
- validate: (v) => v.startsWith("pb_") || "Key must start with pb_"
1448
- });
1449
- }
1450
- console.log(dim(" Validating key..."));
1451
- let result;
1452
- try {
1453
- result = await validateKey(apiKey);
1454
- } catch (err) {
1455
- console.log(chalk4.red(` \u2717 ${err.message}`));
1456
- console.log(dim(" Get a key at https://piut.com/dashboard/keys"));
1457
- throw new CliError(err.message);
1458
- }
1459
- const label = result.slug ? `${result.displayName} (${result.slug})` : result.displayName;
1460
- console.log(success(` \u2713 Connected as ${label}`));
1461
- updateStore({ apiKey });
1462
- return apiKey;
1463
- }
1464
1464
  async function resolveApiKeyWithResult(keyOption) {
1465
1465
  const config = readStore();
1466
1466
  let apiKey = keyOption || config.apiKey;
@@ -1489,7 +1489,7 @@ async function resolveApiKeyWithResult(keyOption) {
1489
1489
  // src/commands/build.ts
1490
1490
  async function buildCommand(options) {
1491
1491
  banner();
1492
- const apiKey = await resolveApiKey(options.key);
1492
+ const { apiKey, serverUrl } = await resolveApiKeyWithResult(options.key);
1493
1493
  let scanFolders;
1494
1494
  if (options.folders) {
1495
1495
  scanFolders = options.folders.split(",").map((f) => expandPath(f.trim()));
@@ -1578,12 +1578,12 @@ async function buildCommand(options) {
1578
1578
  spinner.updateMessage(String(event.data.message || "Processing..."));
1579
1579
  break;
1580
1580
  case "progress":
1581
- spinner.updateTokens(event.data.tokens);
1582
1581
  break;
1583
1582
  case "section":
1584
1583
  spinner.addSection(String(event.data.name));
1585
1584
  break;
1586
1585
  case "complete":
1586
+ spinner.completeAll();
1587
1587
  sections = event.data.sections || {};
1588
1588
  break;
1589
1589
  case "error":
@@ -1600,28 +1600,76 @@ async function buildCommand(options) {
1600
1600
  console.log();
1601
1601
  console.log(success(" Brain built!"));
1602
1602
  console.log();
1603
- const sectionSummary = (content, label) => {
1604
- if (!content || !content.trim()) {
1603
+ const SECTION_LABELS = {
1604
+ about: "About",
1605
+ soul: "Soul",
1606
+ areas: "Areas of Responsibility",
1607
+ projects: "Projects",
1608
+ memory: "Memory"
1609
+ };
1610
+ for (const [key, label] of Object.entries(SECTION_LABELS)) {
1611
+ const content = sections[key] || "";
1612
+ if (!content.trim()) {
1605
1613
  console.log(dim(` ${label} \u2014 (empty)`));
1606
1614
  } else {
1607
- const firstLine = content.split("\n").find((l) => l.trim() && !l.startsWith("#"))?.trim() || "";
1608
- const preview = firstLine.length > 60 ? firstLine.slice(0, 60) + "..." : firstLine;
1609
- console.log(success(` ${label}`) + dim(` \u2014 ${preview}`));
1615
+ console.log(success(` ${label}`));
1616
+ const lines = content.split("\n").filter((l) => l.trim()).slice(0, 5);
1617
+ for (const line of lines) {
1618
+ console.log(dim(` ${line.length > 80 ? line.slice(0, 80) + "..." : line}`));
1619
+ }
1620
+ const totalLines = content.split("\n").filter((l) => l.trim()).length;
1621
+ if (totalLines > 5) {
1622
+ console.log(dim(` ... (${totalLines - 5} more lines)`));
1623
+ }
1610
1624
  }
1611
- };
1612
- sectionSummary(sections.about || "", "About");
1613
- sectionSummary(sections.soul || "", "Soul");
1614
- sectionSummary(sections.areas || "", "Areas of Responsibility");
1615
- sectionSummary(sections.projects || "", "Projects");
1616
- sectionSummary(sections.memory || "", "Memory");
1617
- console.log();
1625
+ console.log();
1626
+ }
1618
1627
  console.log(dim(` Review and edit at ${brand("piut.com/dashboard")}`));
1619
1628
  console.log();
1629
+ let wantPublish;
1630
+ if (options.publish === false) {
1631
+ wantPublish = false;
1632
+ } else if (options.yes) {
1633
+ wantPublish = true;
1634
+ } else {
1635
+ console.log(dim(" You can always make changes later."));
1636
+ wantPublish = await confirm3({
1637
+ message: "Publish your brain now?",
1638
+ default: true
1639
+ });
1640
+ }
1641
+ if (wantPublish) {
1642
+ try {
1643
+ await publishServer(apiKey);
1644
+ console.log();
1645
+ console.log(success(" \u2713 Brain published. MCP server is live."));
1646
+ console.log(` ${brand(serverUrl)}`);
1647
+ console.log(dim(" (accessible only with secure authentication)"));
1648
+ console.log();
1649
+ } catch (err) {
1650
+ console.log();
1651
+ const msg = err.message;
1652
+ if (msg === "REQUIRES_SUBSCRIPTION") {
1653
+ console.log(chalk5.yellow(" Deploy requires an active subscription ($10/mo)."));
1654
+ console.log(` Subscribe at: ${brand("https://piut.com/dashboard/billing")}`);
1655
+ console.log(dim(" 14-day free trial included."));
1656
+ } else {
1657
+ console.log(chalk5.red(` \u2717 ${msg}`));
1658
+ }
1659
+ console.log();
1660
+ }
1661
+ } else {
1662
+ console.log();
1663
+ console.log(dim(" You can publish anytime with: ") + brand("piut deploy"));
1664
+ console.log();
1665
+ }
1620
1666
  } catch (err) {
1621
1667
  spinner.stop();
1622
1668
  if (err instanceof CliError) throw err;
1623
- console.log(chalk5.red(` \u2717 ${err.message}`));
1624
- throw new CliError(err.message);
1669
+ const msg = err.message || "Unknown error";
1670
+ const hint = msg === "terminated" || msg.includes("network") || msg.includes("fetch") ? "The build was interrupted. This can happen if your scan data is very large. Try using --folders to limit which directories are scanned." : msg;
1671
+ console.log(chalk5.red(` \u2717 ${hint}`));
1672
+ throw new CliError(hint);
1625
1673
  }
1626
1674
  }
1627
1675
 
@@ -1808,8 +1856,10 @@ async function connectCommand(options) {
1808
1856
  }
1809
1857
  }
1810
1858
  }
1859
+ const projectsWithActions = new Set(actions.map((a) => a.project.path));
1860
+ const alreadyConnectedCount = projects.filter((p) => !projectsWithActions.has(p.path)).length;
1811
1861
  if (actions.length === 0) {
1812
- console.log(dim(" All projects are already connected."));
1862
+ console.log(success(` All ${projects.length} project(s) are already connected.`));
1813
1863
  console.log();
1814
1864
  return;
1815
1865
  }
@@ -1820,7 +1870,10 @@ async function connectCommand(options) {
1820
1870
  byProject.get(key).push(action);
1821
1871
  }
1822
1872
  console.log();
1823
- console.log(` Found ${brand.bold(String(byProject.size))} project(s) to connect:`);
1873
+ if (alreadyConnectedCount > 0) {
1874
+ console.log(dim(` ${alreadyConnectedCount} project(s) already connected.`));
1875
+ }
1876
+ console.log(` Found ${brand.bold(String(byProject.size))} project(s) with new connections available:`);
1824
1877
  console.log();
1825
1878
  const projectChoices = [];
1826
1879
  for (const [projectPath, projectActions] of byProject) {
@@ -1831,7 +1884,8 @@ async function connectCommand(options) {
1831
1884
  }).join(", ");
1832
1885
  projectChoices.push({
1833
1886
  name: `${projectName} ${dim(`(${desc})`)}`,
1834
- value: projectPath
1887
+ value: projectPath,
1888
+ checked: true
1835
1889
  });
1836
1890
  }
1837
1891
  let selectedPaths;
@@ -1903,7 +1957,7 @@ async function connectCommand(options) {
1903
1957
  // src/commands/disconnect.ts
1904
1958
  import fs10 from "fs";
1905
1959
  import path11 from "path";
1906
- import { checkbox as checkbox5, confirm as confirm4 } from "@inquirer/prompts";
1960
+ import { checkbox as checkbox5, confirm as confirm5 } from "@inquirer/prompts";
1907
1961
  var DEDICATED_FILES = /* @__PURE__ */ new Set([
1908
1962
  ".cursor/rules/piut.mdc",
1909
1963
  ".windsurf/rules/piut.md",
@@ -2031,7 +2085,7 @@ async function disconnectCommand(options) {
2031
2085
  console.log(dim(" No projects selected."));
2032
2086
  return;
2033
2087
  }
2034
- const proceed = await confirm4({
2088
+ const proceed = await confirm5({
2035
2089
  message: `Disconnect ${selectedPaths.length} project(s)?`,
2036
2090
  default: false
2037
2091
  });
@@ -2110,7 +2164,7 @@ import chalk8 from "chalk";
2110
2164
  // src/lib/update-check.ts
2111
2165
  import { execFile } from "child_process";
2112
2166
  import chalk7 from "chalk";
2113
- import { confirm as confirm5 } from "@inquirer/prompts";
2167
+ import { confirm as confirm6 } from "@inquirer/prompts";
2114
2168
  var PACKAGE_NAME = "@piut/cli";
2115
2169
  function isNpx() {
2116
2170
  return process.env.npm_command === "exec" || (process.env._?.includes("npx") ?? false);
@@ -2150,7 +2204,7 @@ async function checkForUpdate(currentVersion) {
2150
2204
  console.log();
2151
2205
  if (npx) return;
2152
2206
  try {
2153
- const shouldUpdate = await confirm5({
2207
+ const shouldUpdate = await confirm6({
2154
2208
  message: `Update to v${latest} now?`,
2155
2209
  default: true
2156
2210
  });
@@ -2374,7 +2428,7 @@ async function doctorCommand(options) {
2374
2428
  }
2375
2429
 
2376
2430
  // src/commands/interactive.ts
2377
- import { select as select2, confirm as confirm6, checkbox as checkbox6, password as password3 } from "@inquirer/prompts";
2431
+ import { select as select2, confirm as confirm7, checkbox as checkbox6, password as password3 } from "@inquirer/prompts";
2378
2432
  import fs12 from "fs";
2379
2433
  import path12 from "path";
2380
2434
  import chalk10 from "chalk";
@@ -2428,7 +2482,7 @@ async function interactiveMenu() {
2428
2482
  console.log(warning(" You haven\u2019t built a brain yet."));
2429
2483
  console.log(dim(" Your brain is how AI tools learn about you \u2014 your projects, preferences, and context."));
2430
2484
  console.log();
2431
- const wantBuild = await confirm6({
2485
+ const wantBuild = await confirm7({
2432
2486
  message: "Build your brain now?",
2433
2487
  default: true
2434
2488
  });
@@ -2444,7 +2498,7 @@ async function interactiveMenu() {
2444
2498
  console.log(warning(" Your brain is built but not deployed yet."));
2445
2499
  console.log(dim(" Deploy it to make your MCP server live so AI tools can read your brain."));
2446
2500
  console.log();
2447
- const wantDeploy = await confirm6({
2501
+ const wantDeploy = await confirm7({
2448
2502
  message: "Deploy your brain now?",
2449
2503
  default: true
2450
2504
  });
@@ -2498,6 +2552,12 @@ async function interactiveMenu() {
2498
2552
  description: "Remove brain references from project configs",
2499
2553
  disabled: !isDeployed && "(deploy brain first)"
2500
2554
  },
2555
+ {
2556
+ name: "View Brain",
2557
+ value: "view-brain",
2558
+ description: "View all 5 brain sections",
2559
+ disabled: !hasBrain && "(build brain first)"
2560
+ },
2501
2561
  {
2502
2562
  name: "Status",
2503
2563
  value: "status",
@@ -2543,6 +2603,9 @@ async function interactiveMenu() {
2543
2603
  case "disconnect-projects":
2544
2604
  await disconnectCommand({});
2545
2605
  break;
2606
+ case "view-brain":
2607
+ await handleViewBrain(apiKey);
2608
+ break;
2546
2609
  case "status":
2547
2610
  statusCommand();
2548
2611
  break;
@@ -2575,7 +2638,7 @@ async function interactiveMenu() {
2575
2638
  }
2576
2639
  }
2577
2640
  async function handleUndeploy(apiKey) {
2578
- const confirmed = await confirm6({
2641
+ const confirmed = await confirm7({
2579
2642
  message: "Undeploy your brain? AI tools will lose access to your MCP server.",
2580
2643
  default: false
2581
2644
  });
@@ -2642,6 +2705,11 @@ async function handleConnectTools(apiKey, validation) {
2642
2705
  mergeConfig(configPath, tool.configKey, serverConfig);
2643
2706
  toolLine(tool.name, success("connected"), "\u2714");
2644
2707
  }
2708
+ if (validation.serverUrl) {
2709
+ await Promise.all(
2710
+ selected.map(({ tool }) => pingMcp(validation.serverUrl, apiKey, tool.name))
2711
+ );
2712
+ }
2645
2713
  console.log();
2646
2714
  console.log(dim(" Restart your AI tools for changes to take effect."));
2647
2715
  console.log();
@@ -2674,7 +2742,7 @@ async function handleDisconnectTools() {
2674
2742
  console.log(dim(" No tools selected."));
2675
2743
  return;
2676
2744
  }
2677
- const proceed = await confirm6({
2745
+ const proceed = await confirm7({
2678
2746
  message: `Disconnect p\u0131ut from ${selected.length} tool(s)?`,
2679
2747
  default: false
2680
2748
  });
@@ -2692,9 +2760,62 @@ async function handleDisconnectTools() {
2692
2760
  console.log(dim(" Restart your AI tools for changes to take effect."));
2693
2761
  console.log();
2694
2762
  }
2763
+ async function handleViewBrain(apiKey) {
2764
+ console.log(dim(" Loading brain..."));
2765
+ const { sections, hasUnpublishedChanges } = await getBrain(apiKey);
2766
+ const SECTION_LABELS = {
2767
+ about: "About",
2768
+ soul: "Soul",
2769
+ areas: "Areas of Responsibility",
2770
+ projects: "Projects",
2771
+ memory: "Memory"
2772
+ };
2773
+ console.log();
2774
+ for (const [key, label] of Object.entries(SECTION_LABELS)) {
2775
+ const content = sections[key] || "";
2776
+ if (!content.trim()) {
2777
+ console.log(dim(` ${label} \u2014 (empty)`));
2778
+ } else {
2779
+ console.log(success(` ${label}`));
2780
+ for (const line of content.split("\n")) {
2781
+ console.log(` ${line}`);
2782
+ }
2783
+ }
2784
+ console.log();
2785
+ }
2786
+ console.log(dim(` Edit at ${brand("piut.com/dashboard")}`));
2787
+ console.log();
2788
+ if (hasUnpublishedChanges) {
2789
+ console.log(warning(" You have unpublished changes."));
2790
+ console.log();
2791
+ const wantPublish = await confirm7({
2792
+ message: "Publish now?",
2793
+ default: true
2794
+ });
2795
+ if (wantPublish) {
2796
+ try {
2797
+ await publishServer(apiKey);
2798
+ console.log();
2799
+ console.log(success(" \u2713 Brain published."));
2800
+ console.log();
2801
+ } catch (err) {
2802
+ console.log();
2803
+ const msg = err.message;
2804
+ if (msg === "REQUIRES_SUBSCRIPTION") {
2805
+ console.log(chalk10.yellow(" Deploy requires an active subscription ($10/mo)."));
2806
+ console.log(` Subscribe at: ${brand("https://piut.com/dashboard/billing")}`);
2807
+ console.log(dim(" 14-day free trial included."));
2808
+ } else {
2809
+ console.log(chalk10.red(` \u2717 ${msg}`));
2810
+ }
2811
+ console.log();
2812
+ }
2813
+ }
2814
+ }
2815
+ }
2695
2816
 
2696
2817
  // src/cli.ts
2697
- var VERSION = "3.4.0";
2818
+ var VERSION = "3.5.1";
2698
2819
  function withExit(fn) {
2699
2820
  return async (...args2) => {
2700
2821
  try {
@@ -2710,7 +2831,7 @@ program.name("piut").description("Build your AI brain instantly. Deploy it as an
2710
2831
  if (actionCommand.name() === "update") return;
2711
2832
  return checkForUpdate(VERSION);
2712
2833
  }).action(interactiveMenu);
2713
- program.command("build").description("Build or rebuild your brain from your files").option("-k, --key <key>", "API key").option("--folders <paths>", "Comma-separated folder paths to scan").action(withExit(buildCommand));
2834
+ program.command("build").description("Build or rebuild your brain from your files").option("-k, --key <key>", "API key").option("--folders <paths>", "Comma-separated folder paths to scan").option("-y, --yes", "Auto-publish after build").option("--no-publish", "Skip publish prompt after build").action(withExit(buildCommand));
2714
2835
  program.command("deploy").description("Publish your MCP server (requires paid account)").option("-k, --key <key>", "API key").action(withExit(deployCommand));
2715
2836
  program.command("connect").description("Add brain references to project config files").option("-k, --key <key>", "API key").option("-y, --yes", "Skip interactive prompts").option("--folders <paths>", "Comma-separated folder paths to scan").action(withExit(connectCommand));
2716
2837
  program.command("disconnect").description("Remove brain references from project config files").option("-y, --yes", "Skip interactive prompts").option("--folders <paths>", "Comma-separated folder paths to scan").action(withExit(disconnectCommand));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@piut/cli",
3
- "version": "3.4.0",
3
+ "version": "3.5.1",
4
4
  "description": "Build your AI brain instantly. Deploy it as an MCP server. Connect it to every project.",
5
5
  "type": "module",
6
6
  "bin": {