@piut/cli 3.0.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 +1047 -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,19 +565,31 @@ 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
- const { slug, displayName } = validationResult;
336
- console.log(success(` \u2714 Authenticated as ${displayName} (${slug})`));
570
+ const { slug, displayName, status } = validationResult;
571
+ console.log(success(` \u2714 Authenticated as ${displayName}${slug ? ` (${slug})` : ""}`));
337
572
  console.log();
573
+ if (status === "no_brain") {
574
+ console.log(warning(" You haven\u2019t built a brain yet."));
575
+ console.log(dim(" Run ") + brand("piut build") + dim(" first, then ") + brand("piut deploy") + dim(" to publish it."));
576
+ console.log();
577
+ return;
578
+ }
579
+ if (status === "unpublished") {
580
+ console.log(warning(" Your brain is built but not deployed yet."));
581
+ console.log(dim(" Run ") + brand("piut deploy") + dim(" to publish your MCP server, then re-run setup."));
582
+ console.log();
583
+ return;
584
+ }
338
585
  const detected = [];
339
586
  const toolFilter = options.tool;
340
587
  for (const tool of TOOLS) {
341
588
  if (toolFilter && tool.id !== toolFilter) continue;
342
589
  const paths = resolveConfigPaths(tool.configPaths);
343
590
  for (const configPath of paths) {
344
- const exists = fs3.existsSync(configPath);
345
- const parentExists = fs3.existsSync(path5.dirname(configPath));
591
+ const exists = fs4.existsSync(configPath);
592
+ const parentExists = fs4.existsSync(path6.dirname(configPath));
346
593
  if (exists || parentExists) {
347
594
  detected.push({
348
595
  tool,
@@ -439,6 +686,26 @@ async function setupCommand(options) {
439
686
  }
440
687
  }
441
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
+ }
442
709
  console.log();
443
710
  console.log(brand.bold(" Setup complete!"));
444
711
  if (configured.length > 0) {
@@ -462,12 +729,12 @@ function isCommandAvailable(cmd) {
462
729
  }
463
730
 
464
731
  // src/commands/status.ts
465
- import fs5 from "fs";
466
- import path7 from "path";
732
+ import fs6 from "fs";
733
+ import path8 from "path";
467
734
 
468
735
  // src/lib/brain-scanner.ts
469
- import fs4 from "fs";
470
- import path6 from "path";
736
+ import fs5 from "fs";
737
+ import path7 from "path";
471
738
  import os3 from "os";
472
739
  var home = os3.homedir();
473
740
  var SKIP_DIRS = /* @__PURE__ */ new Set([
@@ -488,13 +755,16 @@ var SKIP_DIRS = /* @__PURE__ */ new Set([
488
755
  ".yarn",
489
756
  ".pnpm-store",
490
757
  "Caches",
491
- "Cache"
758
+ "Cache",
759
+ ".piut"
492
760
  ]);
493
761
  var FULL_READ_FILES = /* @__PURE__ */ new Set([
494
762
  "README.md",
495
763
  "CLAUDE.md",
496
764
  ".cursorrules",
497
765
  ".windsurfrules",
766
+ ".rules",
767
+ ".clinerules",
498
768
  "AGENTS.md",
499
769
  "CONVENTIONS.md",
500
770
  "MEMORY.md",
@@ -503,18 +773,27 @@ var FULL_READ_FILES = /* @__PURE__ */ new Set([
503
773
  ]);
504
774
  var MAX_FILE_SIZE = 100 * 1024;
505
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
+ ]);
506
785
  function shouldSkipDir(name) {
507
- if (name.startsWith(".") && name !== ".claude" && name !== ".cursor" && name !== ".windsurf" && name !== ".github" && name !== ".zed") {
786
+ if (name.startsWith(".") && !SCAN_DOT_DIRS.has(name)) {
508
787
  return true;
509
788
  }
510
789
  return SKIP_DIRS.has(name);
511
790
  }
512
791
  function readFileSafe(filePath) {
513
792
  try {
514
- const stat = fs4.statSync(filePath);
793
+ const stat = fs5.statSync(filePath);
515
794
  if (stat.size > MAX_FILE_SIZE) return null;
516
795
  if (!stat.isFile()) return null;
517
- return fs4.readFileSync(filePath, "utf-8");
796
+ return fs5.readFileSync(filePath, "utf-8");
518
797
  } catch {
519
798
  return null;
520
799
  }
@@ -523,133 +802,156 @@ function getFolderTree(dir, depth = 0, maxDepth = 3) {
523
802
  if (depth >= maxDepth) return [];
524
803
  const entries = [];
525
804
  try {
526
- const items = fs4.readdirSync(dir, { withFileTypes: true });
805
+ const items = fs5.readdirSync(dir, { withFileTypes: true });
527
806
  for (const item of items) {
528
807
  if (!item.isDirectory()) continue;
529
808
  if (shouldSkipDir(item.name)) continue;
530
809
  const indent = " ".repeat(depth);
531
810
  entries.push(`${indent}${item.name}/`);
532
- entries.push(...getFolderTree(path6.join(dir, item.name), depth + 1, maxDepth));
811
+ entries.push(...getFolderTree(path7.join(dir, item.name), depth + 1, maxDepth));
533
812
  }
534
813
  } catch {
535
814
  }
536
815
  return entries;
537
816
  }
538
- 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) {
539
862
  const projects = [];
540
- for (const dir of scanDirs) {
863
+ const seen = /* @__PURE__ */ new Set();
864
+ function walk(dir, depth) {
865
+ if (depth > MAX_PROJECT_DEPTH) return;
541
866
  try {
542
- const items = fs4.readdirSync(dir, { withFileTypes: true });
867
+ const items = fs5.readdirSync(dir, { withFileTypes: true });
543
868
  for (const item of items) {
544
869
  if (!item.isDirectory()) continue;
545
870
  if (shouldSkipDir(item.name)) continue;
546
- const projectPath = path6.join(dir, item.name);
547
- const hasGit = fs4.existsSync(path6.join(projectPath, ".git"));
548
- const hasPkgJson = fs4.existsSync(path6.join(projectPath, "package.json"));
549
- const hasCargoToml = fs4.existsSync(path6.join(projectPath, "Cargo.toml"));
550
- const hasPyproject = fs4.existsSync(path6.join(projectPath, "pyproject.toml"));
551
- const hasGoMod = fs4.existsSync(path6.join(projectPath, "go.mod"));
552
- if (!hasGit && !hasPkgJson && !hasCargoToml && !hasPyproject && !hasGoMod) continue;
553
- let description = "";
554
- if (hasPkgJson) {
555
- try {
556
- const pkg = JSON.parse(fs4.readFileSync(path6.join(projectPath, "package.json"), "utf-8"));
557
- description = pkg.description || "";
558
- } catch {
559
- }
560
- }
561
- const readmePath = path6.join(projectPath, "README.md");
562
- if (!description && fs4.existsSync(readmePath)) {
563
- const content = readFileSafe(readmePath);
564
- if (content) {
565
- const lines = content.split("\n");
566
- let foundHeading = false;
567
- for (const line of lines) {
568
- if (line.startsWith("#")) {
569
- foundHeading = true;
570
- continue;
571
- }
572
- if (foundHeading && line.trim()) {
573
- description = line.trim().slice(0, 200);
574
- break;
575
- }
576
- }
577
- }
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);
578
880
  }
579
- projects.push({
580
- name: item.name,
581
- path: projectPath,
582
- description,
583
- hasClaudeMd: fs4.existsSync(path6.join(projectPath, "CLAUDE.md")),
584
- hasCursorRules: fs4.existsSync(path6.join(projectPath, ".cursorrules")) || fs4.existsSync(path6.join(projectPath, ".cursor", "rules")),
585
- hasWindsurfRules: fs4.existsSync(path6.join(projectPath, ".windsurfrules")) || fs4.existsSync(path6.join(projectPath, ".windsurf", "rules")),
586
- hasCopilotInstructions: fs4.existsSync(path6.join(projectPath, ".github", "copilot-instructions.md")),
587
- hasConventionsMd: fs4.existsSync(path6.join(projectPath, "CONVENTIONS.md")),
588
- hasZedRules: fs4.existsSync(path6.join(projectPath, ".zed", "rules.md"))
589
- });
590
881
  }
591
882
  } catch {
592
883
  }
593
884
  }
885
+ for (const dir of scanDirs) {
886
+ walk(dir, 0);
887
+ }
594
888
  return projects;
595
889
  }
596
- function collectConfigFiles(projects) {
890
+ function collectConfigFiles(projects, onProgress) {
597
891
  const configs = [];
598
892
  const globalPaths = [
599
- path6.join(home, ".claude", "MEMORY.md"),
600
- path6.join(home, ".claude", "CLAUDE.md"),
601
- path6.join(home, ".openclaw", "workspace", "SOUL.md"),
602
- 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")
603
897
  ];
604
898
  for (const gp of globalPaths) {
605
899
  const content = readFileSafe(gp);
606
900
  if (content && content.trim()) {
607
- 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 });
608
904
  }
609
905
  }
610
906
  for (const project of projects) {
611
907
  for (const fileName of FULL_READ_FILES) {
612
- const filePath = path6.join(project.path, fileName);
908
+ const filePath = path7.join(project.path, fileName);
613
909
  const content = readFileSafe(filePath);
614
910
  if (content && content.trim()) {
615
- configs.push({ name: `${project.name}/${fileName}`, content });
911
+ const name = `${project.name}/${fileName}`;
912
+ configs.push({ name, content });
913
+ onProgress?.({ phase: "configs", message: name });
616
914
  }
617
915
  }
618
- const pkgPath = path6.join(project.path, "package.json");
916
+ const pkgPath = path7.join(project.path, "package.json");
619
917
  const pkgContent = readFileSafe(pkgPath);
620
918
  if (pkgContent) {
621
919
  try {
622
920
  const pkg = JSON.parse(pkgContent);
623
921
  const summary = JSON.stringify({ name: pkg.name, description: pkg.description }, null, 2);
624
- 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 });
625
925
  } catch {
626
926
  }
627
927
  }
628
928
  }
629
929
  return configs;
630
930
  }
631
- function collectRecentDocs(projects) {
931
+ function collectRecentDocs(projects, onProgress) {
632
932
  const docs = [];
633
933
  const cutoff = Date.now() - RECENT_DAYS * 24 * 60 * 60 * 1e3;
634
934
  const seen = /* @__PURE__ */ new Set();
635
935
  for (const project of projects) {
636
936
  try {
637
- const items = fs4.readdirSync(project.path, { withFileTypes: true });
937
+ const items = fs5.readdirSync(project.path, { withFileTypes: true });
638
938
  for (const item of items) {
639
939
  if (!item.isFile()) continue;
640
940
  if (!item.name.endsWith(".md")) continue;
641
941
  if (FULL_READ_FILES.has(item.name)) continue;
642
942
  if (item.name.startsWith(".")) continue;
643
- const filePath = path6.join(project.path, item.name);
943
+ const filePath = path7.join(project.path, item.name);
644
944
  if (seen.has(filePath)) continue;
645
945
  seen.add(filePath);
646
946
  try {
647
- const stat = fs4.statSync(filePath);
947
+ const stat = fs5.statSync(filePath);
648
948
  if (stat.mtimeMs < cutoff) continue;
649
949
  if (stat.size > MAX_FILE_SIZE) continue;
650
- const content = fs4.readFileSync(filePath, "utf-8");
950
+ const content = fs5.readFileSync(filePath, "utf-8");
651
951
  if (content.trim()) {
652
- 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 });
653
955
  }
654
956
  } catch {
655
957
  }
@@ -659,42 +961,66 @@ function collectRecentDocs(projects) {
659
961
  }
660
962
  return docs.slice(0, 20);
661
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
+ ]);
662
983
  function getDefaultScanDirs() {
663
984
  const dirs = [];
664
- const candidates = [
665
- path6.join(home, "Projects"),
666
- path6.join(home, "projects"),
667
- path6.join(home, "Developer"),
668
- path6.join(home, "dev"),
669
- path6.join(home, "Code"),
670
- path6.join(home, "code"),
671
- path6.join(home, "src"),
672
- path6.join(home, "repos"),
673
- path6.join(home, "workspace"),
674
- path6.join(home, "Workspace"),
675
- path6.join(home, "Documents"),
676
- path6.join(home, "Desktop")
677
- ];
678
- for (const dir of candidates) {
679
- if (fs4.existsSync(dir) && fs4.statSync(dir).isDirectory()) {
680
- 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));
681
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
+ }
1006
+ }
1007
+ } catch {
682
1008
  }
683
1009
  if (dirs.length === 0) {
684
1010
  dirs.push(home);
685
1011
  }
686
1012
  return dirs;
687
1013
  }
688
- function scanForBrain(folders) {
1014
+ function scanForBrain(folders, onProgress) {
689
1015
  const scanDirs = folders || getDefaultScanDirs();
690
1016
  const folderTree = [];
691
1017
  for (const dir of scanDirs) {
692
- folderTree.push(`${path6.basename(dir)}/`);
1018
+ folderTree.push(`${path7.basename(dir)}/`);
693
1019
  folderTree.push(...getFolderTree(dir, 1));
694
1020
  }
695
- const projects = detectProjects(scanDirs);
696
- const configFiles = collectConfigFiles(projects);
697
- const recentDocuments = collectRecentDocs(projects);
1021
+ const projects = detectProjects(scanDirs, onProgress);
1022
+ const configFiles = collectConfigFiles(projects, onProgress);
1023
+ const recentDocuments = collectRecentDocs(projects, onProgress);
698
1024
  return {
699
1025
  summary: {
700
1026
  folders: folderTree,
@@ -724,7 +1050,7 @@ var PIUT_FILES = [
724
1050
  ];
725
1051
  function hasPiutReference(filePath) {
726
1052
  try {
727
- const content = fs5.readFileSync(filePath, "utf-8");
1053
+ const content = fs6.readFileSync(filePath, "utf-8");
728
1054
  return content.includes("p\u0131ut Context") || content.includes("piut Context");
729
1055
  } catch {
730
1056
  return false;
@@ -738,7 +1064,7 @@ function statusCommand() {
738
1064
  for (const tool of TOOLS) {
739
1065
  const paths = resolveConfigPaths(tool.configPaths);
740
1066
  for (const configPath of paths) {
741
- if (!fs5.existsSync(configPath)) continue;
1067
+ if (!fs6.existsSync(configPath)) continue;
742
1068
  foundAny = true;
743
1069
  const configured = isPiutConfigured(configPath, tool.configKey);
744
1070
  if (configured) {
@@ -761,8 +1087,8 @@ function statusCommand() {
761
1087
  for (const project of projects) {
762
1088
  const connectedFiles = [];
763
1089
  for (const file of PIUT_FILES) {
764
- const absPath = path7.join(project.path, file);
765
- if (fs5.existsSync(absPath) && hasPiutReference(absPath)) {
1090
+ const absPath = path8.join(project.path, file);
1091
+ if (fs6.existsSync(absPath) && hasPiutReference(absPath)) {
766
1092
  connectedFiles.push(file);
767
1093
  }
768
1094
  }
@@ -782,7 +1108,7 @@ function statusCommand() {
782
1108
  }
783
1109
 
784
1110
  // src/commands/remove.ts
785
- import fs6 from "fs";
1111
+ import fs7 from "fs";
786
1112
  import { checkbox as checkbox2, confirm as confirm2 } from "@inquirer/prompts";
787
1113
  async function removeCommand() {
788
1114
  banner();
@@ -790,7 +1116,7 @@ async function removeCommand() {
790
1116
  for (const tool of TOOLS) {
791
1117
  const paths = resolveConfigPaths(tool.configPaths);
792
1118
  for (const configPath of paths) {
793
- if (fs6.existsSync(configPath) && isPiutConfigured(configPath, tool.configKey)) {
1119
+ if (fs7.existsSync(configPath) && isPiutConfigured(configPath, tool.configKey)) {
794
1120
  configured.push({ tool, configPath });
795
1121
  break;
796
1122
  }
@@ -833,22 +1159,23 @@ async function removeCommand() {
833
1159
  }
834
1160
 
835
1161
  // src/commands/build.ts
836
- import { select } from "@inquirer/prompts";
1162
+ import { select, checkbox as checkbox3, input } from "@inquirer/prompts";
837
1163
  import chalk4 from "chalk";
1164
+ import os5 from "os";
838
1165
 
839
1166
  // src/lib/auth.ts
840
1167
  import { password as password2 } from "@inquirer/prompts";
841
1168
  import chalk3 from "chalk";
842
1169
 
843
1170
  // src/lib/store.ts
844
- import fs7 from "fs";
845
- import path8 from "path";
1171
+ import fs8 from "fs";
1172
+ import path9 from "path";
846
1173
  import os4 from "os";
847
- var CONFIG_DIR = path8.join(os4.homedir(), ".piut");
848
- 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");
849
1176
  function readStore() {
850
1177
  try {
851
- const raw = fs7.readFileSync(CONFIG_FILE, "utf-8");
1178
+ const raw = fs8.readFileSync(CONFIG_FILE2, "utf-8");
852
1179
  return JSON.parse(raw);
853
1180
  } catch {
854
1181
  return {};
@@ -857,10 +1184,16 @@ function readStore() {
857
1184
  function updateStore(updates) {
858
1185
  const config = readStore();
859
1186
  const updated = { ...config, ...updates };
860
- fs7.mkdirSync(CONFIG_DIR, { recursive: true });
861
- 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");
862
1189
  return updated;
863
1190
  }
1191
+ function clearStore() {
1192
+ try {
1193
+ fs8.unlinkSync(CONFIG_FILE2);
1194
+ } catch {
1195
+ }
1196
+ }
864
1197
 
865
1198
  // src/lib/auth.ts
866
1199
  async function resolveApiKey(keyOption) {
@@ -880,9 +1213,10 @@ async function resolveApiKey(keyOption) {
880
1213
  } catch (err) {
881
1214
  console.log(chalk3.red(` \u2717 ${err.message}`));
882
1215
  console.log(dim(" Get a key at https://piut.com/dashboard/keys"));
883
- process.exit(1);
1216
+ throw new CliError(err.message);
884
1217
  }
885
- console.log(success(` \u2713 Connected as ${result.displayName} (${result.slug})`));
1218
+ const label = result.slug ? `${result.displayName} (${result.slug})` : result.displayName;
1219
+ console.log(success(` \u2713 Connected as ${label}`));
886
1220
  updateStore({ apiKey });
887
1221
  return apiKey;
888
1222
  }
@@ -903,9 +1237,10 @@ async function resolveApiKeyWithResult(keyOption) {
903
1237
  } catch (err) {
904
1238
  console.log(chalk3.red(` \u2717 ${err.message}`));
905
1239
  console.log(dim(" Get a key at https://piut.com/dashboard/keys"));
906
- process.exit(1);
1240
+ throw new CliError(err.message);
907
1241
  }
908
- console.log(success(` \u2713 Connected as ${result.displayName} (${result.slug})`));
1242
+ const label = result.slug ? `${result.displayName} (${result.slug})` : result.displayName;
1243
+ console.log(success(` \u2713 Connected as ${label}`));
909
1244
  updateStore({ apiKey });
910
1245
  return { apiKey, ...result };
911
1246
  }
@@ -918,41 +1253,112 @@ async function buildCommand(options) {
918
1253
  if (options.folders) {
919
1254
  scanFolders = options.folders.split(",").map((f) => expandPath(f.trim()));
920
1255
  }
921
- const mode = scanFolders ? "auto" : await select({
922
- message: "How do you want to build your brain?",
923
- choices: [
924
- { name: "Automatically (recommended)", value: "auto", description: "Scan your files and build automatically" },
925
- { name: "Select folder(s)...", value: "folders", description: "Choose specific folders to scan" }
926
- ]
927
- });
928
- if (mode === "folders" && !scanFolders) {
929
- 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.`));
930
1261
  console.log();
931
- console.log(dim(" Detected directories:"));
932
- for (const d of defaults) {
933
- 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
+ }
934
1299
  }
935
- console.log();
936
- console.log(dim(" Tip: pass --folders ~/Projects,~/Documents to specify directly"));
937
- scanFolders = defaults;
938
1300
  }
939
1301
  console.log();
940
- 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;
941
1321
  console.log();
942
- const input = scanForBrain(scanFolders);
943
- const projCount = input.summary.projects.length;
944
- const configCount = input.summary.configFiles.length;
945
- const docCount = input.summary.recentDocuments.length;
946
- 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`));
947
1323
  console.log();
948
- if (projCount === 0 && configCount === 0) {
1324
+ if (projCount === 0 && cfgCount === 0) {
949
1325
  console.log(chalk4.yellow(" No projects or config files found to build from."));
950
1326
  console.log(dim(" Try running from a directory with your projects, or use --folders."));
951
1327
  console.log();
952
1328
  return;
953
1329
  }
1330
+ const spinner = new Spinner();
1331
+ spinner.start("Generating brain...");
954
1332
  try {
955
- 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();
956
1362
  const sectionSummary = (content, label) => {
957
1363
  if (!content || !content.trim()) {
958
1364
  console.log(dim(` ${label} \u2014 (empty)`));
@@ -962,49 +1368,40 @@ async function buildCommand(options) {
962
1368
  console.log(success(` ${label}`) + dim(` \u2014 ${preview}`));
963
1369
  }
964
1370
  };
965
- console.log(success(" Brain built!"));
966
- console.log();
967
- sectionSummary(sections.about, "About");
968
- sectionSummary(sections.soul, "Soul");
969
- sectionSummary(sections.areas, "Areas of Responsibility");
970
- sectionSummary(sections.projects, "Projects");
971
- 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");
972
1376
  console.log();
973
1377
  console.log(dim(` Review and edit at ${brand("piut.com/dashboard")}`));
974
1378
  console.log();
975
1379
  } catch (err) {
1380
+ spinner.stop();
1381
+ if (err instanceof CliError) throw err;
976
1382
  console.log(chalk4.red(` \u2717 ${err.message}`));
977
- process.exit(1);
1383
+ throw new CliError(err.message);
978
1384
  }
979
1385
  }
980
1386
 
981
1387
  // src/commands/deploy.ts
982
- import { confirm as confirm3 } from "@inquirer/prompts";
983
1388
  import chalk5 from "chalk";
984
1389
  async function deployCommand(options) {
985
1390
  banner();
986
- const { apiKey, slug, serverUrl } = await resolveApiKeyWithResult(options.key);
987
- console.log();
988
- console.log(dim(" Your brain will be published as an MCP server at:"));
989
- console.log(` ${brand(serverUrl)}`);
990
- console.log();
991
- console.log(dim(" Any AI tool with this URL can read your brain."));
992
- console.log();
993
- if (!options.yes) {
994
- const proceed = await confirm3({
995
- message: "Deploy?",
996
- default: true
997
- });
998
- if (!proceed) {
999
- console.log(dim(" Cancelled."));
1000
- return;
1001
- }
1391
+ const { apiKey, slug, serverUrl, status } = await resolveApiKeyWithResult(options.key);
1392
+ if (status === "no_brain") {
1393
+ console.log();
1394
+ console.log(warning(" You haven\u2019t built a brain yet."));
1395
+ console.log(dim(" Run ") + brand("piut build") + dim(" first to create your brain, then deploy."));
1396
+ console.log();
1397
+ return;
1002
1398
  }
1003
1399
  try {
1004
1400
  await publishServer(apiKey);
1005
1401
  console.log();
1006
1402
  console.log(success(" \u2713 Brain deployed. MCP server live."));
1007
- console.log(dim(` URL: ${serverUrl}`));
1403
+ console.log(` ${brand(serverUrl)}`);
1404
+ console.log(dim(" (securely accessible only with authentication)"));
1008
1405
  console.log();
1009
1406
  console.log(dim(" Next: run ") + brand("piut connect") + dim(" to add brain references to your projects."));
1010
1407
  console.log();
@@ -1019,39 +1416,39 @@ async function deployCommand(options) {
1019
1416
  console.log();
1020
1417
  } else {
1021
1418
  console.log(chalk5.red(` \u2717 ${msg}`));
1022
- process.exit(1);
1419
+ throw new CliError(msg);
1023
1420
  }
1024
1421
  }
1025
1422
  }
1026
1423
 
1027
1424
  // src/commands/connect.ts
1028
- import fs8 from "fs";
1029
- import path9 from "path";
1030
- 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";
1031
1428
  var RULE_FILES = [
1032
1429
  {
1033
1430
  tool: "Claude Code",
1034
1431
  filePath: "CLAUDE.md",
1035
1432
  strategy: "append",
1036
- detect: (p) => p.hasClaudeMd || fs8.existsSync(path9.join(p.path, ".claude"))
1433
+ detect: (p) => p.hasClaudeMd || fs9.existsSync(path10.join(p.path, ".claude"))
1037
1434
  },
1038
1435
  {
1039
1436
  tool: "Cursor",
1040
1437
  filePath: ".cursor/rules/piut.mdc",
1041
1438
  strategy: "create",
1042
- detect: (p) => p.hasCursorRules || fs8.existsSync(path9.join(p.path, ".cursor"))
1439
+ detect: (p) => p.hasCursorRules || fs9.existsSync(path10.join(p.path, ".cursor"))
1043
1440
  },
1044
1441
  {
1045
1442
  tool: "Windsurf",
1046
1443
  filePath: ".windsurf/rules/piut.md",
1047
1444
  strategy: "create",
1048
- detect: (p) => p.hasWindsurfRules || fs8.existsSync(path9.join(p.path, ".windsurf"))
1445
+ detect: (p) => p.hasWindsurfRules || fs9.existsSync(path10.join(p.path, ".windsurf"))
1049
1446
  },
1050
1447
  {
1051
1448
  tool: "GitHub Copilot",
1052
1449
  filePath: ".github/copilot-instructions.md",
1053
1450
  strategy: "append",
1054
- detect: (p) => p.hasCopilotInstructions || fs8.existsSync(path9.join(p.path, ".github"))
1451
+ detect: (p) => p.hasCopilotInstructions || fs9.existsSync(path10.join(p.path, ".github"))
1055
1452
  },
1056
1453
  {
1057
1454
  tool: "Amazon Q",
@@ -1063,12 +1460,12 @@ var RULE_FILES = [
1063
1460
  tool: "Zed",
1064
1461
  filePath: ".zed/rules.md",
1065
1462
  strategy: "create",
1066
- detect: (p) => p.hasZedRules || fs8.existsSync(path9.join(p.path, ".zed"))
1463
+ detect: (p) => p.hasZedRules || fs9.existsSync(path10.join(p.path, ".zed"))
1067
1464
  }
1068
1465
  ];
1069
1466
  var DEDICATED_FILE_CONTENT = `## p\u0131ut Context
1070
1467
  This project uses p\u0131ut for persistent personal context.
1071
- Skill reference: https://raw.githubusercontent.com/M-Flat-Inc/piut/main/skill.md
1468
+ Full skill reference: .piut/skill.md
1072
1469
 
1073
1470
  Always call \`get_context\` at the start of every conversation.
1074
1471
  Read the \`soul\` section first \u2014 it contains behavioral instructions.
@@ -1077,12 +1474,12 @@ Use \`update_brain\` for substantial new info, \`append_brain\` for quick notes.
1077
1474
  var APPEND_SECTION = `
1078
1475
 
1079
1476
  ## p\u0131ut Context
1080
- Skill reference: https://raw.githubusercontent.com/M-Flat-Inc/piut/main/skill.md
1477
+ Full skill reference: .piut/skill.md
1081
1478
  Always call \`get_context\` at the start of every conversation to load personal context.
1082
1479
  `;
1083
1480
  function hasPiutReference2(filePath) {
1084
1481
  try {
1085
- const content = fs8.readFileSync(filePath, "utf-8");
1482
+ const content = fs9.readFileSync(filePath, "utf-8");
1086
1483
  return content.includes("p\u0131ut Context") || content.includes("piut Context");
1087
1484
  } catch {
1088
1485
  return false;
@@ -1090,7 +1487,21 @@ function hasPiutReference2(filePath) {
1090
1487
  }
1091
1488
  async function connectCommand(options) {
1092
1489
  banner();
1093
- 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
+ }
1094
1505
  let scanFolders;
1095
1506
  if (options.folders) {
1096
1507
  scanFolders = options.folders.split(",").map((f) => expandPath(f.trim()));
@@ -1108,20 +1519,20 @@ async function connectCommand(options) {
1108
1519
  for (const project of projects) {
1109
1520
  for (const rule of RULE_FILES) {
1110
1521
  if (!rule.detect(project)) continue;
1111
- const absPath = path9.join(project.path, rule.filePath);
1112
- if (fs8.existsSync(absPath) && hasPiutReference2(absPath)) continue;
1522
+ const absPath = path10.join(project.path, rule.filePath);
1523
+ if (fs9.existsSync(absPath) && hasPiutReference2(absPath)) continue;
1113
1524
  actions.push({
1114
1525
  project,
1115
1526
  tool: rule.tool,
1116
1527
  filePath: rule.filePath,
1117
1528
  absPath,
1118
- action: rule.strategy === "create" || !fs8.existsSync(absPath) ? "create" : "append"
1529
+ action: rule.strategy === "create" || !fs9.existsSync(absPath) ? "create" : "append"
1119
1530
  });
1120
1531
  }
1121
1532
  const hasAnyAction = actions.some((a) => a.project === project);
1122
1533
  if (!hasAnyAction) {
1123
- const claudeMdPath = path9.join(project.path, "CLAUDE.md");
1124
- if (!fs8.existsSync(claudeMdPath)) {
1534
+ const claudeMdPath = path10.join(project.path, "CLAUDE.md");
1535
+ if (!fs9.existsSync(claudeMdPath)) {
1125
1536
  actions.push({
1126
1537
  project,
1127
1538
  tool: "Claude Code",
@@ -1156,22 +1567,21 @@ async function connectCommand(options) {
1156
1567
  console.log();
1157
1568
  const projectChoices = [];
1158
1569
  for (const [projectPath, projectActions] of byProject) {
1159
- const projectName = path9.basename(projectPath);
1570
+ const projectName = path10.basename(projectPath);
1160
1571
  const desc = projectActions.map((a) => {
1161
1572
  const verb = a.action === "create" ? "will create" : "will append to";
1162
1573
  return `${verb} ${a.filePath}`;
1163
1574
  }).join(", ");
1164
1575
  projectChoices.push({
1165
1576
  name: `${projectName} ${dim(`(${desc})`)}`,
1166
- value: projectPath,
1167
- checked: true
1577
+ value: projectPath
1168
1578
  });
1169
1579
  }
1170
1580
  let selectedPaths;
1171
1581
  if (options.yes) {
1172
1582
  selectedPaths = Array.from(byProject.keys());
1173
1583
  } else {
1174
- selectedPaths = await checkbox3({
1584
+ selectedPaths = await checkbox4({
1175
1585
  message: "Select projects to connect:",
1176
1586
  choices: projectChoices
1177
1587
  });
@@ -1182,32 +1592,46 @@ async function connectCommand(options) {
1182
1592
  }
1183
1593
  console.log();
1184
1594
  let connected = 0;
1595
+ const copilotTool = TOOLS.find((t) => t.id === "copilot");
1185
1596
  for (const projectPath of selectedPaths) {
1186
1597
  const projectActions = byProject.get(projectPath) || [];
1187
- 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
+ }
1188
1612
  for (const action of projectActions) {
1189
1613
  if (action.action === "create") {
1190
1614
  const isAppendType = RULE_FILES.find((r) => r.filePath === action.filePath)?.strategy === "append";
1191
- const content = isAppendType ? SKILL_SNIPPET + "\n" : DEDICATED_FILE_CONTENT;
1192
- fs8.mkdirSync(path9.dirname(action.absPath), { recursive: true });
1193
- 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");
1194
1618
  console.log(success(` \u2713 ${projectName}/${action.filePath}`) + dim(" \u2014 created"));
1195
1619
  } else {
1196
- fs8.appendFileSync(action.absPath, APPEND_SECTION);
1620
+ fs9.appendFileSync(action.absPath, APPEND_SECTION);
1197
1621
  console.log(success(` \u2713 ${projectName}/${action.filePath}`) + dim(" \u2014 appended"));
1198
1622
  }
1199
1623
  connected++;
1200
1624
  }
1201
1625
  }
1202
1626
  console.log();
1203
- console.log(success(` Done. ${connected} file(s) updated across ${selectedPaths.length} project(s).`));
1627
+ console.log(success(` Done. ${selectedPaths.length} project(s) connected.`));
1204
1628
  console.log();
1205
1629
  }
1206
1630
 
1207
1631
  // src/commands/disconnect.ts
1208
- import fs9 from "fs";
1209
- import path10 from "path";
1210
- 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";
1211
1635
  var DEDICATED_FILES = /* @__PURE__ */ new Set([
1212
1636
  ".cursor/rules/piut.mdc",
1213
1637
  ".windsurf/rules/piut.md",
@@ -1220,7 +1644,7 @@ var APPEND_FILES = [
1220
1644
  ];
1221
1645
  function hasPiutReference3(filePath) {
1222
1646
  try {
1223
- const content = fs9.readFileSync(filePath, "utf-8");
1647
+ const content = fs10.readFileSync(filePath, "utf-8");
1224
1648
  return content.includes("p\u0131ut Context") || content.includes("piut Context");
1225
1649
  } catch {
1226
1650
  return false;
@@ -1228,7 +1652,7 @@ function hasPiutReference3(filePath) {
1228
1652
  }
1229
1653
  function removePiutSection(filePath) {
1230
1654
  try {
1231
- let content = fs9.readFileSync(filePath, "utf-8");
1655
+ let content = fs10.readFileSync(filePath, "utf-8");
1232
1656
  const patterns = [
1233
1657
  /\n*## p[ıi]ut Context[\s\S]*?(?=\n## |\n---\n|$)/g
1234
1658
  ];
@@ -1242,7 +1666,7 @@ function removePiutSection(filePath) {
1242
1666
  }
1243
1667
  if (changed) {
1244
1668
  content = content.replace(/\n{3,}/g, "\n\n").trimEnd() + "\n";
1245
- fs9.writeFileSync(filePath, content, "utf-8");
1669
+ fs10.writeFileSync(filePath, content, "utf-8");
1246
1670
  }
1247
1671
  return changed;
1248
1672
  } catch {
@@ -1259,10 +1683,10 @@ async function disconnectCommand(options) {
1259
1683
  const projects = scanForProjects(scanFolders);
1260
1684
  const actions = [];
1261
1685
  for (const project of projects) {
1262
- const projectName = path10.basename(project.path);
1686
+ const projectName = path11.basename(project.path);
1263
1687
  for (const dedicatedFile of DEDICATED_FILES) {
1264
- const absPath = path10.join(project.path, dedicatedFile);
1265
- if (fs9.existsSync(absPath) && hasPiutReference3(absPath)) {
1688
+ const absPath = path11.join(project.path, dedicatedFile);
1689
+ if (fs10.existsSync(absPath) && hasPiutReference3(absPath)) {
1266
1690
  actions.push({
1267
1691
  projectPath: project.path,
1268
1692
  projectName,
@@ -1273,8 +1697,8 @@ async function disconnectCommand(options) {
1273
1697
  }
1274
1698
  }
1275
1699
  for (const appendFile of APPEND_FILES) {
1276
- const absPath = path10.join(project.path, appendFile);
1277
- if (fs9.existsSync(absPath) && hasPiutReference3(absPath)) {
1700
+ const absPath = path11.join(project.path, appendFile);
1701
+ if (fs10.existsSync(absPath) && hasPiutReference3(absPath)) {
1278
1702
  actions.push({
1279
1703
  projectPath: project.path,
1280
1704
  projectName,
@@ -1284,6 +1708,25 @@ async function disconnectCommand(options) {
1284
1708
  });
1285
1709
  }
1286
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
+ }
1287
1730
  }
1288
1731
  if (actions.length === 0) {
1289
1732
  console.log(dim(" No connected projects found."));
@@ -1297,19 +1740,18 @@ async function disconnectCommand(options) {
1297
1740
  }
1298
1741
  console.log();
1299
1742
  const projectChoices = Array.from(byProject.entries()).map(([projectPath, projectActions]) => {
1300
- const name = path10.basename(projectPath);
1743
+ const name = path11.basename(projectPath);
1301
1744
  const files = projectActions.map((a) => a.filePath).join(", ");
1302
1745
  return {
1303
1746
  name: `${name} ${dim(`(${files})`)}`,
1304
- value: projectPath,
1305
- checked: true
1747
+ value: projectPath
1306
1748
  };
1307
1749
  });
1308
1750
  let selectedPaths;
1309
1751
  if (options.yes) {
1310
1752
  selectedPaths = Array.from(byProject.keys());
1311
1753
  } else {
1312
- selectedPaths = await checkbox4({
1754
+ selectedPaths = await checkbox5({
1313
1755
  message: "Select projects to disconnect:",
1314
1756
  choices: projectChoices
1315
1757
  });
@@ -1317,7 +1759,7 @@ async function disconnectCommand(options) {
1317
1759
  console.log(dim(" No projects selected."));
1318
1760
  return;
1319
1761
  }
1320
- const proceed = await confirm5({
1762
+ const proceed = await confirm4({
1321
1763
  message: `Disconnect ${selectedPaths.length} project(s)?`,
1322
1764
  default: false
1323
1765
  });
@@ -1327,16 +1769,29 @@ async function disconnectCommand(options) {
1327
1769
  let disconnected = 0;
1328
1770
  for (const projectPath of selectedPaths) {
1329
1771
  const projectActions = byProject.get(projectPath) || [];
1330
- const projectName = path10.basename(projectPath);
1772
+ const projectName = path11.basename(projectPath);
1331
1773
  for (const action of projectActions) {
1332
1774
  if (action.action === "delete") {
1333
1775
  try {
1334
- fs9.unlinkSync(action.absPath);
1776
+ fs10.unlinkSync(action.absPath);
1335
1777
  console.log(success(` \u2713 ${projectName}/${action.filePath}`) + dim(" \u2014 deleted"));
1336
1778
  disconnected++;
1337
1779
  } catch {
1338
1780
  console.log(warning(` \u2717 ${projectName}/${action.filePath}`) + dim(" \u2014 could not delete"));
1339
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
+ }
1340
1795
  } else {
1341
1796
  const removed = removePiutSection(action.absPath);
1342
1797
  if (removed) {
@@ -1353,18 +1808,35 @@ async function disconnectCommand(options) {
1353
1808
  console.log();
1354
1809
  }
1355
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
+
1356
1827
  // src/commands/interactive.ts
1357
- import { select as select2 } 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";
1358
1831
  import chalk6 from "chalk";
1359
- import { password as password3 } from "@inquirer/prompts";
1360
1832
  async function authenticate() {
1361
1833
  const config = readStore();
1362
1834
  let apiKey = config.apiKey;
1363
1835
  if (apiKey) {
1364
1836
  try {
1365
1837
  const result2 = await validateKey(apiKey);
1366
- console.log(success(` Connected as ${result2.displayName} (${result2.slug})`));
1367
- return apiKey;
1838
+ console.log(success(` Connected as ${result2.displayName}`));
1839
+ return { apiKey, validation: result2 };
1368
1840
  } catch {
1369
1841
  console.log(dim(" Saved key expired. Please re-authenticate."));
1370
1842
  apiKey = void 0;
@@ -1388,51 +1860,368 @@ async function authenticate() {
1388
1860
  console.log(dim(" Get a key at https://piut.com/dashboard/keys"));
1389
1861
  process.exit(1);
1390
1862
  }
1391
- console.log(success(` \u2713 Connected as ${result.displayName} (${result.slug})`));
1863
+ console.log(success(` \u2713 Connected as ${result.displayName}`));
1392
1864
  updateStore({ apiKey });
1393
- return apiKey;
1865
+ return { apiKey, validation: result };
1866
+ }
1867
+ function isPromptCancellation(err) {
1868
+ return !!(err && typeof err === "object" && "name" in err && err.name === "ExitPromptError");
1394
1869
  }
1395
1870
  async function interactiveMenu() {
1396
1871
  banner();
1397
- const apiKey = await authenticate();
1872
+ let apiKey;
1873
+ let currentValidation;
1874
+ const auth = await authenticate();
1875
+ apiKey = auth.apiKey;
1876
+ currentValidation = auth.validation;
1398
1877
  console.log();
1399
- const action = await select2({
1400
- message: "What would you like to do?",
1401
- choices: [
1402
- { name: "Build Brain", value: "build", description: "Build or rebuild your brain from your files" },
1403
- { name: "Deploy Brain", value: "deploy", description: "Publish your MCP server (requires paid account)" },
1404
- { name: "Connect Projects", value: "connect", description: "Add brain references to project config files" },
1405
- { name: "Disconnect Projects", value: "disconnect", description: "Remove brain references from project configs" },
1406
- { name: "Status", value: "status", description: "Show brain, deployment, and connected projects" }
1407
- ]
1408
- });
1409
- switch (action) {
1410
- case "build":
1878
+ if (currentValidation.status === "no_brain") {
1879
+ console.log(warning(" You haven\u2019t built a brain yet."));
1880
+ console.log(dim(" Your brain is how AI tools learn about you \u2014 your projects, preferences, and context."));
1881
+ console.log();
1882
+ const wantBuild = await confirm5({
1883
+ message: "Build your brain now?",
1884
+ default: true
1885
+ });
1886
+ if (wantBuild) {
1411
1887
  await buildCommand({ key: apiKey });
1412
- break;
1413
- case "deploy":
1888
+ } else {
1889
+ console.log();
1890
+ console.log(dim(" You can build your brain anytime with: ") + brand("piut build"));
1891
+ console.log();
1892
+ }
1893
+ }
1894
+ if (currentValidation.status === "unpublished") {
1895
+ console.log(warning(" Your brain is built but not deployed yet."));
1896
+ console.log(dim(" Deploy it to make your MCP server live so AI tools can read your brain."));
1897
+ console.log();
1898
+ const wantDeploy = await confirm5({
1899
+ message: "Deploy your brain now?",
1900
+ default: true
1901
+ });
1902
+ if (wantDeploy) {
1414
1903
  await deployCommand({ key: apiKey });
1415
- break;
1416
- case "connect":
1417
- await connectCommand({ key: apiKey });
1418
- break;
1419
- case "disconnect":
1420
- await disconnectCommand({});
1421
- break;
1422
- case "status":
1423
- statusCommand();
1424
- break;
1904
+ } else {
1905
+ console.log();
1906
+ console.log(dim(" You can deploy anytime with: ") + brand("piut deploy"));
1907
+ console.log();
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();
2071
+ return;
2072
+ }
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
2085
+ });
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
+ }
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();
2145
+ }
2146
+
2147
+ // src/lib/update-check.ts
2148
+ import { execFile } from "child_process";
2149
+ import chalk7 from "chalk";
2150
+ import { confirm as confirm6 } from "@inquirer/prompts";
2151
+ var PACKAGE_NAME = "@piut/cli";
2152
+ async function getLatestVersion() {
2153
+ try {
2154
+ const res = await fetch(`https://registry.npmjs.org/${PACKAGE_NAME}/latest`);
2155
+ if (!res.ok) return null;
2156
+ const data = await res.json();
2157
+ return data.version ?? null;
2158
+ } catch {
2159
+ return null;
2160
+ }
2161
+ }
2162
+ function isNewer(current, latest) {
2163
+ const [cMaj, cMin, cPat] = current.split(".").map(Number);
2164
+ const [lMaj, lMin, lPat] = latest.split(".").map(Number);
2165
+ if (lMaj !== cMaj) return lMaj > cMaj;
2166
+ if (lMin !== cMin) return lMin > cMin;
2167
+ return lPat > cPat;
2168
+ }
2169
+ function runUpdate() {
2170
+ return new Promise((resolve) => {
2171
+ execFile("npm", ["install", "-g", `${PACKAGE_NAME}@latest`], { timeout: 6e4 }, (err) => {
2172
+ resolve(!err);
2173
+ });
2174
+ });
2175
+ }
2176
+ async function checkForUpdate(currentVersion) {
2177
+ const latest = await getLatestVersion();
2178
+ if (!latest || !isNewer(currentVersion, latest)) return;
2179
+ console.log();
2180
+ console.log(brand(" Update available!") + dim(` ${currentVersion} \u2192 ${latest}`));
2181
+ console.log(dim(` Run ${chalk7.bold(`npm install -g ${PACKAGE_NAME}@latest`)} to update`));
2182
+ console.log();
2183
+ try {
2184
+ const shouldUpdate = await confirm6({
2185
+ message: `Update to v${latest} now?`,
2186
+ default: true
2187
+ });
2188
+ if (shouldUpdate) {
2189
+ console.log(dim(" Updating..."));
2190
+ const ok = await runUpdate();
2191
+ if (ok) {
2192
+ console.log(chalk7.green(` \u2713 Updated to v${latest}`));
2193
+ console.log(dim(" Restart the CLI to use the new version."));
2194
+ process.exit(0);
2195
+ } else {
2196
+ console.log(chalk7.yellow(` Could not auto-update. Run manually:`));
2197
+ console.log(chalk7.bold(` npm install -g ${PACKAGE_NAME}@latest`));
2198
+ console.log();
2199
+ }
2200
+ }
2201
+ } catch {
1425
2202
  }
1426
2203
  }
1427
2204
 
1428
2205
  // src/cli.ts
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
+ }
1429
2217
  var program = new Command();
1430
- program.name("piut").description("Build your AI brain instantly. Deploy it as an MCP server. Connect it to every project.").version("3.0.0").action(interactiveMenu);
1431
- 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);
1432
- 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);
1433
- 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);
1434
- 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);
1435
- 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);
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);
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));
1436
2224
  program.command("status").description("Show brain, deployment, and connected projects").action(statusCommand);
1437
- 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);
1438
2227
  program.parse();