@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.
- package/README.md +21 -16
- package/dist/cli.js +960 -58
- 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
|
-
|
|
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
|
|
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
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
|
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 (!
|
|
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("
|
|
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
|
|
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 (
|
|
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
|
|
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
|
|
516
|
-
import
|
|
517
|
-
import
|
|
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 (
|
|
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 =
|
|
576
|
-
if (
|
|
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 =
|
|
911
|
+
const fullDir = path8.join(dir, dirPart);
|
|
583
912
|
if (lastPart.includes("*")) {
|
|
584
|
-
if (
|
|
913
|
+
if (fs7.existsSync(fullDir) && fs7.statSync(fullDir).isDirectory()) {
|
|
585
914
|
try {
|
|
586
|
-
const entries =
|
|
915
|
+
const entries = fs7.readdirSync(fullDir);
|
|
587
916
|
for (const entry of entries) {
|
|
588
917
|
if (matchesGlob(entry, lastPart)) {
|
|
589
|
-
const fullPath =
|
|
590
|
-
if (
|
|
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 =
|
|
600
|
-
if (
|
|
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
|
|
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 (
|
|
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:
|
|
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 =
|
|
630
|
-
if (!
|
|
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 =
|
|
636
|
-
const projectName =
|
|
964
|
+
const relativePath = path8.relative(absDir, filePath);
|
|
965
|
+
const projectName = path8.basename(absDir);
|
|
637
966
|
files.push({
|
|
638
967
|
absolutePath: filePath,
|
|
639
|
-
displayPath:
|
|
640
|
-
sizeBytes:
|
|
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
|
|
734
|
-
import
|
|
735
|
-
import
|
|
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 =
|
|
738
|
-
var CONFIG_FILE =
|
|
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
|
|
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 =
|
|
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
|
-
|
|
764
|
-
|
|
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 =
|
|
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 =
|
|
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 || !
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
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:
|
|
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 (
|
|
1535
|
-
const
|
|
1863
|
+
if (fs9.existsSync(input)) return input;
|
|
1864
|
+
const home3 = process.env.HOME || "";
|
|
1536
1865
|
if (cloudPath.startsWith("~/")) {
|
|
1537
|
-
const expanded = cloudPath.replace("~",
|
|
1538
|
-
if (
|
|
1866
|
+
const expanded = cloudPath.replace("~", home3);
|
|
1867
|
+
if (fs9.existsSync(expanded)) return expanded;
|
|
1539
1868
|
}
|
|
1540
1869
|
const cwdPath = `${process.cwd()}/${cloudPath}`;
|
|
1541
|
-
if (
|
|
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("
|
|
1671
|
-
program.command("
|
|
1672
|
-
program.command("
|
|
1673
|
-
program.command("
|
|
1674
|
-
|
|
1675
|
-
|
|
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": "
|
|
4
|
-
"description": "
|
|
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
|
-
"
|
|
30
|
-
"
|
|
31
|
-
"cloud",
|
|
32
|
-
"agent-config"
|
|
30
|
+
"deploy",
|
|
31
|
+
"personal-context"
|
|
33
32
|
],
|
|
34
33
|
"author": "M-Flat Inc",
|
|
35
34
|
"license": "MIT",
|