@piut/cli 1.1.0 → 2.0.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.
Files changed (3) hide show
  1. package/README.md +21 -16
  2. package/dist/cli.js +960 -58
  3. package/package.json +5 -6
package/README.md CHANGED
@@ -11,7 +11,8 @@ pıut does three things that build on each other:
11
11
  ## Quick Start
12
12
 
13
13
  ```bash
14
- npx @piut/cli
14
+ npm install -g @piut/cli
15
+ piut
15
16
  ```
16
17
 
17
18
  The CLI does everything in one interactive flow:
@@ -43,33 +44,37 @@ See [piut.com/docs](https://piut.com/docs#add-to-ai) for setup guides for 14+ AI
43
44
 
44
45
  ## CLI
45
46
 
46
- Install globally or run with `npx`:
47
+ Install globally, then use the `piut` command:
47
48
 
48
49
  ```bash
50
+ npm install -g @piut/cli
51
+
49
52
  # Setup & Configuration
50
- npx @piut/cli # Interactive setup: MCP + skill.md + cloud backup
51
- npx @piut/cli status # Show which tools are connected
52
- npx @piut/cli remove # Remove pıut from selected tools
53
+ piut # Interactive setup: MCP + skill.md + cloud backup
54
+ piut status # Show which tools are connected
55
+ piut remove # Remove pıut from selected tools
53
56
 
54
57
  # Cloud Backup
55
- npx @piut/cli sync # Show backup status for current workspace
56
- npx @piut/cli sync --install # Scan workspace, detect files, upload to cloud
57
- npx @piut/cli sync --push # Push local changes to cloud
58
- npx @piut/cli sync --pull # Pull cloud changes to local files
59
- npx @piut/cli sync --history # Show version history for a file
60
- npx @piut/cli sync --diff # Show diff between local and cloud
61
- npx @piut/cli sync --restore # Restore files from cloud backup
58
+ piut sync # Show backup status for current workspace
59
+ piut sync --install # Scan workspace, detect files, upload to cloud
60
+ piut sync --push # Push local changes to cloud
61
+ piut sync --pull # Pull cloud changes to local files
62
+ piut sync --history # Show version history for a file
63
+ piut sync --diff # Show diff between local and cloud
64
+ piut sync --restore # Restore files from cloud backup
62
65
  ```
63
66
 
64
67
  **Options:**
65
68
 
66
69
  ```bash
67
- npx @piut/cli --key pb_... # Pass API key non-interactively
68
- npx @piut/cli --tool cursor # Configure a single tool
69
- npx @piut/cli --skip-skill # Skip skill.md file placement
70
- npx @piut/cli sync --install --yes # Non-interactive backup setup
70
+ piut --key pb_... # Pass API key non-interactively
71
+ piut --tool cursor # Configure a single tool
72
+ piut --skip-skill # Skip skill.md file placement
73
+ piut sync --install --yes # Non-interactive backup setup
71
74
  ```
72
75
 
76
+ You can also use `npx @piut/cli` without installing globally.
77
+
73
78
  **Supported tools:** Claude Code, Claude Desktop, Cursor, Windsurf, GitHub Copilot, Amazon Q, Zed
74
79
 
75
80
  ## The Three Features
package/dist/cli.js CHANGED
@@ -22,6 +22,43 @@ async function validateKey(key) {
22
22
  }
23
23
  return res.json();
24
24
  }
25
+ async function buildBrain(key, input) {
26
+ const res = await fetch(`${API_BASE}/api/cli/build-brain`, {
27
+ method: "POST",
28
+ headers: {
29
+ Authorization: `Bearer ${key}`,
30
+ "Content-Type": "application/json"
31
+ },
32
+ body: JSON.stringify(input)
33
+ });
34
+ if (!res.ok) {
35
+ const body = await res.json().catch(() => ({ error: "Unknown error" }));
36
+ if (res.status === 429) {
37
+ throw new Error(body.error || "Rate limit exceeded (3 builds per day)");
38
+ }
39
+ throw new Error(body.error || `Build failed (HTTP ${res.status})`);
40
+ }
41
+ const data = await res.json();
42
+ return data.sections;
43
+ }
44
+ async function publishServer(key) {
45
+ const res = await fetch(`${API_BASE}/api/mcp/publish`, {
46
+ method: "POST",
47
+ headers: {
48
+ Authorization: `Bearer ${key}`,
49
+ "Content-Type": "application/json"
50
+ },
51
+ body: JSON.stringify({ published: true })
52
+ });
53
+ if (!res.ok) {
54
+ const body = await res.json().catch(() => ({ error: "Unknown error" }));
55
+ if (res.status === 402) {
56
+ throw new Error("REQUIRES_SUBSCRIPTION");
57
+ }
58
+ throw new Error(body.error || `Publish failed (HTTP ${res.status})`);
59
+ }
60
+ return res.json();
61
+ }
25
62
 
26
63
  // src/lib/tools.ts
27
64
  import os from "os";
@@ -427,16 +464,283 @@ function isCommandAvailable(cmd) {
427
464
  }
428
465
 
429
466
  // src/commands/status.ts
467
+ import fs5 from "fs";
468
+ import path7 from "path";
469
+
470
+ // src/lib/brain-scanner.ts
430
471
  import fs4 from "fs";
472
+ import path6 from "path";
473
+ import os3 from "os";
474
+ var home2 = os3.homedir();
475
+ var SKIP_DIRS = /* @__PURE__ */ new Set([
476
+ "node_modules",
477
+ ".git",
478
+ "__pycache__",
479
+ ".venv",
480
+ "venv",
481
+ "dist",
482
+ "build",
483
+ ".next",
484
+ ".nuxt",
485
+ ".output",
486
+ ".Trash",
487
+ "Library",
488
+ ".cache",
489
+ ".npm",
490
+ ".yarn",
491
+ ".pnpm-store",
492
+ "Caches",
493
+ "Cache"
494
+ ]);
495
+ var FULL_READ_FILES = /* @__PURE__ */ new Set([
496
+ "README.md",
497
+ "CLAUDE.md",
498
+ ".cursorrules",
499
+ ".windsurfrules",
500
+ "AGENTS.md",
501
+ "CONVENTIONS.md",
502
+ "MEMORY.md",
503
+ "SOUL.md",
504
+ "IDENTITY.md"
505
+ ]);
506
+ var MAX_FILE_SIZE = 100 * 1024;
507
+ var RECENT_DAYS = 30;
508
+ function shouldSkipDir(name) {
509
+ if (name.startsWith(".") && name !== ".claude" && name !== ".cursor" && name !== ".windsurf" && name !== ".github" && name !== ".zed") {
510
+ return true;
511
+ }
512
+ return SKIP_DIRS.has(name);
513
+ }
514
+ function readFileSafe(filePath) {
515
+ try {
516
+ const stat = fs4.statSync(filePath);
517
+ if (stat.size > MAX_FILE_SIZE) return null;
518
+ if (!stat.isFile()) return null;
519
+ return fs4.readFileSync(filePath, "utf-8");
520
+ } catch {
521
+ return null;
522
+ }
523
+ }
524
+ function getFolderTree(dir, depth = 0, maxDepth = 3) {
525
+ if (depth >= maxDepth) return [];
526
+ const entries = [];
527
+ try {
528
+ const items = fs4.readdirSync(dir, { withFileTypes: true });
529
+ for (const item of items) {
530
+ if (!item.isDirectory()) continue;
531
+ if (shouldSkipDir(item.name)) continue;
532
+ const indent = " ".repeat(depth);
533
+ entries.push(`${indent}${item.name}/`);
534
+ entries.push(...getFolderTree(path6.join(dir, item.name), depth + 1, maxDepth));
535
+ }
536
+ } catch {
537
+ }
538
+ return entries;
539
+ }
540
+ function detectProjects(scanDirs) {
541
+ const projects = [];
542
+ for (const dir of scanDirs) {
543
+ try {
544
+ const items = fs4.readdirSync(dir, { withFileTypes: true });
545
+ for (const item of items) {
546
+ if (!item.isDirectory()) continue;
547
+ if (shouldSkipDir(item.name)) continue;
548
+ const projectPath = path6.join(dir, item.name);
549
+ const hasGit = fs4.existsSync(path6.join(projectPath, ".git"));
550
+ const hasPkgJson = fs4.existsSync(path6.join(projectPath, "package.json"));
551
+ const hasCargoToml = fs4.existsSync(path6.join(projectPath, "Cargo.toml"));
552
+ const hasPyproject = fs4.existsSync(path6.join(projectPath, "pyproject.toml"));
553
+ const hasGoMod = fs4.existsSync(path6.join(projectPath, "go.mod"));
554
+ if (!hasGit && !hasPkgJson && !hasCargoToml && !hasPyproject && !hasGoMod) continue;
555
+ let description = "";
556
+ if (hasPkgJson) {
557
+ try {
558
+ const pkg = JSON.parse(fs4.readFileSync(path6.join(projectPath, "package.json"), "utf-8"));
559
+ description = pkg.description || "";
560
+ } catch {
561
+ }
562
+ }
563
+ const readmePath = path6.join(projectPath, "README.md");
564
+ if (!description && fs4.existsSync(readmePath)) {
565
+ const content = readFileSafe(readmePath);
566
+ if (content) {
567
+ const lines = content.split("\n");
568
+ let foundHeading = false;
569
+ for (const line of lines) {
570
+ if (line.startsWith("#")) {
571
+ foundHeading = true;
572
+ continue;
573
+ }
574
+ if (foundHeading && line.trim()) {
575
+ description = line.trim().slice(0, 200);
576
+ break;
577
+ }
578
+ }
579
+ }
580
+ }
581
+ projects.push({
582
+ name: item.name,
583
+ path: projectPath,
584
+ description,
585
+ hasClaudeMd: fs4.existsSync(path6.join(projectPath, "CLAUDE.md")),
586
+ hasCursorRules: fs4.existsSync(path6.join(projectPath, ".cursorrules")) || fs4.existsSync(path6.join(projectPath, ".cursor", "rules")),
587
+ hasWindsurfRules: fs4.existsSync(path6.join(projectPath, ".windsurfrules")) || fs4.existsSync(path6.join(projectPath, ".windsurf", "rules")),
588
+ hasCopilotInstructions: fs4.existsSync(path6.join(projectPath, ".github", "copilot-instructions.md")),
589
+ hasConventionsMd: fs4.existsSync(path6.join(projectPath, "CONVENTIONS.md")),
590
+ hasZedRules: fs4.existsSync(path6.join(projectPath, ".zed", "rules.md"))
591
+ });
592
+ }
593
+ } catch {
594
+ }
595
+ }
596
+ return projects;
597
+ }
598
+ function collectConfigFiles(projects) {
599
+ const configs = [];
600
+ const globalPaths = [
601
+ path6.join(home2, ".claude", "MEMORY.md"),
602
+ path6.join(home2, ".claude", "CLAUDE.md"),
603
+ path6.join(home2, ".openclaw", "workspace", "SOUL.md"),
604
+ path6.join(home2, ".openclaw", "workspace", "MEMORY.md")
605
+ ];
606
+ for (const gp of globalPaths) {
607
+ const content = readFileSafe(gp);
608
+ if (content && content.trim()) {
609
+ configs.push({ name: `~/${path6.relative(home2, gp)}`, content });
610
+ }
611
+ }
612
+ for (const project of projects) {
613
+ for (const fileName of FULL_READ_FILES) {
614
+ const filePath = path6.join(project.path, fileName);
615
+ const content = readFileSafe(filePath);
616
+ if (content && content.trim()) {
617
+ configs.push({ name: `${project.name}/${fileName}`, content });
618
+ }
619
+ }
620
+ const pkgPath = path6.join(project.path, "package.json");
621
+ const pkgContent = readFileSafe(pkgPath);
622
+ if (pkgContent) {
623
+ try {
624
+ const pkg = JSON.parse(pkgContent);
625
+ const summary = JSON.stringify({ name: pkg.name, description: pkg.description }, null, 2);
626
+ configs.push({ name: `${project.name}/package.json`, content: summary });
627
+ } catch {
628
+ }
629
+ }
630
+ }
631
+ return configs;
632
+ }
633
+ function collectRecentDocs(projects) {
634
+ const docs = [];
635
+ const cutoff = Date.now() - RECENT_DAYS * 24 * 60 * 60 * 1e3;
636
+ const seen = /* @__PURE__ */ new Set();
637
+ for (const project of projects) {
638
+ try {
639
+ const items = fs4.readdirSync(project.path, { withFileTypes: true });
640
+ for (const item of items) {
641
+ if (!item.isFile()) continue;
642
+ if (!item.name.endsWith(".md")) continue;
643
+ if (FULL_READ_FILES.has(item.name)) continue;
644
+ if (item.name.startsWith(".")) continue;
645
+ const filePath = path6.join(project.path, item.name);
646
+ if (seen.has(filePath)) continue;
647
+ seen.add(filePath);
648
+ try {
649
+ const stat = fs4.statSync(filePath);
650
+ if (stat.mtimeMs < cutoff) continue;
651
+ if (stat.size > MAX_FILE_SIZE) continue;
652
+ const content = fs4.readFileSync(filePath, "utf-8");
653
+ if (content.trim()) {
654
+ docs.push({ name: `${project.name}/${item.name}`, content });
655
+ }
656
+ } catch {
657
+ }
658
+ }
659
+ } catch {
660
+ }
661
+ }
662
+ return docs.slice(0, 20);
663
+ }
664
+ function getDefaultScanDirs() {
665
+ const dirs = [];
666
+ const candidates = [
667
+ path6.join(home2, "Projects"),
668
+ path6.join(home2, "projects"),
669
+ path6.join(home2, "Developer"),
670
+ path6.join(home2, "dev"),
671
+ path6.join(home2, "Code"),
672
+ path6.join(home2, "code"),
673
+ path6.join(home2, "src"),
674
+ path6.join(home2, "repos"),
675
+ path6.join(home2, "workspace"),
676
+ path6.join(home2, "Workspace"),
677
+ path6.join(home2, "Documents"),
678
+ path6.join(home2, "Desktop")
679
+ ];
680
+ for (const dir of candidates) {
681
+ if (fs4.existsSync(dir) && fs4.statSync(dir).isDirectory()) {
682
+ dirs.push(dir);
683
+ }
684
+ }
685
+ if (dirs.length === 0) {
686
+ dirs.push(home2);
687
+ }
688
+ return dirs;
689
+ }
690
+ function scanForBrain(folders) {
691
+ const scanDirs = folders || getDefaultScanDirs();
692
+ const folderTree = [];
693
+ for (const dir of scanDirs) {
694
+ folderTree.push(`${path6.basename(dir)}/`);
695
+ folderTree.push(...getFolderTree(dir, 1));
696
+ }
697
+ const projects = detectProjects(scanDirs);
698
+ const configFiles = collectConfigFiles(projects);
699
+ const recentDocuments = collectRecentDocs(projects);
700
+ return {
701
+ summary: {
702
+ folders: folderTree,
703
+ projects: projects.map((p) => ({
704
+ name: p.name,
705
+ path: p.path.replace(home2, "~"),
706
+ description: p.description
707
+ })),
708
+ configFiles,
709
+ recentDocuments
710
+ }
711
+ };
712
+ }
713
+ function scanForProjects(folders) {
714
+ const scanDirs = folders || getDefaultScanDirs();
715
+ return detectProjects(scanDirs);
716
+ }
717
+
718
+ // src/commands/status.ts
719
+ var PIUT_FILES = [
720
+ "CLAUDE.md",
721
+ ".cursor/rules/piut.mdc",
722
+ ".windsurf/rules/piut.md",
723
+ ".github/copilot-instructions.md",
724
+ "CONVENTIONS.md",
725
+ ".zed/rules.md"
726
+ ];
727
+ function hasPiutReference(filePath) {
728
+ try {
729
+ const content = fs5.readFileSync(filePath, "utf-8");
730
+ return content.includes("p\u0131ut Context") || content.includes("piut Context");
731
+ } catch {
732
+ return false;
733
+ }
734
+ }
431
735
  function statusCommand() {
432
736
  banner();
433
- console.log(" AI tool configuration status:");
737
+ console.log(" AI tool configuration:");
434
738
  console.log();
435
739
  let foundAny = false;
436
740
  for (const tool of TOOLS) {
437
741
  const paths = resolveConfigPaths(tool.configPaths);
438
742
  for (const configPath of paths) {
439
- if (!fs4.existsSync(configPath)) continue;
743
+ if (!fs5.existsSync(configPath)) continue;
440
744
  foundAny = true;
441
745
  const configured = isPiutConfigured(configPath, tool.configKey);
442
746
  if (configured) {
@@ -449,13 +753,38 @@ function statusCommand() {
449
753
  }
450
754
  if (!foundAny) {
451
755
  console.log(warning(" No supported AI tools detected."));
452
- console.log(dim(" Supported: Claude Code, Claude Desktop, Cursor, Windsurf, GitHub Copilot, Amazon Q, Zed"));
756
+ console.log(dim(" Run ") + brand("piut setup") + dim(" to configure your AI tools."));
757
+ }
758
+ console.log();
759
+ console.log(" Connected projects:");
760
+ console.log();
761
+ const projects = scanForProjects();
762
+ let connectedCount = 0;
763
+ for (const project of projects) {
764
+ const connectedFiles = [];
765
+ for (const file of PIUT_FILES) {
766
+ const absPath = path7.join(project.path, file);
767
+ if (fs5.existsSync(absPath) && hasPiutReference(absPath)) {
768
+ connectedFiles.push(file);
769
+ }
770
+ }
771
+ if (connectedFiles.length > 0) {
772
+ connectedCount++;
773
+ console.log(success(` \u2714 ${project.name}`) + dim(` (${connectedFiles.join(", ")})`));
774
+ }
775
+ }
776
+ if (connectedCount === 0) {
777
+ console.log(dim(" No projects connected."));
778
+ console.log(dim(" Run ") + brand("piut connect") + dim(" to add brain references to your projects."));
779
+ } else {
780
+ console.log();
781
+ console.log(dim(` ${connectedCount} project(s) connected to your brain.`));
453
782
  }
454
783
  console.log();
455
784
  }
456
785
 
457
786
  // src/commands/remove.ts
458
- import fs5 from "fs";
787
+ import fs6 from "fs";
459
788
  import { checkbox as checkbox2, confirm as confirm2 } from "@inquirer/prompts";
460
789
  async function removeCommand() {
461
790
  banner();
@@ -463,7 +792,7 @@ async function removeCommand() {
463
792
  for (const tool of TOOLS) {
464
793
  const paths = resolveConfigPaths(tool.configPaths);
465
794
  for (const configPath of paths) {
466
- if (fs5.existsSync(configPath) && isPiutConfigured(configPath, tool.configKey)) {
795
+ if (fs6.existsSync(configPath) && isPiutConfigured(configPath, tool.configKey)) {
467
796
  configured.push({ tool, configPath });
468
797
  break;
469
798
  }
@@ -506,15 +835,15 @@ async function removeCommand() {
506
835
  }
507
836
 
508
837
  // src/commands/sync.ts
509
- import fs8 from "fs";
838
+ import fs9 from "fs";
510
839
  import crypto2 from "crypto";
511
840
  import { password as password2, confirm as confirm3, checkbox as checkbox3, select } from "@inquirer/prompts";
512
841
  import chalk3 from "chalk";
513
842
 
514
843
  // src/lib/scanner.ts
515
- import fs6 from "fs";
516
- import path6 from "path";
517
- import os3 from "os";
844
+ import fs7 from "fs";
845
+ import path8 from "path";
846
+ import os4 from "os";
518
847
  var KNOWN_FILES = [
519
848
  "CLAUDE.md",
520
849
  ".claude/MEMORY.md",
@@ -543,7 +872,7 @@ function detectInstalledTools() {
543
872
  for (const tool of TOOLS) {
544
873
  const paths = resolveConfigPaths(tool.configPaths);
545
874
  for (const configPath of paths) {
546
- if (fs6.existsSync(configPath) || fs6.existsSync(path6.dirname(configPath))) {
875
+ if (fs7.existsSync(configPath) || fs7.existsSync(path8.dirname(configPath))) {
547
876
  installed.push({ name: tool.name, id: tool.id });
548
877
  break;
549
878
  }
@@ -572,22 +901,22 @@ function scanDirectory(dir, patterns) {
572
901
  for (const pattern of patterns) {
573
902
  const parts = pattern.split("/");
574
903
  if (parts.length === 1) {
575
- const filePath = path6.join(dir, pattern);
576
- if (fs6.existsSync(filePath) && fs6.statSync(filePath).isFile()) {
904
+ const filePath = path8.join(dir, pattern);
905
+ if (fs7.existsSync(filePath) && fs7.statSync(filePath).isFile()) {
577
906
  found.push(filePath);
578
907
  }
579
908
  } else {
580
909
  const lastPart = parts[parts.length - 1];
581
910
  const dirPart = parts.slice(0, -1).join("/");
582
- const fullDir = path6.join(dir, dirPart);
911
+ const fullDir = path8.join(dir, dirPart);
583
912
  if (lastPart.includes("*")) {
584
- if (fs6.existsSync(fullDir) && fs6.statSync(fullDir).isDirectory()) {
913
+ if (fs7.existsSync(fullDir) && fs7.statSync(fullDir).isDirectory()) {
585
914
  try {
586
- const entries = fs6.readdirSync(fullDir);
915
+ const entries = fs7.readdirSync(fullDir);
587
916
  for (const entry of entries) {
588
917
  if (matchesGlob(entry, lastPart)) {
589
- const fullPath = path6.join(fullDir, entry);
590
- if (fs6.statSync(fullPath).isFile()) {
918
+ const fullPath = path8.join(fullDir, entry);
919
+ if (fs7.statSync(fullPath).isFile()) {
591
920
  found.push(fullPath);
592
921
  }
593
922
  }
@@ -596,8 +925,8 @@ function scanDirectory(dir, patterns) {
596
925
  }
597
926
  }
598
927
  } else {
599
- const filePath = path6.join(dir, pattern);
600
- if (fs6.existsSync(filePath) && fs6.statSync(filePath).isFile()) {
928
+ const filePath = path8.join(dir, pattern);
929
+ if (fs7.existsSync(filePath) && fs7.statSync(filePath).isFile()) {
601
930
  found.push(filePath);
602
931
  }
603
932
  }
@@ -606,18 +935,18 @@ function scanDirectory(dir, patterns) {
606
935
  return found;
607
936
  }
608
937
  function scanForFiles(workspaceDirs) {
609
- const home2 = os3.homedir();
938
+ const home3 = os4.homedir();
610
939
  const files = [];
611
940
  const seen = /* @__PURE__ */ new Set();
612
941
  for (const globalPath of GLOBAL_FILES) {
613
942
  const absPath = expandPath(globalPath);
614
- if (fs6.existsSync(absPath) && fs6.statSync(absPath).isFile()) {
943
+ if (fs7.existsSync(absPath) && fs7.statSync(absPath).isFile()) {
615
944
  if (seen.has(absPath)) continue;
616
945
  seen.add(absPath);
617
946
  files.push({
618
947
  absolutePath: absPath,
619
948
  displayPath: globalPath,
620
- sizeBytes: fs6.statSync(absPath).size,
949
+ sizeBytes: fs7.statSync(absPath).size,
621
950
  category: "Global",
622
951
  type: "global",
623
952
  projectName: "Global"
@@ -626,18 +955,18 @@ function scanForFiles(workspaceDirs) {
626
955
  }
627
956
  const dirs = workspaceDirs || [process.cwd()];
628
957
  for (const dir of dirs) {
629
- const absDir = path6.resolve(dir);
630
- if (!fs6.existsSync(absDir)) continue;
958
+ const absDir = path8.resolve(dir);
959
+ if (!fs7.existsSync(absDir)) continue;
631
960
  const foundPaths = scanDirectory(absDir, KNOWN_FILES);
632
961
  for (const filePath of foundPaths) {
633
962
  if (seen.has(filePath)) continue;
634
963
  seen.add(filePath);
635
- const relativePath = path6.relative(absDir, filePath);
636
- const projectName = path6.basename(absDir);
964
+ const relativePath = path8.relative(absDir, filePath);
965
+ const projectName = path8.basename(absDir);
637
966
  files.push({
638
967
  absolutePath: filePath,
639
- displayPath: path6.relative(home2, filePath).startsWith("..") ? filePath : "~/" + path6.relative(home2, filePath),
640
- sizeBytes: fs6.statSync(filePath).size,
968
+ displayPath: path8.relative(home3, filePath).startsWith("..") ? filePath : "~/" + path8.relative(home3, filePath),
969
+ sizeBytes: fs7.statSync(filePath).size,
641
970
  category: categorizeFile(filePath),
642
971
  type: "project",
643
972
  projectName
@@ -730,14 +1059,14 @@ async function resolveConflict(apiKey, fileId, resolution, localContent, deviceI
730
1059
  }
731
1060
 
732
1061
  // src/lib/sync-config.ts
733
- import fs7 from "fs";
734
- import path7 from "path";
735
- import os4 from "os";
1062
+ import fs8 from "fs";
1063
+ import path9 from "path";
1064
+ import os5 from "os";
736
1065
  import crypto from "crypto";
737
- var CONFIG_DIR = path7.join(os4.homedir(), ".piut");
738
- var CONFIG_FILE = path7.join(CONFIG_DIR, "config.json");
1066
+ var CONFIG_DIR = path9.join(os5.homedir(), ".piut");
1067
+ var CONFIG_FILE = path9.join(CONFIG_DIR, "config.json");
739
1068
  function defaultDeviceName() {
740
- return os4.hostname() || "unknown";
1069
+ return os5.hostname() || "unknown";
741
1070
  }
742
1071
  function generateDeviceId() {
743
1072
  return `dev_${crypto.randomBytes(8).toString("hex")}`;
@@ -752,7 +1081,7 @@ function readSyncConfig() {
752
1081
  backedUpFiles: []
753
1082
  };
754
1083
  try {
755
- const raw = fs7.readFileSync(CONFIG_FILE, "utf-8");
1084
+ const raw = fs8.readFileSync(CONFIG_FILE, "utf-8");
756
1085
  const parsed = JSON.parse(raw);
757
1086
  return { ...defaults, ...parsed };
758
1087
  } catch {
@@ -760,8 +1089,8 @@ function readSyncConfig() {
760
1089
  }
761
1090
  }
762
1091
  function writeSyncConfig(config) {
763
- fs7.mkdirSync(CONFIG_DIR, { recursive: true });
764
- fs7.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2) + "\n", "utf-8");
1092
+ fs8.mkdirSync(CONFIG_DIR, { recursive: true });
1093
+ fs8.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2) + "\n", "utf-8");
765
1094
  }
766
1095
  function updateSyncConfig(updates) {
767
1096
  const config = readSyncConfig();
@@ -880,7 +1209,7 @@ function guardAndFilter(files, options) {
880
1209
  const safe = [];
881
1210
  let blocked = 0;
882
1211
  for (const file of files) {
883
- const content = fs8.readFileSync(file.absolutePath, "utf-8");
1212
+ const content = fs9.readFileSync(file.absolutePath, "utf-8");
884
1213
  const result = guardFile(file.displayPath, content);
885
1214
  if (result.blocked) {
886
1215
  blocked++;
@@ -1065,7 +1394,7 @@ async function pushFlow(options) {
1065
1394
  try {
1066
1395
  const cloudFiles = await listFiles(apiKey);
1067
1396
  for (const localFile of safeFiles) {
1068
- const localContent = fs8.readFileSync(localFile.absolutePath, "utf-8");
1397
+ const localContent = fs9.readFileSync(localFile.absolutePath, "utf-8");
1069
1398
  const localHash = hashContent(localContent);
1070
1399
  const cloudFile = cloudFiles.files.find(
1071
1400
  (cf) => cf.file_path === localFile.displayPath && cf.project_name === localFile.projectName
@@ -1169,14 +1498,14 @@ async function diffFlow(filePathOrId) {
1169
1498
  const cloudVersion = await getFileVersion(config.apiKey, fileId, versions.currentVersion);
1170
1499
  const cloudContent = cloudVersion.content;
1171
1500
  const localPath = resolveLocalPath(filePathOrId, versions.filePath);
1172
- if (!localPath || !fs8.existsSync(localPath)) {
1501
+ if (!localPath || !fs9.existsSync(localPath)) {
1173
1502
  console.log(warning(` Local file not found: ${filePathOrId}`));
1174
1503
  console.log(dim(" Showing cloud content only:"));
1175
1504
  console.log();
1176
1505
  console.log(cloudContent);
1177
1506
  return;
1178
1507
  }
1179
- const localContent = fs8.readFileSync(localPath, "utf-8");
1508
+ const localContent = fs9.readFileSync(localPath, "utf-8");
1180
1509
  if (localContent === cloudContent) {
1181
1510
  console.log(success(" \u2713 Local and cloud are identical"));
1182
1511
  console.log();
@@ -1262,7 +1591,7 @@ async function restoreFlow(filePathOrId) {
1262
1591
  default: true
1263
1592
  });
1264
1593
  if (writeLocal) {
1265
- fs8.writeFileSync(localPath, versionData.content, "utf-8");
1594
+ fs9.writeFileSync(localPath, versionData.content, "utf-8");
1266
1595
  console.log(success(` \u2713 Local file updated: ${localPath}`));
1267
1596
  }
1268
1597
  }
@@ -1314,7 +1643,7 @@ async function watchFlow() {
1314
1643
  debounceMap.delete(changedPath);
1315
1644
  const file = safeFiles.find((f) => f.absolutePath === changedPath);
1316
1645
  if (!file) return;
1317
- const content = fs8.readFileSync(changedPath, "utf-8");
1646
+ const content = fs9.readFileSync(changedPath, "utf-8");
1318
1647
  const guardResult = guardFile(file.displayPath, content);
1319
1648
  if (guardResult.blocked) {
1320
1649
  console.log(chalk3.red(` BLOCKED ${file.displayPath}`) + dim(" (sensitive content detected)"));
@@ -1410,9 +1739,9 @@ async function installMacDaemon() {
1410
1739
  console.log(dim(" Cancelled."));
1411
1740
  return;
1412
1741
  }
1413
- fs8.mkdirSync(logDir, { recursive: true });
1414
- fs8.mkdirSync(plistDir, { recursive: true });
1415
- fs8.writeFileSync(plistPath, plistContent, "utf-8");
1742
+ fs9.mkdirSync(logDir, { recursive: true });
1743
+ fs9.mkdirSync(plistDir, { recursive: true });
1744
+ fs9.writeFileSync(plistPath, plistContent, "utf-8");
1416
1745
  console.log(success(` \u2713 Plist written: ${plistPath}`));
1417
1746
  const { execSync: execSync2 } = await import("child_process");
1418
1747
  try {
@@ -1485,7 +1814,7 @@ async function uploadScannedFiles(apiKey, files) {
1485
1814
  const payloads = files.map((file) => ({
1486
1815
  projectName: file.projectName,
1487
1816
  filePath: file.displayPath,
1488
- content: fs8.readFileSync(file.absolutePath, "utf-8"),
1817
+ content: fs9.readFileSync(file.absolutePath, "utf-8"),
1489
1818
  category: file.type,
1490
1819
  deviceId: syncConfig.deviceId,
1491
1820
  deviceName: syncConfig.deviceName
@@ -1531,14 +1860,14 @@ async function resolveFileId(apiKey, pathOrId) {
1531
1860
  return match?.id || null;
1532
1861
  }
1533
1862
  function resolveLocalPath(input, cloudPath) {
1534
- if (fs8.existsSync(input)) return input;
1535
- const home2 = process.env.HOME || "";
1863
+ if (fs9.existsSync(input)) return input;
1864
+ const home3 = process.env.HOME || "";
1536
1865
  if (cloudPath.startsWith("~/")) {
1537
- const expanded = cloudPath.replace("~", home2);
1538
- if (fs8.existsSync(expanded)) return expanded;
1866
+ const expanded = cloudPath.replace("~", home3);
1867
+ if (fs9.existsSync(expanded)) return expanded;
1539
1868
  }
1540
1869
  const cwdPath = `${process.cwd()}/${cloudPath}`;
1541
- if (fs8.existsSync(cwdPath)) return cwdPath;
1870
+ if (fs9.existsSync(cwdPath)) return cwdPath;
1542
1871
  return null;
1543
1872
  }
1544
1873
  async function resolveNpxPath() {
@@ -1665,12 +1994,585 @@ function parseBool(value) {
1665
1994
  return null;
1666
1995
  }
1667
1996
 
1997
+ // src/commands/build.ts
1998
+ import { select as select2 } from "@inquirer/prompts";
1999
+ import chalk6 from "chalk";
2000
+
2001
+ // src/lib/auth.ts
2002
+ import { password as password3 } from "@inquirer/prompts";
2003
+ import chalk5 from "chalk";
2004
+ async function resolveApiKey(keyOption) {
2005
+ const config = readSyncConfig();
2006
+ let apiKey = keyOption || config.apiKey;
2007
+ if (!apiKey) {
2008
+ apiKey = await password3({
2009
+ message: "Enter your p\u0131ut API key:",
2010
+ mask: "*",
2011
+ validate: (v) => v.startsWith("pb_") || "Key must start with pb_"
2012
+ });
2013
+ }
2014
+ console.log(dim(" Validating key..."));
2015
+ let result;
2016
+ try {
2017
+ result = await validateKey(apiKey);
2018
+ } catch (err) {
2019
+ console.log(chalk5.red(` \u2717 ${err.message}`));
2020
+ console.log(dim(" Get a key at https://piut.com/dashboard/keys"));
2021
+ process.exit(1);
2022
+ }
2023
+ console.log(success(` \u2713 Connected as ${result.displayName} (${result.slug})`));
2024
+ updateSyncConfig({ apiKey });
2025
+ return apiKey;
2026
+ }
2027
+ async function resolveApiKeyWithResult(keyOption) {
2028
+ const config = readSyncConfig();
2029
+ let apiKey = keyOption || config.apiKey;
2030
+ if (!apiKey) {
2031
+ apiKey = await password3({
2032
+ message: "Enter your p\u0131ut API key:",
2033
+ mask: "*",
2034
+ validate: (v) => v.startsWith("pb_") || "Key must start with pb_"
2035
+ });
2036
+ }
2037
+ console.log(dim(" Validating key..."));
2038
+ let result;
2039
+ try {
2040
+ result = await validateKey(apiKey);
2041
+ } catch (err) {
2042
+ console.log(chalk5.red(` \u2717 ${err.message}`));
2043
+ console.log(dim(" Get a key at https://piut.com/dashboard/keys"));
2044
+ process.exit(1);
2045
+ }
2046
+ console.log(success(` \u2713 Connected as ${result.displayName} (${result.slug})`));
2047
+ updateSyncConfig({ apiKey });
2048
+ return { apiKey, ...result };
2049
+ }
2050
+
2051
+ // src/commands/build.ts
2052
+ async function buildCommand(options) {
2053
+ banner();
2054
+ const apiKey = await resolveApiKey(options.key);
2055
+ let scanFolders;
2056
+ if (options.folders) {
2057
+ scanFolders = options.folders.split(",").map((f) => expandPath(f.trim()));
2058
+ }
2059
+ const mode = scanFolders ? "auto" : await select2({
2060
+ message: "How do you want to build your brain?",
2061
+ choices: [
2062
+ { name: "Automatically (recommended)", value: "auto", description: "Scan your files and build automatically" },
2063
+ { name: "Select folder(s)...", value: "folders", description: "Choose specific folders to scan" }
2064
+ ]
2065
+ });
2066
+ if (mode === "folders" && !scanFolders) {
2067
+ const defaults = getDefaultScanDirs();
2068
+ console.log();
2069
+ console.log(dim(" Detected directories:"));
2070
+ for (const d of defaults) {
2071
+ console.log(dim(` ${d}`));
2072
+ }
2073
+ console.log();
2074
+ console.log(dim(" Tip: pass --folders ~/Projects,~/Documents to specify directly"));
2075
+ scanFolders = defaults;
2076
+ }
2077
+ console.log();
2078
+ console.log(dim(" Building your brain..."));
2079
+ console.log();
2080
+ const input = scanForBrain(scanFolders);
2081
+ const projCount = input.summary.projects.length;
2082
+ const configCount = input.summary.configFiles.length;
2083
+ const docCount = input.summary.recentDocuments.length;
2084
+ console.log(dim(` Scanned: ${projCount} projects, ${configCount} config files, ${docCount} recent docs`));
2085
+ console.log();
2086
+ if (projCount === 0 && configCount === 0) {
2087
+ console.log(chalk6.yellow(" No projects or config files found to build from."));
2088
+ console.log(dim(" Try running from a directory with your projects, or use --folders."));
2089
+ console.log();
2090
+ return;
2091
+ }
2092
+ try {
2093
+ const sections = await buildBrain(apiKey, input);
2094
+ const sectionSummary = (content, label) => {
2095
+ if (!content || !content.trim()) {
2096
+ console.log(dim(` ${label} \u2014 (empty)`));
2097
+ } else {
2098
+ const firstLine = content.split("\n").find((l) => l.trim() && !l.startsWith("#"))?.trim() || "";
2099
+ const preview = firstLine.length > 60 ? firstLine.slice(0, 60) + "..." : firstLine;
2100
+ console.log(success(` ${label}`) + dim(` \u2014 ${preview}`));
2101
+ }
2102
+ };
2103
+ console.log(success(" Brain built!"));
2104
+ console.log();
2105
+ sectionSummary(sections.about, "About");
2106
+ sectionSummary(sections.soul, "Soul");
2107
+ sectionSummary(sections.areas, "Areas of Responsibility");
2108
+ sectionSummary(sections.projects, "Projects");
2109
+ sectionSummary(sections.memory, "Memory");
2110
+ console.log();
2111
+ console.log(dim(` Review and edit at ${brand("piut.com/dashboard")}`));
2112
+ console.log();
2113
+ } catch (err) {
2114
+ console.log(chalk6.red(` \u2717 ${err.message}`));
2115
+ process.exit(1);
2116
+ }
2117
+ }
2118
+
2119
+ // src/commands/deploy.ts
2120
+ import { confirm as confirm4 } from "@inquirer/prompts";
2121
+ import chalk7 from "chalk";
2122
+ async function deployCommand(options) {
2123
+ banner();
2124
+ const { apiKey, slug, serverUrl } = await resolveApiKeyWithResult(options.key);
2125
+ console.log();
2126
+ console.log(dim(" Your brain will be published as an MCP server at:"));
2127
+ console.log(` ${brand(serverUrl)}`);
2128
+ console.log();
2129
+ console.log(dim(" Any AI tool with this URL can read your brain."));
2130
+ console.log();
2131
+ if (!options.yes) {
2132
+ const proceed = await confirm4({
2133
+ message: "Deploy?",
2134
+ default: true
2135
+ });
2136
+ if (!proceed) {
2137
+ console.log(dim(" Cancelled."));
2138
+ return;
2139
+ }
2140
+ }
2141
+ try {
2142
+ await publishServer(apiKey);
2143
+ console.log();
2144
+ console.log(success(" \u2713 Brain deployed. MCP server live."));
2145
+ console.log(dim(` URL: ${serverUrl}`));
2146
+ console.log();
2147
+ console.log(dim(" Next: run ") + brand("piut connect") + dim(" to add brain references to your projects."));
2148
+ console.log();
2149
+ } catch (err) {
2150
+ const msg = err.message;
2151
+ if (msg === "REQUIRES_SUBSCRIPTION") {
2152
+ console.log();
2153
+ console.log(chalk7.yellow(" Deploy requires an active subscription ($10/mo)."));
2154
+ console.log();
2155
+ console.log(` Subscribe at: ${brand("https://piut.com/dashboard/billing")}`);
2156
+ console.log(dim(" 14-day free trial included."));
2157
+ console.log();
2158
+ } else {
2159
+ console.log(chalk7.red(` \u2717 ${msg}`));
2160
+ process.exit(1);
2161
+ }
2162
+ }
2163
+ }
2164
+
2165
+ // src/commands/connect.ts
2166
+ import fs10 from "fs";
2167
+ import path10 from "path";
2168
+ import { checkbox as checkbox4 } from "@inquirer/prompts";
2169
+ var RULE_FILES = [
2170
+ {
2171
+ tool: "Claude Code",
2172
+ filePath: "CLAUDE.md",
2173
+ strategy: "append",
2174
+ detect: (p) => p.hasClaudeMd || fs10.existsSync(path10.join(p.path, ".claude"))
2175
+ },
2176
+ {
2177
+ tool: "Cursor",
2178
+ filePath: ".cursor/rules/piut.mdc",
2179
+ strategy: "create",
2180
+ detect: (p) => p.hasCursorRules || fs10.existsSync(path10.join(p.path, ".cursor"))
2181
+ },
2182
+ {
2183
+ tool: "Windsurf",
2184
+ filePath: ".windsurf/rules/piut.md",
2185
+ strategy: "create",
2186
+ detect: (p) => p.hasWindsurfRules || fs10.existsSync(path10.join(p.path, ".windsurf"))
2187
+ },
2188
+ {
2189
+ tool: "GitHub Copilot",
2190
+ filePath: ".github/copilot-instructions.md",
2191
+ strategy: "append",
2192
+ detect: (p) => p.hasCopilotInstructions || fs10.existsSync(path10.join(p.path, ".github"))
2193
+ },
2194
+ {
2195
+ tool: "Amazon Q",
2196
+ filePath: "CONVENTIONS.md",
2197
+ strategy: "append",
2198
+ detect: (p) => p.hasConventionsMd
2199
+ },
2200
+ {
2201
+ tool: "Zed",
2202
+ filePath: ".zed/rules.md",
2203
+ strategy: "create",
2204
+ detect: (p) => p.hasZedRules || fs10.existsSync(path10.join(p.path, ".zed"))
2205
+ }
2206
+ ];
2207
+ var DEDICATED_FILE_CONTENT = `## p\u0131ut Context
2208
+ This project uses p\u0131ut for persistent personal context.
2209
+ Skill reference: https://raw.githubusercontent.com/M-Flat-Inc/piut/main/skill.md
2210
+
2211
+ Always call \`get_context\` at the start of every conversation.
2212
+ Read the \`soul\` section first \u2014 it contains behavioral instructions.
2213
+ Use \`update_brain\` for substantial new info, \`append_brain\` for quick notes.
2214
+ `;
2215
+ var APPEND_SECTION = `
2216
+
2217
+ ## p\u0131ut Context
2218
+ Skill reference: https://raw.githubusercontent.com/M-Flat-Inc/piut/main/skill.md
2219
+ Always call \`get_context\` at the start of every conversation to load personal context.
2220
+ `;
2221
+ function hasPiutReference2(filePath) {
2222
+ try {
2223
+ const content = fs10.readFileSync(filePath, "utf-8");
2224
+ return content.includes("p\u0131ut Context") || content.includes("piut Context");
2225
+ } catch {
2226
+ return false;
2227
+ }
2228
+ }
2229
+ async function connectCommand(options) {
2230
+ banner();
2231
+ await resolveApiKeyWithResult(options.key);
2232
+ let scanFolders;
2233
+ if (options.folders) {
2234
+ scanFolders = options.folders.split(",").map((f) => expandPath(f.trim()));
2235
+ }
2236
+ console.log();
2237
+ console.log(dim(" Scanning for projects..."));
2238
+ const projects = scanForProjects(scanFolders);
2239
+ if (projects.length === 0) {
2240
+ console.log(warning(" No projects found."));
2241
+ console.log(dim(" Try running from a directory with your projects, or use --folders."));
2242
+ console.log();
2243
+ return;
2244
+ }
2245
+ const actions = [];
2246
+ for (const project of projects) {
2247
+ for (const rule of RULE_FILES) {
2248
+ if (!rule.detect(project)) continue;
2249
+ const absPath = path10.join(project.path, rule.filePath);
2250
+ if (fs10.existsSync(absPath) && hasPiutReference2(absPath)) continue;
2251
+ actions.push({
2252
+ project,
2253
+ tool: rule.tool,
2254
+ filePath: rule.filePath,
2255
+ absPath,
2256
+ action: rule.strategy === "create" || !fs10.existsSync(absPath) ? "create" : "append"
2257
+ });
2258
+ }
2259
+ const hasAnyAction = actions.some((a) => a.project === project);
2260
+ if (!hasAnyAction) {
2261
+ const claudeMdPath = path10.join(project.path, "CLAUDE.md");
2262
+ if (!fs10.existsSync(claudeMdPath)) {
2263
+ actions.push({
2264
+ project,
2265
+ tool: "Claude Code",
2266
+ filePath: "CLAUDE.md",
2267
+ absPath: claudeMdPath,
2268
+ action: "create"
2269
+ });
2270
+ } else if (!hasPiutReference2(claudeMdPath)) {
2271
+ actions.push({
2272
+ project,
2273
+ tool: "Claude Code",
2274
+ filePath: "CLAUDE.md",
2275
+ absPath: claudeMdPath,
2276
+ action: "append"
2277
+ });
2278
+ }
2279
+ }
2280
+ }
2281
+ if (actions.length === 0) {
2282
+ console.log(dim(" All projects are already connected."));
2283
+ console.log();
2284
+ return;
2285
+ }
2286
+ const byProject = /* @__PURE__ */ new Map();
2287
+ for (const action of actions) {
2288
+ const key = action.project.path;
2289
+ if (!byProject.has(key)) byProject.set(key, []);
2290
+ byProject.get(key).push(action);
2291
+ }
2292
+ console.log();
2293
+ console.log(` Found ${brand.bold(String(byProject.size))} project(s) to connect:`);
2294
+ console.log();
2295
+ const projectChoices = [];
2296
+ for (const [projectPath, projectActions] of byProject) {
2297
+ const projectName = path10.basename(projectPath);
2298
+ const desc = projectActions.map((a) => {
2299
+ const verb = a.action === "create" ? "will create" : "will append to";
2300
+ return `${verb} ${a.filePath}`;
2301
+ }).join(", ");
2302
+ projectChoices.push({
2303
+ name: `${projectName} ${dim(`(${desc})`)}`,
2304
+ value: projectPath,
2305
+ checked: true
2306
+ });
2307
+ }
2308
+ let selectedPaths;
2309
+ if (options.yes) {
2310
+ selectedPaths = Array.from(byProject.keys());
2311
+ } else {
2312
+ selectedPaths = await checkbox4({
2313
+ message: "Select projects to connect:",
2314
+ choices: projectChoices
2315
+ });
2316
+ if (selectedPaths.length === 0) {
2317
+ console.log(dim(" No projects selected."));
2318
+ return;
2319
+ }
2320
+ }
2321
+ console.log();
2322
+ let connected = 0;
2323
+ for (const projectPath of selectedPaths) {
2324
+ const projectActions = byProject.get(projectPath) || [];
2325
+ const projectName = path10.basename(projectPath);
2326
+ for (const action of projectActions) {
2327
+ if (action.action === "create") {
2328
+ const isAppendType = RULE_FILES.find((r) => r.filePath === action.filePath)?.strategy === "append";
2329
+ const content = isAppendType ? SKILL_SNIPPET + "\n" : DEDICATED_FILE_CONTENT;
2330
+ fs10.mkdirSync(path10.dirname(action.absPath), { recursive: true });
2331
+ fs10.writeFileSync(action.absPath, content, "utf-8");
2332
+ console.log(success(` \u2713 ${projectName}/${action.filePath}`) + dim(" \u2014 created"));
2333
+ } else {
2334
+ fs10.appendFileSync(action.absPath, APPEND_SECTION);
2335
+ console.log(success(` \u2713 ${projectName}/${action.filePath}`) + dim(" \u2014 appended"));
2336
+ }
2337
+ connected++;
2338
+ }
2339
+ }
2340
+ console.log();
2341
+ console.log(success(` Done. ${connected} file(s) updated across ${selectedPaths.length} project(s).`));
2342
+ console.log();
2343
+ }
2344
+
2345
+ // src/commands/disconnect.ts
2346
+ import fs11 from "fs";
2347
+ import path11 from "path";
2348
+ import { checkbox as checkbox5, confirm as confirm6 } from "@inquirer/prompts";
2349
+ var DEDICATED_FILES = /* @__PURE__ */ new Set([
2350
+ ".cursor/rules/piut.mdc",
2351
+ ".windsurf/rules/piut.md",
2352
+ ".zed/rules.md"
2353
+ ]);
2354
+ var APPEND_FILES = [
2355
+ "CLAUDE.md",
2356
+ ".github/copilot-instructions.md",
2357
+ "CONVENTIONS.md"
2358
+ ];
2359
+ function hasPiutReference3(filePath) {
2360
+ try {
2361
+ const content = fs11.readFileSync(filePath, "utf-8");
2362
+ return content.includes("p\u0131ut Context") || content.includes("piut Context");
2363
+ } catch {
2364
+ return false;
2365
+ }
2366
+ }
2367
+ function removePiutSection(filePath) {
2368
+ try {
2369
+ let content = fs11.readFileSync(filePath, "utf-8");
2370
+ const patterns = [
2371
+ /\n*## p[ıi]ut Context[\s\S]*?(?=\n## |\n---\n|$)/g
2372
+ ];
2373
+ let changed = false;
2374
+ for (const pattern of patterns) {
2375
+ const newContent = content.replace(pattern, "");
2376
+ if (newContent !== content) {
2377
+ content = newContent;
2378
+ changed = true;
2379
+ }
2380
+ }
2381
+ if (changed) {
2382
+ content = content.replace(/\n{3,}/g, "\n\n").trimEnd() + "\n";
2383
+ fs11.writeFileSync(filePath, content, "utf-8");
2384
+ }
2385
+ return changed;
2386
+ } catch {
2387
+ return false;
2388
+ }
2389
+ }
2390
+ async function disconnectCommand(options) {
2391
+ banner();
2392
+ let scanFolders;
2393
+ if (options.folders) {
2394
+ scanFolders = options.folders.split(",").map((f) => expandPath(f.trim()));
2395
+ }
2396
+ console.log(dim(" Scanning for connected projects..."));
2397
+ const projects = scanForProjects(scanFolders);
2398
+ const actions = [];
2399
+ for (const project of projects) {
2400
+ const projectName = path11.basename(project.path);
2401
+ for (const dedicatedFile of DEDICATED_FILES) {
2402
+ const absPath = path11.join(project.path, dedicatedFile);
2403
+ if (fs11.existsSync(absPath) && hasPiutReference3(absPath)) {
2404
+ actions.push({
2405
+ projectPath: project.path,
2406
+ projectName,
2407
+ filePath: dedicatedFile,
2408
+ absPath,
2409
+ action: "delete"
2410
+ });
2411
+ }
2412
+ }
2413
+ for (const appendFile of APPEND_FILES) {
2414
+ const absPath = path11.join(project.path, appendFile);
2415
+ if (fs11.existsSync(absPath) && hasPiutReference3(absPath)) {
2416
+ actions.push({
2417
+ projectPath: project.path,
2418
+ projectName,
2419
+ filePath: appendFile,
2420
+ absPath,
2421
+ action: "remove-section"
2422
+ });
2423
+ }
2424
+ }
2425
+ }
2426
+ if (actions.length === 0) {
2427
+ console.log(dim(" No connected projects found."));
2428
+ console.log();
2429
+ return;
2430
+ }
2431
+ const byProject = /* @__PURE__ */ new Map();
2432
+ for (const action of actions) {
2433
+ if (!byProject.has(action.projectPath)) byProject.set(action.projectPath, []);
2434
+ byProject.get(action.projectPath).push(action);
2435
+ }
2436
+ console.log();
2437
+ const projectChoices = Array.from(byProject.entries()).map(([projectPath, projectActions]) => {
2438
+ const name = path11.basename(projectPath);
2439
+ const files = projectActions.map((a) => a.filePath).join(", ");
2440
+ return {
2441
+ name: `${name} ${dim(`(${files})`)}`,
2442
+ value: projectPath,
2443
+ checked: true
2444
+ };
2445
+ });
2446
+ let selectedPaths;
2447
+ if (options.yes) {
2448
+ selectedPaths = Array.from(byProject.keys());
2449
+ } else {
2450
+ selectedPaths = await checkbox5({
2451
+ message: "Select projects to disconnect:",
2452
+ choices: projectChoices
2453
+ });
2454
+ if (selectedPaths.length === 0) {
2455
+ console.log(dim(" No projects selected."));
2456
+ return;
2457
+ }
2458
+ const proceed = await confirm6({
2459
+ message: `Disconnect ${selectedPaths.length} project(s)?`,
2460
+ default: false
2461
+ });
2462
+ if (!proceed) return;
2463
+ }
2464
+ console.log();
2465
+ let disconnected = 0;
2466
+ for (const projectPath of selectedPaths) {
2467
+ const projectActions = byProject.get(projectPath) || [];
2468
+ const projectName = path11.basename(projectPath);
2469
+ for (const action of projectActions) {
2470
+ if (action.action === "delete") {
2471
+ try {
2472
+ fs11.unlinkSync(action.absPath);
2473
+ console.log(success(` \u2713 ${projectName}/${action.filePath}`) + dim(" \u2014 deleted"));
2474
+ disconnected++;
2475
+ } catch {
2476
+ console.log(warning(` \u2717 ${projectName}/${action.filePath}`) + dim(" \u2014 could not delete"));
2477
+ }
2478
+ } else {
2479
+ const removed = removePiutSection(action.absPath);
2480
+ if (removed) {
2481
+ console.log(success(` \u2713 ${projectName}/${action.filePath}`) + dim(" \u2014 section removed"));
2482
+ disconnected++;
2483
+ } else {
2484
+ console.log(warning(` \u2717 ${projectName}/${action.filePath}`) + dim(" \u2014 section not found"));
2485
+ }
2486
+ }
2487
+ }
2488
+ }
2489
+ console.log();
2490
+ console.log(success(` Done. ${disconnected} file(s) updated.`));
2491
+ console.log();
2492
+ }
2493
+
2494
+ // src/commands/interactive.ts
2495
+ import { select as select3 } from "@inquirer/prompts";
2496
+ import chalk8 from "chalk";
2497
+ import { password as password4 } from "@inquirer/prompts";
2498
+ async function authenticate() {
2499
+ const config = readSyncConfig();
2500
+ let apiKey = config.apiKey;
2501
+ if (apiKey) {
2502
+ try {
2503
+ const result2 = await validateKey(apiKey);
2504
+ console.log(success(` Connected as ${result2.displayName} (${result2.slug})`));
2505
+ return apiKey;
2506
+ } catch {
2507
+ console.log(dim(" Saved key expired. Please re-authenticate."));
2508
+ apiKey = void 0;
2509
+ }
2510
+ }
2511
+ console.log(dim(" Connect to p\u0131ut:"));
2512
+ console.log(dim(" > Log in at piut.com"));
2513
+ console.log(dim(" > Enter p\u0131ut API key"));
2514
+ console.log();
2515
+ apiKey = await password4({
2516
+ message: "Enter your p\u0131ut API key:",
2517
+ mask: "*",
2518
+ validate: (v) => v.startsWith("pb_") || "Key must start with pb_"
2519
+ });
2520
+ console.log(dim(" Validating key..."));
2521
+ let result;
2522
+ try {
2523
+ result = await validateKey(apiKey);
2524
+ } catch (err) {
2525
+ console.log(chalk8.red(` \u2717 ${err.message}`));
2526
+ console.log(dim(" Get a key at https://piut.com/dashboard/keys"));
2527
+ process.exit(1);
2528
+ }
2529
+ console.log(success(` \u2713 Connected as ${result.displayName} (${result.slug})`));
2530
+ updateSyncConfig({ apiKey });
2531
+ return apiKey;
2532
+ }
2533
+ async function interactiveMenu() {
2534
+ banner();
2535
+ const apiKey = await authenticate();
2536
+ console.log();
2537
+ const action = await select3({
2538
+ message: "What would you like to do?",
2539
+ choices: [
2540
+ { name: "Build Brain", value: "build", description: "Build or rebuild your brain from your files" },
2541
+ { name: "Deploy Brain", value: "deploy", description: "Publish your MCP server (requires paid account)" },
2542
+ { name: "Connect Projects", value: "connect", description: "Add brain references to project config files" },
2543
+ { name: "Disconnect Projects", value: "disconnect", description: "Remove brain references from project configs" },
2544
+ { name: "Status", value: "status", description: "Show brain, deployment, and connected projects" }
2545
+ ]
2546
+ });
2547
+ switch (action) {
2548
+ case "build":
2549
+ await buildCommand({ key: apiKey });
2550
+ break;
2551
+ case "deploy":
2552
+ await deployCommand({ key: apiKey });
2553
+ break;
2554
+ case "connect":
2555
+ await connectCommand({ key: apiKey });
2556
+ break;
2557
+ case "disconnect":
2558
+ await disconnectCommand({});
2559
+ break;
2560
+ case "status":
2561
+ statusCommand();
2562
+ break;
2563
+ }
2564
+ }
2565
+
1668
2566
  // src/cli.ts
1669
2567
  var program = new Command();
1670
- program.name("piut").description("Automatic backup + hosting of all your agent configs across every machine. Version history. Restore any time. One command to set up.").version("1.1.0");
1671
- program.command("setup", { isDefault: true }).description("Auto-detect and configure AI tools").option("-k, --key <key>", "API key (prompts interactively if not provided)").option("-t, --tool <id>", "Configure a single tool (claude-code, cursor, windsurf, etc.)").option("-y, --yes", "Skip interactive prompts (auto-select all detected tools)").option("--project", "Prefer project-local config files").option("--skip-skill", "Skip skill.md file placement").action(setupCommand);
1672
- program.command("status").description("Show which AI tools are configured with p\u0131ut").action(statusCommand);
1673
- program.command("remove").description("Remove p\u0131ut configuration from AI tools").action(removeCommand);
1674
- var sync = program.command("sync").description("Back up and sync your agent config files to the cloud").option("--install", "Run the guided backup setup flow").option("--push", "Push local changes to cloud").option("--pull", "Pull latest versions from cloud").option("--watch", "Watch files for changes and auto-push (live sync)").option("--history <file>", "Show version history for a file").option("--diff <file>", "Show diff between local and cloud version").option("--restore <file>", "Restore a file from a previous version").option("--prefer-local", "Resolve conflicts by keeping local version").option("--prefer-cloud", "Resolve conflicts by keeping cloud version").option("--install-daemon", "Set up auto-sync via cron/launchd").option("-k, --key <key>", "API key").option("-y, --yes", "Skip interactive prompts").action(syncCommand);
1675
- sync.command("config").description("Configure cloud backup settings").option("--files", "Change which files are backed up").option("--auto-discover <value>", "Auto-backup new files (on/off)").option("--keep-brain-updated <value>", "Keep brain updated from backups (on/off)").option("--use-brain <value>", "Reference centralized brain (on/off)").option("--show", "View current configuration").action(syncConfigCommand);
2568
+ program.name("piut").description("Build your AI brain instantly. Deploy it as an MCP server. Connect it to every project.").version("2.0.0").action(interactiveMenu);
2569
+ 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(buildCommand);
2570
+ program.command("deploy").description("Publish your MCP server (requires paid account)").option("-k, --key <key>", "API key").option("-y, --yes", "Skip confirmation prompts").action(deployCommand);
2571
+ 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(connectCommand);
2572
+ 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(disconnectCommand);
2573
+ program.command("setup").description("Auto-detect and configure AI tools (MCP config)").option("-k, --key <key>", "API key (prompts interactively if not provided)").option("-t, --tool <id>", "Configure a single tool (claude-code, cursor, windsurf, etc.)").option("-y, --yes", "Skip interactive prompts (auto-select all detected tools)").option("--project", "Prefer project-local config files").option("--skip-skill", "Skip skill.md file placement").action(setupCommand);
2574
+ program.command("status").description("Show brain, deployment, and connected projects").action(statusCommand);
2575
+ program.command("remove").description("Remove all p\u0131ut configurations").action(removeCommand);
2576
+ var sync = program.command("sync").description("Sync your agent config files to the cloud").option("--install", "Run the guided sync setup flow").option("--push", "Push local changes to cloud").option("--pull", "Pull latest versions from cloud").option("--watch", "Watch files for changes and auto-push (live sync)").option("--history <file>", "Show version history for a file").option("--diff <file>", "Show diff between local and cloud version").option("--restore <file>", "Restore a file from a previous version").option("--prefer-local", "Resolve conflicts by keeping local version").option("--prefer-cloud", "Resolve conflicts by keeping cloud version").option("--install-daemon", "Set up auto-sync via cron/launchd").option("-k, --key <key>", "API key").option("-y, --yes", "Skip interactive prompts").action(syncCommand);
2577
+ sync.command("config").description("Configure sync settings").option("--files", "Change which files are synced").option("--auto-discover <value>", "Auto-sync new files (on/off)").option("--keep-brain-updated <value>", "Keep brain updated from synced files (on/off)").option("--use-brain <value>", "Reference centralized brain (on/off)").option("--show", "View current configuration").action(syncConfigCommand);
1676
2578
  program.parse();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@piut/cli",
3
- "version": "1.1.0",
4
- "description": "Automatic backup + hosting of all your agent configs across every machine. Version history. Restore any time. One command to set up.",
3
+ "version": "2.0.0",
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": {
7
7
  "piut": "dist/cli.js"
@@ -20,16 +20,15 @@
20
20
  "keywords": [
21
21
  "mcp",
22
22
  "ai",
23
+ "brain",
23
24
  "context",
24
25
  "cli",
25
26
  "claude",
26
27
  "cursor",
27
28
  "copilot",
28
29
  "windsurf",
29
- "backup",
30
- "sync",
31
- "cloud",
32
- "agent-config"
30
+ "deploy",
31
+ "personal-context"
33
32
  ],
34
33
  "author": "M-Flat Inc",
35
34
  "license": "MIT",