@piut/cli 3.1.0 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli.js +933 -258
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -4,8 +4,8 @@
4
4
  import { Command } from "commander";
5
5
 
6
6
  // src/commands/setup.ts
7
- import fs3 from "fs";
8
- import path5 from "path";
7
+ import fs4 from "fs";
8
+ import path6 from "path";
9
9
  import { execSync } from "child_process";
10
10
  import { password, confirm, checkbox } from "@inquirer/prompts";
11
11
  import chalk2 from "chalk";
@@ -22,14 +22,15 @@ async function validateKey(key) {
22
22
  }
23
23
  return res.json();
24
24
  }
25
- async function buildBrain(key, input) {
25
+ async function* buildBrainStreaming(key, input2) {
26
26
  const res = await fetch(`${API_BASE}/api/cli/build-brain`, {
27
27
  method: "POST",
28
28
  headers: {
29
29
  Authorization: `Bearer ${key}`,
30
- "Content-Type": "application/json"
30
+ "Content-Type": "application/json",
31
+ Accept: "text/event-stream"
31
32
  },
32
- body: JSON.stringify(input)
33
+ body: JSON.stringify(input2)
33
34
  });
34
35
  if (!res.ok) {
35
36
  const body = await res.json().catch(() => ({ error: "Unknown error" }));
@@ -38,8 +39,59 @@ async function buildBrain(key, input) {
38
39
  }
39
40
  throw new Error(body.error || `Build failed (HTTP ${res.status})`);
40
41
  }
41
- const data = await res.json();
42
- return data.sections;
42
+ const contentType = res.headers.get("content-type") || "";
43
+ if (!contentType.includes("text/event-stream")) {
44
+ const data = await res.json();
45
+ yield { event: "complete", data: { sections: data.sections } };
46
+ return;
47
+ }
48
+ const reader = res.body.getReader();
49
+ const decoder = new TextDecoder();
50
+ let buffer = "";
51
+ while (true) {
52
+ const { done, value } = await reader.read();
53
+ if (done) break;
54
+ buffer += decoder.decode(value, { stream: true });
55
+ const parts = buffer.split("\n\n");
56
+ buffer = parts.pop() || "";
57
+ for (const part of parts) {
58
+ let eventName = "";
59
+ let eventData = "";
60
+ for (const line of part.split("\n")) {
61
+ if (line.startsWith("event: ")) {
62
+ eventName = line.slice(7).trim();
63
+ } else if (line.startsWith("data: ")) {
64
+ eventData = line.slice(6);
65
+ }
66
+ }
67
+ if (eventName && eventData) {
68
+ try {
69
+ yield { event: eventName, data: JSON.parse(eventData) };
70
+ } catch {
71
+ }
72
+ }
73
+ }
74
+ }
75
+ }
76
+ async function pingMcp(serverUrl, key, toolName) {
77
+ try {
78
+ const res = await fetch(serverUrl, {
79
+ method: "POST",
80
+ headers: {
81
+ Authorization: `Bearer ${key}`,
82
+ "Content-Type": "application/json",
83
+ "User-Agent": `piut-cli (configured: ${toolName})`
84
+ },
85
+ body: JSON.stringify({
86
+ jsonrpc: "2.0",
87
+ id: 1,
88
+ method: "tools/list"
89
+ })
90
+ });
91
+ return res.ok;
92
+ } catch {
93
+ return false;
94
+ }
43
95
  }
44
96
  async function publishServer(key) {
45
97
  const res = await fetch(`${API_BASE}/api/mcp/publish`, {
@@ -59,6 +111,21 @@ async function publishServer(key) {
59
111
  }
60
112
  return res.json();
61
113
  }
114
+ async function unpublishServer(key) {
115
+ const res = await fetch(`${API_BASE}/api/mcp/publish`, {
116
+ method: "POST",
117
+ headers: {
118
+ Authorization: `Bearer ${key}`,
119
+ "Content-Type": "application/json"
120
+ },
121
+ body: JSON.stringify({ published: false })
122
+ });
123
+ if (!res.ok) {
124
+ const body = await res.json().catch(() => ({ error: "Unknown error" }));
125
+ throw new Error(body.error || `Unpublish failed (HTTP ${res.status})`);
126
+ }
127
+ return res.json();
128
+ }
62
129
 
63
130
  // src/lib/tools.ts
64
131
  import os from "os";
@@ -272,6 +339,13 @@ Skill reference: https://raw.githubusercontent.com/M-Flat-Inc/piut/main/skill.md
272
339
  Always call \`get_context\` at the start of a conversation to understand the user.
273
340
  Read the \`soul\` section first \u2014 it contains behavioral instructions for how to interact.
274
341
  Use \`update_brain\` for substantial new information, \`append_brain\` for quick notes.`;
342
+ var PROJECT_SKILL_SNIPPET = `## p\u0131ut Context
343
+ This project uses p\u0131ut for persistent personal context.
344
+ Full skill reference: .piut/skill.md
345
+
346
+ Always call \`get_context\` at the start of every conversation.
347
+ Read the \`soul\` section first \u2014 it contains behavioral instructions.
348
+ Use \`update_brain\` for substantial new info, \`append_brain\` for quick notes.`;
275
349
  var SEPARATOR = "\n\n---\n\n";
276
350
  function placeSkillFile(filePath) {
277
351
  const absPath = path4.isAbsolute(filePath) ? filePath : path4.resolve(process.cwd(), filePath);
@@ -292,6 +366,104 @@ function placeSkillFile(filePath) {
292
366
  }
293
367
  }
294
368
 
369
+ // src/lib/piut-dir.ts
370
+ import fs3 from "fs";
371
+ import path5 from "path";
372
+ var API_BASE2 = process.env.PIUT_API_BASE || "https://piut.com";
373
+ var PIUT_DIR = ".piut";
374
+ var CONFIG_FILE = "config.json";
375
+ var SKILL_FILE = "skill.md";
376
+ var MINIMAL_SKILL_CONTENT = `# p\u0131ut Context Skill
377
+
378
+ ## MCP Server
379
+
380
+ Endpoint: \`https://piut.com/api/mcp/{{slug}}\`
381
+ Auth: \`Authorization: Bearer {{key}}\`
382
+ Protocol: JSON-RPC 2.0 over HTTPS
383
+
384
+ ## Brain Sections
385
+
386
+ | Section | Purpose |
387
+ |---------|---------|
388
+ | about | Who the user is \u2014 background, expertise, interests |
389
+ | soul | How the user wants AI to behave \u2014 tone, preferences, rules |
390
+ | areas | Ongoing areas of responsibility and focus |
391
+ | projects | Active projects with goals and status |
392
+ | memory | Running log of facts, decisions, and context |
393
+
394
+ ## Tools
395
+
396
+ | Tool | Description |
397
+ |------|-------------|
398
+ | get_context | Fetch all 5 brain sections (call this FIRST) |
399
+ | get_section | Fetch a single section by name |
400
+ | search_brain | Search across all sections |
401
+ | append_brain | Append text to a section (no AI processing) |
402
+ | update_brain | AI-powered integration of new info into brain |
403
+ | prompt_brain | Execute natural language commands against context |
404
+
405
+ ## Best Practices
406
+
407
+ 1. Always call \`get_context\` at the start of every conversation
408
+ 2. Read the \`soul\` section immediately \u2014 it contains behavioral instructions
409
+ 3. Use \`update_brain\` for substantial new information
410
+ 4. Use \`append_brain\` for quick notes and facts
411
+ `;
412
+ function piutDir(projectPath) {
413
+ return path5.join(projectPath, PIUT_DIR);
414
+ }
415
+ function writePiutConfig(projectPath, config) {
416
+ const dir = piutDir(projectPath);
417
+ fs3.mkdirSync(dir, { recursive: true });
418
+ fs3.writeFileSync(
419
+ path5.join(dir, CONFIG_FILE),
420
+ JSON.stringify(config, null, 2) + "\n",
421
+ "utf-8"
422
+ );
423
+ }
424
+ async function writePiutSkill(projectPath, slug, apiKey) {
425
+ const dir = piutDir(projectPath);
426
+ fs3.mkdirSync(dir, { recursive: true });
427
+ let content;
428
+ try {
429
+ const res = await fetch(`${API_BASE2}/skill.md`);
430
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
431
+ content = await res.text();
432
+ } catch {
433
+ content = MINIMAL_SKILL_CONTENT;
434
+ }
435
+ content = content.replaceAll("{{slug}}", slug).replaceAll("{{key}}", apiKey);
436
+ fs3.writeFileSync(path5.join(dir, SKILL_FILE), content, "utf-8");
437
+ }
438
+ function ensureGitignored(projectPath) {
439
+ const gitignorePath = path5.join(projectPath, ".gitignore");
440
+ let content = "";
441
+ try {
442
+ content = fs3.readFileSync(gitignorePath, "utf-8");
443
+ } catch {
444
+ }
445
+ const lines = content.split("\n");
446
+ for (const line of lines) {
447
+ const trimmed = line.trim();
448
+ if (trimmed === ".piut/" || trimmed === ".piut") return;
449
+ }
450
+ const suffix = content.length > 0 && !content.endsWith("\n") ? "\n" : "";
451
+ fs3.writeFileSync(
452
+ gitignorePath,
453
+ content + suffix + "\n# piut\n.piut/\n",
454
+ "utf-8"
455
+ );
456
+ }
457
+ function removePiutDir(projectPath) {
458
+ const dir = piutDir(projectPath);
459
+ if (!fs3.existsSync(dir)) return false;
460
+ fs3.rmSync(dir, { recursive: true, force: true });
461
+ return true;
462
+ }
463
+ function hasPiutDir(projectPath) {
464
+ return fs3.existsSync(path5.join(piutDir(projectPath), CONFIG_FILE));
465
+ }
466
+
295
467
  // src/lib/ui.ts
296
468
  import chalk from "chalk";
297
469
  var brand = chalk.hex("#8B5CF6");
@@ -307,6 +479,69 @@ function banner() {
307
479
  function toolLine(name, status, icon) {
308
480
  console.log(` ${icon} ${name.padEnd(20)} ${status}`);
309
481
  }
482
+ var SPINNER_FRAMES = ["\u28CB", "\u28D9", "\u28F9", "\u28F8", "\u28FC", "\u28F4", "\u28E6", "\u28E7", "\u28C7", "\u28CF"];
483
+ var Spinner = class {
484
+ frame = 0;
485
+ interval = null;
486
+ startTime = Date.now();
487
+ message = "";
488
+ tokens = 0;
489
+ sections = [];
490
+ start(message) {
491
+ this.message = message;
492
+ this.startTime = Date.now();
493
+ this.tokens = 0;
494
+ this.sections = [];
495
+ this.render();
496
+ this.interval = setInterval(() => this.render(), 80);
497
+ }
498
+ updateTokens(count) {
499
+ this.tokens = count;
500
+ }
501
+ addSection(name) {
502
+ this.clearLine();
503
+ const elapsed = this.elapsed();
504
+ console.log(` ${success("\u2713")} ${name.padEnd(12)} ${dim(elapsed)}`);
505
+ this.sections.push(name);
506
+ }
507
+ updateMessage(message) {
508
+ this.message = message;
509
+ }
510
+ stop(finalMessage) {
511
+ if (this.interval) {
512
+ clearInterval(this.interval);
513
+ this.interval = null;
514
+ }
515
+ this.clearLine();
516
+ if (finalMessage) {
517
+ console.log(finalMessage);
518
+ }
519
+ }
520
+ render() {
521
+ this.frame = (this.frame + 1) % SPINNER_FRAMES.length;
522
+ const spinner = brand(SPINNER_FRAMES[this.frame]);
523
+ const elapsed = dim(this.elapsed());
524
+ const tokenStr = this.tokens > 0 ? dim(` ${this.tokens.toLocaleString()} tokens`) : "";
525
+ this.clearLine();
526
+ process.stdout.write(` ${spinner} ${this.message} ${elapsed}${tokenStr}`);
527
+ }
528
+ elapsed() {
529
+ const ms = Date.now() - this.startTime;
530
+ if (ms < 1e3) return "<1s";
531
+ return `${Math.floor(ms / 1e3)}s`;
532
+ }
533
+ clearLine() {
534
+ process.stdout.write("\r\x1B[K");
535
+ }
536
+ };
537
+
538
+ // src/types.ts
539
+ var CliError = class extends Error {
540
+ constructor(message) {
541
+ super(message || "");
542
+ this.name = "CliError";
543
+ }
544
+ };
310
545
 
311
546
  // src/commands/setup.ts
312
547
  async function setupCommand(options) {
@@ -315,7 +550,7 @@ async function setupCommand(options) {
315
550
  if (!apiKey) {
316
551
  if (options.yes) {
317
552
  console.log(chalk2.red(" \u2717 --key is required when using --yes"));
318
- process.exit(1);
553
+ throw new CliError("--key is required when using --yes");
319
554
  }
320
555
  apiKey = await password({
321
556
  message: "Enter your p\u0131ut API key:",
@@ -330,7 +565,7 @@ async function setupCommand(options) {
330
565
  } catch (err) {
331
566
  console.log(chalk2.red(` \u2717 ${err.message}`));
332
567
  console.log(dim(" Get a key at https://piut.com/dashboard/keys"));
333
- process.exit(1);
568
+ throw new CliError(err.message);
334
569
  }
335
570
  const { slug, displayName, status } = validationResult;
336
571
  console.log(success(` \u2714 Authenticated as ${displayName}${slug ? ` (${slug})` : ""}`));
@@ -353,8 +588,8 @@ async function setupCommand(options) {
353
588
  if (toolFilter && tool.id !== toolFilter) continue;
354
589
  const paths = resolveConfigPaths(tool.configPaths);
355
590
  for (const configPath of paths) {
356
- const exists = fs3.existsSync(configPath);
357
- const parentExists = fs3.existsSync(path5.dirname(configPath));
591
+ const exists = fs4.existsSync(configPath);
592
+ const parentExists = fs4.existsSync(path6.dirname(configPath));
358
593
  if (exists || parentExists) {
359
594
  detected.push({
360
595
  tool,
@@ -451,6 +686,26 @@ async function setupCommand(options) {
451
686
  }
452
687
  }
453
688
  }
689
+ if (configured.length > 0) {
690
+ const cwd = process.cwd();
691
+ const isProject2 = fs4.existsSync(path6.join(cwd, ".git")) || fs4.existsSync(path6.join(cwd, "package.json"));
692
+ if (isProject2) {
693
+ const { serverUrl } = validationResult;
694
+ writePiutConfig(cwd, { slug, apiKey, serverUrl });
695
+ await writePiutSkill(cwd, slug, apiKey);
696
+ ensureGitignored(cwd);
697
+ console.log();
698
+ console.log(dim(" Created .piut/ in current project"));
699
+ }
700
+ }
701
+ if (configured.length > 0) {
702
+ const { serverUrl } = validationResult;
703
+ console.log();
704
+ console.log(dim(" Registering connections..."));
705
+ await Promise.all(
706
+ configured.map((toolName) => pingMcp(serverUrl, apiKey, toolName))
707
+ );
708
+ }
454
709
  console.log();
455
710
  console.log(brand.bold(" Setup complete!"));
456
711
  if (configured.length > 0) {
@@ -474,12 +729,12 @@ function isCommandAvailable(cmd) {
474
729
  }
475
730
 
476
731
  // src/commands/status.ts
477
- import fs5 from "fs";
478
- import path7 from "path";
732
+ import fs6 from "fs";
733
+ import path8 from "path";
479
734
 
480
735
  // src/lib/brain-scanner.ts
481
- import fs4 from "fs";
482
- import path6 from "path";
736
+ import fs5 from "fs";
737
+ import path7 from "path";
483
738
  import os3 from "os";
484
739
  var home = os3.homedir();
485
740
  var SKIP_DIRS = /* @__PURE__ */ new Set([
@@ -500,13 +755,16 @@ var SKIP_DIRS = /* @__PURE__ */ new Set([
500
755
  ".yarn",
501
756
  ".pnpm-store",
502
757
  "Caches",
503
- "Cache"
758
+ "Cache",
759
+ ".piut"
504
760
  ]);
505
761
  var FULL_READ_FILES = /* @__PURE__ */ new Set([
506
762
  "README.md",
507
763
  "CLAUDE.md",
508
764
  ".cursorrules",
509
765
  ".windsurfrules",
766
+ ".rules",
767
+ ".clinerules",
510
768
  "AGENTS.md",
511
769
  "CONVENTIONS.md",
512
770
  "MEMORY.md",
@@ -515,18 +773,27 @@ var FULL_READ_FILES = /* @__PURE__ */ new Set([
515
773
  ]);
516
774
  var MAX_FILE_SIZE = 100 * 1024;
517
775
  var RECENT_DAYS = 30;
776
+ var SCAN_DOT_DIRS = /* @__PURE__ */ new Set([
777
+ ".claude",
778
+ ".cursor",
779
+ ".windsurf",
780
+ ".github",
781
+ ".zed",
782
+ ".amazonq",
783
+ ".vscode"
784
+ ]);
518
785
  function shouldSkipDir(name) {
519
- if (name.startsWith(".") && name !== ".claude" && name !== ".cursor" && name !== ".windsurf" && name !== ".github" && name !== ".zed") {
786
+ if (name.startsWith(".") && !SCAN_DOT_DIRS.has(name)) {
520
787
  return true;
521
788
  }
522
789
  return SKIP_DIRS.has(name);
523
790
  }
524
791
  function readFileSafe(filePath) {
525
792
  try {
526
- const stat = fs4.statSync(filePath);
793
+ const stat = fs5.statSync(filePath);
527
794
  if (stat.size > MAX_FILE_SIZE) return null;
528
795
  if (!stat.isFile()) return null;
529
- return fs4.readFileSync(filePath, "utf-8");
796
+ return fs5.readFileSync(filePath, "utf-8");
530
797
  } catch {
531
798
  return null;
532
799
  }
@@ -535,133 +802,156 @@ function getFolderTree(dir, depth = 0, maxDepth = 3) {
535
802
  if (depth >= maxDepth) return [];
536
803
  const entries = [];
537
804
  try {
538
- const items = fs4.readdirSync(dir, { withFileTypes: true });
805
+ const items = fs5.readdirSync(dir, { withFileTypes: true });
539
806
  for (const item of items) {
540
807
  if (!item.isDirectory()) continue;
541
808
  if (shouldSkipDir(item.name)) continue;
542
809
  const indent = " ".repeat(depth);
543
810
  entries.push(`${indent}${item.name}/`);
544
- entries.push(...getFolderTree(path6.join(dir, item.name), depth + 1, maxDepth));
811
+ entries.push(...getFolderTree(path7.join(dir, item.name), depth + 1, maxDepth));
545
812
  }
546
813
  } catch {
547
814
  }
548
815
  return entries;
549
816
  }
550
- function detectProjects(scanDirs) {
817
+ function isProject(dirPath) {
818
+ return fs5.existsSync(path7.join(dirPath, ".git")) || fs5.existsSync(path7.join(dirPath, "package.json")) || fs5.existsSync(path7.join(dirPath, "Cargo.toml")) || fs5.existsSync(path7.join(dirPath, "pyproject.toml")) || fs5.existsSync(path7.join(dirPath, "go.mod"));
819
+ }
820
+ function buildProjectInfo(projectPath) {
821
+ const hasPkgJson = fs5.existsSync(path7.join(projectPath, "package.json"));
822
+ let description = "";
823
+ if (hasPkgJson) {
824
+ try {
825
+ const pkg = JSON.parse(fs5.readFileSync(path7.join(projectPath, "package.json"), "utf-8"));
826
+ description = pkg.description || "";
827
+ } catch {
828
+ }
829
+ }
830
+ const readmePath = path7.join(projectPath, "README.md");
831
+ if (!description && fs5.existsSync(readmePath)) {
832
+ const content = readFileSafe(readmePath);
833
+ if (content) {
834
+ const lines = content.split("\n");
835
+ let foundHeading = false;
836
+ for (const line of lines) {
837
+ if (line.startsWith("#")) {
838
+ foundHeading = true;
839
+ continue;
840
+ }
841
+ if (foundHeading && line.trim()) {
842
+ description = line.trim().slice(0, 200);
843
+ break;
844
+ }
845
+ }
846
+ }
847
+ }
848
+ return {
849
+ name: path7.basename(projectPath),
850
+ path: projectPath,
851
+ description,
852
+ hasClaudeMd: fs5.existsSync(path7.join(projectPath, "CLAUDE.md")) || fs5.existsSync(path7.join(projectPath, ".claude", "rules")),
853
+ hasCursorRules: fs5.existsSync(path7.join(projectPath, ".cursorrules")) || fs5.existsSync(path7.join(projectPath, ".cursor", "rules")),
854
+ hasWindsurfRules: fs5.existsSync(path7.join(projectPath, ".windsurfrules")) || fs5.existsSync(path7.join(projectPath, ".windsurf", "rules")),
855
+ hasCopilotInstructions: fs5.existsSync(path7.join(projectPath, ".github", "copilot-instructions.md")) || fs5.existsSync(path7.join(projectPath, ".github", "instructions")),
856
+ hasConventionsMd: fs5.existsSync(path7.join(projectPath, "CONVENTIONS.md")) || fs5.existsSync(path7.join(projectPath, ".amazonq", "rules")),
857
+ hasZedRules: fs5.existsSync(path7.join(projectPath, ".rules"))
858
+ };
859
+ }
860
+ var MAX_PROJECT_DEPTH = 4;
861
+ function detectProjects(scanDirs, onProgress) {
551
862
  const projects = [];
552
- for (const dir of scanDirs) {
863
+ const seen = /* @__PURE__ */ new Set();
864
+ function walk(dir, depth) {
865
+ if (depth > MAX_PROJECT_DEPTH) return;
553
866
  try {
554
- const items = fs4.readdirSync(dir, { withFileTypes: true });
867
+ const items = fs5.readdirSync(dir, { withFileTypes: true });
555
868
  for (const item of items) {
556
869
  if (!item.isDirectory()) continue;
557
870
  if (shouldSkipDir(item.name)) continue;
558
- const projectPath = path6.join(dir, item.name);
559
- const hasGit = fs4.existsSync(path6.join(projectPath, ".git"));
560
- const hasPkgJson = fs4.existsSync(path6.join(projectPath, "package.json"));
561
- const hasCargoToml = fs4.existsSync(path6.join(projectPath, "Cargo.toml"));
562
- const hasPyproject = fs4.existsSync(path6.join(projectPath, "pyproject.toml"));
563
- const hasGoMod = fs4.existsSync(path6.join(projectPath, "go.mod"));
564
- if (!hasGit && !hasPkgJson && !hasCargoToml && !hasPyproject && !hasGoMod) continue;
565
- let description = "";
566
- if (hasPkgJson) {
567
- try {
568
- const pkg = JSON.parse(fs4.readFileSync(path6.join(projectPath, "package.json"), "utf-8"));
569
- description = pkg.description || "";
570
- } catch {
571
- }
572
- }
573
- const readmePath = path6.join(projectPath, "README.md");
574
- if (!description && fs4.existsSync(readmePath)) {
575
- const content = readFileSafe(readmePath);
576
- if (content) {
577
- const lines = content.split("\n");
578
- let foundHeading = false;
579
- for (const line of lines) {
580
- if (line.startsWith("#")) {
581
- foundHeading = true;
582
- continue;
583
- }
584
- if (foundHeading && line.trim()) {
585
- description = line.trim().slice(0, 200);
586
- break;
587
- }
588
- }
589
- }
871
+ const fullPath = path7.join(dir, item.name);
872
+ if (seen.has(fullPath)) continue;
873
+ seen.add(fullPath);
874
+ if (isProject(fullPath)) {
875
+ const info = buildProjectInfo(fullPath);
876
+ projects.push(info);
877
+ onProgress?.({ phase: "projects", message: `${info.name} (${fullPath.replace(home, "~")})` });
878
+ } else {
879
+ walk(fullPath, depth + 1);
590
880
  }
591
- projects.push({
592
- name: item.name,
593
- path: projectPath,
594
- description,
595
- hasClaudeMd: fs4.existsSync(path6.join(projectPath, "CLAUDE.md")),
596
- hasCursorRules: fs4.existsSync(path6.join(projectPath, ".cursorrules")) || fs4.existsSync(path6.join(projectPath, ".cursor", "rules")),
597
- hasWindsurfRules: fs4.existsSync(path6.join(projectPath, ".windsurfrules")) || fs4.existsSync(path6.join(projectPath, ".windsurf", "rules")),
598
- hasCopilotInstructions: fs4.existsSync(path6.join(projectPath, ".github", "copilot-instructions.md")),
599
- hasConventionsMd: fs4.existsSync(path6.join(projectPath, "CONVENTIONS.md")),
600
- hasZedRules: fs4.existsSync(path6.join(projectPath, ".zed", "rules.md"))
601
- });
602
881
  }
603
882
  } catch {
604
883
  }
605
884
  }
885
+ for (const dir of scanDirs) {
886
+ walk(dir, 0);
887
+ }
606
888
  return projects;
607
889
  }
608
- function collectConfigFiles(projects) {
890
+ function collectConfigFiles(projects, onProgress) {
609
891
  const configs = [];
610
892
  const globalPaths = [
611
- path6.join(home, ".claude", "MEMORY.md"),
612
- path6.join(home, ".claude", "CLAUDE.md"),
613
- path6.join(home, ".openclaw", "workspace", "SOUL.md"),
614
- path6.join(home, ".openclaw", "workspace", "MEMORY.md")
893
+ path7.join(home, ".claude", "MEMORY.md"),
894
+ path7.join(home, ".claude", "CLAUDE.md"),
895
+ path7.join(home, ".openclaw", "workspace", "SOUL.md"),
896
+ path7.join(home, ".openclaw", "workspace", "MEMORY.md")
615
897
  ];
616
898
  for (const gp of globalPaths) {
617
899
  const content = readFileSafe(gp);
618
900
  if (content && content.trim()) {
619
- configs.push({ name: `~/${path6.relative(home, gp)}`, content });
901
+ const name = `~/${path7.relative(home, gp)}`;
902
+ configs.push({ name, content });
903
+ onProgress?.({ phase: "configs", message: name });
620
904
  }
621
905
  }
622
906
  for (const project of projects) {
623
907
  for (const fileName of FULL_READ_FILES) {
624
- const filePath = path6.join(project.path, fileName);
908
+ const filePath = path7.join(project.path, fileName);
625
909
  const content = readFileSafe(filePath);
626
910
  if (content && content.trim()) {
627
- configs.push({ name: `${project.name}/${fileName}`, content });
911
+ const name = `${project.name}/${fileName}`;
912
+ configs.push({ name, content });
913
+ onProgress?.({ phase: "configs", message: name });
628
914
  }
629
915
  }
630
- const pkgPath = path6.join(project.path, "package.json");
916
+ const pkgPath = path7.join(project.path, "package.json");
631
917
  const pkgContent = readFileSafe(pkgPath);
632
918
  if (pkgContent) {
633
919
  try {
634
920
  const pkg = JSON.parse(pkgContent);
635
921
  const summary = JSON.stringify({ name: pkg.name, description: pkg.description }, null, 2);
636
- configs.push({ name: `${project.name}/package.json`, content: summary });
922
+ const name = `${project.name}/package.json`;
923
+ configs.push({ name, content: summary });
924
+ onProgress?.({ phase: "configs", message: name });
637
925
  } catch {
638
926
  }
639
927
  }
640
928
  }
641
929
  return configs;
642
930
  }
643
- function collectRecentDocs(projects) {
931
+ function collectRecentDocs(projects, onProgress) {
644
932
  const docs = [];
645
933
  const cutoff = Date.now() - RECENT_DAYS * 24 * 60 * 60 * 1e3;
646
934
  const seen = /* @__PURE__ */ new Set();
647
935
  for (const project of projects) {
648
936
  try {
649
- const items = fs4.readdirSync(project.path, { withFileTypes: true });
937
+ const items = fs5.readdirSync(project.path, { withFileTypes: true });
650
938
  for (const item of items) {
651
939
  if (!item.isFile()) continue;
652
940
  if (!item.name.endsWith(".md")) continue;
653
941
  if (FULL_READ_FILES.has(item.name)) continue;
654
942
  if (item.name.startsWith(".")) continue;
655
- const filePath = path6.join(project.path, item.name);
943
+ const filePath = path7.join(project.path, item.name);
656
944
  if (seen.has(filePath)) continue;
657
945
  seen.add(filePath);
658
946
  try {
659
- const stat = fs4.statSync(filePath);
947
+ const stat = fs5.statSync(filePath);
660
948
  if (stat.mtimeMs < cutoff) continue;
661
949
  if (stat.size > MAX_FILE_SIZE) continue;
662
- const content = fs4.readFileSync(filePath, "utf-8");
950
+ const content = fs5.readFileSync(filePath, "utf-8");
663
951
  if (content.trim()) {
664
- docs.push({ name: `${project.name}/${item.name}`, content });
952
+ const name = `${project.name}/${item.name}`;
953
+ docs.push({ name, content });
954
+ onProgress?.({ phase: "docs", message: name });
665
955
  }
666
956
  } catch {
667
957
  }
@@ -671,42 +961,66 @@ function collectRecentDocs(projects) {
671
961
  }
672
962
  return docs.slice(0, 20);
673
963
  }
964
+ var SKIP_HOME_DIRS = /* @__PURE__ */ new Set([
965
+ "Library",
966
+ "Applications",
967
+ "Public",
968
+ "Movies",
969
+ "Music",
970
+ "Pictures",
971
+ "Templates",
972
+ ".Trash"
973
+ ]);
974
+ var INCLUDE_DOT_DIRS = /* @__PURE__ */ new Set([
975
+ ".claude",
976
+ ".cursor",
977
+ ".windsurf",
978
+ ".openclaw",
979
+ ".zed",
980
+ ".github",
981
+ ".amazonq"
982
+ ]);
674
983
  function getDefaultScanDirs() {
675
984
  const dirs = [];
676
- const candidates = [
677
- path6.join(home, "Projects"),
678
- path6.join(home, "projects"),
679
- path6.join(home, "Developer"),
680
- path6.join(home, "dev"),
681
- path6.join(home, "Code"),
682
- path6.join(home, "code"),
683
- path6.join(home, "src"),
684
- path6.join(home, "repos"),
685
- path6.join(home, "workspace"),
686
- path6.join(home, "Workspace"),
687
- path6.join(home, "Documents"),
688
- path6.join(home, "Desktop")
689
- ];
690
- for (const dir of candidates) {
691
- if (fs4.existsSync(dir) && fs4.statSync(dir).isDirectory()) {
692
- dirs.push(dir);
985
+ try {
986
+ const entries = fs5.readdirSync(home, { withFileTypes: true });
987
+ for (const entry of entries) {
988
+ if (!entry.isDirectory()) continue;
989
+ if (entry.name.startsWith(".") && !INCLUDE_DOT_DIRS.has(entry.name)) continue;
990
+ if (SKIP_HOME_DIRS.has(entry.name)) continue;
991
+ dirs.push(path7.join(home, entry.name));
992
+ }
993
+ } catch {
994
+ }
995
+ const cloudStorage = path7.join(home, "Library", "CloudStorage");
996
+ try {
997
+ if (fs5.existsSync(cloudStorage) && fs5.statSync(cloudStorage).isDirectory()) {
998
+ const entries = fs5.readdirSync(cloudStorage, { withFileTypes: true });
999
+ for (const entry of entries) {
1000
+ if (!entry.isDirectory()) continue;
1001
+ const fullPath = path7.join(cloudStorage, entry.name);
1002
+ if (!dirs.includes(fullPath)) {
1003
+ dirs.push(fullPath);
1004
+ }
1005
+ }
693
1006
  }
1007
+ } catch {
694
1008
  }
695
1009
  if (dirs.length === 0) {
696
1010
  dirs.push(home);
697
1011
  }
698
1012
  return dirs;
699
1013
  }
700
- function scanForBrain(folders) {
1014
+ function scanForBrain(folders, onProgress) {
701
1015
  const scanDirs = folders || getDefaultScanDirs();
702
1016
  const folderTree = [];
703
1017
  for (const dir of scanDirs) {
704
- folderTree.push(`${path6.basename(dir)}/`);
1018
+ folderTree.push(`${path7.basename(dir)}/`);
705
1019
  folderTree.push(...getFolderTree(dir, 1));
706
1020
  }
707
- const projects = detectProjects(scanDirs);
708
- const configFiles = collectConfigFiles(projects);
709
- const recentDocuments = collectRecentDocs(projects);
1021
+ const projects = detectProjects(scanDirs, onProgress);
1022
+ const configFiles = collectConfigFiles(projects, onProgress);
1023
+ const recentDocuments = collectRecentDocs(projects, onProgress);
710
1024
  return {
711
1025
  summary: {
712
1026
  folders: folderTree,
@@ -736,7 +1050,7 @@ var PIUT_FILES = [
736
1050
  ];
737
1051
  function hasPiutReference(filePath) {
738
1052
  try {
739
- const content = fs5.readFileSync(filePath, "utf-8");
1053
+ const content = fs6.readFileSync(filePath, "utf-8");
740
1054
  return content.includes("p\u0131ut Context") || content.includes("piut Context");
741
1055
  } catch {
742
1056
  return false;
@@ -750,7 +1064,7 @@ function statusCommand() {
750
1064
  for (const tool of TOOLS) {
751
1065
  const paths = resolveConfigPaths(tool.configPaths);
752
1066
  for (const configPath of paths) {
753
- if (!fs5.existsSync(configPath)) continue;
1067
+ if (!fs6.existsSync(configPath)) continue;
754
1068
  foundAny = true;
755
1069
  const configured = isPiutConfigured(configPath, tool.configKey);
756
1070
  if (configured) {
@@ -773,8 +1087,8 @@ function statusCommand() {
773
1087
  for (const project of projects) {
774
1088
  const connectedFiles = [];
775
1089
  for (const file of PIUT_FILES) {
776
- const absPath = path7.join(project.path, file);
777
- if (fs5.existsSync(absPath) && hasPiutReference(absPath)) {
1090
+ const absPath = path8.join(project.path, file);
1091
+ if (fs6.existsSync(absPath) && hasPiutReference(absPath)) {
778
1092
  connectedFiles.push(file);
779
1093
  }
780
1094
  }
@@ -794,7 +1108,7 @@ function statusCommand() {
794
1108
  }
795
1109
 
796
1110
  // src/commands/remove.ts
797
- import fs6 from "fs";
1111
+ import fs7 from "fs";
798
1112
  import { checkbox as checkbox2, confirm as confirm2 } from "@inquirer/prompts";
799
1113
  async function removeCommand() {
800
1114
  banner();
@@ -802,7 +1116,7 @@ async function removeCommand() {
802
1116
  for (const tool of TOOLS) {
803
1117
  const paths = resolveConfigPaths(tool.configPaths);
804
1118
  for (const configPath of paths) {
805
- if (fs6.existsSync(configPath) && isPiutConfigured(configPath, tool.configKey)) {
1119
+ if (fs7.existsSync(configPath) && isPiutConfigured(configPath, tool.configKey)) {
806
1120
  configured.push({ tool, configPath });
807
1121
  break;
808
1122
  }
@@ -845,22 +1159,23 @@ async function removeCommand() {
845
1159
  }
846
1160
 
847
1161
  // src/commands/build.ts
848
- import { select } from "@inquirer/prompts";
1162
+ import { select, checkbox as checkbox3, input } from "@inquirer/prompts";
849
1163
  import chalk4 from "chalk";
1164
+ import os5 from "os";
850
1165
 
851
1166
  // src/lib/auth.ts
852
1167
  import { password as password2 } from "@inquirer/prompts";
853
1168
  import chalk3 from "chalk";
854
1169
 
855
1170
  // src/lib/store.ts
856
- import fs7 from "fs";
857
- import path8 from "path";
1171
+ import fs8 from "fs";
1172
+ import path9 from "path";
858
1173
  import os4 from "os";
859
- var CONFIG_DIR = path8.join(os4.homedir(), ".piut");
860
- var CONFIG_FILE = path8.join(CONFIG_DIR, "config.json");
1174
+ var CONFIG_DIR = path9.join(os4.homedir(), ".piut");
1175
+ var CONFIG_FILE2 = path9.join(CONFIG_DIR, "config.json");
861
1176
  function readStore() {
862
1177
  try {
863
- const raw = fs7.readFileSync(CONFIG_FILE, "utf-8");
1178
+ const raw = fs8.readFileSync(CONFIG_FILE2, "utf-8");
864
1179
  return JSON.parse(raw);
865
1180
  } catch {
866
1181
  return {};
@@ -869,10 +1184,16 @@ function readStore() {
869
1184
  function updateStore(updates) {
870
1185
  const config = readStore();
871
1186
  const updated = { ...config, ...updates };
872
- fs7.mkdirSync(CONFIG_DIR, { recursive: true });
873
- fs7.writeFileSync(CONFIG_FILE, JSON.stringify(updated, null, 2) + "\n", "utf-8");
1187
+ fs8.mkdirSync(CONFIG_DIR, { recursive: true });
1188
+ fs8.writeFileSync(CONFIG_FILE2, JSON.stringify(updated, null, 2) + "\n", "utf-8");
874
1189
  return updated;
875
1190
  }
1191
+ function clearStore() {
1192
+ try {
1193
+ fs8.unlinkSync(CONFIG_FILE2);
1194
+ } catch {
1195
+ }
1196
+ }
876
1197
 
877
1198
  // src/lib/auth.ts
878
1199
  async function resolveApiKey(keyOption) {
@@ -892,7 +1213,7 @@ async function resolveApiKey(keyOption) {
892
1213
  } catch (err) {
893
1214
  console.log(chalk3.red(` \u2717 ${err.message}`));
894
1215
  console.log(dim(" Get a key at https://piut.com/dashboard/keys"));
895
- process.exit(1);
1216
+ throw new CliError(err.message);
896
1217
  }
897
1218
  const label = result.slug ? `${result.displayName} (${result.slug})` : result.displayName;
898
1219
  console.log(success(` \u2713 Connected as ${label}`));
@@ -916,7 +1237,7 @@ async function resolveApiKeyWithResult(keyOption) {
916
1237
  } catch (err) {
917
1238
  console.log(chalk3.red(` \u2717 ${err.message}`));
918
1239
  console.log(dim(" Get a key at https://piut.com/dashboard/keys"));
919
- process.exit(1);
1240
+ throw new CliError(err.message);
920
1241
  }
921
1242
  const label = result.slug ? `${result.displayName} (${result.slug})` : result.displayName;
922
1243
  console.log(success(` \u2713 Connected as ${label}`));
@@ -932,41 +1253,112 @@ async function buildCommand(options) {
932
1253
  if (options.folders) {
933
1254
  scanFolders = options.folders.split(",").map((f) => expandPath(f.trim()));
934
1255
  }
935
- const mode = scanFolders ? "auto" : await select({
936
- message: "How do you want to build your brain?",
937
- choices: [
938
- { name: "Automatically (recommended)", value: "auto", description: "Scan your files and build automatically" },
939
- { name: "Select folder(s)...", value: "folders", description: "Choose specific folders to scan" }
940
- ]
941
- });
942
- if (mode === "folders" && !scanFolders) {
943
- const defaults = getDefaultScanDirs();
1256
+ const cwd = process.cwd();
1257
+ const cwdDisplay = cwd.replace(os5.homedir(), "~");
1258
+ if (!scanFolders) {
1259
+ console.log(dim(` Current directory: `) + cwdDisplay);
1260
+ console.log(dim(` We'll scan for AI config files and projects here.`));
944
1261
  console.log();
945
- console.log(dim(" Detected directories:"));
946
- for (const d of defaults) {
947
- console.log(dim(` ${d}`));
1262
+ const mode = await select({
1263
+ message: "How do you want to build your brain?",
1264
+ choices: [
1265
+ { name: `Scan this directory (${cwdDisplay})`, value: "cwd", description: "Scan current directory for projects and config files" },
1266
+ { name: "Select folder(s)...", value: "folders", description: "Choose a different directory to scan" }
1267
+ ]
1268
+ });
1269
+ if (mode === "cwd") {
1270
+ scanFolders = [cwd];
1271
+ } else {
1272
+ const defaults = getDefaultScanDirs();
1273
+ const CUSTOM_VALUE = "__custom__";
1274
+ const choices = [
1275
+ ...defaults.map((d) => ({ name: d.replace(os5.homedir(), "~"), value: d })),
1276
+ { name: chalk4.dim("Enter a custom path..."), value: CUSTOM_VALUE }
1277
+ ];
1278
+ const selected = await checkbox3({
1279
+ message: "Which folders should we scan?",
1280
+ choices,
1281
+ required: true
1282
+ });
1283
+ if (selected.includes(CUSTOM_VALUE)) {
1284
+ const custom = await input({
1285
+ message: "Enter folder path(s), comma-separated:"
1286
+ });
1287
+ const customPaths = custom.split(",").map((f) => expandPath(f.trim())).filter(Boolean);
1288
+ scanFolders = [
1289
+ ...selected.filter((v) => v !== CUSTOM_VALUE),
1290
+ ...customPaths
1291
+ ];
1292
+ } else {
1293
+ scanFolders = selected;
1294
+ }
1295
+ if (scanFolders.length === 0) {
1296
+ console.log(chalk4.yellow(" No folders selected."));
1297
+ return;
1298
+ }
948
1299
  }
949
- console.log();
950
- console.log(dim(" Tip: pass --folders ~/Projects,~/Documents to specify directly"));
951
- scanFolders = defaults;
952
1300
  }
953
1301
  console.log();
954
- console.log(dim(" Building your brain..."));
1302
+ let projectCount = 0;
1303
+ let configCount = 0;
1304
+ let docCount = 0;
1305
+ const onProgress = (progress) => {
1306
+ if (progress.phase === "projects") {
1307
+ projectCount++;
1308
+ console.log(dim(` [${projectCount}] ${progress.message}`));
1309
+ } else if (progress.phase === "configs") {
1310
+ configCount++;
1311
+ console.log(dim(` [${configCount}] ${progress.message}`));
1312
+ } else if (progress.phase === "docs") {
1313
+ docCount++;
1314
+ console.log(dim(` [${docCount}] ${progress.message}`));
1315
+ }
1316
+ };
1317
+ const brainInput = scanForBrain(scanFolders, onProgress);
1318
+ const projCount = brainInput.summary.projects.length;
1319
+ const cfgCount = brainInput.summary.configFiles.length;
1320
+ const dcCount = brainInput.summary.recentDocuments.length;
955
1321
  console.log();
956
- const input = scanForBrain(scanFolders);
957
- const projCount = input.summary.projects.length;
958
- const configCount = input.summary.configFiles.length;
959
- const docCount = input.summary.recentDocuments.length;
960
- console.log(dim(` Scanned: ${projCount} projects, ${configCount} config files, ${docCount} recent docs`));
1322
+ console.log(success(` Scanned: ${projCount} projects, ${cfgCount} config files, ${dcCount} recent docs`));
961
1323
  console.log();
962
- if (projCount === 0 && configCount === 0) {
1324
+ if (projCount === 0 && cfgCount === 0) {
963
1325
  console.log(chalk4.yellow(" No projects or config files found to build from."));
964
1326
  console.log(dim(" Try running from a directory with your projects, or use --folders."));
965
1327
  console.log();
966
1328
  return;
967
1329
  }
1330
+ const spinner = new Spinner();
1331
+ spinner.start("Generating brain...");
968
1332
  try {
969
- const sections = await buildBrain(apiKey, input);
1333
+ let sections = null;
1334
+ for await (const event of buildBrainStreaming(apiKey, brainInput)) {
1335
+ switch (event.event) {
1336
+ case "status":
1337
+ spinner.updateMessage(String(event.data.message || "Processing..."));
1338
+ break;
1339
+ case "progress":
1340
+ spinner.updateTokens(event.data.tokens);
1341
+ break;
1342
+ case "section":
1343
+ spinner.addSection(String(event.data.name));
1344
+ break;
1345
+ case "complete":
1346
+ sections = event.data.sections || {};
1347
+ break;
1348
+ case "error":
1349
+ spinner.stop();
1350
+ console.log(chalk4.red(` \u2717 ${event.data.message || "Build failed"}`));
1351
+ throw new CliError(String(event.data.message || "Build failed"));
1352
+ }
1353
+ }
1354
+ spinner.stop();
1355
+ if (!sections) {
1356
+ console.log(chalk4.red(" \u2717 No response received from server"));
1357
+ throw new CliError("No response received from server");
1358
+ }
1359
+ console.log();
1360
+ console.log(success(" Brain built!"));
1361
+ console.log();
970
1362
  const sectionSummary = (content, label) => {
971
1363
  if (!content || !content.trim()) {
972
1364
  console.log(dim(` ${label} \u2014 (empty)`));
@@ -976,24 +1368,23 @@ async function buildCommand(options) {
976
1368
  console.log(success(` ${label}`) + dim(` \u2014 ${preview}`));
977
1369
  }
978
1370
  };
979
- console.log(success(" Brain built!"));
980
- console.log();
981
- sectionSummary(sections.about, "About");
982
- sectionSummary(sections.soul, "Soul");
983
- sectionSummary(sections.areas, "Areas of Responsibility");
984
- sectionSummary(sections.projects, "Projects");
985
- sectionSummary(sections.memory, "Memory");
1371
+ sectionSummary(sections.about || "", "About");
1372
+ sectionSummary(sections.soul || "", "Soul");
1373
+ sectionSummary(sections.areas || "", "Areas of Responsibility");
1374
+ sectionSummary(sections.projects || "", "Projects");
1375
+ sectionSummary(sections.memory || "", "Memory");
986
1376
  console.log();
987
1377
  console.log(dim(` Review and edit at ${brand("piut.com/dashboard")}`));
988
1378
  console.log();
989
1379
  } catch (err) {
1380
+ spinner.stop();
1381
+ if (err instanceof CliError) throw err;
990
1382
  console.log(chalk4.red(` \u2717 ${err.message}`));
991
- process.exit(1);
1383
+ throw new CliError(err.message);
992
1384
  }
993
1385
  }
994
1386
 
995
1387
  // src/commands/deploy.ts
996
- import { confirm as confirm3 } from "@inquirer/prompts";
997
1388
  import chalk5 from "chalk";
998
1389
  async function deployCommand(options) {
999
1390
  banner();
@@ -1005,27 +1396,12 @@ async function deployCommand(options) {
1005
1396
  console.log();
1006
1397
  return;
1007
1398
  }
1008
- console.log();
1009
- console.log(dim(" Your brain will be published as an MCP server at:"));
1010
- console.log(` ${brand(serverUrl)}`);
1011
- console.log();
1012
- console.log(dim(" Any AI tool with this URL can read your brain."));
1013
- console.log();
1014
- if (!options.yes) {
1015
- const proceed = await confirm3({
1016
- message: "Deploy?",
1017
- default: true
1018
- });
1019
- if (!proceed) {
1020
- console.log(dim(" Cancelled."));
1021
- return;
1022
- }
1023
- }
1024
1399
  try {
1025
1400
  await publishServer(apiKey);
1026
1401
  console.log();
1027
1402
  console.log(success(" \u2713 Brain deployed. MCP server live."));
1028
- console.log(dim(` URL: ${serverUrl}`));
1403
+ console.log(` ${brand(serverUrl)}`);
1404
+ console.log(dim(" (securely accessible only with authentication)"));
1029
1405
  console.log();
1030
1406
  console.log(dim(" Next: run ") + brand("piut connect") + dim(" to add brain references to your projects."));
1031
1407
  console.log();
@@ -1040,39 +1416,39 @@ async function deployCommand(options) {
1040
1416
  console.log();
1041
1417
  } else {
1042
1418
  console.log(chalk5.red(` \u2717 ${msg}`));
1043
- process.exit(1);
1419
+ throw new CliError(msg);
1044
1420
  }
1045
1421
  }
1046
1422
  }
1047
1423
 
1048
1424
  // src/commands/connect.ts
1049
- import fs8 from "fs";
1050
- import path9 from "path";
1051
- import { checkbox as checkbox3 } from "@inquirer/prompts";
1425
+ import fs9 from "fs";
1426
+ import path10 from "path";
1427
+ import { checkbox as checkbox4 } from "@inquirer/prompts";
1052
1428
  var RULE_FILES = [
1053
1429
  {
1054
1430
  tool: "Claude Code",
1055
1431
  filePath: "CLAUDE.md",
1056
1432
  strategy: "append",
1057
- detect: (p) => p.hasClaudeMd || fs8.existsSync(path9.join(p.path, ".claude"))
1433
+ detect: (p) => p.hasClaudeMd || fs9.existsSync(path10.join(p.path, ".claude"))
1058
1434
  },
1059
1435
  {
1060
1436
  tool: "Cursor",
1061
1437
  filePath: ".cursor/rules/piut.mdc",
1062
1438
  strategy: "create",
1063
- detect: (p) => p.hasCursorRules || fs8.existsSync(path9.join(p.path, ".cursor"))
1439
+ detect: (p) => p.hasCursorRules || fs9.existsSync(path10.join(p.path, ".cursor"))
1064
1440
  },
1065
1441
  {
1066
1442
  tool: "Windsurf",
1067
1443
  filePath: ".windsurf/rules/piut.md",
1068
1444
  strategy: "create",
1069
- detect: (p) => p.hasWindsurfRules || fs8.existsSync(path9.join(p.path, ".windsurf"))
1445
+ detect: (p) => p.hasWindsurfRules || fs9.existsSync(path10.join(p.path, ".windsurf"))
1070
1446
  },
1071
1447
  {
1072
1448
  tool: "GitHub Copilot",
1073
1449
  filePath: ".github/copilot-instructions.md",
1074
1450
  strategy: "append",
1075
- detect: (p) => p.hasCopilotInstructions || fs8.existsSync(path9.join(p.path, ".github"))
1451
+ detect: (p) => p.hasCopilotInstructions || fs9.existsSync(path10.join(p.path, ".github"))
1076
1452
  },
1077
1453
  {
1078
1454
  tool: "Amazon Q",
@@ -1084,12 +1460,12 @@ var RULE_FILES = [
1084
1460
  tool: "Zed",
1085
1461
  filePath: ".zed/rules.md",
1086
1462
  strategy: "create",
1087
- detect: (p) => p.hasZedRules || fs8.existsSync(path9.join(p.path, ".zed"))
1463
+ detect: (p) => p.hasZedRules || fs9.existsSync(path10.join(p.path, ".zed"))
1088
1464
  }
1089
1465
  ];
1090
1466
  var DEDICATED_FILE_CONTENT = `## p\u0131ut Context
1091
1467
  This project uses p\u0131ut for persistent personal context.
1092
- Skill reference: https://raw.githubusercontent.com/M-Flat-Inc/piut/main/skill.md
1468
+ Full skill reference: .piut/skill.md
1093
1469
 
1094
1470
  Always call \`get_context\` at the start of every conversation.
1095
1471
  Read the \`soul\` section first \u2014 it contains behavioral instructions.
@@ -1098,12 +1474,12 @@ Use \`update_brain\` for substantial new info, \`append_brain\` for quick notes.
1098
1474
  var APPEND_SECTION = `
1099
1475
 
1100
1476
  ## p\u0131ut Context
1101
- Skill reference: https://raw.githubusercontent.com/M-Flat-Inc/piut/main/skill.md
1477
+ Full skill reference: .piut/skill.md
1102
1478
  Always call \`get_context\` at the start of every conversation to load personal context.
1103
1479
  `;
1104
1480
  function hasPiutReference2(filePath) {
1105
1481
  try {
1106
- const content = fs8.readFileSync(filePath, "utf-8");
1482
+ const content = fs9.readFileSync(filePath, "utf-8");
1107
1483
  return content.includes("p\u0131ut Context") || content.includes("piut Context");
1108
1484
  } catch {
1109
1485
  return false;
@@ -1111,7 +1487,21 @@ function hasPiutReference2(filePath) {
1111
1487
  }
1112
1488
  async function connectCommand(options) {
1113
1489
  banner();
1114
- await resolveApiKeyWithResult(options.key);
1490
+ const { apiKey, slug, serverUrl, status } = await resolveApiKeyWithResult(options.key);
1491
+ if (status === "no_brain") {
1492
+ console.log();
1493
+ console.log(warning(" You haven\u2019t built a brain yet."));
1494
+ console.log(dim(" Run ") + brand("piut build") + dim(" first, then ") + brand("piut deploy") + dim("."));
1495
+ console.log();
1496
+ return;
1497
+ }
1498
+ if (status === "unpublished") {
1499
+ console.log();
1500
+ console.log(warning(" Your brain is built but not deployed yet."));
1501
+ console.log(dim(" Run ") + brand("piut deploy") + dim(" to publish your MCP server, then re-run connect."));
1502
+ console.log();
1503
+ return;
1504
+ }
1115
1505
  let scanFolders;
1116
1506
  if (options.folders) {
1117
1507
  scanFolders = options.folders.split(",").map((f) => expandPath(f.trim()));
@@ -1129,20 +1519,20 @@ async function connectCommand(options) {
1129
1519
  for (const project of projects) {
1130
1520
  for (const rule of RULE_FILES) {
1131
1521
  if (!rule.detect(project)) continue;
1132
- const absPath = path9.join(project.path, rule.filePath);
1133
- if (fs8.existsSync(absPath) && hasPiutReference2(absPath)) continue;
1522
+ const absPath = path10.join(project.path, rule.filePath);
1523
+ if (fs9.existsSync(absPath) && hasPiutReference2(absPath)) continue;
1134
1524
  actions.push({
1135
1525
  project,
1136
1526
  tool: rule.tool,
1137
1527
  filePath: rule.filePath,
1138
1528
  absPath,
1139
- action: rule.strategy === "create" || !fs8.existsSync(absPath) ? "create" : "append"
1529
+ action: rule.strategy === "create" || !fs9.existsSync(absPath) ? "create" : "append"
1140
1530
  });
1141
1531
  }
1142
1532
  const hasAnyAction = actions.some((a) => a.project === project);
1143
1533
  if (!hasAnyAction) {
1144
- const claudeMdPath = path9.join(project.path, "CLAUDE.md");
1145
- if (!fs8.existsSync(claudeMdPath)) {
1534
+ const claudeMdPath = path10.join(project.path, "CLAUDE.md");
1535
+ if (!fs9.existsSync(claudeMdPath)) {
1146
1536
  actions.push({
1147
1537
  project,
1148
1538
  tool: "Claude Code",
@@ -1177,22 +1567,21 @@ async function connectCommand(options) {
1177
1567
  console.log();
1178
1568
  const projectChoices = [];
1179
1569
  for (const [projectPath, projectActions] of byProject) {
1180
- const projectName = path9.basename(projectPath);
1570
+ const projectName = path10.basename(projectPath);
1181
1571
  const desc = projectActions.map((a) => {
1182
1572
  const verb = a.action === "create" ? "will create" : "will append to";
1183
1573
  return `${verb} ${a.filePath}`;
1184
1574
  }).join(", ");
1185
1575
  projectChoices.push({
1186
1576
  name: `${projectName} ${dim(`(${desc})`)}`,
1187
- value: projectPath,
1188
- checked: true
1577
+ value: projectPath
1189
1578
  });
1190
1579
  }
1191
1580
  let selectedPaths;
1192
1581
  if (options.yes) {
1193
1582
  selectedPaths = Array.from(byProject.keys());
1194
1583
  } else {
1195
- selectedPaths = await checkbox3({
1584
+ selectedPaths = await checkbox4({
1196
1585
  message: "Select projects to connect:",
1197
1586
  choices: projectChoices
1198
1587
  });
@@ -1203,32 +1592,46 @@ async function connectCommand(options) {
1203
1592
  }
1204
1593
  console.log();
1205
1594
  let connected = 0;
1595
+ const copilotTool = TOOLS.find((t) => t.id === "copilot");
1206
1596
  for (const projectPath of selectedPaths) {
1207
1597
  const projectActions = byProject.get(projectPath) || [];
1208
- const projectName = path9.basename(projectPath);
1598
+ const projectName = path10.basename(projectPath);
1599
+ writePiutConfig(projectPath, { slug, apiKey, serverUrl });
1600
+ await writePiutSkill(projectPath, slug, apiKey);
1601
+ ensureGitignored(projectPath);
1602
+ console.log(success(` \u2713 ${projectName}/.piut/`) + dim(" \u2014 credentials + skill"));
1603
+ if (copilotTool) {
1604
+ const hasCopilot = fs9.existsSync(path10.join(projectPath, ".github", "copilot-instructions.md")) || fs9.existsSync(path10.join(projectPath, ".github"));
1605
+ if (hasCopilot) {
1606
+ const vscodeMcpPath = path10.join(projectPath, ".vscode", "mcp.json");
1607
+ const serverConfig = copilotTool.generateConfig(slug, apiKey);
1608
+ mergeConfig(vscodeMcpPath, copilotTool.configKey, serverConfig);
1609
+ console.log(success(` \u2713 ${projectName}/.vscode/mcp.json`) + dim(" \u2014 Copilot MCP"));
1610
+ }
1611
+ }
1209
1612
  for (const action of projectActions) {
1210
1613
  if (action.action === "create") {
1211
1614
  const isAppendType = RULE_FILES.find((r) => r.filePath === action.filePath)?.strategy === "append";
1212
- const content = isAppendType ? SKILL_SNIPPET + "\n" : DEDICATED_FILE_CONTENT;
1213
- fs8.mkdirSync(path9.dirname(action.absPath), { recursive: true });
1214
- fs8.writeFileSync(action.absPath, content, "utf-8");
1615
+ const content = isAppendType ? PROJECT_SKILL_SNIPPET + "\n" : DEDICATED_FILE_CONTENT;
1616
+ fs9.mkdirSync(path10.dirname(action.absPath), { recursive: true });
1617
+ fs9.writeFileSync(action.absPath, content, "utf-8");
1215
1618
  console.log(success(` \u2713 ${projectName}/${action.filePath}`) + dim(" \u2014 created"));
1216
1619
  } else {
1217
- fs8.appendFileSync(action.absPath, APPEND_SECTION);
1620
+ fs9.appendFileSync(action.absPath, APPEND_SECTION);
1218
1621
  console.log(success(` \u2713 ${projectName}/${action.filePath}`) + dim(" \u2014 appended"));
1219
1622
  }
1220
1623
  connected++;
1221
1624
  }
1222
1625
  }
1223
1626
  console.log();
1224
- console.log(success(` Done. ${connected} file(s) updated across ${selectedPaths.length} project(s).`));
1627
+ console.log(success(` Done. ${selectedPaths.length} project(s) connected.`));
1225
1628
  console.log();
1226
1629
  }
1227
1630
 
1228
1631
  // src/commands/disconnect.ts
1229
- import fs9 from "fs";
1230
- import path10 from "path";
1231
- import { checkbox as checkbox4, confirm as confirm5 } from "@inquirer/prompts";
1632
+ import fs10 from "fs";
1633
+ import path11 from "path";
1634
+ import { checkbox as checkbox5, confirm as confirm4 } from "@inquirer/prompts";
1232
1635
  var DEDICATED_FILES = /* @__PURE__ */ new Set([
1233
1636
  ".cursor/rules/piut.mdc",
1234
1637
  ".windsurf/rules/piut.md",
@@ -1241,7 +1644,7 @@ var APPEND_FILES = [
1241
1644
  ];
1242
1645
  function hasPiutReference3(filePath) {
1243
1646
  try {
1244
- const content = fs9.readFileSync(filePath, "utf-8");
1647
+ const content = fs10.readFileSync(filePath, "utf-8");
1245
1648
  return content.includes("p\u0131ut Context") || content.includes("piut Context");
1246
1649
  } catch {
1247
1650
  return false;
@@ -1249,7 +1652,7 @@ function hasPiutReference3(filePath) {
1249
1652
  }
1250
1653
  function removePiutSection(filePath) {
1251
1654
  try {
1252
- let content = fs9.readFileSync(filePath, "utf-8");
1655
+ let content = fs10.readFileSync(filePath, "utf-8");
1253
1656
  const patterns = [
1254
1657
  /\n*## p[ıi]ut Context[\s\S]*?(?=\n## |\n---\n|$)/g
1255
1658
  ];
@@ -1263,7 +1666,7 @@ function removePiutSection(filePath) {
1263
1666
  }
1264
1667
  if (changed) {
1265
1668
  content = content.replace(/\n{3,}/g, "\n\n").trimEnd() + "\n";
1266
- fs9.writeFileSync(filePath, content, "utf-8");
1669
+ fs10.writeFileSync(filePath, content, "utf-8");
1267
1670
  }
1268
1671
  return changed;
1269
1672
  } catch {
@@ -1280,10 +1683,10 @@ async function disconnectCommand(options) {
1280
1683
  const projects = scanForProjects(scanFolders);
1281
1684
  const actions = [];
1282
1685
  for (const project of projects) {
1283
- const projectName = path10.basename(project.path);
1686
+ const projectName = path11.basename(project.path);
1284
1687
  for (const dedicatedFile of DEDICATED_FILES) {
1285
- const absPath = path10.join(project.path, dedicatedFile);
1286
- if (fs9.existsSync(absPath) && hasPiutReference3(absPath)) {
1688
+ const absPath = path11.join(project.path, dedicatedFile);
1689
+ if (fs10.existsSync(absPath) && hasPiutReference3(absPath)) {
1287
1690
  actions.push({
1288
1691
  projectPath: project.path,
1289
1692
  projectName,
@@ -1294,8 +1697,8 @@ async function disconnectCommand(options) {
1294
1697
  }
1295
1698
  }
1296
1699
  for (const appendFile of APPEND_FILES) {
1297
- const absPath = path10.join(project.path, appendFile);
1298
- if (fs9.existsSync(absPath) && hasPiutReference3(absPath)) {
1700
+ const absPath = path11.join(project.path, appendFile);
1701
+ if (fs10.existsSync(absPath) && hasPiutReference3(absPath)) {
1299
1702
  actions.push({
1300
1703
  projectPath: project.path,
1301
1704
  projectName,
@@ -1305,6 +1708,25 @@ async function disconnectCommand(options) {
1305
1708
  });
1306
1709
  }
1307
1710
  }
1711
+ if (hasPiutDir(project.path)) {
1712
+ actions.push({
1713
+ projectPath: project.path,
1714
+ projectName,
1715
+ filePath: ".piut/",
1716
+ absPath: path11.join(project.path, ".piut"),
1717
+ action: "remove-dir"
1718
+ });
1719
+ }
1720
+ const vscodeMcpPath = path11.join(project.path, ".vscode", "mcp.json");
1721
+ if (fs10.existsSync(vscodeMcpPath) && isPiutConfigured(vscodeMcpPath, "servers")) {
1722
+ actions.push({
1723
+ projectPath: project.path,
1724
+ projectName,
1725
+ filePath: ".vscode/mcp.json",
1726
+ absPath: vscodeMcpPath,
1727
+ action: "remove-mcp"
1728
+ });
1729
+ }
1308
1730
  }
1309
1731
  if (actions.length === 0) {
1310
1732
  console.log(dim(" No connected projects found."));
@@ -1318,19 +1740,18 @@ async function disconnectCommand(options) {
1318
1740
  }
1319
1741
  console.log();
1320
1742
  const projectChoices = Array.from(byProject.entries()).map(([projectPath, projectActions]) => {
1321
- const name = path10.basename(projectPath);
1743
+ const name = path11.basename(projectPath);
1322
1744
  const files = projectActions.map((a) => a.filePath).join(", ");
1323
1745
  return {
1324
1746
  name: `${name} ${dim(`(${files})`)}`,
1325
- value: projectPath,
1326
- checked: true
1747
+ value: projectPath
1327
1748
  };
1328
1749
  });
1329
1750
  let selectedPaths;
1330
1751
  if (options.yes) {
1331
1752
  selectedPaths = Array.from(byProject.keys());
1332
1753
  } else {
1333
- selectedPaths = await checkbox4({
1754
+ selectedPaths = await checkbox5({
1334
1755
  message: "Select projects to disconnect:",
1335
1756
  choices: projectChoices
1336
1757
  });
@@ -1338,7 +1759,7 @@ async function disconnectCommand(options) {
1338
1759
  console.log(dim(" No projects selected."));
1339
1760
  return;
1340
1761
  }
1341
- const proceed = await confirm5({
1762
+ const proceed = await confirm4({
1342
1763
  message: `Disconnect ${selectedPaths.length} project(s)?`,
1343
1764
  default: false
1344
1765
  });
@@ -1348,16 +1769,29 @@ async function disconnectCommand(options) {
1348
1769
  let disconnected = 0;
1349
1770
  for (const projectPath of selectedPaths) {
1350
1771
  const projectActions = byProject.get(projectPath) || [];
1351
- const projectName = path10.basename(projectPath);
1772
+ const projectName = path11.basename(projectPath);
1352
1773
  for (const action of projectActions) {
1353
1774
  if (action.action === "delete") {
1354
1775
  try {
1355
- fs9.unlinkSync(action.absPath);
1776
+ fs10.unlinkSync(action.absPath);
1356
1777
  console.log(success(` \u2713 ${projectName}/${action.filePath}`) + dim(" \u2014 deleted"));
1357
1778
  disconnected++;
1358
1779
  } catch {
1359
1780
  console.log(warning(` \u2717 ${projectName}/${action.filePath}`) + dim(" \u2014 could not delete"));
1360
1781
  }
1782
+ } else if (action.action === "remove-dir") {
1783
+ if (removePiutDir(action.projectPath)) {
1784
+ console.log(success(` \u2713 ${projectName}/${action.filePath}`) + dim(" \u2014 removed"));
1785
+ disconnected++;
1786
+ }
1787
+ } else if (action.action === "remove-mcp") {
1788
+ try {
1789
+ removeFromConfig(action.absPath, "servers");
1790
+ console.log(success(` \u2713 ${projectName}/${action.filePath}`) + dim(" \u2014 piut-context removed"));
1791
+ disconnected++;
1792
+ } catch {
1793
+ console.log(warning(` \u2717 ${projectName}/${action.filePath}`) + dim(" \u2014 could not update"));
1794
+ }
1361
1795
  } else {
1362
1796
  const removed = removePiutSection(action.absPath);
1363
1797
  if (removed) {
@@ -1374,10 +1808,27 @@ async function disconnectCommand(options) {
1374
1808
  console.log();
1375
1809
  }
1376
1810
 
1811
+ // src/commands/logout.ts
1812
+ async function logoutCommand() {
1813
+ banner();
1814
+ const config = readStore();
1815
+ if (!config.apiKey) {
1816
+ console.log(dim(" Not logged in \u2014 nothing to do."));
1817
+ console.log();
1818
+ return;
1819
+ }
1820
+ clearStore();
1821
+ console.log(success(" \u2713 Logged out. Saved API key removed."));
1822
+ console.log();
1823
+ console.log(dim(" To log in again, run: ") + "npx @piut/cli");
1824
+ console.log();
1825
+ }
1826
+
1377
1827
  // src/commands/interactive.ts
1378
- import { select as select2, confirm as confirm6 } from "@inquirer/prompts";
1828
+ import { select as select2, confirm as confirm5, checkbox as checkbox6, password as password3 } from "@inquirer/prompts";
1829
+ import fs11 from "fs";
1830
+ import path12 from "path";
1379
1831
  import chalk6 from "chalk";
1380
- import { password as password3 } from "@inquirer/prompts";
1381
1832
  async function authenticate() {
1382
1833
  const config = readStore();
1383
1834
  let apiKey = config.apiKey;
@@ -1413,15 +1864,22 @@ async function authenticate() {
1413
1864
  updateStore({ apiKey });
1414
1865
  return { apiKey, validation: result };
1415
1866
  }
1867
+ function isPromptCancellation(err) {
1868
+ return !!(err && typeof err === "object" && "name" in err && err.name === "ExitPromptError");
1869
+ }
1416
1870
  async function interactiveMenu() {
1417
1871
  banner();
1418
- const { apiKey, validation } = await authenticate();
1872
+ let apiKey;
1873
+ let currentValidation;
1874
+ const auth = await authenticate();
1875
+ apiKey = auth.apiKey;
1876
+ currentValidation = auth.validation;
1419
1877
  console.log();
1420
- if (validation.status === "no_brain") {
1878
+ if (currentValidation.status === "no_brain") {
1421
1879
  console.log(warning(" You haven\u2019t built a brain yet."));
1422
1880
  console.log(dim(" Your brain is how AI tools learn about you \u2014 your projects, preferences, and context."));
1423
1881
  console.log();
1424
- const wantBuild = await confirm6({
1882
+ const wantBuild = await confirm5({
1425
1883
  message: "Build your brain now?",
1426
1884
  default: true
1427
1885
  });
@@ -1432,13 +1890,12 @@ async function interactiveMenu() {
1432
1890
  console.log(dim(" You can build your brain anytime with: ") + brand("piut build"));
1433
1891
  console.log();
1434
1892
  }
1435
- return;
1436
1893
  }
1437
- if (validation.status === "unpublished") {
1894
+ if (currentValidation.status === "unpublished") {
1438
1895
  console.log(warning(" Your brain is built but not deployed yet."));
1439
1896
  console.log(dim(" Deploy it to make your MCP server live so AI tools can read your brain."));
1440
1897
  console.log();
1441
- const wantDeploy = await confirm6({
1898
+ const wantDeploy = await confirm5({
1442
1899
  message: "Deploy your brain now?",
1443
1900
  default: true
1444
1901
  });
@@ -1449,41 +1906,248 @@ async function interactiveMenu() {
1449
1906
  console.log(dim(" You can deploy anytime with: ") + brand("piut deploy"));
1450
1907
  console.log();
1451
1908
  }
1909
+ }
1910
+ while (true) {
1911
+ const hasBrain = currentValidation.status !== "no_brain";
1912
+ const isDeployed = currentValidation.status === "active";
1913
+ let action;
1914
+ try {
1915
+ action = await select2({
1916
+ message: "What would you like to do?",
1917
+ choices: [
1918
+ {
1919
+ name: hasBrain ? "Rebuild Brain" : "Build Brain",
1920
+ value: "build",
1921
+ description: hasBrain ? "Rebuild your brain from your files" : "Build your brain from your files"
1922
+ },
1923
+ {
1924
+ name: isDeployed ? "Undeploy Brain" : "Deploy Brain",
1925
+ value: "deploy",
1926
+ description: isDeployed ? "Take your MCP server offline" : "Publish your MCP server (requires paid account)"
1927
+ },
1928
+ {
1929
+ name: "Connect Tools",
1930
+ value: "connect-tools",
1931
+ description: "Configure AI tools to use your MCP server",
1932
+ disabled: !isDeployed && "(deploy brain first)"
1933
+ },
1934
+ {
1935
+ name: "Disconnect Tools",
1936
+ value: "disconnect-tools",
1937
+ description: "Remove p\u0131ut from AI tool configs",
1938
+ disabled: !isDeployed && "(deploy brain first)"
1939
+ },
1940
+ {
1941
+ name: "Connect Projects",
1942
+ value: "connect-projects",
1943
+ description: "Add brain references to project config files",
1944
+ disabled: !isDeployed && "(deploy brain first)"
1945
+ },
1946
+ {
1947
+ name: "Disconnect Projects",
1948
+ value: "disconnect-projects",
1949
+ description: "Remove brain references from project configs",
1950
+ disabled: !isDeployed && "(deploy brain first)"
1951
+ },
1952
+ {
1953
+ name: "Status",
1954
+ value: "status",
1955
+ description: "Show brain, deployment, and connected tools/projects"
1956
+ },
1957
+ {
1958
+ name: "Logout",
1959
+ value: "logout",
1960
+ description: "Remove saved API key"
1961
+ },
1962
+ {
1963
+ name: "Exit",
1964
+ value: "exit",
1965
+ description: "Quit p\u0131ut CLI"
1966
+ }
1967
+ ]
1968
+ });
1969
+ } catch {
1970
+ return;
1971
+ }
1972
+ if (action === "exit") return;
1973
+ try {
1974
+ switch (action) {
1975
+ case "build":
1976
+ await buildCommand({ key: apiKey });
1977
+ break;
1978
+ case "deploy":
1979
+ if (isDeployed) {
1980
+ await handleUndeploy(apiKey);
1981
+ } else {
1982
+ await deployCommand({ key: apiKey });
1983
+ }
1984
+ break;
1985
+ case "connect-tools":
1986
+ await handleConnectTools(apiKey, currentValidation);
1987
+ break;
1988
+ case "disconnect-tools":
1989
+ await handleDisconnectTools();
1990
+ break;
1991
+ case "connect-projects":
1992
+ await connectCommand({ key: apiKey });
1993
+ break;
1994
+ case "disconnect-projects":
1995
+ await disconnectCommand({});
1996
+ break;
1997
+ case "status":
1998
+ statusCommand();
1999
+ break;
2000
+ case "logout":
2001
+ await logoutCommand();
2002
+ console.log();
2003
+ try {
2004
+ const newAuth = await authenticate();
2005
+ apiKey = newAuth.apiKey;
2006
+ currentValidation = newAuth.validation;
2007
+ } catch {
2008
+ return;
2009
+ }
2010
+ break;
2011
+ }
2012
+ } catch (err) {
2013
+ if (isPromptCancellation(err)) {
2014
+ console.log();
2015
+ } else if (err instanceof CliError) {
2016
+ console.log();
2017
+ } else {
2018
+ console.log(chalk6.red(` Error: ${err.message}`));
2019
+ console.log();
2020
+ }
2021
+ }
2022
+ try {
2023
+ currentValidation = await validateKey(apiKey);
2024
+ } catch {
2025
+ }
2026
+ }
2027
+ }
2028
+ async function handleUndeploy(apiKey) {
2029
+ const confirmed = await confirm5({
2030
+ message: "Undeploy your brain? AI tools will lose access to your MCP server.",
2031
+ default: false
2032
+ });
2033
+ if (!confirmed) return;
2034
+ try {
2035
+ await unpublishServer(apiKey);
2036
+ console.log();
2037
+ console.log(success(" \u2713 Brain undeployed. MCP server is offline."));
2038
+ console.log(dim(" Run ") + brand("piut deploy") + dim(" to re-deploy anytime."));
2039
+ console.log();
2040
+ } catch (err) {
2041
+ console.log(chalk6.red(` \u2717 ${err.message}`));
2042
+ }
2043
+ }
2044
+ async function handleConnectTools(apiKey, validation) {
2045
+ const { slug } = validation;
2046
+ const unconfigured = [];
2047
+ const alreadyConnected = [];
2048
+ for (const tool of TOOLS) {
2049
+ const paths = resolveConfigPaths(tool.configPaths);
2050
+ for (const configPath of paths) {
2051
+ const exists = fs11.existsSync(configPath);
2052
+ const parentExists = fs11.existsSync(path12.dirname(configPath));
2053
+ if (exists || parentExists) {
2054
+ if (exists && isPiutConfigured(configPath, tool.configKey)) {
2055
+ alreadyConnected.push(tool.name);
2056
+ } else {
2057
+ unconfigured.push({ tool, configPath });
2058
+ }
2059
+ break;
2060
+ }
2061
+ }
2062
+ }
2063
+ if (unconfigured.length === 0) {
2064
+ if (alreadyConnected.length > 0) {
2065
+ console.log(dim(" All detected tools are already connected."));
2066
+ } else {
2067
+ console.log(warning(" No supported AI tools detected."));
2068
+ console.log(dim(" Supported: Claude Code, Claude Desktop, Cursor, Windsurf, GitHub Copilot, Amazon Q, Zed"));
2069
+ }
2070
+ console.log();
1452
2071
  return;
1453
2072
  }
1454
- const action = await select2({
1455
- message: "What would you like to do?",
1456
- choices: [
1457
- { name: "Build Brain", value: "build", description: "Build or rebuild your brain from your files" },
1458
- { name: "Deploy Brain", value: "deploy", description: "Publish your MCP server (requires paid account)" },
1459
- { name: "Connect Projects", value: "connect", description: "Add brain references to project config files" },
1460
- { name: "Disconnect Projects", value: "disconnect", description: "Remove brain references from project configs" },
1461
- { name: "Status", value: "status", description: "Show brain, deployment, and connected projects" }
1462
- ]
2073
+ if (alreadyConnected.length > 0) {
2074
+ console.log(dim(` Already connected: ${alreadyConnected.join(", ")}`));
2075
+ console.log();
2076
+ }
2077
+ const choices = unconfigured.map((u) => ({
2078
+ name: u.tool.name,
2079
+ value: u,
2080
+ checked: true
2081
+ }));
2082
+ const selected = await checkbox6({
2083
+ message: "Select tools to connect:",
2084
+ choices
1463
2085
  });
1464
- switch (action) {
1465
- case "build":
1466
- await buildCommand({ key: apiKey });
1467
- break;
1468
- case "deploy":
1469
- await deployCommand({ key: apiKey });
1470
- break;
1471
- case "connect":
1472
- await connectCommand({ key: apiKey });
1473
- break;
1474
- case "disconnect":
1475
- await disconnectCommand({});
1476
- break;
1477
- case "status":
1478
- statusCommand();
1479
- break;
2086
+ if (selected.length === 0) {
2087
+ console.log(dim(" No tools selected."));
2088
+ return;
2089
+ }
2090
+ console.log();
2091
+ for (const { tool, configPath } of selected) {
2092
+ const serverConfig = tool.generateConfig(slug, apiKey);
2093
+ mergeConfig(configPath, tool.configKey, serverConfig);
2094
+ toolLine(tool.name, success("connected"), "\u2714");
2095
+ }
2096
+ console.log();
2097
+ console.log(dim(" Restart your AI tools for changes to take effect."));
2098
+ console.log();
2099
+ }
2100
+ async function handleDisconnectTools() {
2101
+ const configured = [];
2102
+ for (const tool of TOOLS) {
2103
+ const paths = resolveConfigPaths(tool.configPaths);
2104
+ for (const configPath of paths) {
2105
+ if (fs11.existsSync(configPath) && isPiutConfigured(configPath, tool.configKey)) {
2106
+ configured.push({ tool, configPath });
2107
+ break;
2108
+ }
2109
+ }
1480
2110
  }
2111
+ if (configured.length === 0) {
2112
+ console.log(dim(" p\u0131ut is not configured in any detected AI tools."));
2113
+ console.log();
2114
+ return;
2115
+ }
2116
+ const choices = configured.map((c) => ({
2117
+ name: c.tool.name,
2118
+ value: c
2119
+ }));
2120
+ const selected = await checkbox6({
2121
+ message: "Select tools to disconnect:",
2122
+ choices
2123
+ });
2124
+ if (selected.length === 0) {
2125
+ console.log(dim(" No tools selected."));
2126
+ return;
2127
+ }
2128
+ const proceed = await confirm5({
2129
+ message: `Disconnect p\u0131ut from ${selected.length} tool(s)?`,
2130
+ default: false
2131
+ });
2132
+ if (!proceed) return;
2133
+ console.log();
2134
+ for (const { tool, configPath } of selected) {
2135
+ const removed = removeFromConfig(configPath, tool.configKey);
2136
+ if (removed) {
2137
+ toolLine(tool.name, success("disconnected"), "\u2714");
2138
+ } else {
2139
+ toolLine(tool.name, warning("not found"), "\xD7");
2140
+ }
2141
+ }
2142
+ console.log();
2143
+ console.log(dim(" Restart your AI tools for changes to take effect."));
2144
+ console.log();
1481
2145
  }
1482
2146
 
1483
2147
  // src/lib/update-check.ts
1484
2148
  import { execFile } from "child_process";
1485
2149
  import chalk7 from "chalk";
1486
- import { confirm as confirm7 } from "@inquirer/prompts";
2150
+ import { confirm as confirm6 } from "@inquirer/prompts";
1487
2151
  var PACKAGE_NAME = "@piut/cli";
1488
2152
  async function getLatestVersion() {
1489
2153
  try {
@@ -1517,7 +2181,7 @@ async function checkForUpdate(currentVersion) {
1517
2181
  console.log(dim(` Run ${chalk7.bold(`npm install -g ${PACKAGE_NAME}@latest`)} to update`));
1518
2182
  console.log();
1519
2183
  try {
1520
- const shouldUpdate = await confirm7({
2184
+ const shouldUpdate = await confirm6({
1521
2185
  message: `Update to v${latest} now?`,
1522
2186
  default: true
1523
2187
  });
@@ -1539,14 +2203,25 @@ async function checkForUpdate(currentVersion) {
1539
2203
  }
1540
2204
 
1541
2205
  // src/cli.ts
1542
- var VERSION = "3.1.0";
2206
+ var VERSION = "3.2.0";
2207
+ function withExit(fn) {
2208
+ return async (...args) => {
2209
+ try {
2210
+ await fn(...args);
2211
+ } catch (err) {
2212
+ if (err instanceof CliError) process.exit(1);
2213
+ throw err;
2214
+ }
2215
+ };
2216
+ }
1543
2217
  var program = new Command();
1544
2218
  program.name("piut").description("Build your AI brain instantly. Deploy it as an MCP server. Connect it to every project.").version(VERSION).hook("preAction", () => checkForUpdate(VERSION)).action(interactiveMenu);
1545
- 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);
1546
- 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);
1547
- 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);
1548
- 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);
1549
- 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);
2219
+ program.command("build").description("Build or rebuild your brain from your files").option("-k, --key <key>", "API key").option("--folders <paths>", "Comma-separated folder paths to scan").action(withExit(buildCommand));
2220
+ program.command("deploy").description("Publish your MCP server (requires paid account)").option("-k, --key <key>", "API key").action(withExit(deployCommand));
2221
+ program.command("connect").description("Add brain references to project config files").option("-k, --key <key>", "API key").option("-y, --yes", "Skip interactive prompts").option("--folders <paths>", "Comma-separated folder paths to scan").action(withExit(connectCommand));
2222
+ program.command("disconnect").description("Remove brain references from project config files").option("-y, --yes", "Skip interactive prompts").option("--folders <paths>", "Comma-separated folder paths to scan").action(withExit(disconnectCommand));
2223
+ 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(withExit(setupCommand));
1550
2224
  program.command("status").description("Show brain, deployment, and connected projects").action(statusCommand);
1551
- program.command("remove").description("Remove all p\u0131ut configurations").action(removeCommand);
2225
+ program.command("remove").description("Remove all p\u0131ut configurations").action(withExit(removeCommand));
2226
+ program.command("logout").description("Remove saved API key").action(logoutCommand);
1552
2227
  program.parse();