@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.
- package/dist/cli.js +1047 -258
- 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
|
|
8
|
-
import
|
|
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
|
|
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(
|
|
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
|
|
42
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
345
|
-
const parentExists =
|
|
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
|
|
466
|
-
import
|
|
732
|
+
import fs6 from "fs";
|
|
733
|
+
import path8 from "path";
|
|
467
734
|
|
|
468
735
|
// src/lib/brain-scanner.ts
|
|
469
|
-
import
|
|
470
|
-
import
|
|
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(".") &&
|
|
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 =
|
|
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
|
|
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 =
|
|
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(
|
|
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
|
|
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
|
-
|
|
863
|
+
const seen = /* @__PURE__ */ new Set();
|
|
864
|
+
function walk(dir, depth) {
|
|
865
|
+
if (depth > MAX_PROJECT_DEPTH) return;
|
|
541
866
|
try {
|
|
542
|
-
const items =
|
|
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
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
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
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
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
|
-
|
|
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 =
|
|
908
|
+
const filePath = path7.join(project.path, fileName);
|
|
613
909
|
const content = readFileSafe(filePath);
|
|
614
910
|
if (content && content.trim()) {
|
|
615
|
-
|
|
911
|
+
const name = `${project.name}/${fileName}`;
|
|
912
|
+
configs.push({ name, content });
|
|
913
|
+
onProgress?.({ phase: "configs", message: name });
|
|
616
914
|
}
|
|
617
915
|
}
|
|
618
|
-
const pkgPath =
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
950
|
+
const content = fs5.readFileSync(filePath, "utf-8");
|
|
651
951
|
if (content.trim()) {
|
|
652
|
-
|
|
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
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
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(`${
|
|
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 =
|
|
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 (!
|
|
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 =
|
|
765
|
-
if (
|
|
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
|
|
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 (
|
|
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
|
|
845
|
-
import
|
|
1171
|
+
import fs8 from "fs";
|
|
1172
|
+
import path9 from "path";
|
|
846
1173
|
import os4 from "os";
|
|
847
|
-
var CONFIG_DIR =
|
|
848
|
-
var
|
|
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 =
|
|
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
|
-
|
|
861
|
-
|
|
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
|
-
|
|
1216
|
+
throw new CliError(err.message);
|
|
884
1217
|
}
|
|
885
|
-
|
|
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
|
-
|
|
1240
|
+
throw new CliError(err.message);
|
|
907
1241
|
}
|
|
908
|
-
|
|
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
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
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
|
-
|
|
932
|
-
|
|
933
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 &&
|
|
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
|
-
|
|
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
|
-
|
|
966
|
-
|
|
967
|
-
sectionSummary(sections.
|
|
968
|
-
sectionSummary(sections.
|
|
969
|
-
sectionSummary(sections.
|
|
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
|
-
|
|
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
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
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(
|
|
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
|
-
|
|
1419
|
+
throw new CliError(msg);
|
|
1023
1420
|
}
|
|
1024
1421
|
}
|
|
1025
1422
|
}
|
|
1026
1423
|
|
|
1027
1424
|
// src/commands/connect.ts
|
|
1028
|
-
import
|
|
1029
|
-
import
|
|
1030
|
-
import { checkbox as
|
|
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 ||
|
|
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 ||
|
|
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 ||
|
|
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 ||
|
|
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 ||
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
1112
|
-
if (
|
|
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" || !
|
|
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 =
|
|
1124
|
-
if (!
|
|
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 =
|
|
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
|
|
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 =
|
|
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 ?
|
|
1192
|
-
|
|
1193
|
-
|
|
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
|
-
|
|
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. ${
|
|
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
|
|
1209
|
-
import
|
|
1210
|
-
import { checkbox as
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
1686
|
+
const projectName = path11.basename(project.path);
|
|
1263
1687
|
for (const dedicatedFile of DEDICATED_FILES) {
|
|
1264
|
-
const absPath =
|
|
1265
|
-
if (
|
|
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 =
|
|
1277
|
-
if (
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
1772
|
+
const projectName = path11.basename(projectPath);
|
|
1331
1773
|
for (const action of projectActions) {
|
|
1332
1774
|
if (action.action === "delete") {
|
|
1333
1775
|
try {
|
|
1334
|
-
|
|
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}
|
|
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}
|
|
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
|
-
|
|
1872
|
+
let apiKey;
|
|
1873
|
+
let currentValidation;
|
|
1874
|
+
const auth = await authenticate();
|
|
1875
|
+
apiKey = auth.apiKey;
|
|
1876
|
+
currentValidation = auth.validation;
|
|
1398
1877
|
console.log();
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
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
|
-
|
|
1413
|
-
|
|
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
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
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(
|
|
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").
|
|
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();
|