@piut/cli 3.5.0 → 3.6.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 +1272 -474
  2. package/package.json +6 -2
package/dist/cli.js CHANGED
@@ -24,7 +24,19 @@ async function validateKey(key) {
24
24
  }
25
25
  return res.json();
26
26
  }
27
- async function* buildBrainStreaming(key, input2) {
27
+ async function loginWithEmail(email, password3) {
28
+ const res = await fetch(`${API_BASE}/api/cli/login`, {
29
+ method: "POST",
30
+ headers: { "Content-Type": "application/json" },
31
+ body: JSON.stringify({ email, password: password3 })
32
+ });
33
+ if (!res.ok) {
34
+ const body = await res.json().catch(() => ({ error: "Unknown error" }));
35
+ throw new Error(body.error || `Login failed (HTTP ${res.status})`);
36
+ }
37
+ return res.json();
38
+ }
39
+ async function* buildBrainStreaming(key, input3) {
28
40
  const res = await fetch(`${API_BASE}/api/cli/build-brain`, {
29
41
  method: "POST",
30
42
  headers: {
@@ -32,7 +44,7 @@ async function* buildBrainStreaming(key, input2) {
32
44
  "Content-Type": "application/json",
33
45
  Accept: "text/event-stream"
34
46
  },
35
- body: JSON.stringify(input2)
47
+ body: JSON.stringify(input3)
36
48
  });
37
49
  if (!res.ok) {
38
50
  const body = await res.json().catch(() => ({ error: "Unknown error" }));
@@ -184,6 +196,47 @@ async function unregisterProject(key, projectPath, machineId) {
184
196
  throw new Error(body.error || `Unregister project failed (HTTP ${res.status})`);
185
197
  }
186
198
  }
199
+ async function deleteConnections(key, toolNames) {
200
+ try {
201
+ await fetch(`${API_BASE}/api/mcp/connections`, {
202
+ method: "DELETE",
203
+ headers: {
204
+ Authorization: `Bearer ${key}`,
205
+ "Content-Type": "application/json"
206
+ },
207
+ body: JSON.stringify({ toolNames })
208
+ });
209
+ } catch {
210
+ }
211
+ }
212
+ async function resyncBrain(serverUrl, key, content) {
213
+ const res = await fetch(serverUrl, {
214
+ method: "POST",
215
+ headers: {
216
+ Authorization: `Bearer ${key}`,
217
+ "Content-Type": "application/json"
218
+ },
219
+ body: JSON.stringify({
220
+ jsonrpc: "2.0",
221
+ id: 1,
222
+ method: "tools/call",
223
+ params: {
224
+ name: "update_brain",
225
+ arguments: { content }
226
+ }
227
+ })
228
+ });
229
+ if (!res.ok) {
230
+ throw new Error(`Resync failed (HTTP ${res.status})`);
231
+ }
232
+ const data = await res.json();
233
+ if (data.error) {
234
+ throw new Error(data.error.message || "Resync failed");
235
+ }
236
+ const resultContent = data.result?.content;
237
+ const text = Array.isArray(resultContent) && resultContent[0]?.text ? resultContent[0].text : "Brain updated";
238
+ return { summary: text };
239
+ }
187
240
  async function unpublishServer(key) {
188
241
  const res = await fetch(`${API_BASE}/api/mcp/publish`, {
189
242
  method: "POST",
@@ -614,18 +667,35 @@ var Spinner = class {
614
667
  startTime = Date.now();
615
668
  message = "";
616
669
  sections = [];
670
+ currentSection = null;
617
671
  start(message) {
618
672
  this.message = message;
619
673
  this.startTime = Date.now();
620
674
  this.sections = [];
675
+ this.currentSection = null;
621
676
  this.render();
622
677
  this.interval = setInterval(() => this.render(), 80);
623
678
  }
624
679
  addSection(name) {
625
- this.clearLine();
626
- const elapsed = this.elapsed();
627
- console.log(` ${success("\u2713")} ${name.padEnd(12)} ${dim(elapsed)}`);
680
+ if (this.currentSection) {
681
+ this.clearLine();
682
+ const elapsed = this.elapsed();
683
+ const label = this.capitalize(this.currentSection);
684
+ console.log(` ${success("\u2713")} ${label.padEnd(14)} ${dim(elapsed)}`);
685
+ }
686
+ this.currentSection = name;
628
687
  this.sections.push(name);
688
+ this.message = `Building ${this.capitalize(name)}...`;
689
+ }
690
+ completeAll() {
691
+ if (this.currentSection) {
692
+ this.clearLine();
693
+ const elapsed = this.elapsed();
694
+ const label = this.capitalize(this.currentSection);
695
+ console.log(` ${success("\u2713")} ${label.padEnd(14)} ${dim(elapsed)}`);
696
+ this.currentSection = null;
697
+ }
698
+ this.message = "Finalizing...";
629
699
  }
630
700
  updateMessage(message) {
631
701
  this.message = message;
@@ -655,6 +725,9 @@ var Spinner = class {
655
725
  clearLine() {
656
726
  process.stdout.write("\r\x1B[K");
657
727
  }
728
+ capitalize(s) {
729
+ return s.charAt(0).toUpperCase() + s.slice(1);
730
+ }
658
731
  };
659
732
 
660
733
  // src/types.ts
@@ -902,15 +975,459 @@ function isCommandAvailable(cmd) {
902
975
  }
903
976
 
904
977
  // src/commands/status.ts
905
- import fs7 from "fs";
906
- import path9 from "path";
978
+ import fs9 from "fs";
979
+ import path12 from "path";
907
980
  import chalk3 from "chalk";
908
981
 
909
982
  // src/lib/brain-scanner.ts
910
- import fs5 from "fs";
983
+ import fs7 from "fs";
984
+ import path10 from "path";
985
+ import os5 from "os";
986
+
987
+ // src/lib/file-types.ts
911
988
  import path7 from "path";
989
+ var DOCUMENT_EXTENSIONS = /* @__PURE__ */ new Set([
990
+ ".pdf",
991
+ ".docx",
992
+ ".doc",
993
+ ".pptx",
994
+ ".pages",
995
+ ".key",
996
+ ".rtf",
997
+ ".odt",
998
+ ".odp",
999
+ ".eml",
1000
+ ".mbox"
1001
+ ]);
1002
+ var PLAIN_TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
1003
+ ".md",
1004
+ ".markdown",
1005
+ ".txt",
1006
+ ".csv",
1007
+ ".xml",
1008
+ ".html",
1009
+ ".htm",
1010
+ ".json",
1011
+ ".yaml",
1012
+ ".yml",
1013
+ ".toml",
1014
+ ".rst",
1015
+ ".adoc",
1016
+ ".tex",
1017
+ ".ini",
1018
+ ".cfg",
1019
+ ".conf",
1020
+ ".log"
1021
+ ]);
1022
+ var AI_CONFIG_FILENAMES = /* @__PURE__ */ new Set([
1023
+ "CLAUDE.md",
1024
+ ".cursorrules",
1025
+ ".windsurfrules",
1026
+ ".rules",
1027
+ ".clinerules",
1028
+ "AGENTS.md",
1029
+ "CONVENTIONS.md",
1030
+ "MEMORY.md",
1031
+ "SOUL.md",
1032
+ "IDENTITY.md",
1033
+ "copilot-instructions.md"
1034
+ ]);
1035
+ var PARSEABLE_EXTENSIONS = /* @__PURE__ */ new Set([
1036
+ ...DOCUMENT_EXTENSIONS,
1037
+ ...PLAIN_TEXT_EXTENSIONS
1038
+ ]);
1039
+ var EXCLUDED_EXTENSIONS = /* @__PURE__ */ new Set([
1040
+ ".js",
1041
+ ".ts",
1042
+ ".jsx",
1043
+ ".tsx",
1044
+ ".mjs",
1045
+ ".cjs",
1046
+ ".py",
1047
+ ".pyw",
1048
+ ".pyi",
1049
+ ".rs",
1050
+ ".go",
1051
+ ".java",
1052
+ ".kt",
1053
+ ".scala",
1054
+ ".clj",
1055
+ ".c",
1056
+ ".cpp",
1057
+ ".cc",
1058
+ ".h",
1059
+ ".hpp",
1060
+ ".cs",
1061
+ ".rb",
1062
+ ".php",
1063
+ ".swift",
1064
+ ".m",
1065
+ ".mm",
1066
+ ".lua",
1067
+ ".r",
1068
+ ".jl",
1069
+ ".zig",
1070
+ ".nim",
1071
+ ".v",
1072
+ ".sh",
1073
+ ".bash",
1074
+ ".zsh",
1075
+ ".fish",
1076
+ ".ps1",
1077
+ ".bat",
1078
+ ".cmd",
1079
+ ".sql",
1080
+ ".graphql",
1081
+ ".gql",
1082
+ ".proto",
1083
+ ".css",
1084
+ ".scss",
1085
+ ".sass",
1086
+ ".less",
1087
+ ".styl",
1088
+ ".xls",
1089
+ ".xlsx",
1090
+ ".numbers",
1091
+ ".sqlite",
1092
+ ".db",
1093
+ ".dat",
1094
+ ".parquet",
1095
+ ".avro",
1096
+ ".png",
1097
+ ".jpg",
1098
+ ".jpeg",
1099
+ ".gif",
1100
+ ".svg",
1101
+ ".ico",
1102
+ ".webp",
1103
+ ".bmp",
1104
+ ".tiff",
1105
+ ".heic",
1106
+ ".mp3",
1107
+ ".mp4",
1108
+ ".wav",
1109
+ ".aac",
1110
+ ".flac",
1111
+ ".ogg",
1112
+ ".avi",
1113
+ ".mov",
1114
+ ".mkv",
1115
+ ".wmv",
1116
+ ".webm",
1117
+ ".zip",
1118
+ ".tar",
1119
+ ".gz",
1120
+ ".bz2",
1121
+ ".xz",
1122
+ ".7z",
1123
+ ".rar",
1124
+ ".dmg",
1125
+ ".iso",
1126
+ ".exe",
1127
+ ".dll",
1128
+ ".so",
1129
+ ".dylib",
1130
+ ".o",
1131
+ ".a",
1132
+ ".wasm",
1133
+ ".class",
1134
+ ".jar",
1135
+ ".pyc",
1136
+ ".pyo",
1137
+ ".ttf",
1138
+ ".otf",
1139
+ ".woff",
1140
+ ".woff2",
1141
+ ".eot",
1142
+ ".lock",
1143
+ ".map",
1144
+ ".min.js",
1145
+ ".min.css"
1146
+ ]);
1147
+ function canParse(filename) {
1148
+ return PARSEABLE_EXTENSIONS.has(path7.extname(filename).toLowerCase());
1149
+ }
1150
+ function isAiConfigFile(filename) {
1151
+ return AI_CONFIG_FILENAMES.has(path7.basename(filename));
1152
+ }
1153
+ function getFileCategory(filename) {
1154
+ const base = path7.basename(filename);
1155
+ if (AI_CONFIG_FILENAMES.has(base)) return "config";
1156
+ const ext = path7.extname(filename).toLowerCase();
1157
+ if (DOCUMENT_EXTENSIONS.has(ext)) return "document";
1158
+ if (PLAIN_TEXT_EXTENSIONS.has(ext)) return "text";
1159
+ if (EXCLUDED_EXTENSIONS.has(ext)) return "excluded";
1160
+ return "unknown";
1161
+ }
1162
+
1163
+ // src/lib/file-parsers.ts
1164
+ import fs5 from "fs";
1165
+ import path8 from "path";
1166
+ var MAX_RAW_SIZE = 500 * 1024;
1167
+ var MAX_EXTRACTED_TEXT = 100 * 1024;
1168
+ function truncate(text) {
1169
+ if (text.length <= MAX_EXTRACTED_TEXT) return text;
1170
+ return text.slice(0, MAX_EXTRACTED_TEXT);
1171
+ }
1172
+ function stripHtml(html) {
1173
+ return html.replace(/<br\s*\/?>/gi, "\n").replace(/<\/p>/gi, "\n").replace(/<\/div>/gi, "\n").replace(/<\/li>/gi, "\n").replace(/<[^>]+>/g, "").replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&nbsp;/g, " ").replace(/\n{3,}/g, "\n\n").trim();
1174
+ }
1175
+ async function parsePdf(buffer) {
1176
+ const { PDFParse } = await import("pdf-parse");
1177
+ const parser = new PDFParse({ data: new Uint8Array(buffer) });
1178
+ const result = await parser.getText();
1179
+ return result.text;
1180
+ }
1181
+ async function parseDocx(buffer) {
1182
+ const { extractRawText } = await import("mammoth");
1183
+ const result = await extractRawText({ buffer });
1184
+ return result.value;
1185
+ }
1186
+ async function parsePptx(buffer) {
1187
+ const JSZip = (await import("jszip")).default;
1188
+ const zip = await JSZip.loadAsync(buffer);
1189
+ const texts = [];
1190
+ const slideFiles = Object.keys(zip.files).filter((name) => /^ppt\/slides\/slide\d+\.xml$/i.test(name)).sort((a, b) => {
1191
+ const numA = parseInt(a.match(/slide(\d+)/)?.[1] || "0");
1192
+ const numB = parseInt(b.match(/slide(\d+)/)?.[1] || "0");
1193
+ return numA - numB;
1194
+ });
1195
+ for (const slidePath of slideFiles) {
1196
+ const xml = await zip.files[slidePath].async("string");
1197
+ const matches = xml.match(/<a:t[^>]*>([^<]*)<\/a:t>/g);
1198
+ if (matches) {
1199
+ texts.push(matches.map((m) => m.replace(/<[^>]+>/g, "")).join(" "));
1200
+ }
1201
+ }
1202
+ return texts.join("\n\n");
1203
+ }
1204
+ async function parseAppleDoc(buffer) {
1205
+ const JSZip = (await import("jszip")).default;
1206
+ const zip = await JSZip.loadAsync(buffer);
1207
+ if (zip.files["preview.pdf"]) {
1208
+ const pdfBuf = await zip.files["preview.pdf"].async("nodebuffer");
1209
+ try {
1210
+ return await parsePdf(pdfBuf);
1211
+ } catch {
1212
+ }
1213
+ }
1214
+ const texts = [];
1215
+ for (const [name, file] of Object.entries(zip.files)) {
1216
+ if (name.endsWith(".iwa") && !file.dir) {
1217
+ try {
1218
+ const buf = await file.async("nodebuffer");
1219
+ const str = buf.toString("utf-8");
1220
+ const readable = str.match(/[\x20-\x7E\xC0-\xFF]{4,}/g);
1221
+ if (readable) {
1222
+ texts.push(...readable.filter((s) => s.length > 10));
1223
+ }
1224
+ } catch {
1225
+ }
1226
+ }
1227
+ }
1228
+ return texts.join("\n\n");
1229
+ }
1230
+ async function parseRtf(buffer) {
1231
+ const { fromString } = await import("@iarna/rtf-to-html");
1232
+ return new Promise((resolve, reject) => {
1233
+ fromString(buffer.toString("utf-8"), (err, html) => {
1234
+ if (err) {
1235
+ reject(err);
1236
+ return;
1237
+ }
1238
+ resolve(stripHtml(html));
1239
+ });
1240
+ });
1241
+ }
1242
+ async function parseOpenDocument(buffer) {
1243
+ const JSZip = (await import("jszip")).default;
1244
+ const zip = await JSZip.loadAsync(buffer);
1245
+ const contentXml = zip.files["content.xml"];
1246
+ if (!contentXml) return "";
1247
+ return stripHtml(await contentXml.async("string"));
1248
+ }
1249
+ function parseEml(buffer) {
1250
+ const content = buffer.toString("utf-8");
1251
+ const boundaryMatch = content.match(/boundary="?([^\s"]+)"?/i);
1252
+ if (boundaryMatch) {
1253
+ const parts = content.split(`--${boundaryMatch[1]}`);
1254
+ for (const part of parts) {
1255
+ if (/content-type:\s*text\/plain/i.test(part)) {
1256
+ const bodyStart = part.indexOf("\n\n");
1257
+ if (bodyStart !== -1) return part.slice(bodyStart + 2).trim();
1258
+ }
1259
+ }
1260
+ }
1261
+ const headerEnd = content.indexOf("\n\n");
1262
+ if (headerEnd !== -1) {
1263
+ const body = content.slice(headerEnd + 2);
1264
+ if (!/<html/i.test(body.slice(0, 200))) return body.trim();
1265
+ return stripHtml(body);
1266
+ }
1267
+ return content.trim();
1268
+ }
1269
+ function parseMbox(buffer) {
1270
+ const content = buffer.toString("utf-8");
1271
+ const messages = content.split(/^From /m).filter(Boolean);
1272
+ const texts = [];
1273
+ for (const msg of messages.slice(0, 50)) {
1274
+ const headerEnd = msg.indexOf("\n\n");
1275
+ if (headerEnd !== -1) {
1276
+ const body = msg.slice(headerEnd + 2);
1277
+ texts.push(!/<html/i.test(body.slice(0, 200)) ? body.trim() : stripHtml(body));
1278
+ }
1279
+ }
1280
+ return texts.join("\n\n---\n\n");
1281
+ }
1282
+ async function extractTextFromFile(filePath) {
1283
+ const ext = path8.extname(filePath).toLowerCase();
1284
+ try {
1285
+ const stat = fs5.statSync(filePath);
1286
+ if (!stat.isFile()) return null;
1287
+ if (stat.size > MAX_RAW_SIZE) return null;
1288
+ } catch {
1289
+ return null;
1290
+ }
1291
+ if (PLAIN_TEXT_EXTENSIONS.has(ext)) {
1292
+ try {
1293
+ return truncate(fs5.readFileSync(filePath, "utf-8"));
1294
+ } catch {
1295
+ return null;
1296
+ }
1297
+ }
1298
+ if (!DOCUMENT_EXTENSIONS.has(ext)) return null;
1299
+ try {
1300
+ const buffer = fs5.readFileSync(filePath);
1301
+ let text;
1302
+ switch (ext) {
1303
+ case ".pdf":
1304
+ text = await parsePdf(buffer);
1305
+ break;
1306
+ case ".docx":
1307
+ case ".doc":
1308
+ text = await parseDocx(buffer);
1309
+ break;
1310
+ case ".pptx":
1311
+ text = await parsePptx(buffer);
1312
+ break;
1313
+ case ".pages":
1314
+ case ".key":
1315
+ text = await parseAppleDoc(buffer);
1316
+ break;
1317
+ case ".rtf":
1318
+ text = await parseRtf(buffer);
1319
+ break;
1320
+ case ".odt":
1321
+ case ".odp":
1322
+ text = await parseOpenDocument(buffer);
1323
+ break;
1324
+ case ".eml":
1325
+ text = parseEml(buffer);
1326
+ break;
1327
+ case ".mbox":
1328
+ text = parseMbox(buffer);
1329
+ break;
1330
+ default:
1331
+ return null;
1332
+ }
1333
+ return truncate(text);
1334
+ } catch {
1335
+ return null;
1336
+ }
1337
+ }
1338
+ function formatSize(bytes) {
1339
+ if (bytes < 1024) return `${bytes} B`;
1340
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
1341
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
1342
+ }
1343
+
1344
+ // src/lib/folder-tree.ts
1345
+ import fs6 from "fs";
1346
+ import path9 from "path";
912
1347
  import os4 from "os";
913
1348
  var home = os4.homedir();
1349
+ function displayPath(p) {
1350
+ return p.startsWith(home) ? "~" + p.slice(home.length) : p;
1351
+ }
1352
+ function groupFilesByFolder(files) {
1353
+ const map = /* @__PURE__ */ new Map();
1354
+ for (const file of files) {
1355
+ const folder = file.folder;
1356
+ if (!map.has(folder)) map.set(folder, []);
1357
+ map.get(folder).push(file);
1358
+ }
1359
+ const results = [];
1360
+ for (const [folderPath, folderFiles] of map) {
1361
+ results.push({
1362
+ path: folderPath,
1363
+ displayPath: displayPath(folderPath),
1364
+ files: folderFiles,
1365
+ fileCount: folderFiles.length,
1366
+ totalBytes: folderFiles.reduce((sum, f) => sum + f.sizeBytes, 0)
1367
+ });
1368
+ }
1369
+ results.sort((a, b) => a.displayPath.localeCompare(b.displayPath));
1370
+ return results;
1371
+ }
1372
+ function formatFolderChoice(folder) {
1373
+ return `${folder.displayPath} \u2014 ${folder.fileCount} file${folder.fileCount === 1 ? "" : "s"} (${formatSize(folder.totalBytes)})`;
1374
+ }
1375
+ function formatSelectionSummary(folders) {
1376
+ const totalFiles = folders.reduce((sum, f) => sum + f.fileCount, 0);
1377
+ const totalBytes = folders.reduce((sum, f) => sum + f.totalBytes, 0);
1378
+ return `${folders.length} folder${folders.length === 1 ? "" : "s"} selected (${totalFiles} file${totalFiles === 1 ? "" : "s"}, ${formatSize(totalBytes)})`;
1379
+ }
1380
+ var SKIP_HOME_DIRS = /* @__PURE__ */ new Set([
1381
+ "Library",
1382
+ "Applications",
1383
+ "Public",
1384
+ "Movies",
1385
+ "Music",
1386
+ "Pictures",
1387
+ "Templates",
1388
+ ".Trash"
1389
+ ]);
1390
+ var INCLUDE_DOT_DIRS = /* @__PURE__ */ new Set([
1391
+ ".claude",
1392
+ ".cursor",
1393
+ ".windsurf",
1394
+ ".openclaw",
1395
+ ".zed",
1396
+ ".github",
1397
+ ".amazonq"
1398
+ ]);
1399
+ function getDefaultScanDirs() {
1400
+ const dirs = [];
1401
+ try {
1402
+ const entries = fs6.readdirSync(home, { withFileTypes: true });
1403
+ for (const entry of entries) {
1404
+ if (!entry.isDirectory()) continue;
1405
+ if (entry.name.startsWith(".") && !INCLUDE_DOT_DIRS.has(entry.name)) continue;
1406
+ if (SKIP_HOME_DIRS.has(entry.name)) continue;
1407
+ dirs.push(path9.join(home, entry.name));
1408
+ }
1409
+ } catch {
1410
+ }
1411
+ const cloudStorage = path9.join(home, "Library", "CloudStorage");
1412
+ try {
1413
+ if (fs6.existsSync(cloudStorage) && fs6.statSync(cloudStorage).isDirectory()) {
1414
+ const entries = fs6.readdirSync(cloudStorage, { withFileTypes: true });
1415
+ for (const entry of entries) {
1416
+ if (!entry.isDirectory()) continue;
1417
+ const fullPath = path9.join(cloudStorage, entry.name);
1418
+ if (!dirs.includes(fullPath)) {
1419
+ dirs.push(fullPath);
1420
+ }
1421
+ }
1422
+ }
1423
+ } catch {
1424
+ }
1425
+ if (dirs.length === 0) dirs.push(home);
1426
+ return dirs;
1427
+ }
1428
+
1429
+ // src/lib/brain-scanner.ts
1430
+ var home2 = os5.homedir();
914
1431
  var SKIP_DIRS = /* @__PURE__ */ new Set([
915
1432
  "node_modules",
916
1433
  ".git",
@@ -932,21 +1449,6 @@ var SKIP_DIRS = /* @__PURE__ */ new Set([
932
1449
  "Cache",
933
1450
  ".piut"
934
1451
  ]);
935
- var FULL_READ_FILES = /* @__PURE__ */ new Set([
936
- "README.md",
937
- "CLAUDE.md",
938
- ".cursorrules",
939
- ".windsurfrules",
940
- ".rules",
941
- ".clinerules",
942
- "AGENTS.md",
943
- "CONVENTIONS.md",
944
- "MEMORY.md",
945
- "SOUL.md",
946
- "IDENTITY.md"
947
- ]);
948
- var MAX_FILE_SIZE = 100 * 1024;
949
- var RECENT_DAYS = 30;
950
1452
  var SCAN_DOT_DIRS = /* @__PURE__ */ new Set([
951
1453
  ".claude",
952
1454
  ".cursor",
@@ -957,54 +1459,26 @@ var SCAN_DOT_DIRS = /* @__PURE__ */ new Set([
957
1459
  ".vscode"
958
1460
  ]);
959
1461
  function shouldSkipDir(name) {
960
- if (name.startsWith(".") && !SCAN_DOT_DIRS.has(name)) {
961
- return true;
962
- }
1462
+ if (name.startsWith(".") && !SCAN_DOT_DIRS.has(name)) return true;
963
1463
  return SKIP_DIRS.has(name);
964
1464
  }
965
- function readFileSafe(filePath) {
966
- try {
967
- const stat = fs5.statSync(filePath);
968
- if (stat.size > MAX_FILE_SIZE) return null;
969
- if (!stat.isFile()) return null;
970
- return fs5.readFileSync(filePath, "utf-8");
971
- } catch {
972
- return null;
973
- }
974
- }
975
- function getFolderTree(dir, depth = 0, maxDepth = 3) {
976
- if (depth >= maxDepth) return [];
977
- const entries = [];
978
- try {
979
- const items = fs5.readdirSync(dir, { withFileTypes: true });
980
- for (const item of items) {
981
- if (!item.isDirectory()) continue;
982
- if (shouldSkipDir(item.name)) continue;
983
- const indent = " ".repeat(depth);
984
- entries.push(`${indent}${item.name}/`);
985
- entries.push(...getFolderTree(path7.join(dir, item.name), depth + 1, maxDepth));
986
- }
987
- } catch {
988
- }
989
- return entries;
990
- }
991
1465
  function isProject(dirPath) {
992
- 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"));
1466
+ return fs7.existsSync(path10.join(dirPath, ".git")) || fs7.existsSync(path10.join(dirPath, "package.json")) || fs7.existsSync(path10.join(dirPath, "Cargo.toml")) || fs7.existsSync(path10.join(dirPath, "pyproject.toml")) || fs7.existsSync(path10.join(dirPath, "go.mod"));
993
1467
  }
994
1468
  function buildProjectInfo(projectPath) {
995
- const hasPkgJson = fs5.existsSync(path7.join(projectPath, "package.json"));
1469
+ const hasPkgJson = fs7.existsSync(path10.join(projectPath, "package.json"));
996
1470
  let description = "";
997
1471
  if (hasPkgJson) {
998
1472
  try {
999
- const pkg = JSON.parse(fs5.readFileSync(path7.join(projectPath, "package.json"), "utf-8"));
1473
+ const pkg = JSON.parse(fs7.readFileSync(path10.join(projectPath, "package.json"), "utf-8"));
1000
1474
  description = pkg.description || "";
1001
1475
  } catch {
1002
1476
  }
1003
1477
  }
1004
- const readmePath = path7.join(projectPath, "README.md");
1005
- if (!description && fs5.existsSync(readmePath)) {
1006
- const content = readFileSafe(readmePath);
1007
- if (content) {
1478
+ const readmePath = path10.join(projectPath, "README.md");
1479
+ if (!description && fs7.existsSync(readmePath)) {
1480
+ try {
1481
+ const content = fs7.readFileSync(readmePath, "utf-8");
1008
1482
  const lines = content.split("\n");
1009
1483
  let foundHeading = false;
1010
1484
  for (const line of lines) {
@@ -1017,18 +1491,19 @@ function buildProjectInfo(projectPath) {
1017
1491
  break;
1018
1492
  }
1019
1493
  }
1494
+ } catch {
1020
1495
  }
1021
1496
  }
1022
1497
  return {
1023
- name: path7.basename(projectPath),
1498
+ name: path10.basename(projectPath),
1024
1499
  path: projectPath,
1025
1500
  description,
1026
- hasClaudeMd: fs5.existsSync(path7.join(projectPath, "CLAUDE.md")) || fs5.existsSync(path7.join(projectPath, ".claude", "rules")),
1027
- hasCursorRules: fs5.existsSync(path7.join(projectPath, ".cursorrules")) || fs5.existsSync(path7.join(projectPath, ".cursor", "rules")),
1028
- hasWindsurfRules: fs5.existsSync(path7.join(projectPath, ".windsurfrules")) || fs5.existsSync(path7.join(projectPath, ".windsurf", "rules")),
1029
- hasCopilotInstructions: fs5.existsSync(path7.join(projectPath, ".github", "copilot-instructions.md")) || fs5.existsSync(path7.join(projectPath, ".github", "instructions")),
1030
- hasConventionsMd: fs5.existsSync(path7.join(projectPath, "CONVENTIONS.md")) || fs5.existsSync(path7.join(projectPath, ".amazonq", "rules")),
1031
- hasZedRules: fs5.existsSync(path7.join(projectPath, ".rules"))
1501
+ hasClaudeMd: fs7.existsSync(path10.join(projectPath, "CLAUDE.md")) || fs7.existsSync(path10.join(projectPath, ".claude", "rules")),
1502
+ hasCursorRules: fs7.existsSync(path10.join(projectPath, ".cursorrules")) || fs7.existsSync(path10.join(projectPath, ".cursor", "rules")),
1503
+ hasWindsurfRules: fs7.existsSync(path10.join(projectPath, ".windsurfrules")) || fs7.existsSync(path10.join(projectPath, ".windsurf", "rules")),
1504
+ hasCopilotInstructions: fs7.existsSync(path10.join(projectPath, ".github", "copilot-instructions.md")) || fs7.existsSync(path10.join(projectPath, ".github", "instructions")),
1505
+ hasConventionsMd: fs7.existsSync(path10.join(projectPath, "CONVENTIONS.md")) || fs7.existsSync(path10.join(projectPath, ".amazonq", "rules")),
1506
+ hasZedRules: fs7.existsSync(path10.join(projectPath, ".rules"))
1032
1507
  };
1033
1508
  }
1034
1509
  var MAX_PROJECT_DEPTH = 4;
@@ -1038,17 +1513,17 @@ function detectProjects(scanDirs, onProgress) {
1038
1513
  function walk(dir, depth) {
1039
1514
  if (depth > MAX_PROJECT_DEPTH) return;
1040
1515
  try {
1041
- const items = fs5.readdirSync(dir, { withFileTypes: true });
1516
+ const items = fs7.readdirSync(dir, { withFileTypes: true });
1042
1517
  for (const item of items) {
1043
1518
  if (!item.isDirectory()) continue;
1044
1519
  if (shouldSkipDir(item.name)) continue;
1045
- const fullPath = path7.join(dir, item.name);
1520
+ const fullPath = path10.join(dir, item.name);
1046
1521
  if (seen.has(fullPath)) continue;
1047
1522
  seen.add(fullPath);
1048
1523
  if (isProject(fullPath)) {
1049
1524
  const info = buildProjectInfo(fullPath);
1050
1525
  projects.push(info);
1051
- onProgress?.({ phase: "projects", message: `${info.name} (${fullPath.replace(home, "~")})` });
1526
+ onProgress?.({ phase: "projects", message: `${info.name} (${fullPath.replace(home2, "~")})` });
1052
1527
  } else {
1053
1528
  walk(fullPath, depth + 1);
1054
1529
  }
@@ -1061,150 +1536,148 @@ function detectProjects(scanDirs, onProgress) {
1061
1536
  }
1062
1537
  return projects;
1063
1538
  }
1539
+ var MAX_CONFIG_SIZE = 100 * 1024;
1064
1540
  function collectConfigFiles(projects, onProgress) {
1065
1541
  const configs = [];
1066
1542
  const globalPaths = [
1067
- path7.join(home, ".claude", "MEMORY.md"),
1068
- path7.join(home, ".claude", "CLAUDE.md"),
1069
- path7.join(home, ".openclaw", "workspace", "SOUL.md"),
1070
- path7.join(home, ".openclaw", "workspace", "MEMORY.md")
1543
+ path10.join(home2, ".claude", "MEMORY.md"),
1544
+ path10.join(home2, ".claude", "CLAUDE.md"),
1545
+ path10.join(home2, ".openclaw", "workspace", "SOUL.md"),
1546
+ path10.join(home2, ".openclaw", "workspace", "MEMORY.md")
1071
1547
  ];
1072
1548
  for (const gp of globalPaths) {
1073
- const content = readFileSafe(gp);
1074
- if (content && content.trim()) {
1075
- const name = `~/${path7.relative(home, gp)}`;
1076
- configs.push({ name, content });
1077
- onProgress?.({ phase: "configs", message: name });
1078
- }
1079
- }
1080
- for (const project of projects) {
1081
- for (const fileName of FULL_READ_FILES) {
1082
- const filePath = path7.join(project.path, fileName);
1083
- const content = readFileSafe(filePath);
1084
- if (content && content.trim()) {
1085
- const name = `${project.name}/${fileName}`;
1549
+ try {
1550
+ const stat = fs7.statSync(gp);
1551
+ if (!stat.isFile() || stat.size > MAX_CONFIG_SIZE) continue;
1552
+ const content = fs7.readFileSync(gp, "utf-8");
1553
+ if (content.trim()) {
1554
+ const name = `~/${path10.relative(home2, gp)}`;
1086
1555
  configs.push({ name, content });
1087
1556
  onProgress?.({ phase: "configs", message: name });
1088
1557
  }
1558
+ } catch {
1089
1559
  }
1090
- const pkgPath = path7.join(project.path, "package.json");
1091
- const pkgContent = readFileSafe(pkgPath);
1092
- if (pkgContent) {
1560
+ }
1561
+ for (const project of projects) {
1562
+ for (const fileName of AI_CONFIG_FILENAMES) {
1563
+ const filePath = path10.join(project.path, fileName);
1093
1564
  try {
1094
- const pkg = JSON.parse(pkgContent);
1095
- const summary = JSON.stringify({ name: pkg.name, description: pkg.description }, null, 2);
1096
- const name = `${project.name}/package.json`;
1097
- configs.push({ name, content: summary });
1098
- onProgress?.({ phase: "configs", message: name });
1565
+ const stat = fs7.statSync(filePath);
1566
+ if (!stat.isFile() || stat.size > MAX_CONFIG_SIZE) continue;
1567
+ const content = fs7.readFileSync(filePath, "utf-8");
1568
+ if (content.trim()) {
1569
+ const name = `${project.name}/${fileName}`;
1570
+ configs.push({ name, content });
1571
+ onProgress?.({ phase: "configs", message: name });
1572
+ }
1099
1573
  } catch {
1100
1574
  }
1101
1575
  }
1576
+ const pkgPath = path10.join(project.path, "package.json");
1577
+ try {
1578
+ const stat = fs7.statSync(pkgPath);
1579
+ if (stat.isFile() && stat.size <= MAX_CONFIG_SIZE) {
1580
+ const pkg = JSON.parse(fs7.readFileSync(pkgPath, "utf-8"));
1581
+ const summary = JSON.stringify({ name: pkg.name, description: pkg.description }, null, 2);
1582
+ configs.push({ name: `${project.name}/package.json`, content: summary });
1583
+ onProgress?.({ phase: "configs", message: `${project.name}/package.json` });
1584
+ }
1585
+ } catch {
1586
+ }
1102
1587
  }
1103
1588
  return configs;
1104
1589
  }
1105
- function collectRecentDocs(projects, onProgress) {
1106
- const docs = [];
1107
- const cutoff = Date.now() - RECENT_DAYS * 24 * 60 * 60 * 1e3;
1590
+ var MAX_SCAN_DEPTH = 6;
1591
+ var MAX_FILES = 500;
1592
+ async function scanFilesInDirs(dirs, onProgress) {
1593
+ const files = [];
1108
1594
  const seen = /* @__PURE__ */ new Set();
1109
- for (const project of projects) {
1595
+ function walk(dir, depth) {
1596
+ if (depth > MAX_SCAN_DEPTH) return [];
1597
+ const found = [];
1110
1598
  try {
1111
- const items = fs5.readdirSync(project.path, { withFileTypes: true });
1599
+ const items = fs7.readdirSync(dir, { withFileTypes: true });
1112
1600
  for (const item of items) {
1113
- if (!item.isFile()) continue;
1114
- if (!item.name.endsWith(".md")) continue;
1115
- if (FULL_READ_FILES.has(item.name)) continue;
1116
- if (item.name.startsWith(".")) continue;
1117
- const filePath = path7.join(project.path, item.name);
1118
- if (seen.has(filePath)) continue;
1119
- seen.add(filePath);
1120
- try {
1121
- const stat = fs5.statSync(filePath);
1122
- if (stat.mtimeMs < cutoff) continue;
1123
- if (stat.size > MAX_FILE_SIZE) continue;
1124
- const content = fs5.readFileSync(filePath, "utf-8");
1125
- if (content.trim()) {
1126
- const name = `${project.name}/${item.name}`;
1127
- docs.push({ name, content });
1128
- onProgress?.({ phase: "docs", message: name });
1601
+ if (item.isDirectory()) {
1602
+ if (!shouldSkipDir(item.name)) {
1603
+ found.push(...walk(path10.join(dir, item.name), depth + 1));
1604
+ }
1605
+ } else if (item.isFile()) {
1606
+ if (canParse(item.name) && !isAiConfigFile(item.name)) {
1607
+ const fullPath = path10.join(dir, item.name);
1608
+ if (!seen.has(fullPath)) {
1609
+ seen.add(fullPath);
1610
+ found.push(fullPath);
1611
+ }
1129
1612
  }
1130
- } catch {
1131
1613
  }
1132
1614
  }
1133
1615
  } catch {
1134
1616
  }
1617
+ return found;
1135
1618
  }
1136
- return docs.slice(0, 20);
1137
- }
1138
- var SKIP_HOME_DIRS = /* @__PURE__ */ new Set([
1139
- "Library",
1140
- "Applications",
1141
- "Public",
1142
- "Movies",
1143
- "Music",
1144
- "Pictures",
1145
- "Templates",
1146
- ".Trash"
1147
- ]);
1148
- var INCLUDE_DOT_DIRS = /* @__PURE__ */ new Set([
1149
- ".claude",
1150
- ".cursor",
1151
- ".windsurf",
1152
- ".openclaw",
1153
- ".zed",
1154
- ".github",
1155
- ".amazonq"
1156
- ]);
1157
- function getDefaultScanDirs() {
1158
- const dirs = [];
1159
- try {
1160
- const entries = fs5.readdirSync(home, { withFileTypes: true });
1161
- for (const entry of entries) {
1162
- if (!entry.isDirectory()) continue;
1163
- if (entry.name.startsWith(".") && !INCLUDE_DOT_DIRS.has(entry.name)) continue;
1164
- if (SKIP_HOME_DIRS.has(entry.name)) continue;
1165
- dirs.push(path7.join(home, entry.name));
1166
- }
1167
- } catch {
1619
+ const allPaths = [];
1620
+ for (const dir of dirs) {
1621
+ onProgress?.({ phase: "scanning", message: displayPath(dir) });
1622
+ allPaths.push(...walk(dir, 0));
1623
+ if (allPaths.length > MAX_FILES) break;
1168
1624
  }
1169
- const cloudStorage = path7.join(home, "Library", "CloudStorage");
1170
- try {
1171
- if (fs5.existsSync(cloudStorage) && fs5.statSync(cloudStorage).isDirectory()) {
1172
- const entries = fs5.readdirSync(cloudStorage, { withFileTypes: true });
1173
- for (const entry of entries) {
1174
- if (!entry.isDirectory()) continue;
1175
- const fullPath = path7.join(cloudStorage, entry.name);
1176
- if (!dirs.includes(fullPath)) {
1177
- dirs.push(fullPath);
1178
- }
1625
+ const pathsToProcess = allPaths.slice(0, MAX_FILES);
1626
+ for (const filePath of pathsToProcess) {
1627
+ try {
1628
+ const stat = fs7.statSync(filePath);
1629
+ onProgress?.({ phase: "parsing", message: displayPath(filePath) });
1630
+ const content = await extractTextFromFile(filePath);
1631
+ if (content && content.trim()) {
1632
+ const category = getFileCategory(filePath);
1633
+ files.push({
1634
+ path: filePath,
1635
+ displayPath: displayPath(filePath),
1636
+ content,
1637
+ format: category === "document" ? path10.extname(filePath).slice(1) : "text",
1638
+ sizeBytes: stat.size,
1639
+ folder: path10.dirname(filePath)
1640
+ });
1179
1641
  }
1642
+ } catch {
1180
1643
  }
1181
- } catch {
1182
- }
1183
- if (dirs.length === 0) {
1184
- dirs.push(home);
1185
1644
  }
1186
- return dirs;
1645
+ return files;
1187
1646
  }
1188
- function scanForBrain(folders, onProgress) {
1189
- const scanDirs = folders || getDefaultScanDirs();
1647
+ async function scanFolders(dirs, onProgress) {
1648
+ const allFiles = await scanFilesInDirs(dirs, onProgress);
1649
+ const folders = groupFilesByFolder(allFiles);
1650
+ const projects = detectProjects(dirs, onProgress);
1651
+ const configFiles = collectConfigFiles(projects, onProgress);
1652
+ const totalFiles = allFiles.length;
1653
+ const totalBytes = allFiles.reduce((sum, f) => sum + f.sizeBytes, 0);
1654
+ return { folders, configFiles, projects, allFiles, totalFiles, totalBytes };
1655
+ }
1656
+ function buildBrainInput(scanResult, selectedFolderPaths) {
1657
+ const selectedSet = new Set(selectedFolderPaths);
1658
+ const selectedFiles = scanResult.allFiles.filter((f) => selectedSet.has(f.folder));
1190
1659
  const folderTree = [];
1191
- for (const dir of scanDirs) {
1192
- folderTree.push(`${path7.basename(dir)}/`);
1193
- folderTree.push(...getFolderTree(dir, 1));
1660
+ for (const folder of scanResult.folders) {
1661
+ if (selectedSet.has(folder.path)) {
1662
+ folderTree.push(`${folder.displayPath}/ (${folder.fileCount} files)`);
1663
+ }
1194
1664
  }
1195
- const projects = detectProjects(scanDirs, onProgress);
1196
- const configFiles = collectConfigFiles(projects, onProgress);
1197
- const recentDocuments = collectRecentDocs(projects, onProgress);
1665
+ const personalDocuments = selectedFiles.map((f) => ({
1666
+ name: f.displayPath,
1667
+ content: f.content,
1668
+ format: f.format
1669
+ }));
1198
1670
  return {
1199
1671
  summary: {
1200
1672
  folders: folderTree,
1201
- projects: projects.map((p) => ({
1673
+ projects: scanResult.projects.map((p) => ({
1202
1674
  name: p.name,
1203
- path: p.path.replace(home, "~"),
1675
+ path: p.path.replace(home2, "~"),
1204
1676
  description: p.description
1205
1677
  })),
1206
- configFiles,
1207
- recentDocuments
1678
+ configFiles: scanResult.configFiles,
1679
+ recentDocuments: [],
1680
+ personalDocuments
1208
1681
  }
1209
1682
  };
1210
1683
  }
@@ -1214,14 +1687,14 @@ function scanForProjects(folders) {
1214
1687
  }
1215
1688
 
1216
1689
  // src/lib/store.ts
1217
- import fs6 from "fs";
1218
- import path8 from "path";
1219
- import os5 from "os";
1220
- var CONFIG_DIR = path8.join(os5.homedir(), ".piut");
1221
- var CONFIG_FILE2 = path8.join(CONFIG_DIR, "config.json");
1690
+ import fs8 from "fs";
1691
+ import path11 from "path";
1692
+ import os6 from "os";
1693
+ var CONFIG_DIR = path11.join(os6.homedir(), ".piut");
1694
+ var CONFIG_FILE2 = path11.join(CONFIG_DIR, "config.json");
1222
1695
  function readStore() {
1223
1696
  try {
1224
- const raw = fs6.readFileSync(CONFIG_FILE2, "utf-8");
1697
+ const raw = fs8.readFileSync(CONFIG_FILE2, "utf-8");
1225
1698
  return JSON.parse(raw);
1226
1699
  } catch {
1227
1700
  return {};
@@ -1230,13 +1703,13 @@ function readStore() {
1230
1703
  function updateStore(updates) {
1231
1704
  const config = readStore();
1232
1705
  const updated = { ...config, ...updates };
1233
- fs6.mkdirSync(CONFIG_DIR, { recursive: true });
1234
- fs6.writeFileSync(CONFIG_FILE2, JSON.stringify(updated, null, 2) + "\n", "utf-8");
1706
+ fs8.mkdirSync(CONFIG_DIR, { recursive: true });
1707
+ fs8.writeFileSync(CONFIG_FILE2, JSON.stringify(updated, null, 2) + "\n", "utf-8");
1235
1708
  return updated;
1236
1709
  }
1237
1710
  function clearStore() {
1238
1711
  try {
1239
- fs6.unlinkSync(CONFIG_FILE2);
1712
+ fs8.unlinkSync(CONFIG_FILE2);
1240
1713
  } catch {
1241
1714
  }
1242
1715
  }
@@ -1252,7 +1725,7 @@ var PIUT_FILES = [
1252
1725
  ];
1253
1726
  function hasPiutReference(filePath) {
1254
1727
  try {
1255
- const content = fs7.readFileSync(filePath, "utf-8");
1728
+ const content = fs9.readFileSync(filePath, "utf-8");
1256
1729
  return content.includes("p\u0131ut Context") || content.includes("piut Context");
1257
1730
  } catch {
1258
1731
  return false;
@@ -1270,7 +1743,7 @@ async function statusCommand(options = {}) {
1270
1743
  for (const tool of TOOLS) {
1271
1744
  const paths = resolveConfigPaths(tool.configPaths);
1272
1745
  for (const configPath of paths) {
1273
- if (!fs7.existsSync(configPath)) continue;
1746
+ if (!fs9.existsSync(configPath)) continue;
1274
1747
  foundAny = true;
1275
1748
  const configured = isPiutConfigured(configPath, tool.configKey);
1276
1749
  if (configured) {
@@ -1293,8 +1766,8 @@ async function statusCommand(options = {}) {
1293
1766
  for (const project of projects) {
1294
1767
  const connectedFiles = [];
1295
1768
  for (const file of PIUT_FILES) {
1296
- const absPath = path9.join(project.path, file);
1297
- if (fs7.existsSync(absPath) && hasPiutReference(absPath)) {
1769
+ const absPath = path12.join(project.path, file);
1770
+ if (fs9.existsSync(absPath) && hasPiutReference(absPath)) {
1298
1771
  connectedFiles.push(file);
1299
1772
  }
1300
1773
  }
@@ -1340,7 +1813,7 @@ async function verifyStatus() {
1340
1813
  for (const tool of TOOLS) {
1341
1814
  const paths = resolveConfigPaths(tool.configPaths);
1342
1815
  for (const configPath of paths) {
1343
- if (!fs7.existsSync(configPath)) continue;
1816
+ if (!fs9.existsSync(configPath)) continue;
1344
1817
  const piutConfig = getPiutConfig(configPath, tool.configKey);
1345
1818
  if (!piutConfig) {
1346
1819
  toolLine(tool.name, dim("installed, not connected"), "\u25CB");
@@ -1383,7 +1856,7 @@ async function verifyStatus() {
1383
1856
  }
1384
1857
 
1385
1858
  // src/commands/remove.ts
1386
- import fs8 from "fs";
1859
+ import fs10 from "fs";
1387
1860
  import { checkbox as checkbox2, confirm as confirm2 } from "@inquirer/prompts";
1388
1861
  async function removeCommand() {
1389
1862
  banner();
@@ -1391,7 +1864,7 @@ async function removeCommand() {
1391
1864
  for (const tool of TOOLS) {
1392
1865
  const paths = resolveConfigPaths(tool.configPaths);
1393
1866
  for (const configPath of paths) {
1394
- if (fs8.existsSync(configPath) && isPiutConfigured(configPath, tool.configKey)) {
1867
+ if (fs10.existsSync(configPath) && isPiutConfigured(configPath, tool.configKey)) {
1395
1868
  configured.push({ tool, configPath });
1396
1869
  break;
1397
1870
  }
@@ -1420,158 +1893,219 @@ async function removeCommand() {
1420
1893
  });
1421
1894
  if (!proceed) return;
1422
1895
  console.log();
1896
+ const removedNames = [];
1423
1897
  for (const { tool, configPath } of selected) {
1424
1898
  const removed = removeFromConfig(configPath, tool.configKey);
1425
1899
  if (removed) {
1900
+ removedNames.push(tool.name);
1426
1901
  toolLine(tool.name, success("removed"), "\u2714");
1427
1902
  } else {
1428
1903
  toolLine(tool.name, warning("not found"), "\xD7");
1429
1904
  }
1430
1905
  }
1906
+ const store = readStore();
1907
+ if (store.apiKey && removedNames.length > 0) {
1908
+ deleteConnections(store.apiKey, removedNames).catch(() => {
1909
+ });
1910
+ }
1431
1911
  console.log();
1432
1912
  console.log(dim(" Restart your AI tools for changes to take effect."));
1433
1913
  console.log();
1434
1914
  }
1435
1915
 
1436
1916
  // src/commands/build.ts
1437
- import { select, checkbox as checkbox3, input, confirm as confirm3 } from "@inquirer/prompts";
1917
+ import { select as select2, checkbox as checkbox3, input as input2, confirm as confirm3 } from "@inquirer/prompts";
1438
1918
  import chalk5 from "chalk";
1439
- import os6 from "os";
1919
+ import os7 from "os";
1440
1920
 
1441
1921
  // src/lib/auth.ts
1442
- import { password as password2 } from "@inquirer/prompts";
1922
+ import { select, input, password as password2 } from "@inquirer/prompts";
1923
+ import { exec } from "child_process";
1443
1924
  import chalk4 from "chalk";
1444
- async function resolveApiKey(keyOption) {
1445
- const config = readStore();
1446
- let apiKey = keyOption || config.apiKey;
1447
- if (!apiKey) {
1448
- apiKey = await password2({
1449
- message: "Enter your p\u0131ut API key:",
1450
- mask: "*",
1451
- validate: (v) => v.startsWith("pb_") || "Key must start with pb_"
1452
- });
1453
- }
1925
+ function openBrowser(url) {
1926
+ const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
1927
+ exec(`${cmd} "${url}"`);
1928
+ }
1929
+ async function loginWithEmailFlow() {
1930
+ const email = await input({
1931
+ message: "Email:",
1932
+ validate: (v) => v.includes("@") || "Enter a valid email address"
1933
+ });
1934
+ const pw = await password2({
1935
+ message: "Password:",
1936
+ mask: "*"
1937
+ });
1938
+ console.log(dim(" Authenticating..."));
1939
+ const result = await loginWithEmail(email, pw);
1940
+ return {
1941
+ apiKey: result.apiKey,
1942
+ validation: {
1943
+ slug: result.slug,
1944
+ displayName: result.displayName,
1945
+ serverUrl: result.serverUrl,
1946
+ planType: result.planType,
1947
+ status: result.status,
1948
+ _contractVersion: result._contractVersion
1949
+ }
1950
+ };
1951
+ }
1952
+ async function pasteKeyFlow() {
1953
+ const apiKey = await password2({
1954
+ message: "Enter your p\u0131ut API key:",
1955
+ mask: "*",
1956
+ validate: (v) => v.startsWith("pb_") || "Key must start with pb_"
1957
+ });
1454
1958
  console.log(dim(" Validating key..."));
1455
- let result;
1456
- try {
1457
- result = await validateKey(apiKey);
1458
- } catch (err) {
1459
- console.log(chalk4.red(` \u2717 ${err.message}`));
1460
- console.log(dim(" Get a key at https://piut.com/dashboard/keys"));
1461
- throw new CliError(err.message);
1959
+ const validation = await validateKey(apiKey);
1960
+ return { apiKey, validation };
1961
+ }
1962
+ async function browserFlow() {
1963
+ const url = "https://piut.com/dashboard/keys";
1964
+ console.log(dim(` Opening ${brand(url)}...`));
1965
+ openBrowser(url);
1966
+ console.log(dim(" Copy your API key from the dashboard, then paste it here."));
1967
+ console.log();
1968
+ return pasteKeyFlow();
1969
+ }
1970
+ async function promptLogin() {
1971
+ const method = await select({
1972
+ message: "How would you like to connect?",
1973
+ choices: [
1974
+ {
1975
+ name: "Log in with email",
1976
+ value: "email",
1977
+ description: "Use your p\u0131ut email and password"
1978
+ },
1979
+ {
1980
+ name: "Log in with browser",
1981
+ value: "browser",
1982
+ description: "Open piut.com to get your API key"
1983
+ },
1984
+ {
1985
+ name: "Paste API key",
1986
+ value: "api-key",
1987
+ description: "Enter an API key directly"
1988
+ }
1989
+ ]
1990
+ });
1991
+ switch (method) {
1992
+ case "email":
1993
+ return loginWithEmailFlow();
1994
+ case "browser":
1995
+ return browserFlow();
1996
+ case "api-key":
1997
+ return pasteKeyFlow();
1462
1998
  }
1463
- const label = result.slug ? `${result.displayName} (${result.slug})` : result.displayName;
1464
- console.log(success(` \u2713 Connected as ${label}`));
1465
- updateStore({ apiKey });
1466
- return apiKey;
1467
1999
  }
1468
2000
  async function resolveApiKeyWithResult(keyOption) {
1469
2001
  const config = readStore();
1470
2002
  let apiKey = keyOption || config.apiKey;
1471
- if (!apiKey) {
1472
- apiKey = await password2({
1473
- message: "Enter your p\u0131ut API key:",
1474
- mask: "*",
1475
- validate: (v) => v.startsWith("pb_") || "Key must start with pb_"
1476
- });
2003
+ if (apiKey) {
2004
+ console.log(dim(" Validating key..."));
2005
+ let result;
2006
+ try {
2007
+ result = await validateKey(apiKey);
2008
+ } catch (err) {
2009
+ console.log(chalk4.red(` \u2717 ${err.message}`));
2010
+ console.log(dim(" Get a key at https://piut.com/dashboard/keys"));
2011
+ throw new CliError(err.message);
2012
+ }
2013
+ const label = result.slug ? `${result.displayName} (${result.slug})` : result.displayName;
2014
+ console.log(success(` \u2713 Connected as ${label}`));
2015
+ updateStore({ apiKey });
2016
+ return { apiKey, ...result };
1477
2017
  }
1478
- console.log(dim(" Validating key..."));
1479
- let result;
1480
2018
  try {
1481
- result = await validateKey(apiKey);
2019
+ const { apiKey: newKey, validation } = await promptLogin();
2020
+ const label = validation.slug ? `${validation.displayName} (${validation.slug})` : validation.displayName;
2021
+ console.log(success(` \u2713 Connected as ${label}`));
2022
+ updateStore({ apiKey: newKey });
2023
+ return { apiKey: newKey, ...validation };
1482
2024
  } catch (err) {
1483
2025
  console.log(chalk4.red(` \u2717 ${err.message}`));
1484
- console.log(dim(" Get a key at https://piut.com/dashboard/keys"));
1485
2026
  throw new CliError(err.message);
1486
2027
  }
1487
- const label = result.slug ? `${result.displayName} (${result.slug})` : result.displayName;
1488
- console.log(success(` \u2713 Connected as ${label}`));
1489
- updateStore({ apiKey });
1490
- return { apiKey, ...result };
1491
2028
  }
1492
2029
 
1493
2030
  // src/commands/build.ts
1494
2031
  async function buildCommand(options) {
1495
2032
  banner();
1496
- const apiKey = await resolveApiKey(options.key);
1497
- let scanFolders;
2033
+ const { apiKey, serverUrl } = await resolveApiKeyWithResult(options.key);
2034
+ let scanDirs;
1498
2035
  if (options.folders) {
1499
- scanFolders = options.folders.split(",").map((f) => expandPath(f.trim()));
1500
- }
1501
- const cwd = process.cwd();
1502
- const cwdDisplay = cwd.replace(os6.homedir(), "~");
1503
- if (!scanFolders) {
1504
- console.log(dim(` Current directory: `) + cwdDisplay);
1505
- console.log(dim(` We'll scan for AI config files and projects here.`));
2036
+ scanDirs = options.folders.split(",").map((f) => expandPath(f.trim()));
2037
+ } else {
2038
+ console.log(dim(" \u2501\u2501\u2501 BUILD YOUR BRAIN \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"));
1506
2039
  console.log();
1507
- const mode = await select({
1508
- message: "How do you want to build your brain?",
1509
- choices: [
1510
- { name: `Scan this directory (${cwdDisplay})`, value: "cwd", description: "Scan current directory for projects and config files" },
1511
- { name: "Select folder(s)...", value: "folders", description: "Choose a different directory to scan" }
1512
- ]
1513
- });
1514
- if (mode === "cwd") {
1515
- scanFolders = [cwd];
1516
- } else {
1517
- const defaults = getDefaultScanDirs();
1518
- const CUSTOM_VALUE = "__custom__";
1519
- const choices = [
1520
- ...defaults.map((d) => ({ name: d.replace(os6.homedir(), "~"), value: d })),
1521
- { name: chalk5.dim("Enter a custom path..."), value: CUSTOM_VALUE }
1522
- ];
1523
- const selected = await checkbox3({
1524
- message: "Which folders should we scan?",
1525
- choices,
1526
- required: true
1527
- });
1528
- if (selected.includes(CUSTOM_VALUE)) {
1529
- const custom = await input({
1530
- message: "Enter folder path(s), comma-separated:"
1531
- });
1532
- const customPaths = custom.split(",").map((f) => expandPath(f.trim())).filter(Boolean);
1533
- scanFolders = [
1534
- ...selected.filter((v) => v !== CUSTOM_VALUE),
1535
- ...customPaths
1536
- ];
1537
- } else {
1538
- scanFolders = selected;
1539
- }
1540
- if (scanFolders.length === 0) {
1541
- console.log(chalk5.yellow(" No folders selected."));
1542
- return;
1543
- }
1544
- }
2040
+ console.log(dim(" This is a local scan only \u2014 no files leave your machine"));
2041
+ console.log(dim(" until you review and explicitly approve."));
2042
+ console.log();
2043
+ scanDirs = await selectFolders();
2044
+ }
2045
+ if (scanDirs.length === 0) {
2046
+ console.log(chalk5.yellow(" No folders selected."));
2047
+ return;
1545
2048
  }
1546
2049
  console.log();
1547
- let projectCount = 0;
1548
- let configCount = 0;
1549
- let docCount = 0;
2050
+ console.log(dim(" Scanning locally \u2014 no data is shared..."));
2051
+ console.log();
2052
+ let fileCount = 0;
1550
2053
  const onProgress = (progress) => {
1551
- if (progress.phase === "projects") {
1552
- projectCount++;
1553
- console.log(dim(` [${projectCount}] ${progress.message}`));
2054
+ if (progress.phase === "scanning") {
2055
+ console.log(dim(` ${progress.message}`));
2056
+ } else if (progress.phase === "parsing") {
2057
+ fileCount++;
2058
+ console.log(dim(` [${fileCount}] ${progress.message}`));
2059
+ } else if (progress.phase === "projects") {
2060
+ console.log(dim(` [project] ${progress.message}`));
1554
2061
  } else if (progress.phase === "configs") {
1555
- configCount++;
1556
- console.log(dim(` [${configCount}] ${progress.message}`));
1557
- } else if (progress.phase === "docs") {
1558
- docCount++;
1559
- console.log(dim(` [${docCount}] ${progress.message}`));
2062
+ console.log(dim(` [config] ${progress.message}`));
1560
2063
  }
1561
2064
  };
1562
- const brainInput = scanForBrain(scanFolders, onProgress);
1563
- const projCount = brainInput.summary.projects.length;
1564
- const cfgCount = brainInput.summary.configFiles.length;
1565
- const dcCount = brainInput.summary.recentDocuments.length;
2065
+ const scanResult = await scanFolders(scanDirs, onProgress);
1566
2066
  console.log();
1567
- console.log(success(` Scanned: ${projCount} projects, ${cfgCount} config files, ${dcCount} recent docs`));
2067
+ console.log(success(` \u2713 Scan complete: ${scanResult.totalFiles} files found across ${scanResult.folders.length} folders (local only)`));
1568
2068
  console.log();
1569
- if (projCount === 0 && cfgCount === 0) {
1570
- console.log(chalk5.yellow(" No projects or config files found to build from."));
1571
- console.log(dim(" Try running from a directory with your projects, or use --folders."));
2069
+ if (scanResult.totalFiles === 0 && scanResult.configFiles.length === 0) {
2070
+ console.log(chalk5.yellow(" No parseable files found in the selected folders."));
2071
+ console.log(dim(" Try scanning a different directory, or use --folders to specify paths."));
1572
2072
  console.log();
1573
2073
  return;
1574
2074
  }
2075
+ let selectedFolderPaths;
2076
+ if (options.yes || scanResult.folders.length === 0) {
2077
+ selectedFolderPaths = scanResult.folders.map((f) => f.path);
2078
+ } else {
2079
+ selectedFolderPaths = await reviewFolders(scanResult);
2080
+ }
2081
+ if (selectedFolderPaths.length === 0 && scanResult.configFiles.length === 0) {
2082
+ console.log(chalk5.yellow(" No folders selected."));
2083
+ return;
2084
+ }
2085
+ const selectedFolders = scanResult.folders.filter((f) => selectedFolderPaths.includes(f.path));
2086
+ const totalSelectedFiles = selectedFolders.reduce((sum, f) => sum + f.fileCount, 0) + scanResult.configFiles.length;
2087
+ if (!options.yes) {
2088
+ console.log();
2089
+ console.log(dim(" \u2501\u2501\u2501 READY TO BUILD \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"));
2090
+ console.log();
2091
+ console.log(dim(` ${totalSelectedFiles} files will be sent to p\u0131ut and processed by Claude`));
2092
+ console.log(dim(" Sonnet to design your brain. File contents are used for"));
2093
+ console.log(dim(" brain generation only and are not retained."));
2094
+ console.log();
2095
+ console.log(dim(` Privacy policy: ${brand("https://piut.com/privacy")}`));
2096
+ console.log();
2097
+ const consent = await confirm3({
2098
+ message: "Send files and build your brain?",
2099
+ default: false
2100
+ });
2101
+ if (!consent) {
2102
+ console.log();
2103
+ console.log(dim(" Build cancelled. No files were sent."));
2104
+ console.log();
2105
+ return;
2106
+ }
2107
+ }
2108
+ const brainInput = buildBrainInput(scanResult, selectedFolderPaths);
1575
2109
  const spinner = new Spinner();
1576
2110
  spinner.start("Generating brain...");
1577
2111
  try {
@@ -1587,6 +2121,7 @@ async function buildCommand(options) {
1587
2121
  spinner.addSection(String(event.data.name));
1588
2122
  break;
1589
2123
  case "complete":
2124
+ spinner.completeAll();
1590
2125
  sections = event.data.sections || {};
1591
2126
  break;
1592
2127
  case "error":
@@ -1629,15 +2164,25 @@ async function buildCommand(options) {
1629
2164
  }
1630
2165
  console.log(dim(` Review and edit at ${brand("piut.com/dashboard")}`));
1631
2166
  console.log();
1632
- const wantPublish = options.publish === false ? false : options.yes ? true : await confirm3({
1633
- message: "Publish your brain now?",
1634
- default: true
1635
- });
2167
+ let wantPublish;
2168
+ if (options.publish === false) {
2169
+ wantPublish = false;
2170
+ } else if (options.yes) {
2171
+ wantPublish = true;
2172
+ } else {
2173
+ console.log(dim(" You can always make changes later."));
2174
+ wantPublish = await confirm3({
2175
+ message: "Publish your brain now?",
2176
+ default: true
2177
+ });
2178
+ }
1636
2179
  if (wantPublish) {
1637
2180
  try {
1638
2181
  await publishServer(apiKey);
1639
2182
  console.log();
1640
2183
  console.log(success(" \u2713 Brain published. MCP server is live."));
2184
+ console.log(` ${brand(serverUrl)}`);
2185
+ console.log(dim(" (accessible only with secure authentication)"));
1641
2186
  console.log();
1642
2187
  } catch (err) {
1643
2188
  console.log();
@@ -1665,6 +2210,78 @@ async function buildCommand(options) {
1665
2210
  throw new CliError(hint);
1666
2211
  }
1667
2212
  }
2213
+ async function selectFolders() {
2214
+ const defaults = getDefaultScanDirs();
2215
+ const homeDir = os7.homedir();
2216
+ const ALL_VALUE = "__all__";
2217
+ const CUSTOM_VALUE = "__custom__";
2218
+ const choices = [
2219
+ { name: `All home folders (~) ${chalk5.dim("(Recommended)")}`, value: ALL_VALUE },
2220
+ { name: chalk5.dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"), value: "__sep__", disabled: true },
2221
+ ...defaults.map((d) => ({ name: displayPath(d), value: d })),
2222
+ { name: chalk5.dim("Browse to a folder..."), value: CUSTOM_VALUE }
2223
+ ];
2224
+ const selected = await checkbox3({
2225
+ message: "Select folders to scan:",
2226
+ choices,
2227
+ required: true
2228
+ });
2229
+ let scanDirs;
2230
+ if (selected.includes(ALL_VALUE)) {
2231
+ scanDirs = defaults;
2232
+ } else {
2233
+ scanDirs = selected.filter((v) => v !== CUSTOM_VALUE);
2234
+ if (selected.includes(CUSTOM_VALUE)) {
2235
+ const custom = await input2({
2236
+ message: "Enter folder path(s), comma-separated:"
2237
+ });
2238
+ const customPaths = custom.split(",").map((f) => expandPath(f.trim())).filter(Boolean);
2239
+ scanDirs = [...scanDirs, ...customPaths];
2240
+ }
2241
+ }
2242
+ if (scanDirs.length > 0 && !selected.includes(ALL_VALUE)) {
2243
+ console.log();
2244
+ console.log(dim(" Selected:"));
2245
+ for (const d of scanDirs) {
2246
+ console.log(dim(` ${displayPath(d)}`));
2247
+ }
2248
+ const addMore = await select2({
2249
+ message: "Add more folders or continue?",
2250
+ choices: [
2251
+ { name: "Continue with scan", value: "continue" },
2252
+ { name: "Add another folder...", value: "add" }
2253
+ ]
2254
+ });
2255
+ if (addMore === "add") {
2256
+ const extra = await input2({
2257
+ message: "Enter folder path(s), comma-separated:"
2258
+ });
2259
+ const extraPaths = extra.split(",").map((f) => expandPath(f.trim())).filter(Boolean);
2260
+ scanDirs = [...scanDirs, ...extraPaths];
2261
+ }
2262
+ }
2263
+ return scanDirs;
2264
+ }
2265
+ async function reviewFolders(scanResult) {
2266
+ if (scanResult.folders.length === 0) return [];
2267
+ console.log(dim(" \u2501\u2501\u2501 REVIEW SCANNED FOLDERS \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"));
2268
+ console.log();
2269
+ console.log(dim(" All folders selected by default. Deselect any you want to exclude."));
2270
+ console.log();
2271
+ const choices = scanResult.folders.map((folder) => ({
2272
+ name: formatFolderChoice(folder),
2273
+ value: folder.path,
2274
+ checked: true
2275
+ }));
2276
+ const selected = await checkbox3({
2277
+ message: "Select folders to include in your brain:",
2278
+ choices
2279
+ });
2280
+ const selectedFolders = scanResult.folders.filter((f) => selected.includes(f.path));
2281
+ console.log();
2282
+ console.log(dim(` ${formatSelectionSummary(selectedFolders)}`));
2283
+ return selected;
2284
+ }
1668
2285
 
1669
2286
  // src/commands/deploy.ts
1670
2287
  import chalk6 from "chalk";
@@ -1704,33 +2321,33 @@ async function deployCommand(options) {
1704
2321
  }
1705
2322
 
1706
2323
  // src/commands/connect.ts
1707
- import fs9 from "fs";
1708
- import path10 from "path";
2324
+ import fs11 from "fs";
2325
+ import path13 from "path";
1709
2326
  import { checkbox as checkbox4 } from "@inquirer/prompts";
1710
2327
  var RULE_FILES = [
1711
2328
  {
1712
2329
  tool: "Claude Code",
1713
2330
  filePath: "CLAUDE.md",
1714
2331
  strategy: "append",
1715
- detect: (p) => p.hasClaudeMd || fs9.existsSync(path10.join(p.path, ".claude"))
2332
+ detect: (p) => p.hasClaudeMd || fs11.existsSync(path13.join(p.path, ".claude"))
1716
2333
  },
1717
2334
  {
1718
2335
  tool: "Cursor",
1719
2336
  filePath: ".cursor/rules/piut.mdc",
1720
2337
  strategy: "create",
1721
- detect: (p) => p.hasCursorRules || fs9.existsSync(path10.join(p.path, ".cursor"))
2338
+ detect: (p) => p.hasCursorRules || fs11.existsSync(path13.join(p.path, ".cursor"))
1722
2339
  },
1723
2340
  {
1724
2341
  tool: "Windsurf",
1725
2342
  filePath: ".windsurf/rules/piut.md",
1726
2343
  strategy: "create",
1727
- detect: (p) => p.hasWindsurfRules || fs9.existsSync(path10.join(p.path, ".windsurf"))
2344
+ detect: (p) => p.hasWindsurfRules || fs11.existsSync(path13.join(p.path, ".windsurf"))
1728
2345
  },
1729
2346
  {
1730
2347
  tool: "GitHub Copilot",
1731
2348
  filePath: ".github/copilot-instructions.md",
1732
2349
  strategy: "append",
1733
- detect: (p) => p.hasCopilotInstructions || fs9.existsSync(path10.join(p.path, ".github"))
2350
+ detect: (p) => p.hasCopilotInstructions || fs11.existsSync(path13.join(p.path, ".github"))
1734
2351
  },
1735
2352
  {
1736
2353
  tool: "Amazon Q",
@@ -1742,7 +2359,7 @@ var RULE_FILES = [
1742
2359
  tool: "Zed",
1743
2360
  filePath: ".zed/rules.md",
1744
2361
  strategy: "create",
1745
- detect: (p) => p.hasZedRules || fs9.existsSync(path10.join(p.path, ".zed"))
2362
+ detect: (p) => p.hasZedRules || fs11.existsSync(path13.join(p.path, ".zed"))
1746
2363
  }
1747
2364
  ];
1748
2365
  var DEDICATED_FILE_CONTENT = `## p\u0131ut Context (MCP Server: piut-context)
@@ -1777,7 +2394,7 @@ Full skill reference: .piut/skill.md
1777
2394
  `;
1778
2395
  function hasPiutReference2(filePath) {
1779
2396
  try {
1780
- const content = fs9.readFileSync(filePath, "utf-8");
2397
+ const content = fs11.readFileSync(filePath, "utf-8");
1781
2398
  return content.includes("p\u0131ut Context") || content.includes("piut Context");
1782
2399
  } catch {
1783
2400
  return false;
@@ -1800,13 +2417,13 @@ async function connectCommand(options) {
1800
2417
  console.log();
1801
2418
  return;
1802
2419
  }
1803
- let scanFolders;
2420
+ let scanFolders2;
1804
2421
  if (options.folders) {
1805
- scanFolders = options.folders.split(",").map((f) => expandPath(f.trim()));
2422
+ scanFolders2 = options.folders.split(",").map((f) => expandPath(f.trim()));
1806
2423
  }
1807
2424
  console.log();
1808
2425
  console.log(dim(" Scanning for projects..."));
1809
- const projects = scanForProjects(scanFolders);
2426
+ const projects = scanForProjects(scanFolders2);
1810
2427
  if (projects.length === 0) {
1811
2428
  console.log(warning(" No projects found."));
1812
2429
  console.log(dim(" Try running from a directory with your projects, or use --folders."));
@@ -1817,20 +2434,20 @@ async function connectCommand(options) {
1817
2434
  for (const project of projects) {
1818
2435
  for (const rule of RULE_FILES) {
1819
2436
  if (!rule.detect(project)) continue;
1820
- const absPath = path10.join(project.path, rule.filePath);
1821
- if (fs9.existsSync(absPath) && hasPiutReference2(absPath)) continue;
2437
+ const absPath = path13.join(project.path, rule.filePath);
2438
+ if (fs11.existsSync(absPath) && hasPiutReference2(absPath)) continue;
1822
2439
  actions.push({
1823
2440
  project,
1824
2441
  tool: rule.tool,
1825
2442
  filePath: rule.filePath,
1826
2443
  absPath,
1827
- action: rule.strategy === "create" || !fs9.existsSync(absPath) ? "create" : "append"
2444
+ action: rule.strategy === "create" || !fs11.existsSync(absPath) ? "create" : "append"
1828
2445
  });
1829
2446
  }
1830
2447
  const hasAnyAction = actions.some((a) => a.project === project);
1831
2448
  if (!hasAnyAction) {
1832
- const claudeMdPath = path10.join(project.path, "CLAUDE.md");
1833
- if (!fs9.existsSync(claudeMdPath)) {
2449
+ const claudeMdPath = path13.join(project.path, "CLAUDE.md");
2450
+ if (!fs11.existsSync(claudeMdPath)) {
1834
2451
  actions.push({
1835
2452
  project,
1836
2453
  tool: "Claude Code",
@@ -1870,7 +2487,7 @@ async function connectCommand(options) {
1870
2487
  console.log();
1871
2488
  const projectChoices = [];
1872
2489
  for (const [projectPath, projectActions] of byProject) {
1873
- const projectName = path10.basename(projectPath);
2490
+ const projectName = path13.basename(projectPath);
1874
2491
  const desc = projectActions.map((a) => {
1875
2492
  const verb = a.action === "create" ? "will create" : "will append to";
1876
2493
  return `${verb} ${a.filePath}`;
@@ -1899,15 +2516,15 @@ async function connectCommand(options) {
1899
2516
  const copilotTool = TOOLS.find((t) => t.id === "copilot");
1900
2517
  for (const projectPath of selectedPaths) {
1901
2518
  const projectActions = byProject.get(projectPath) || [];
1902
- const projectName = path10.basename(projectPath);
2519
+ const projectName = path13.basename(projectPath);
1903
2520
  writePiutConfig(projectPath, { slug, apiKey, serverUrl });
1904
2521
  await writePiutSkill(projectPath, slug, apiKey);
1905
2522
  ensureGitignored(projectPath);
1906
2523
  console.log(success(` \u2713 ${projectName}/.piut/`) + dim(" \u2014 credentials + skill"));
1907
2524
  if (copilotTool) {
1908
- const hasCopilot = fs9.existsSync(path10.join(projectPath, ".github", "copilot-instructions.md")) || fs9.existsSync(path10.join(projectPath, ".github"));
2525
+ const hasCopilot = fs11.existsSync(path13.join(projectPath, ".github", "copilot-instructions.md")) || fs11.existsSync(path13.join(projectPath, ".github"));
1909
2526
  if (hasCopilot) {
1910
- const vscodeMcpPath = path10.join(projectPath, ".vscode", "mcp.json");
2527
+ const vscodeMcpPath = path13.join(projectPath, ".vscode", "mcp.json");
1911
2528
  const serverConfig = copilotTool.generateConfig(slug, apiKey);
1912
2529
  mergeConfig(vscodeMcpPath, copilotTool.configKey, serverConfig);
1913
2530
  console.log(success(` \u2713 ${projectName}/.vscode/mcp.json`) + dim(" \u2014 Copilot MCP"));
@@ -1917,11 +2534,11 @@ async function connectCommand(options) {
1917
2534
  if (action.action === "create") {
1918
2535
  const isAppendType = RULE_FILES.find((r) => r.filePath === action.filePath)?.strategy === "append";
1919
2536
  const content = isAppendType ? PROJECT_SKILL_SNIPPET + "\n" : DEDICATED_FILE_CONTENT;
1920
- fs9.mkdirSync(path10.dirname(action.absPath), { recursive: true });
1921
- fs9.writeFileSync(action.absPath, content, "utf-8");
2537
+ fs11.mkdirSync(path13.dirname(action.absPath), { recursive: true });
2538
+ fs11.writeFileSync(action.absPath, content, "utf-8");
1922
2539
  console.log(success(` \u2713 ${projectName}/${action.filePath}`) + dim(" \u2014 created"));
1923
2540
  } else {
1924
- fs9.appendFileSync(action.absPath, APPEND_SECTION);
2541
+ fs11.appendFileSync(action.absPath, APPEND_SECTION);
1925
2542
  console.log(success(` \u2713 ${projectName}/${action.filePath}`) + dim(" \u2014 appended"));
1926
2543
  }
1927
2544
  connected++;
@@ -1930,7 +2547,7 @@ async function connectCommand(options) {
1930
2547
  const machineId = getMachineId();
1931
2548
  for (const projectPath of selectedPaths) {
1932
2549
  const projectActions = byProject.get(projectPath) || [];
1933
- const projectName = path10.basename(projectPath);
2550
+ const projectName = path13.basename(projectPath);
1934
2551
  const toolsDetected = [...new Set(projectActions.map((a) => a.tool))];
1935
2552
  const configFilesWritten = projectActions.map((a) => a.filePath);
1936
2553
  registerProject(apiKey, {
@@ -1948,8 +2565,8 @@ async function connectCommand(options) {
1948
2565
  }
1949
2566
 
1950
2567
  // src/commands/disconnect.ts
1951
- import fs10 from "fs";
1952
- import path11 from "path";
2568
+ import fs12 from "fs";
2569
+ import path14 from "path";
1953
2570
  import { checkbox as checkbox5, confirm as confirm5 } from "@inquirer/prompts";
1954
2571
  var DEDICATED_FILES = /* @__PURE__ */ new Set([
1955
2572
  ".cursor/rules/piut.mdc",
@@ -1963,7 +2580,7 @@ var APPEND_FILES = [
1963
2580
  ];
1964
2581
  function hasPiutReference3(filePath) {
1965
2582
  try {
1966
- const content = fs10.readFileSync(filePath, "utf-8");
2583
+ const content = fs12.readFileSync(filePath, "utf-8");
1967
2584
  return content.includes("p\u0131ut Context") || content.includes("piut Context");
1968
2585
  } catch {
1969
2586
  return false;
@@ -1971,7 +2588,7 @@ function hasPiutReference3(filePath) {
1971
2588
  }
1972
2589
  function removePiutSection(filePath) {
1973
2590
  try {
1974
- let content = fs10.readFileSync(filePath, "utf-8");
2591
+ let content = fs12.readFileSync(filePath, "utf-8");
1975
2592
  const patterns = [
1976
2593
  /\n*## p[ıi]ut Context[\s\S]*?(?=\n## |\n---\n|$)/g
1977
2594
  ];
@@ -1985,7 +2602,7 @@ function removePiutSection(filePath) {
1985
2602
  }
1986
2603
  if (changed) {
1987
2604
  content = content.replace(/\n{3,}/g, "\n\n").trimEnd() + "\n";
1988
- fs10.writeFileSync(filePath, content, "utf-8");
2605
+ fs12.writeFileSync(filePath, content, "utf-8");
1989
2606
  }
1990
2607
  return changed;
1991
2608
  } catch {
@@ -1994,18 +2611,18 @@ function removePiutSection(filePath) {
1994
2611
  }
1995
2612
  async function disconnectCommand(options) {
1996
2613
  banner();
1997
- let scanFolders;
2614
+ let scanFolders2;
1998
2615
  if (options.folders) {
1999
- scanFolders = options.folders.split(",").map((f) => expandPath(f.trim()));
2616
+ scanFolders2 = options.folders.split(",").map((f) => expandPath(f.trim()));
2000
2617
  }
2001
2618
  console.log(dim(" Scanning for connected projects..."));
2002
- const projects = scanForProjects(scanFolders);
2619
+ const projects = scanForProjects(scanFolders2);
2003
2620
  const actions = [];
2004
2621
  for (const project of projects) {
2005
- const projectName = path11.basename(project.path);
2622
+ const projectName = path14.basename(project.path);
2006
2623
  for (const dedicatedFile of DEDICATED_FILES) {
2007
- const absPath = path11.join(project.path, dedicatedFile);
2008
- if (fs10.existsSync(absPath) && hasPiutReference3(absPath)) {
2624
+ const absPath = path14.join(project.path, dedicatedFile);
2625
+ if (fs12.existsSync(absPath) && hasPiutReference3(absPath)) {
2009
2626
  actions.push({
2010
2627
  projectPath: project.path,
2011
2628
  projectName,
@@ -2016,8 +2633,8 @@ async function disconnectCommand(options) {
2016
2633
  }
2017
2634
  }
2018
2635
  for (const appendFile of APPEND_FILES) {
2019
- const absPath = path11.join(project.path, appendFile);
2020
- if (fs10.existsSync(absPath) && hasPiutReference3(absPath)) {
2636
+ const absPath = path14.join(project.path, appendFile);
2637
+ if (fs12.existsSync(absPath) && hasPiutReference3(absPath)) {
2021
2638
  actions.push({
2022
2639
  projectPath: project.path,
2023
2640
  projectName,
@@ -2032,12 +2649,12 @@ async function disconnectCommand(options) {
2032
2649
  projectPath: project.path,
2033
2650
  projectName,
2034
2651
  filePath: ".piut/",
2035
- absPath: path11.join(project.path, ".piut"),
2652
+ absPath: path14.join(project.path, ".piut"),
2036
2653
  action: "remove-dir"
2037
2654
  });
2038
2655
  }
2039
- const vscodeMcpPath = path11.join(project.path, ".vscode", "mcp.json");
2040
- if (fs10.existsSync(vscodeMcpPath) && isPiutConfigured(vscodeMcpPath, "servers")) {
2656
+ const vscodeMcpPath = path14.join(project.path, ".vscode", "mcp.json");
2657
+ if (fs12.existsSync(vscodeMcpPath) && isPiutConfigured(vscodeMcpPath, "servers")) {
2041
2658
  actions.push({
2042
2659
  projectPath: project.path,
2043
2660
  projectName,
@@ -2059,7 +2676,7 @@ async function disconnectCommand(options) {
2059
2676
  }
2060
2677
  console.log();
2061
2678
  const projectChoices = Array.from(byProject.entries()).map(([projectPath, projectActions]) => {
2062
- const name = path11.basename(projectPath);
2679
+ const name = path14.basename(projectPath);
2063
2680
  const files = projectActions.map((a) => a.filePath).join(", ");
2064
2681
  return {
2065
2682
  name: `${name} ${dim(`(${files})`)}`,
@@ -2088,11 +2705,11 @@ async function disconnectCommand(options) {
2088
2705
  let disconnected = 0;
2089
2706
  for (const projectPath of selectedPaths) {
2090
2707
  const projectActions = byProject.get(projectPath) || [];
2091
- const projectName = path11.basename(projectPath);
2708
+ const projectName = path14.basename(projectPath);
2092
2709
  for (const action of projectActions) {
2093
2710
  if (action.action === "delete") {
2094
2711
  try {
2095
- fs10.unlinkSync(action.absPath);
2712
+ fs12.unlinkSync(action.absPath);
2096
2713
  console.log(success(` \u2713 ${projectName}/${action.filePath}`) + dim(" \u2014 deleted"));
2097
2714
  disconnected++;
2098
2715
  } catch {
@@ -2135,6 +2752,21 @@ async function disconnectCommand(options) {
2135
2752
  console.log();
2136
2753
  }
2137
2754
 
2755
+ // src/commands/login.ts
2756
+ import chalk7 from "chalk";
2757
+ async function loginCommand() {
2758
+ try {
2759
+ const { apiKey, validation } = await promptLogin();
2760
+ const label = validation.slug ? `${validation.displayName} (${validation.slug})` : validation.displayName;
2761
+ console.log(success(` \u2713 Connected as ${label}`));
2762
+ updateStore({ apiKey });
2763
+ console.log(dim(" API key saved. You can now use all p\u0131ut commands."));
2764
+ } catch (err) {
2765
+ console.log(chalk7.red(` \u2717 ${err.message}`));
2766
+ throw new CliError(err.message);
2767
+ }
2768
+ }
2769
+
2138
2770
  // src/commands/logout.ts
2139
2771
  async function logoutCommand() {
2140
2772
  banner();
@@ -2152,11 +2784,11 @@ async function logoutCommand() {
2152
2784
  }
2153
2785
 
2154
2786
  // src/commands/update.ts
2155
- import chalk8 from "chalk";
2787
+ import chalk9 from "chalk";
2156
2788
 
2157
2789
  // src/lib/update-check.ts
2158
2790
  import { execFile } from "child_process";
2159
- import chalk7 from "chalk";
2791
+ import chalk8 from "chalk";
2160
2792
  import { confirm as confirm6 } from "@inquirer/prompts";
2161
2793
  var PACKAGE_NAME = "@piut/cli";
2162
2794
  function isNpx() {
@@ -2193,7 +2825,7 @@ async function checkForUpdate(currentVersion) {
2193
2825
  const updateCmd = npx ? `npx ${PACKAGE_NAME}@latest` : `npm install -g ${PACKAGE_NAME}@latest`;
2194
2826
  console.log();
2195
2827
  console.log(brand(" Update available!") + dim(` ${currentVersion} \u2192 ${latest}`));
2196
- console.log(dim(` Run ${chalk7.bold(updateCmd)} to update`));
2828
+ console.log(dim(` Run ${chalk8.bold(updateCmd)} to update`));
2197
2829
  console.log();
2198
2830
  if (npx) return;
2199
2831
  try {
@@ -2205,12 +2837,12 @@ async function checkForUpdate(currentVersion) {
2205
2837
  console.log(dim(" Updating..."));
2206
2838
  const ok = await runUpdate();
2207
2839
  if (ok) {
2208
- console.log(chalk7.green(` \u2713 Updated to v${latest}`));
2840
+ console.log(chalk8.green(` \u2713 Updated to v${latest}`));
2209
2841
  console.log(dim(" Restart the CLI to use the new version."));
2210
2842
  process.exit(0);
2211
2843
  } else {
2212
- console.log(chalk7.yellow(` Could not auto-update. Run manually:`));
2213
- console.log(chalk7.bold(` npm install -g ${PACKAGE_NAME}@latest`));
2844
+ console.log(chalk8.yellow(` Could not auto-update. Run manually:`));
2845
+ console.log(chalk8.bold(` npm install -g ${PACKAGE_NAME}@latest`));
2214
2846
  console.log();
2215
2847
  }
2216
2848
  }
@@ -2225,7 +2857,7 @@ async function updateCommand(currentVersion) {
2225
2857
  console.log(dim(" Checking for updates..."));
2226
2858
  const latest = await getLatestVersion();
2227
2859
  if (!latest) {
2228
- console.log(chalk8.yellow(" Could not reach the npm registry. Check your connection."));
2860
+ console.log(chalk9.yellow(" Could not reach the npm registry. Check your connection."));
2229
2861
  return;
2230
2862
  }
2231
2863
  if (!isNewer(currentVersion, latest)) {
@@ -2237,7 +2869,7 @@ async function updateCommand(currentVersion) {
2237
2869
  if (isNpx()) {
2238
2870
  console.log();
2239
2871
  console.log(dim(" You're running via npx. Use the latest version with:"));
2240
- console.log(chalk8.bold(` npx ${PACKAGE_NAME2}@latest`));
2872
+ console.log(chalk9.bold(` npx ${PACKAGE_NAME2}@latest`));
2241
2873
  console.log();
2242
2874
  return;
2243
2875
  }
@@ -2247,14 +2879,14 @@ async function updateCommand(currentVersion) {
2247
2879
  console.log(success(` \u2713 Updated to v${latest}`));
2248
2880
  console.log(dim(" Restart the CLI to use the new version."));
2249
2881
  } else {
2250
- console.log(chalk8.yellow(" Could not auto-update. Run manually:"));
2251
- console.log(chalk8.bold(` npm install -g ${PACKAGE_NAME2}@latest`));
2882
+ console.log(chalk9.yellow(" Could not auto-update. Run manually:"));
2883
+ console.log(chalk9.bold(` npm install -g ${PACKAGE_NAME2}@latest`));
2252
2884
  }
2253
2885
  }
2254
2886
 
2255
2887
  // src/commands/doctor.ts
2256
- import fs11 from "fs";
2257
- import chalk9 from "chalk";
2888
+ import fs13 from "fs";
2889
+ import chalk10 from "chalk";
2258
2890
  async function doctorCommand(options) {
2259
2891
  if (!options.json) banner();
2260
2892
  const result = {
@@ -2305,7 +2937,7 @@ async function doctorCommand(options) {
2305
2937
  for (const tool of TOOLS) {
2306
2938
  const paths = resolveConfigPaths(tool.configPaths);
2307
2939
  for (const configPath of paths) {
2308
- if (!fs11.existsSync(configPath)) continue;
2940
+ if (!fs13.existsSync(configPath)) continue;
2309
2941
  const piutConfig = getPiutConfig(configPath, tool.configKey);
2310
2942
  if (!piutConfig) {
2311
2943
  result.tools.push({
@@ -2410,10 +3042,10 @@ async function doctorCommand(options) {
2410
3042
  const staleTools = result.tools.filter((t) => t.keyMatch === "stale" && !t.fixed);
2411
3043
  if (staleTools.length > 0 && !options.fix) {
2412
3044
  console.log();
2413
- console.log(dim(" Fix stale configs: ") + chalk9.cyan("piut doctor --fix"));
3045
+ console.log(dim(" Fix stale configs: ") + chalk10.cyan("piut doctor --fix"));
2414
3046
  }
2415
3047
  if (!result.key.valid) {
2416
- console.log(dim(" Set a valid key: ") + chalk9.cyan("piut setup --key pb_YOUR_KEY"));
3048
+ console.log(dim(" Set a valid key: ") + chalk10.cyan("piut setup --key pb_YOUR_KEY"));
2417
3049
  }
2418
3050
  }
2419
3051
  console.log();
@@ -2421,48 +3053,38 @@ async function doctorCommand(options) {
2421
3053
  }
2422
3054
 
2423
3055
  // src/commands/interactive.ts
2424
- import { select as select2, confirm as confirm7, checkbox as checkbox6, password as password3 } from "@inquirer/prompts";
2425
- import fs12 from "fs";
2426
- import path12 from "path";
2427
- import chalk10 from "chalk";
3056
+ import { select as select3, confirm as confirm7, checkbox as checkbox6, Separator } from "@inquirer/prompts";
3057
+ import fs14 from "fs";
3058
+ import path15 from "path";
3059
+ import { exec as exec2 } from "child_process";
3060
+ import os8 from "os";
3061
+ import chalk11 from "chalk";
2428
3062
  async function authenticate() {
2429
3063
  const config = readStore();
2430
- let apiKey = config.apiKey;
3064
+ const apiKey = config.apiKey;
2431
3065
  if (apiKey) {
2432
3066
  try {
2433
- const result2 = await validateKey(apiKey);
2434
- console.log(success(` Connected as ${result2.displayName}`));
2435
- return { apiKey, validation: result2 };
3067
+ const result = await validateKey(apiKey);
3068
+ console.log(success(` Connected as ${result.displayName}`));
3069
+ return { apiKey, validation: result };
2436
3070
  } catch {
2437
3071
  console.log(dim(" Saved key expired. Please re-authenticate."));
2438
- apiKey = void 0;
2439
3072
  }
2440
3073
  }
2441
- console.log(dim(" Connect to p\u0131ut:"));
2442
- console.log(dim(" > Log in at piut.com"));
2443
- console.log(dim(" > Enter p\u0131ut API key"));
2444
- console.log();
2445
- apiKey = await password3({
2446
- message: "Enter your p\u0131ut API key:",
2447
- mask: "*",
2448
- validate: (v) => v.startsWith("pb_") || "Key must start with pb_"
2449
- });
2450
- console.log(dim(" Validating key..."));
2451
- let result;
2452
- try {
2453
- result = await validateKey(apiKey);
2454
- } catch (err) {
2455
- console.log(chalk10.red(` \u2717 ${err.message}`));
2456
- console.log(dim(" Get a key at https://piut.com/dashboard/keys"));
2457
- process.exit(1);
2458
- }
2459
- console.log(success(` \u2713 Connected as ${result.displayName}`));
2460
- updateStore({ apiKey });
2461
- return { apiKey, validation: result };
3074
+ const { apiKey: newKey, validation } = await promptLogin();
3075
+ const label = validation.slug ? `${validation.displayName} (${validation.slug})` : validation.displayName;
3076
+ console.log(success(` \u2713 Connected as ${label}`));
3077
+ updateStore({ apiKey: newKey });
3078
+ return { apiKey: newKey, validation };
2462
3079
  }
2463
3080
  function isPromptCancellation(err) {
2464
3081
  return !!(err && typeof err === "object" && "name" in err && err.name === "ExitPromptError");
2465
3082
  }
3083
+ function openInBrowser(url) {
3084
+ const platform = process.platform;
3085
+ const cmd = platform === "darwin" ? "open" : platform === "win32" ? "start" : "xdg-open";
3086
+ exec2(`${cmd} ${url}`);
3087
+ }
2466
3088
  async function interactiveMenu() {
2467
3089
  banner();
2468
3090
  let apiKey;
@@ -2508,42 +3130,39 @@ async function interactiveMenu() {
2508
3130
  const isDeployed = currentValidation.status === "active";
2509
3131
  let action;
2510
3132
  try {
2511
- action = await select2({
3133
+ action = await select3({
2512
3134
  message: "What would you like to do?",
3135
+ loop: false,
2513
3136
  choices: [
2514
3137
  {
2515
- name: hasBrain ? "Rebuild Brain" : "Build Brain",
3138
+ name: hasBrain ? "Resync Brain" : "Build Brain",
2516
3139
  value: "build",
2517
- description: hasBrain ? "Rebuild your brain from your files" : "Build your brain from your files"
3140
+ description: hasBrain ? "Rescan your files and merge updates into your brain" : "Build your brain from your files"
2518
3141
  },
2519
3142
  {
2520
3143
  name: isDeployed ? "Undeploy Brain" : "Deploy Brain",
2521
3144
  value: "deploy",
2522
3145
  description: isDeployed ? "Take your MCP server offline" : "Publish your MCP server (requires paid account)"
2523
3146
  },
3147
+ new Separator(),
2524
3148
  {
2525
3149
  name: "Connect Tools",
2526
3150
  value: "connect-tools",
2527
- description: "Configure AI tools to use your MCP server",
2528
- disabled: !isDeployed && "(deploy brain first)"
2529
- },
2530
- {
2531
- name: "Disconnect Tools",
2532
- value: "disconnect-tools",
2533
- description: "Remove p\u0131ut from AI tool configs",
3151
+ description: "Manage which AI tools use your MCP server",
2534
3152
  disabled: !isDeployed && "(deploy brain first)"
2535
3153
  },
2536
3154
  {
2537
3155
  name: "Connect Projects",
2538
3156
  value: "connect-projects",
2539
- description: "Add brain references to project config files",
3157
+ description: "Manage brain references in project config files",
2540
3158
  disabled: !isDeployed && "(deploy brain first)"
2541
3159
  },
3160
+ new Separator(),
2542
3161
  {
2543
- name: "Disconnect Projects",
2544
- value: "disconnect-projects",
2545
- description: "Remove brain references from project configs",
2546
- disabled: !isDeployed && "(deploy brain first)"
3162
+ name: "Edit Brain",
3163
+ value: "edit-brain",
3164
+ description: "Open piut.com to edit your brain",
3165
+ disabled: !hasBrain && "(build brain first)"
2547
3166
  },
2548
3167
  {
2549
3168
  name: "View Brain",
@@ -2556,6 +3175,7 @@ async function interactiveMenu() {
2556
3175
  value: "status",
2557
3176
  description: "Show brain, deployment, and connected tools/projects"
2558
3177
  },
3178
+ new Separator(),
2559
3179
  {
2560
3180
  name: "Logout",
2561
3181
  value: "logout",
@@ -2575,7 +3195,11 @@ async function interactiveMenu() {
2575
3195
  try {
2576
3196
  switch (action) {
2577
3197
  case "build":
2578
- await buildCommand({ key: apiKey });
3198
+ if (hasBrain) {
3199
+ await handleResyncBrain(apiKey, currentValidation);
3200
+ } else {
3201
+ await buildCommand({ key: apiKey });
3202
+ }
2579
3203
  break;
2580
3204
  case "deploy":
2581
3205
  if (isDeployed) {
@@ -2587,14 +3211,11 @@ async function interactiveMenu() {
2587
3211
  case "connect-tools":
2588
3212
  await handleConnectTools(apiKey, currentValidation);
2589
3213
  break;
2590
- case "disconnect-tools":
2591
- await handleDisconnectTools();
2592
- break;
2593
3214
  case "connect-projects":
2594
- await connectCommand({ key: apiKey });
3215
+ await handleManageProjects(apiKey, currentValidation);
2595
3216
  break;
2596
- case "disconnect-projects":
2597
- await disconnectCommand({});
3217
+ case "edit-brain":
3218
+ handleEditBrain();
2598
3219
  break;
2599
3220
  case "view-brain":
2600
3221
  await handleViewBrain(apiKey);
@@ -2620,7 +3241,7 @@ async function interactiveMenu() {
2620
3241
  } else if (err instanceof CliError) {
2621
3242
  console.log();
2622
3243
  } else {
2623
- console.log(chalk10.red(` Error: ${err.message}`));
3244
+ console.log(chalk11.red(` Error: ${err.message}`));
2624
3245
  console.log();
2625
3246
  }
2626
3247
  }
@@ -2643,115 +3264,291 @@ async function handleUndeploy(apiKey) {
2643
3264
  console.log(dim(" Run ") + brand("piut deploy") + dim(" to re-deploy anytime."));
2644
3265
  console.log();
2645
3266
  } catch (err) {
2646
- console.log(chalk10.red(` \u2717 ${err.message}`));
3267
+ console.log(chalk11.red(` \u2717 ${err.message}`));
2647
3268
  }
2648
3269
  }
2649
3270
  async function handleConnectTools(apiKey, validation) {
2650
3271
  const { slug } = validation;
2651
- const unconfigured = [];
2652
- const alreadyConnected = [];
3272
+ const detected = [];
2653
3273
  for (const tool of TOOLS) {
2654
3274
  const paths = resolveConfigPaths(tool.configPaths);
2655
3275
  for (const configPath of paths) {
2656
- const exists = fs12.existsSync(configPath);
2657
- const parentExists = fs12.existsSync(path12.dirname(configPath));
3276
+ const exists = fs14.existsSync(configPath);
3277
+ const parentExists = fs14.existsSync(path15.dirname(configPath));
2658
3278
  if (exists || parentExists) {
2659
- if (exists && isPiutConfigured(configPath, tool.configKey)) {
2660
- alreadyConnected.push(tool.name);
2661
- } else {
2662
- unconfigured.push({ tool, configPath });
2663
- }
3279
+ const connected = exists && isPiutConfigured(configPath, tool.configKey);
3280
+ detected.push({ tool, configPath, connected });
2664
3281
  break;
2665
3282
  }
2666
3283
  }
2667
3284
  }
2668
- if (unconfigured.length === 0) {
2669
- if (alreadyConnected.length > 0) {
2670
- console.log(dim(" All detected tools are already connected."));
2671
- } else {
2672
- console.log(warning(" No supported AI tools detected."));
2673
- console.log(dim(" Supported: Claude Code, Claude Desktop, Cursor, Windsurf, GitHub Copilot, Amazon Q, Zed"));
2674
- }
3285
+ if (detected.length === 0) {
3286
+ console.log(warning(" No supported AI tools detected."));
3287
+ console.log(dim(" Supported: Claude Code, Claude Desktop, Cursor, Windsurf, GitHub Copilot, Amazon Q, Zed"));
2675
3288
  console.log();
2676
3289
  return;
2677
3290
  }
2678
- if (alreadyConnected.length > 0) {
2679
- console.log(dim(` Already connected: ${alreadyConnected.join(", ")}`));
2680
- console.log();
3291
+ const connectedCount = detected.filter((d) => d.connected).length;
3292
+ const availableCount = detected.length - connectedCount;
3293
+ if (connectedCount > 0 || availableCount > 0) {
3294
+ const parts = [];
3295
+ if (connectedCount > 0) parts.push(`${connectedCount} connected`);
3296
+ if (availableCount > 0) parts.push(`${availableCount} available`);
3297
+ console.log(dim(` ${parts.join(", ")}`));
2681
3298
  }
2682
- const choices = unconfigured.map((u) => ({
2683
- name: u.tool.name,
2684
- value: u,
2685
- checked: true
3299
+ console.log();
3300
+ const choices = detected.map((d) => ({
3301
+ name: `${d.tool.name}${d.connected ? dim(" (connected)") : ""}`,
3302
+ value: d,
3303
+ checked: d.connected
2686
3304
  }));
2687
3305
  const selected = await checkbox6({
2688
- message: "Select tools to connect:",
3306
+ message: "Select tools to keep connected (toggle with space):",
2689
3307
  choices
2690
3308
  });
2691
- if (selected.length === 0) {
2692
- console.log(dim(" No tools selected."));
3309
+ const toConnect = selected.filter((s) => !s.connected);
3310
+ const toDisconnect = detected.filter((d) => d.connected && !selected.includes(d));
3311
+ if (toConnect.length === 0 && toDisconnect.length === 0) {
3312
+ console.log(dim(" No changes."));
3313
+ console.log();
2693
3314
  return;
2694
3315
  }
2695
3316
  console.log();
2696
- for (const { tool, configPath } of selected) {
3317
+ for (const { tool, configPath } of toConnect) {
2697
3318
  const serverConfig = tool.generateConfig(slug, apiKey);
2698
3319
  mergeConfig(configPath, tool.configKey, serverConfig);
2699
3320
  toolLine(tool.name, success("connected"), "\u2714");
2700
3321
  }
2701
- if (validation.serverUrl) {
3322
+ const removedNames = [];
3323
+ for (const { tool, configPath } of toDisconnect) {
3324
+ const removed = removeFromConfig(configPath, tool.configKey);
3325
+ if (removed) {
3326
+ removedNames.push(tool.name);
3327
+ toolLine(tool.name, warning("disconnected"), "\u2714");
3328
+ }
3329
+ }
3330
+ if (toConnect.length > 0 && validation.serverUrl) {
2702
3331
  await Promise.all(
2703
- selected.map(({ tool }) => pingMcp(validation.serverUrl, apiKey, tool.name))
3332
+ toConnect.map(({ tool }) => pingMcp(validation.serverUrl, apiKey, tool.name))
2704
3333
  );
2705
3334
  }
3335
+ if (removedNames.length > 0) {
3336
+ deleteConnections(apiKey, removedNames).catch(() => {
3337
+ });
3338
+ }
2706
3339
  console.log();
2707
3340
  console.log(dim(" Restart your AI tools for changes to take effect."));
2708
3341
  console.log();
2709
3342
  }
2710
- async function handleDisconnectTools() {
2711
- const configured = [];
2712
- for (const tool of TOOLS) {
2713
- const paths = resolveConfigPaths(tool.configPaths);
2714
- for (const configPath of paths) {
2715
- if (fs12.existsSync(configPath) && isPiutConfigured(configPath, tool.configKey)) {
2716
- configured.push({ tool, configPath });
2717
- break;
2718
- }
2719
- }
2720
- }
2721
- if (configured.length === 0) {
2722
- console.log(dim(" p\u0131ut is not configured in any detected AI tools."));
3343
+ async function handleManageProjects(apiKey, validation) {
3344
+ const { slug, serverUrl } = validation;
3345
+ console.log(dim(" Scanning for projects..."));
3346
+ const projects = scanForProjects();
3347
+ if (projects.length === 0) {
3348
+ console.log(warning(" No projects found."));
3349
+ console.log(dim(" Try running from a directory with your projects."));
2723
3350
  console.log();
2724
3351
  return;
2725
3352
  }
2726
- const choices = configured.map((c) => ({
2727
- name: c.tool.name,
2728
- value: c
3353
+ const items = projects.map((p) => ({
3354
+ project: p,
3355
+ connected: hasPiutDir(p.path)
3356
+ }));
3357
+ const connectedCount = items.filter((i) => i.connected).length;
3358
+ const availableCount = items.length - connectedCount;
3359
+ const parts = [];
3360
+ if (connectedCount > 0) parts.push(`${connectedCount} connected`);
3361
+ if (availableCount > 0) parts.push(`${availableCount} available`);
3362
+ console.log(dim(` ${parts.join(", ")}`));
3363
+ console.log();
3364
+ const choices = items.map((i) => ({
3365
+ name: `${i.project.name}${i.connected ? dim(" (connected)") : ""}`,
3366
+ value: i,
3367
+ checked: i.connected
2729
3368
  }));
2730
3369
  const selected = await checkbox6({
2731
- message: "Select tools to disconnect:",
3370
+ message: "Select projects to keep connected (toggle with space):",
2732
3371
  choices
2733
3372
  });
2734
- if (selected.length === 0) {
2735
- console.log(dim(" No tools selected."));
3373
+ const toConnect = selected.filter((s) => !s.connected);
3374
+ const toDisconnect = items.filter((i) => i.connected && !selected.includes(i));
3375
+ if (toConnect.length === 0 && toDisconnect.length === 0) {
3376
+ console.log(dim(" No changes."));
3377
+ console.log();
2736
3378
  return;
2737
3379
  }
2738
- const proceed = await confirm7({
2739
- message: `Disconnect p\u0131ut from ${selected.length} tool(s)?`,
2740
- default: false
2741
- });
2742
- if (!proceed) return;
2743
3380
  console.log();
2744
- for (const { tool, configPath } of selected) {
2745
- const removed = removeFromConfig(configPath, tool.configKey);
2746
- if (removed) {
2747
- toolLine(tool.name, success("disconnected"), "\u2714");
2748
- } else {
2749
- toolLine(tool.name, warning("not found"), "\xD7");
3381
+ const copilotTool = TOOLS.find((t) => t.id === "copilot");
3382
+ for (const { project } of toConnect) {
3383
+ const projectName = path15.basename(project.path);
3384
+ writePiutConfig(project.path, { slug, apiKey, serverUrl });
3385
+ await writePiutSkill(project.path, slug, apiKey);
3386
+ ensureGitignored(project.path);
3387
+ if (copilotTool) {
3388
+ const hasCopilot = fs14.existsSync(path15.join(project.path, ".github", "copilot-instructions.md")) || fs14.existsSync(path15.join(project.path, ".github"));
3389
+ if (hasCopilot) {
3390
+ const vscodeMcpPath = path15.join(project.path, ".vscode", "mcp.json");
3391
+ const serverConfig = copilotTool.generateConfig(slug, apiKey);
3392
+ mergeConfig(vscodeMcpPath, copilotTool.configKey, serverConfig);
3393
+ }
3394
+ }
3395
+ for (const rule of RULE_FILES) {
3396
+ if (!rule.detect(project)) continue;
3397
+ const absPath = path15.join(project.path, rule.filePath);
3398
+ if (fs14.existsSync(absPath) && hasPiutReference2(absPath)) continue;
3399
+ if (rule.strategy === "create" || !fs14.existsSync(absPath)) {
3400
+ const isAppendType = rule.strategy === "append";
3401
+ const content = isAppendType ? PROJECT_SKILL_SNIPPET + "\n" : DEDICATED_FILE_CONTENT;
3402
+ fs14.mkdirSync(path15.dirname(absPath), { recursive: true });
3403
+ fs14.writeFileSync(absPath, content, "utf-8");
3404
+ } else {
3405
+ fs14.appendFileSync(absPath, APPEND_SECTION);
3406
+ }
3407
+ }
3408
+ toolLine(projectName, success("connected"), "\u2714");
3409
+ const machineId = getMachineId();
3410
+ const toolsDetected = RULE_FILES.filter((r) => r.detect(project)).map((r) => r.tool);
3411
+ registerProject(apiKey, {
3412
+ projectName,
3413
+ projectPath: project.path,
3414
+ machineId,
3415
+ toolsDetected,
3416
+ configFiles: RULE_FILES.filter((r) => r.detect(project)).map((r) => r.filePath)
3417
+ }).catch(() => {
3418
+ });
3419
+ }
3420
+ for (const { project } of toDisconnect) {
3421
+ const projectName = path15.basename(project.path);
3422
+ for (const dedicatedFile of DEDICATED_FILES) {
3423
+ const absPath = path15.join(project.path, dedicatedFile);
3424
+ if (fs14.existsSync(absPath) && hasPiutReference2(absPath)) {
3425
+ try {
3426
+ fs14.unlinkSync(absPath);
3427
+ } catch {
3428
+ }
3429
+ }
3430
+ }
3431
+ for (const appendFile of APPEND_FILES) {
3432
+ const absPath = path15.join(project.path, appendFile);
3433
+ if (fs14.existsSync(absPath) && hasPiutReference2(absPath)) {
3434
+ removePiutSection(absPath);
3435
+ }
3436
+ }
3437
+ const vscodeMcpPath = path15.join(project.path, ".vscode", "mcp.json");
3438
+ if (fs14.existsSync(vscodeMcpPath) && isPiutConfigured(vscodeMcpPath, "servers")) {
3439
+ removeFromConfig(vscodeMcpPath, "servers");
2750
3440
  }
3441
+ removePiutDir(project.path);
3442
+ toolLine(projectName, warning("disconnected"), "\u2714");
3443
+ const machineId = getMachineId();
3444
+ unregisterProject(apiKey, project.path, machineId).catch(() => {
3445
+ });
2751
3446
  }
2752
3447
  console.log();
2753
- console.log(dim(" Restart your AI tools for changes to take effect."));
3448
+ if (toConnect.length > 0) {
3449
+ console.log(success(` ${toConnect.length} project(s) connected.`));
3450
+ }
3451
+ if (toDisconnect.length > 0) {
3452
+ console.log(success(` ${toDisconnect.length} project(s) disconnected.`));
3453
+ }
3454
+ console.log();
3455
+ }
3456
+ function handleEditBrain() {
3457
+ console.log(dim(" Opening piut.com/dashboard..."));
3458
+ openInBrowser("https://piut.com/dashboard");
3459
+ console.log(success(" \u2713 Opened in browser."));
3460
+ console.log();
3461
+ }
3462
+ function formatScanContent(input3) {
3463
+ const { summary } = input3;
3464
+ const parts = [];
3465
+ parts.push("The user has re-scanned their filesystem. Below is updated information about their projects, config files, and documents. Please merge this into the existing brain sections, preserving existing content and updating what has changed.");
3466
+ parts.push("");
3467
+ if (summary.folders.length > 0) {
3468
+ parts.push(`## Folder Structure
3469
+ ${summary.folders.join("\n")}`);
3470
+ }
3471
+ if (summary.projects.length > 0) {
3472
+ const projectLines = summary.projects.map(
3473
+ (p) => `- **${p.name}** (${p.path})${p.description ? ` \u2014 ${p.description}` : ""}`
3474
+ );
3475
+ parts.push(`## Projects Found
3476
+ ${projectLines.join("\n")}`);
3477
+ }
3478
+ const totalFiles = (summary.configFiles?.length || 0) + (summary.recentDocuments?.length || 0) + (summary.personalDocuments?.length || 0);
3479
+ const fileLimit = totalFiles <= 10 ? 1e4 : totalFiles <= 30 ? 5e3 : totalFiles <= 60 ? 3e3 : 2e3;
3480
+ if (summary.configFiles.length > 0) {
3481
+ const configBlocks = summary.configFiles.map(
3482
+ (f) => `### ${f.name}
3483
+ \`\`\`
3484
+ ${f.content.slice(0, fileLimit)}
3485
+ \`\`\``
3486
+ );
3487
+ parts.push(`## AI Config Files
3488
+ ${configBlocks.join("\n\n")}`);
3489
+ }
3490
+ if (summary.recentDocuments.length > 0) {
3491
+ const docBlocks = summary.recentDocuments.map(
3492
+ (f) => `### ${f.name}
3493
+ ${f.content.slice(0, fileLimit)}`
3494
+ );
3495
+ parts.push(`## Recent Documents
3496
+ ${docBlocks.join("\n\n")}`);
3497
+ }
3498
+ if (summary.personalDocuments && summary.personalDocuments.length > 0) {
3499
+ const docBlocks = summary.personalDocuments.map(
3500
+ (f) => `### ${f.name}
3501
+ ${f.content.slice(0, fileLimit)}`
3502
+ );
3503
+ parts.push(`## Personal Documents
3504
+ ${docBlocks.join("\n\n")}`);
3505
+ }
3506
+ let result = parts.join("\n\n");
3507
+ if (result.length > 4e5) {
3508
+ result = result.slice(0, 4e5);
3509
+ }
3510
+ return result;
3511
+ }
3512
+ async function handleResyncBrain(apiKey, validation) {
3513
+ if (!validation.serverUrl) {
3514
+ console.log(warning(" Brain must be deployed before resyncing."));
3515
+ console.log(dim(" Run ") + brand("piut deploy") + dim(" first."));
3516
+ console.log();
3517
+ return;
3518
+ }
3519
+ const cwd = process.cwd();
3520
+ const cwdDisplay = cwd.replace(os8.homedir(), "~");
3521
+ console.log(dim(` Scanning ${cwdDisplay}...`));
3522
+ const scanResult = await scanFolders([cwd]);
3523
+ const allFolderPaths = scanResult.folders.map((f) => f.path);
3524
+ const brainInput = buildBrainInput(scanResult, allFolderPaths);
3525
+ const projCount = brainInput.summary.projects.length;
3526
+ const cfgCount = brainInput.summary.configFiles.length;
3527
+ const dcCount = (brainInput.summary.personalDocuments?.length || 0) + brainInput.summary.recentDocuments.length;
3528
+ console.log(success(` Scanned: ${projCount} projects, ${cfgCount} config files, ${dcCount} docs`));
2754
3529
  console.log();
3530
+ if (projCount === 0 && cfgCount === 0) {
3531
+ console.log(chalk11.yellow(" No projects or config files found to resync from."));
3532
+ console.log();
3533
+ return;
3534
+ }
3535
+ const content = formatScanContent(brainInput);
3536
+ const spinner = new Spinner();
3537
+ spinner.start("Resyncing brain...");
3538
+ try {
3539
+ const result = await resyncBrain(validation.serverUrl, apiKey, content);
3540
+ spinner.stop();
3541
+ console.log();
3542
+ console.log(success(" \u2713 Brain resynced."));
3543
+ console.log(dim(` ${result.summary}`));
3544
+ console.log();
3545
+ } catch (err) {
3546
+ spinner.stop();
3547
+ const msg = err.message;
3548
+ console.log(chalk11.red(` \u2717 ${msg}`));
3549
+ console.log();
3550
+ throw new CliError(msg);
3551
+ }
2755
3552
  }
2756
3553
  async function handleViewBrain(apiKey) {
2757
3554
  console.log(dim(" Loading brain..."));
@@ -2795,11 +3592,11 @@ async function handleViewBrain(apiKey) {
2795
3592
  console.log();
2796
3593
  const msg = err.message;
2797
3594
  if (msg === "REQUIRES_SUBSCRIPTION") {
2798
- console.log(chalk10.yellow(" Deploy requires an active subscription ($10/mo)."));
3595
+ console.log(chalk11.yellow(" Deploy requires an active subscription ($10/mo)."));
2799
3596
  console.log(` Subscribe at: ${brand("https://piut.com/dashboard/billing")}`);
2800
3597
  console.log(dim(" 14-day free trial included."));
2801
3598
  } else {
2802
- console.log(chalk10.red(` \u2717 ${msg}`));
3599
+ console.log(chalk11.red(` \u2717 ${msg}`));
2803
3600
  }
2804
3601
  console.log();
2805
3602
  }
@@ -2808,7 +3605,7 @@ async function handleViewBrain(apiKey) {
2808
3605
  }
2809
3606
 
2810
3607
  // src/cli.ts
2811
- var VERSION = "3.5.0";
3608
+ var VERSION = "3.6.0";
2812
3609
  function withExit(fn) {
2813
3610
  return async (...args2) => {
2814
3611
  try {
@@ -2831,6 +3628,7 @@ program.command("disconnect").description("Remove brain references from project
2831
3628
  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));
2832
3629
  program.command("status").description("Show brain, deployment, and connected projects").option("--verify", "Validate API key, check tool configs, and test MCP endpoint").action(withExit(statusCommand));
2833
3630
  program.command("remove").description("Remove all p\u0131ut configurations").action(withExit(removeCommand));
3631
+ program.command("login").description("Authenticate with p\u0131ut (email, browser, or API key)").action(withExit(loginCommand));
2834
3632
  program.command("logout").description("Remove saved API key").action(logoutCommand);
2835
3633
  program.command("doctor").description("Diagnose and fix connection issues").option("-k, --key <key>", "API key to verify against").option("--fix", "Auto-fix stale configurations").option("--json", "Output results as JSON").action(withExit(doctorCommand));
2836
3634
  program.command("update").description("Check for and install CLI updates").action(() => updateCommand(VERSION));