@piut/cli 3.5.1 → 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 +1236 -445
  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",
@@ -922,15 +975,459 @@ function isCommandAvailable(cmd) {
922
975
  }
923
976
 
924
977
  // src/commands/status.ts
925
- import fs7 from "fs";
926
- import path9 from "path";
978
+ import fs9 from "fs";
979
+ import path12 from "path";
927
980
  import chalk3 from "chalk";
928
981
 
929
982
  // src/lib/brain-scanner.ts
930
- 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
931
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";
932
1347
  import os4 from "os";
933
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();
934
1431
  var SKIP_DIRS = /* @__PURE__ */ new Set([
935
1432
  "node_modules",
936
1433
  ".git",
@@ -952,21 +1449,6 @@ var SKIP_DIRS = /* @__PURE__ */ new Set([
952
1449
  "Cache",
953
1450
  ".piut"
954
1451
  ]);
955
- var FULL_READ_FILES = /* @__PURE__ */ new Set([
956
- "README.md",
957
- "CLAUDE.md",
958
- ".cursorrules",
959
- ".windsurfrules",
960
- ".rules",
961
- ".clinerules",
962
- "AGENTS.md",
963
- "CONVENTIONS.md",
964
- "MEMORY.md",
965
- "SOUL.md",
966
- "IDENTITY.md"
967
- ]);
968
- var MAX_FILE_SIZE = 100 * 1024;
969
- var RECENT_DAYS = 30;
970
1452
  var SCAN_DOT_DIRS = /* @__PURE__ */ new Set([
971
1453
  ".claude",
972
1454
  ".cursor",
@@ -977,54 +1459,26 @@ var SCAN_DOT_DIRS = /* @__PURE__ */ new Set([
977
1459
  ".vscode"
978
1460
  ]);
979
1461
  function shouldSkipDir(name) {
980
- if (name.startsWith(".") && !SCAN_DOT_DIRS.has(name)) {
981
- return true;
982
- }
1462
+ if (name.startsWith(".") && !SCAN_DOT_DIRS.has(name)) return true;
983
1463
  return SKIP_DIRS.has(name);
984
1464
  }
985
- function readFileSafe(filePath) {
986
- try {
987
- const stat = fs5.statSync(filePath);
988
- if (stat.size > MAX_FILE_SIZE) return null;
989
- if (!stat.isFile()) return null;
990
- return fs5.readFileSync(filePath, "utf-8");
991
- } catch {
992
- return null;
993
- }
994
- }
995
- function getFolderTree(dir, depth = 0, maxDepth = 3) {
996
- if (depth >= maxDepth) return [];
997
- const entries = [];
998
- try {
999
- const items = fs5.readdirSync(dir, { withFileTypes: true });
1000
- for (const item of items) {
1001
- if (!item.isDirectory()) continue;
1002
- if (shouldSkipDir(item.name)) continue;
1003
- const indent = " ".repeat(depth);
1004
- entries.push(`${indent}${item.name}/`);
1005
- entries.push(...getFolderTree(path7.join(dir, item.name), depth + 1, maxDepth));
1006
- }
1007
- } catch {
1008
- }
1009
- return entries;
1010
- }
1011
1465
  function isProject(dirPath) {
1012
- 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"));
1013
1467
  }
1014
1468
  function buildProjectInfo(projectPath) {
1015
- const hasPkgJson = fs5.existsSync(path7.join(projectPath, "package.json"));
1469
+ const hasPkgJson = fs7.existsSync(path10.join(projectPath, "package.json"));
1016
1470
  let description = "";
1017
1471
  if (hasPkgJson) {
1018
1472
  try {
1019
- 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"));
1020
1474
  description = pkg.description || "";
1021
1475
  } catch {
1022
1476
  }
1023
1477
  }
1024
- const readmePath = path7.join(projectPath, "README.md");
1025
- if (!description && fs5.existsSync(readmePath)) {
1026
- const content = readFileSafe(readmePath);
1027
- 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");
1028
1482
  const lines = content.split("\n");
1029
1483
  let foundHeading = false;
1030
1484
  for (const line of lines) {
@@ -1037,18 +1491,19 @@ function buildProjectInfo(projectPath) {
1037
1491
  break;
1038
1492
  }
1039
1493
  }
1494
+ } catch {
1040
1495
  }
1041
1496
  }
1042
1497
  return {
1043
- name: path7.basename(projectPath),
1498
+ name: path10.basename(projectPath),
1044
1499
  path: projectPath,
1045
1500
  description,
1046
- hasClaudeMd: fs5.existsSync(path7.join(projectPath, "CLAUDE.md")) || fs5.existsSync(path7.join(projectPath, ".claude", "rules")),
1047
- hasCursorRules: fs5.existsSync(path7.join(projectPath, ".cursorrules")) || fs5.existsSync(path7.join(projectPath, ".cursor", "rules")),
1048
- hasWindsurfRules: fs5.existsSync(path7.join(projectPath, ".windsurfrules")) || fs5.existsSync(path7.join(projectPath, ".windsurf", "rules")),
1049
- hasCopilotInstructions: fs5.existsSync(path7.join(projectPath, ".github", "copilot-instructions.md")) || fs5.existsSync(path7.join(projectPath, ".github", "instructions")),
1050
- hasConventionsMd: fs5.existsSync(path7.join(projectPath, "CONVENTIONS.md")) || fs5.existsSync(path7.join(projectPath, ".amazonq", "rules")),
1051
- 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"))
1052
1507
  };
1053
1508
  }
1054
1509
  var MAX_PROJECT_DEPTH = 4;
@@ -1058,17 +1513,17 @@ function detectProjects(scanDirs, onProgress) {
1058
1513
  function walk(dir, depth) {
1059
1514
  if (depth > MAX_PROJECT_DEPTH) return;
1060
1515
  try {
1061
- const items = fs5.readdirSync(dir, { withFileTypes: true });
1516
+ const items = fs7.readdirSync(dir, { withFileTypes: true });
1062
1517
  for (const item of items) {
1063
1518
  if (!item.isDirectory()) continue;
1064
1519
  if (shouldSkipDir(item.name)) continue;
1065
- const fullPath = path7.join(dir, item.name);
1520
+ const fullPath = path10.join(dir, item.name);
1066
1521
  if (seen.has(fullPath)) continue;
1067
1522
  seen.add(fullPath);
1068
1523
  if (isProject(fullPath)) {
1069
1524
  const info = buildProjectInfo(fullPath);
1070
1525
  projects.push(info);
1071
- onProgress?.({ phase: "projects", message: `${info.name} (${fullPath.replace(home, "~")})` });
1526
+ onProgress?.({ phase: "projects", message: `${info.name} (${fullPath.replace(home2, "~")})` });
1072
1527
  } else {
1073
1528
  walk(fullPath, depth + 1);
1074
1529
  }
@@ -1081,150 +1536,148 @@ function detectProjects(scanDirs, onProgress) {
1081
1536
  }
1082
1537
  return projects;
1083
1538
  }
1539
+ var MAX_CONFIG_SIZE = 100 * 1024;
1084
1540
  function collectConfigFiles(projects, onProgress) {
1085
1541
  const configs = [];
1086
1542
  const globalPaths = [
1087
- path7.join(home, ".claude", "MEMORY.md"),
1088
- path7.join(home, ".claude", "CLAUDE.md"),
1089
- path7.join(home, ".openclaw", "workspace", "SOUL.md"),
1090
- 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")
1091
1547
  ];
1092
1548
  for (const gp of globalPaths) {
1093
- const content = readFileSafe(gp);
1094
- if (content && content.trim()) {
1095
- const name = `~/${path7.relative(home, gp)}`;
1096
- configs.push({ name, content });
1097
- onProgress?.({ phase: "configs", message: name });
1098
- }
1099
- }
1100
- for (const project of projects) {
1101
- for (const fileName of FULL_READ_FILES) {
1102
- const filePath = path7.join(project.path, fileName);
1103
- const content = readFileSafe(filePath);
1104
- if (content && content.trim()) {
1105
- 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)}`;
1106
1555
  configs.push({ name, content });
1107
1556
  onProgress?.({ phase: "configs", message: name });
1108
1557
  }
1558
+ } catch {
1109
1559
  }
1110
- const pkgPath = path7.join(project.path, "package.json");
1111
- const pkgContent = readFileSafe(pkgPath);
1112
- 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);
1113
1564
  try {
1114
- const pkg = JSON.parse(pkgContent);
1115
- const summary = JSON.stringify({ name: pkg.name, description: pkg.description }, null, 2);
1116
- const name = `${project.name}/package.json`;
1117
- configs.push({ name, content: summary });
1118
- 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
+ }
1119
1573
  } catch {
1120
1574
  }
1121
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
+ }
1122
1587
  }
1123
1588
  return configs;
1124
1589
  }
1125
- function collectRecentDocs(projects, onProgress) {
1126
- const docs = [];
1127
- 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 = [];
1128
1594
  const seen = /* @__PURE__ */ new Set();
1129
- for (const project of projects) {
1595
+ function walk(dir, depth) {
1596
+ if (depth > MAX_SCAN_DEPTH) return [];
1597
+ const found = [];
1130
1598
  try {
1131
- const items = fs5.readdirSync(project.path, { withFileTypes: true });
1599
+ const items = fs7.readdirSync(dir, { withFileTypes: true });
1132
1600
  for (const item of items) {
1133
- if (!item.isFile()) continue;
1134
- if (!item.name.endsWith(".md")) continue;
1135
- if (FULL_READ_FILES.has(item.name)) continue;
1136
- if (item.name.startsWith(".")) continue;
1137
- const filePath = path7.join(project.path, item.name);
1138
- if (seen.has(filePath)) continue;
1139
- seen.add(filePath);
1140
- try {
1141
- const stat = fs5.statSync(filePath);
1142
- if (stat.mtimeMs < cutoff) continue;
1143
- if (stat.size > MAX_FILE_SIZE) continue;
1144
- const content = fs5.readFileSync(filePath, "utf-8");
1145
- if (content.trim()) {
1146
- const name = `${project.name}/${item.name}`;
1147
- docs.push({ name, content });
1148
- 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
+ }
1149
1612
  }
1150
- } catch {
1151
1613
  }
1152
1614
  }
1153
1615
  } catch {
1154
1616
  }
1617
+ return found;
1155
1618
  }
1156
- return docs.slice(0, 20);
1157
- }
1158
- var SKIP_HOME_DIRS = /* @__PURE__ */ new Set([
1159
- "Library",
1160
- "Applications",
1161
- "Public",
1162
- "Movies",
1163
- "Music",
1164
- "Pictures",
1165
- "Templates",
1166
- ".Trash"
1167
- ]);
1168
- var INCLUDE_DOT_DIRS = /* @__PURE__ */ new Set([
1169
- ".claude",
1170
- ".cursor",
1171
- ".windsurf",
1172
- ".openclaw",
1173
- ".zed",
1174
- ".github",
1175
- ".amazonq"
1176
- ]);
1177
- function getDefaultScanDirs() {
1178
- const dirs = [];
1179
- try {
1180
- const entries = fs5.readdirSync(home, { withFileTypes: true });
1181
- for (const entry of entries) {
1182
- if (!entry.isDirectory()) continue;
1183
- if (entry.name.startsWith(".") && !INCLUDE_DOT_DIRS.has(entry.name)) continue;
1184
- if (SKIP_HOME_DIRS.has(entry.name)) continue;
1185
- dirs.push(path7.join(home, entry.name));
1186
- }
1187
- } 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;
1188
1624
  }
1189
- const cloudStorage = path7.join(home, "Library", "CloudStorage");
1190
- try {
1191
- if (fs5.existsSync(cloudStorage) && fs5.statSync(cloudStorage).isDirectory()) {
1192
- const entries = fs5.readdirSync(cloudStorage, { withFileTypes: true });
1193
- for (const entry of entries) {
1194
- if (!entry.isDirectory()) continue;
1195
- const fullPath = path7.join(cloudStorage, entry.name);
1196
- if (!dirs.includes(fullPath)) {
1197
- dirs.push(fullPath);
1198
- }
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
+ });
1199
1641
  }
1642
+ } catch {
1200
1643
  }
1201
- } catch {
1202
1644
  }
1203
- if (dirs.length === 0) {
1204
- dirs.push(home);
1205
- }
1206
- return dirs;
1645
+ return files;
1207
1646
  }
1208
- function scanForBrain(folders, onProgress) {
1209
- 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));
1210
1659
  const folderTree = [];
1211
- for (const dir of scanDirs) {
1212
- folderTree.push(`${path7.basename(dir)}/`);
1213
- 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
+ }
1214
1664
  }
1215
- const projects = detectProjects(scanDirs, onProgress);
1216
- const configFiles = collectConfigFiles(projects, onProgress);
1217
- const recentDocuments = collectRecentDocs(projects, onProgress);
1665
+ const personalDocuments = selectedFiles.map((f) => ({
1666
+ name: f.displayPath,
1667
+ content: f.content,
1668
+ format: f.format
1669
+ }));
1218
1670
  return {
1219
1671
  summary: {
1220
1672
  folders: folderTree,
1221
- projects: projects.map((p) => ({
1673
+ projects: scanResult.projects.map((p) => ({
1222
1674
  name: p.name,
1223
- path: p.path.replace(home, "~"),
1675
+ path: p.path.replace(home2, "~"),
1224
1676
  description: p.description
1225
1677
  })),
1226
- configFiles,
1227
- recentDocuments
1678
+ configFiles: scanResult.configFiles,
1679
+ recentDocuments: [],
1680
+ personalDocuments
1228
1681
  }
1229
1682
  };
1230
1683
  }
@@ -1234,14 +1687,14 @@ function scanForProjects(folders) {
1234
1687
  }
1235
1688
 
1236
1689
  // src/lib/store.ts
1237
- import fs6 from "fs";
1238
- import path8 from "path";
1239
- import os5 from "os";
1240
- var CONFIG_DIR = path8.join(os5.homedir(), ".piut");
1241
- 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");
1242
1695
  function readStore() {
1243
1696
  try {
1244
- const raw = fs6.readFileSync(CONFIG_FILE2, "utf-8");
1697
+ const raw = fs8.readFileSync(CONFIG_FILE2, "utf-8");
1245
1698
  return JSON.parse(raw);
1246
1699
  } catch {
1247
1700
  return {};
@@ -1250,13 +1703,13 @@ function readStore() {
1250
1703
  function updateStore(updates) {
1251
1704
  const config = readStore();
1252
1705
  const updated = { ...config, ...updates };
1253
- fs6.mkdirSync(CONFIG_DIR, { recursive: true });
1254
- 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");
1255
1708
  return updated;
1256
1709
  }
1257
1710
  function clearStore() {
1258
1711
  try {
1259
- fs6.unlinkSync(CONFIG_FILE2);
1712
+ fs8.unlinkSync(CONFIG_FILE2);
1260
1713
  } catch {
1261
1714
  }
1262
1715
  }
@@ -1272,7 +1725,7 @@ var PIUT_FILES = [
1272
1725
  ];
1273
1726
  function hasPiutReference(filePath) {
1274
1727
  try {
1275
- const content = fs7.readFileSync(filePath, "utf-8");
1728
+ const content = fs9.readFileSync(filePath, "utf-8");
1276
1729
  return content.includes("p\u0131ut Context") || content.includes("piut Context");
1277
1730
  } catch {
1278
1731
  return false;
@@ -1290,7 +1743,7 @@ async function statusCommand(options = {}) {
1290
1743
  for (const tool of TOOLS) {
1291
1744
  const paths = resolveConfigPaths(tool.configPaths);
1292
1745
  for (const configPath of paths) {
1293
- if (!fs7.existsSync(configPath)) continue;
1746
+ if (!fs9.existsSync(configPath)) continue;
1294
1747
  foundAny = true;
1295
1748
  const configured = isPiutConfigured(configPath, tool.configKey);
1296
1749
  if (configured) {
@@ -1313,8 +1766,8 @@ async function statusCommand(options = {}) {
1313
1766
  for (const project of projects) {
1314
1767
  const connectedFiles = [];
1315
1768
  for (const file of PIUT_FILES) {
1316
- const absPath = path9.join(project.path, file);
1317
- if (fs7.existsSync(absPath) && hasPiutReference(absPath)) {
1769
+ const absPath = path12.join(project.path, file);
1770
+ if (fs9.existsSync(absPath) && hasPiutReference(absPath)) {
1318
1771
  connectedFiles.push(file);
1319
1772
  }
1320
1773
  }
@@ -1360,7 +1813,7 @@ async function verifyStatus() {
1360
1813
  for (const tool of TOOLS) {
1361
1814
  const paths = resolveConfigPaths(tool.configPaths);
1362
1815
  for (const configPath of paths) {
1363
- if (!fs7.existsSync(configPath)) continue;
1816
+ if (!fs9.existsSync(configPath)) continue;
1364
1817
  const piutConfig = getPiutConfig(configPath, tool.configKey);
1365
1818
  if (!piutConfig) {
1366
1819
  toolLine(tool.name, dim("installed, not connected"), "\u25CB");
@@ -1403,7 +1856,7 @@ async function verifyStatus() {
1403
1856
  }
1404
1857
 
1405
1858
  // src/commands/remove.ts
1406
- import fs8 from "fs";
1859
+ import fs10 from "fs";
1407
1860
  import { checkbox as checkbox2, confirm as confirm2 } from "@inquirer/prompts";
1408
1861
  async function removeCommand() {
1409
1862
  banner();
@@ -1411,7 +1864,7 @@ async function removeCommand() {
1411
1864
  for (const tool of TOOLS) {
1412
1865
  const paths = resolveConfigPaths(tool.configPaths);
1413
1866
  for (const configPath of paths) {
1414
- if (fs8.existsSync(configPath) && isPiutConfigured(configPath, tool.configKey)) {
1867
+ if (fs10.existsSync(configPath) && isPiutConfigured(configPath, tool.configKey)) {
1415
1868
  configured.push({ tool, configPath });
1416
1869
  break;
1417
1870
  }
@@ -1440,134 +1893,219 @@ async function removeCommand() {
1440
1893
  });
1441
1894
  if (!proceed) return;
1442
1895
  console.log();
1896
+ const removedNames = [];
1443
1897
  for (const { tool, configPath } of selected) {
1444
1898
  const removed = removeFromConfig(configPath, tool.configKey);
1445
1899
  if (removed) {
1900
+ removedNames.push(tool.name);
1446
1901
  toolLine(tool.name, success("removed"), "\u2714");
1447
1902
  } else {
1448
1903
  toolLine(tool.name, warning("not found"), "\xD7");
1449
1904
  }
1450
1905
  }
1906
+ const store = readStore();
1907
+ if (store.apiKey && removedNames.length > 0) {
1908
+ deleteConnections(store.apiKey, removedNames).catch(() => {
1909
+ });
1910
+ }
1451
1911
  console.log();
1452
1912
  console.log(dim(" Restart your AI tools for changes to take effect."));
1453
1913
  console.log();
1454
1914
  }
1455
1915
 
1456
1916
  // src/commands/build.ts
1457
- 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";
1458
1918
  import chalk5 from "chalk";
1459
- import os6 from "os";
1919
+ import os7 from "os";
1460
1920
 
1461
1921
  // src/lib/auth.ts
1462
- import { password as password2 } from "@inquirer/prompts";
1922
+ import { select, input, password as password2 } from "@inquirer/prompts";
1923
+ import { exec } from "child_process";
1463
1924
  import chalk4 from "chalk";
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
+ });
1958
+ console.log(dim(" Validating key..."));
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();
1998
+ }
1999
+ }
1464
2000
  async function resolveApiKeyWithResult(keyOption) {
1465
2001
  const config = readStore();
1466
2002
  let apiKey = keyOption || config.apiKey;
1467
- if (!apiKey) {
1468
- apiKey = await password2({
1469
- message: "Enter your p\u0131ut API key:",
1470
- mask: "*",
1471
- validate: (v) => v.startsWith("pb_") || "Key must start with pb_"
1472
- });
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 };
1473
2017
  }
1474
- console.log(dim(" Validating key..."));
1475
- let result;
1476
2018
  try {
1477
- 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 };
1478
2024
  } catch (err) {
1479
2025
  console.log(chalk4.red(` \u2717 ${err.message}`));
1480
- console.log(dim(" Get a key at https://piut.com/dashboard/keys"));
1481
2026
  throw new CliError(err.message);
1482
2027
  }
1483
- const label = result.slug ? `${result.displayName} (${result.slug})` : result.displayName;
1484
- console.log(success(` \u2713 Connected as ${label}`));
1485
- updateStore({ apiKey });
1486
- return { apiKey, ...result };
1487
2028
  }
1488
2029
 
1489
2030
  // src/commands/build.ts
1490
2031
  async function buildCommand(options) {
1491
2032
  banner();
1492
2033
  const { apiKey, serverUrl } = await resolveApiKeyWithResult(options.key);
1493
- let scanFolders;
2034
+ let scanDirs;
1494
2035
  if (options.folders) {
1495
- scanFolders = options.folders.split(",").map((f) => expandPath(f.trim()));
1496
- }
1497
- const cwd = process.cwd();
1498
- const cwdDisplay = cwd.replace(os6.homedir(), "~");
1499
- if (!scanFolders) {
1500
- console.log(dim(` Current directory: `) + cwdDisplay);
1501
- 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"));
1502
2039
  console.log();
1503
- const mode = await select({
1504
- message: "How do you want to build your brain?",
1505
- choices: [
1506
- { name: `Scan this directory (${cwdDisplay})`, value: "cwd", description: "Scan current directory for projects and config files" },
1507
- { name: "Select folder(s)...", value: "folders", description: "Choose a different directory to scan" }
1508
- ]
1509
- });
1510
- if (mode === "cwd") {
1511
- scanFolders = [cwd];
1512
- } else {
1513
- const defaults = getDefaultScanDirs();
1514
- const CUSTOM_VALUE = "__custom__";
1515
- const choices = [
1516
- ...defaults.map((d) => ({ name: d.replace(os6.homedir(), "~"), value: d })),
1517
- { name: chalk5.dim("Enter a custom path..."), value: CUSTOM_VALUE }
1518
- ];
1519
- const selected = await checkbox3({
1520
- message: "Which folders should we scan?",
1521
- choices,
1522
- required: true
1523
- });
1524
- if (selected.includes(CUSTOM_VALUE)) {
1525
- const custom = await input({
1526
- message: "Enter folder path(s), comma-separated:"
1527
- });
1528
- const customPaths = custom.split(",").map((f) => expandPath(f.trim())).filter(Boolean);
1529
- scanFolders = [
1530
- ...selected.filter((v) => v !== CUSTOM_VALUE),
1531
- ...customPaths
1532
- ];
1533
- } else {
1534
- scanFolders = selected;
1535
- }
1536
- if (scanFolders.length === 0) {
1537
- console.log(chalk5.yellow(" No folders selected."));
1538
- return;
1539
- }
1540
- }
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;
1541
2048
  }
1542
2049
  console.log();
1543
- let projectCount = 0;
1544
- let configCount = 0;
1545
- let docCount = 0;
2050
+ console.log(dim(" Scanning locally \u2014 no data is shared..."));
2051
+ console.log();
2052
+ let fileCount = 0;
1546
2053
  const onProgress = (progress) => {
1547
- if (progress.phase === "projects") {
1548
- projectCount++;
1549
- 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}`));
1550
2061
  } else if (progress.phase === "configs") {
1551
- configCount++;
1552
- console.log(dim(` [${configCount}] ${progress.message}`));
1553
- } else if (progress.phase === "docs") {
1554
- docCount++;
1555
- console.log(dim(` [${docCount}] ${progress.message}`));
2062
+ console.log(dim(` [config] ${progress.message}`));
1556
2063
  }
1557
2064
  };
1558
- const brainInput = scanForBrain(scanFolders, onProgress);
1559
- const projCount = brainInput.summary.projects.length;
1560
- const cfgCount = brainInput.summary.configFiles.length;
1561
- const dcCount = brainInput.summary.recentDocuments.length;
2065
+ const scanResult = await scanFolders(scanDirs, onProgress);
1562
2066
  console.log();
1563
- 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)`));
1564
2068
  console.log();
1565
- if (projCount === 0 && cfgCount === 0) {
1566
- console.log(chalk5.yellow(" No projects or config files found to build from."));
1567
- 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."));
1568
2072
  console.log();
1569
2073
  return;
1570
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);
1571
2109
  const spinner = new Spinner();
1572
2110
  spinner.start("Generating brain...");
1573
2111
  try {
@@ -1672,6 +2210,78 @@ async function buildCommand(options) {
1672
2210
  throw new CliError(hint);
1673
2211
  }
1674
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
+ }
1675
2285
 
1676
2286
  // src/commands/deploy.ts
1677
2287
  import chalk6 from "chalk";
@@ -1711,33 +2321,33 @@ async function deployCommand(options) {
1711
2321
  }
1712
2322
 
1713
2323
  // src/commands/connect.ts
1714
- import fs9 from "fs";
1715
- import path10 from "path";
2324
+ import fs11 from "fs";
2325
+ import path13 from "path";
1716
2326
  import { checkbox as checkbox4 } from "@inquirer/prompts";
1717
2327
  var RULE_FILES = [
1718
2328
  {
1719
2329
  tool: "Claude Code",
1720
2330
  filePath: "CLAUDE.md",
1721
2331
  strategy: "append",
1722
- detect: (p) => p.hasClaudeMd || fs9.existsSync(path10.join(p.path, ".claude"))
2332
+ detect: (p) => p.hasClaudeMd || fs11.existsSync(path13.join(p.path, ".claude"))
1723
2333
  },
1724
2334
  {
1725
2335
  tool: "Cursor",
1726
2336
  filePath: ".cursor/rules/piut.mdc",
1727
2337
  strategy: "create",
1728
- detect: (p) => p.hasCursorRules || fs9.existsSync(path10.join(p.path, ".cursor"))
2338
+ detect: (p) => p.hasCursorRules || fs11.existsSync(path13.join(p.path, ".cursor"))
1729
2339
  },
1730
2340
  {
1731
2341
  tool: "Windsurf",
1732
2342
  filePath: ".windsurf/rules/piut.md",
1733
2343
  strategy: "create",
1734
- detect: (p) => p.hasWindsurfRules || fs9.existsSync(path10.join(p.path, ".windsurf"))
2344
+ detect: (p) => p.hasWindsurfRules || fs11.existsSync(path13.join(p.path, ".windsurf"))
1735
2345
  },
1736
2346
  {
1737
2347
  tool: "GitHub Copilot",
1738
2348
  filePath: ".github/copilot-instructions.md",
1739
2349
  strategy: "append",
1740
- detect: (p) => p.hasCopilotInstructions || fs9.existsSync(path10.join(p.path, ".github"))
2350
+ detect: (p) => p.hasCopilotInstructions || fs11.existsSync(path13.join(p.path, ".github"))
1741
2351
  },
1742
2352
  {
1743
2353
  tool: "Amazon Q",
@@ -1749,7 +2359,7 @@ var RULE_FILES = [
1749
2359
  tool: "Zed",
1750
2360
  filePath: ".zed/rules.md",
1751
2361
  strategy: "create",
1752
- detect: (p) => p.hasZedRules || fs9.existsSync(path10.join(p.path, ".zed"))
2362
+ detect: (p) => p.hasZedRules || fs11.existsSync(path13.join(p.path, ".zed"))
1753
2363
  }
1754
2364
  ];
1755
2365
  var DEDICATED_FILE_CONTENT = `## p\u0131ut Context (MCP Server: piut-context)
@@ -1784,7 +2394,7 @@ Full skill reference: .piut/skill.md
1784
2394
  `;
1785
2395
  function hasPiutReference2(filePath) {
1786
2396
  try {
1787
- const content = fs9.readFileSync(filePath, "utf-8");
2397
+ const content = fs11.readFileSync(filePath, "utf-8");
1788
2398
  return content.includes("p\u0131ut Context") || content.includes("piut Context");
1789
2399
  } catch {
1790
2400
  return false;
@@ -1807,13 +2417,13 @@ async function connectCommand(options) {
1807
2417
  console.log();
1808
2418
  return;
1809
2419
  }
1810
- let scanFolders;
2420
+ let scanFolders2;
1811
2421
  if (options.folders) {
1812
- scanFolders = options.folders.split(",").map((f) => expandPath(f.trim()));
2422
+ scanFolders2 = options.folders.split(",").map((f) => expandPath(f.trim()));
1813
2423
  }
1814
2424
  console.log();
1815
2425
  console.log(dim(" Scanning for projects..."));
1816
- const projects = scanForProjects(scanFolders);
2426
+ const projects = scanForProjects(scanFolders2);
1817
2427
  if (projects.length === 0) {
1818
2428
  console.log(warning(" No projects found."));
1819
2429
  console.log(dim(" Try running from a directory with your projects, or use --folders."));
@@ -1824,20 +2434,20 @@ async function connectCommand(options) {
1824
2434
  for (const project of projects) {
1825
2435
  for (const rule of RULE_FILES) {
1826
2436
  if (!rule.detect(project)) continue;
1827
- const absPath = path10.join(project.path, rule.filePath);
1828
- if (fs9.existsSync(absPath) && hasPiutReference2(absPath)) continue;
2437
+ const absPath = path13.join(project.path, rule.filePath);
2438
+ if (fs11.existsSync(absPath) && hasPiutReference2(absPath)) continue;
1829
2439
  actions.push({
1830
2440
  project,
1831
2441
  tool: rule.tool,
1832
2442
  filePath: rule.filePath,
1833
2443
  absPath,
1834
- action: rule.strategy === "create" || !fs9.existsSync(absPath) ? "create" : "append"
2444
+ action: rule.strategy === "create" || !fs11.existsSync(absPath) ? "create" : "append"
1835
2445
  });
1836
2446
  }
1837
2447
  const hasAnyAction = actions.some((a) => a.project === project);
1838
2448
  if (!hasAnyAction) {
1839
- const claudeMdPath = path10.join(project.path, "CLAUDE.md");
1840
- if (!fs9.existsSync(claudeMdPath)) {
2449
+ const claudeMdPath = path13.join(project.path, "CLAUDE.md");
2450
+ if (!fs11.existsSync(claudeMdPath)) {
1841
2451
  actions.push({
1842
2452
  project,
1843
2453
  tool: "Claude Code",
@@ -1877,7 +2487,7 @@ async function connectCommand(options) {
1877
2487
  console.log();
1878
2488
  const projectChoices = [];
1879
2489
  for (const [projectPath, projectActions] of byProject) {
1880
- const projectName = path10.basename(projectPath);
2490
+ const projectName = path13.basename(projectPath);
1881
2491
  const desc = projectActions.map((a) => {
1882
2492
  const verb = a.action === "create" ? "will create" : "will append to";
1883
2493
  return `${verb} ${a.filePath}`;
@@ -1906,15 +2516,15 @@ async function connectCommand(options) {
1906
2516
  const copilotTool = TOOLS.find((t) => t.id === "copilot");
1907
2517
  for (const projectPath of selectedPaths) {
1908
2518
  const projectActions = byProject.get(projectPath) || [];
1909
- const projectName = path10.basename(projectPath);
2519
+ const projectName = path13.basename(projectPath);
1910
2520
  writePiutConfig(projectPath, { slug, apiKey, serverUrl });
1911
2521
  await writePiutSkill(projectPath, slug, apiKey);
1912
2522
  ensureGitignored(projectPath);
1913
2523
  console.log(success(` \u2713 ${projectName}/.piut/`) + dim(" \u2014 credentials + skill"));
1914
2524
  if (copilotTool) {
1915
- 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"));
1916
2526
  if (hasCopilot) {
1917
- const vscodeMcpPath = path10.join(projectPath, ".vscode", "mcp.json");
2527
+ const vscodeMcpPath = path13.join(projectPath, ".vscode", "mcp.json");
1918
2528
  const serverConfig = copilotTool.generateConfig(slug, apiKey);
1919
2529
  mergeConfig(vscodeMcpPath, copilotTool.configKey, serverConfig);
1920
2530
  console.log(success(` \u2713 ${projectName}/.vscode/mcp.json`) + dim(" \u2014 Copilot MCP"));
@@ -1924,11 +2534,11 @@ async function connectCommand(options) {
1924
2534
  if (action.action === "create") {
1925
2535
  const isAppendType = RULE_FILES.find((r) => r.filePath === action.filePath)?.strategy === "append";
1926
2536
  const content = isAppendType ? PROJECT_SKILL_SNIPPET + "\n" : DEDICATED_FILE_CONTENT;
1927
- fs9.mkdirSync(path10.dirname(action.absPath), { recursive: true });
1928
- fs9.writeFileSync(action.absPath, content, "utf-8");
2537
+ fs11.mkdirSync(path13.dirname(action.absPath), { recursive: true });
2538
+ fs11.writeFileSync(action.absPath, content, "utf-8");
1929
2539
  console.log(success(` \u2713 ${projectName}/${action.filePath}`) + dim(" \u2014 created"));
1930
2540
  } else {
1931
- fs9.appendFileSync(action.absPath, APPEND_SECTION);
2541
+ fs11.appendFileSync(action.absPath, APPEND_SECTION);
1932
2542
  console.log(success(` \u2713 ${projectName}/${action.filePath}`) + dim(" \u2014 appended"));
1933
2543
  }
1934
2544
  connected++;
@@ -1937,7 +2547,7 @@ async function connectCommand(options) {
1937
2547
  const machineId = getMachineId();
1938
2548
  for (const projectPath of selectedPaths) {
1939
2549
  const projectActions = byProject.get(projectPath) || [];
1940
- const projectName = path10.basename(projectPath);
2550
+ const projectName = path13.basename(projectPath);
1941
2551
  const toolsDetected = [...new Set(projectActions.map((a) => a.tool))];
1942
2552
  const configFilesWritten = projectActions.map((a) => a.filePath);
1943
2553
  registerProject(apiKey, {
@@ -1955,8 +2565,8 @@ async function connectCommand(options) {
1955
2565
  }
1956
2566
 
1957
2567
  // src/commands/disconnect.ts
1958
- import fs10 from "fs";
1959
- import path11 from "path";
2568
+ import fs12 from "fs";
2569
+ import path14 from "path";
1960
2570
  import { checkbox as checkbox5, confirm as confirm5 } from "@inquirer/prompts";
1961
2571
  var DEDICATED_FILES = /* @__PURE__ */ new Set([
1962
2572
  ".cursor/rules/piut.mdc",
@@ -1970,7 +2580,7 @@ var APPEND_FILES = [
1970
2580
  ];
1971
2581
  function hasPiutReference3(filePath) {
1972
2582
  try {
1973
- const content = fs10.readFileSync(filePath, "utf-8");
2583
+ const content = fs12.readFileSync(filePath, "utf-8");
1974
2584
  return content.includes("p\u0131ut Context") || content.includes("piut Context");
1975
2585
  } catch {
1976
2586
  return false;
@@ -1978,7 +2588,7 @@ function hasPiutReference3(filePath) {
1978
2588
  }
1979
2589
  function removePiutSection(filePath) {
1980
2590
  try {
1981
- let content = fs10.readFileSync(filePath, "utf-8");
2591
+ let content = fs12.readFileSync(filePath, "utf-8");
1982
2592
  const patterns = [
1983
2593
  /\n*## p[ıi]ut Context[\s\S]*?(?=\n## |\n---\n|$)/g
1984
2594
  ];
@@ -1992,7 +2602,7 @@ function removePiutSection(filePath) {
1992
2602
  }
1993
2603
  if (changed) {
1994
2604
  content = content.replace(/\n{3,}/g, "\n\n").trimEnd() + "\n";
1995
- fs10.writeFileSync(filePath, content, "utf-8");
2605
+ fs12.writeFileSync(filePath, content, "utf-8");
1996
2606
  }
1997
2607
  return changed;
1998
2608
  } catch {
@@ -2001,18 +2611,18 @@ function removePiutSection(filePath) {
2001
2611
  }
2002
2612
  async function disconnectCommand(options) {
2003
2613
  banner();
2004
- let scanFolders;
2614
+ let scanFolders2;
2005
2615
  if (options.folders) {
2006
- scanFolders = options.folders.split(",").map((f) => expandPath(f.trim()));
2616
+ scanFolders2 = options.folders.split(",").map((f) => expandPath(f.trim()));
2007
2617
  }
2008
2618
  console.log(dim(" Scanning for connected projects..."));
2009
- const projects = scanForProjects(scanFolders);
2619
+ const projects = scanForProjects(scanFolders2);
2010
2620
  const actions = [];
2011
2621
  for (const project of projects) {
2012
- const projectName = path11.basename(project.path);
2622
+ const projectName = path14.basename(project.path);
2013
2623
  for (const dedicatedFile of DEDICATED_FILES) {
2014
- const absPath = path11.join(project.path, dedicatedFile);
2015
- if (fs10.existsSync(absPath) && hasPiutReference3(absPath)) {
2624
+ const absPath = path14.join(project.path, dedicatedFile);
2625
+ if (fs12.existsSync(absPath) && hasPiutReference3(absPath)) {
2016
2626
  actions.push({
2017
2627
  projectPath: project.path,
2018
2628
  projectName,
@@ -2023,8 +2633,8 @@ async function disconnectCommand(options) {
2023
2633
  }
2024
2634
  }
2025
2635
  for (const appendFile of APPEND_FILES) {
2026
- const absPath = path11.join(project.path, appendFile);
2027
- if (fs10.existsSync(absPath) && hasPiutReference3(absPath)) {
2636
+ const absPath = path14.join(project.path, appendFile);
2637
+ if (fs12.existsSync(absPath) && hasPiutReference3(absPath)) {
2028
2638
  actions.push({
2029
2639
  projectPath: project.path,
2030
2640
  projectName,
@@ -2039,12 +2649,12 @@ async function disconnectCommand(options) {
2039
2649
  projectPath: project.path,
2040
2650
  projectName,
2041
2651
  filePath: ".piut/",
2042
- absPath: path11.join(project.path, ".piut"),
2652
+ absPath: path14.join(project.path, ".piut"),
2043
2653
  action: "remove-dir"
2044
2654
  });
2045
2655
  }
2046
- const vscodeMcpPath = path11.join(project.path, ".vscode", "mcp.json");
2047
- 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")) {
2048
2658
  actions.push({
2049
2659
  projectPath: project.path,
2050
2660
  projectName,
@@ -2066,7 +2676,7 @@ async function disconnectCommand(options) {
2066
2676
  }
2067
2677
  console.log();
2068
2678
  const projectChoices = Array.from(byProject.entries()).map(([projectPath, projectActions]) => {
2069
- const name = path11.basename(projectPath);
2679
+ const name = path14.basename(projectPath);
2070
2680
  const files = projectActions.map((a) => a.filePath).join(", ");
2071
2681
  return {
2072
2682
  name: `${name} ${dim(`(${files})`)}`,
@@ -2095,11 +2705,11 @@ async function disconnectCommand(options) {
2095
2705
  let disconnected = 0;
2096
2706
  for (const projectPath of selectedPaths) {
2097
2707
  const projectActions = byProject.get(projectPath) || [];
2098
- const projectName = path11.basename(projectPath);
2708
+ const projectName = path14.basename(projectPath);
2099
2709
  for (const action of projectActions) {
2100
2710
  if (action.action === "delete") {
2101
2711
  try {
2102
- fs10.unlinkSync(action.absPath);
2712
+ fs12.unlinkSync(action.absPath);
2103
2713
  console.log(success(` \u2713 ${projectName}/${action.filePath}`) + dim(" \u2014 deleted"));
2104
2714
  disconnected++;
2105
2715
  } catch {
@@ -2142,6 +2752,21 @@ async function disconnectCommand(options) {
2142
2752
  console.log();
2143
2753
  }
2144
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
+
2145
2770
  // src/commands/logout.ts
2146
2771
  async function logoutCommand() {
2147
2772
  banner();
@@ -2159,11 +2784,11 @@ async function logoutCommand() {
2159
2784
  }
2160
2785
 
2161
2786
  // src/commands/update.ts
2162
- import chalk8 from "chalk";
2787
+ import chalk9 from "chalk";
2163
2788
 
2164
2789
  // src/lib/update-check.ts
2165
2790
  import { execFile } from "child_process";
2166
- import chalk7 from "chalk";
2791
+ import chalk8 from "chalk";
2167
2792
  import { confirm as confirm6 } from "@inquirer/prompts";
2168
2793
  var PACKAGE_NAME = "@piut/cli";
2169
2794
  function isNpx() {
@@ -2200,7 +2825,7 @@ async function checkForUpdate(currentVersion) {
2200
2825
  const updateCmd = npx ? `npx ${PACKAGE_NAME}@latest` : `npm install -g ${PACKAGE_NAME}@latest`;
2201
2826
  console.log();
2202
2827
  console.log(brand(" Update available!") + dim(` ${currentVersion} \u2192 ${latest}`));
2203
- console.log(dim(` Run ${chalk7.bold(updateCmd)} to update`));
2828
+ console.log(dim(` Run ${chalk8.bold(updateCmd)} to update`));
2204
2829
  console.log();
2205
2830
  if (npx) return;
2206
2831
  try {
@@ -2212,12 +2837,12 @@ async function checkForUpdate(currentVersion) {
2212
2837
  console.log(dim(" Updating..."));
2213
2838
  const ok = await runUpdate();
2214
2839
  if (ok) {
2215
- console.log(chalk7.green(` \u2713 Updated to v${latest}`));
2840
+ console.log(chalk8.green(` \u2713 Updated to v${latest}`));
2216
2841
  console.log(dim(" Restart the CLI to use the new version."));
2217
2842
  process.exit(0);
2218
2843
  } else {
2219
- console.log(chalk7.yellow(` Could not auto-update. Run manually:`));
2220
- 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`));
2221
2846
  console.log();
2222
2847
  }
2223
2848
  }
@@ -2232,7 +2857,7 @@ async function updateCommand(currentVersion) {
2232
2857
  console.log(dim(" Checking for updates..."));
2233
2858
  const latest = await getLatestVersion();
2234
2859
  if (!latest) {
2235
- 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."));
2236
2861
  return;
2237
2862
  }
2238
2863
  if (!isNewer(currentVersion, latest)) {
@@ -2244,7 +2869,7 @@ async function updateCommand(currentVersion) {
2244
2869
  if (isNpx()) {
2245
2870
  console.log();
2246
2871
  console.log(dim(" You're running via npx. Use the latest version with:"));
2247
- console.log(chalk8.bold(` npx ${PACKAGE_NAME2}@latest`));
2872
+ console.log(chalk9.bold(` npx ${PACKAGE_NAME2}@latest`));
2248
2873
  console.log();
2249
2874
  return;
2250
2875
  }
@@ -2254,14 +2879,14 @@ async function updateCommand(currentVersion) {
2254
2879
  console.log(success(` \u2713 Updated to v${latest}`));
2255
2880
  console.log(dim(" Restart the CLI to use the new version."));
2256
2881
  } else {
2257
- console.log(chalk8.yellow(" Could not auto-update. Run manually:"));
2258
- 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`));
2259
2884
  }
2260
2885
  }
2261
2886
 
2262
2887
  // src/commands/doctor.ts
2263
- import fs11 from "fs";
2264
- import chalk9 from "chalk";
2888
+ import fs13 from "fs";
2889
+ import chalk10 from "chalk";
2265
2890
  async function doctorCommand(options) {
2266
2891
  if (!options.json) banner();
2267
2892
  const result = {
@@ -2312,7 +2937,7 @@ async function doctorCommand(options) {
2312
2937
  for (const tool of TOOLS) {
2313
2938
  const paths = resolveConfigPaths(tool.configPaths);
2314
2939
  for (const configPath of paths) {
2315
- if (!fs11.existsSync(configPath)) continue;
2940
+ if (!fs13.existsSync(configPath)) continue;
2316
2941
  const piutConfig = getPiutConfig(configPath, tool.configKey);
2317
2942
  if (!piutConfig) {
2318
2943
  result.tools.push({
@@ -2417,10 +3042,10 @@ async function doctorCommand(options) {
2417
3042
  const staleTools = result.tools.filter((t) => t.keyMatch === "stale" && !t.fixed);
2418
3043
  if (staleTools.length > 0 && !options.fix) {
2419
3044
  console.log();
2420
- console.log(dim(" Fix stale configs: ") + chalk9.cyan("piut doctor --fix"));
3045
+ console.log(dim(" Fix stale configs: ") + chalk10.cyan("piut doctor --fix"));
2421
3046
  }
2422
3047
  if (!result.key.valid) {
2423
- 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"));
2424
3049
  }
2425
3050
  }
2426
3051
  console.log();
@@ -2428,48 +3053,38 @@ async function doctorCommand(options) {
2428
3053
  }
2429
3054
 
2430
3055
  // src/commands/interactive.ts
2431
- import { select as select2, confirm as confirm7, checkbox as checkbox6, password as password3 } from "@inquirer/prompts";
2432
- import fs12 from "fs";
2433
- import path12 from "path";
2434
- 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";
2435
3062
  async function authenticate() {
2436
3063
  const config = readStore();
2437
- let apiKey = config.apiKey;
3064
+ const apiKey = config.apiKey;
2438
3065
  if (apiKey) {
2439
3066
  try {
2440
- const result2 = await validateKey(apiKey);
2441
- console.log(success(` Connected as ${result2.displayName}`));
2442
- return { apiKey, validation: result2 };
3067
+ const result = await validateKey(apiKey);
3068
+ console.log(success(` Connected as ${result.displayName}`));
3069
+ return { apiKey, validation: result };
2443
3070
  } catch {
2444
3071
  console.log(dim(" Saved key expired. Please re-authenticate."));
2445
- apiKey = void 0;
2446
3072
  }
2447
3073
  }
2448
- console.log(dim(" Connect to p\u0131ut:"));
2449
- console.log(dim(" > Log in at piut.com"));
2450
- console.log(dim(" > Enter p\u0131ut API key"));
2451
- console.log();
2452
- apiKey = await password3({
2453
- message: "Enter your p\u0131ut API key:",
2454
- mask: "*",
2455
- validate: (v) => v.startsWith("pb_") || "Key must start with pb_"
2456
- });
2457
- console.log(dim(" Validating key..."));
2458
- let result;
2459
- try {
2460
- result = await validateKey(apiKey);
2461
- } catch (err) {
2462
- console.log(chalk10.red(` \u2717 ${err.message}`));
2463
- console.log(dim(" Get a key at https://piut.com/dashboard/keys"));
2464
- process.exit(1);
2465
- }
2466
- console.log(success(` \u2713 Connected as ${result.displayName}`));
2467
- updateStore({ apiKey });
2468
- 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 };
2469
3079
  }
2470
3080
  function isPromptCancellation(err) {
2471
3081
  return !!(err && typeof err === "object" && "name" in err && err.name === "ExitPromptError");
2472
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
+ }
2473
3088
  async function interactiveMenu() {
2474
3089
  banner();
2475
3090
  let apiKey;
@@ -2515,42 +3130,39 @@ async function interactiveMenu() {
2515
3130
  const isDeployed = currentValidation.status === "active";
2516
3131
  let action;
2517
3132
  try {
2518
- action = await select2({
3133
+ action = await select3({
2519
3134
  message: "What would you like to do?",
3135
+ loop: false,
2520
3136
  choices: [
2521
3137
  {
2522
- name: hasBrain ? "Rebuild Brain" : "Build Brain",
3138
+ name: hasBrain ? "Resync Brain" : "Build Brain",
2523
3139
  value: "build",
2524
- 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"
2525
3141
  },
2526
3142
  {
2527
3143
  name: isDeployed ? "Undeploy Brain" : "Deploy Brain",
2528
3144
  value: "deploy",
2529
3145
  description: isDeployed ? "Take your MCP server offline" : "Publish your MCP server (requires paid account)"
2530
3146
  },
3147
+ new Separator(),
2531
3148
  {
2532
3149
  name: "Connect Tools",
2533
3150
  value: "connect-tools",
2534
- description: "Configure AI tools to use your MCP server",
2535
- disabled: !isDeployed && "(deploy brain first)"
2536
- },
2537
- {
2538
- name: "Disconnect Tools",
2539
- value: "disconnect-tools",
2540
- description: "Remove p\u0131ut from AI tool configs",
3151
+ description: "Manage which AI tools use your MCP server",
2541
3152
  disabled: !isDeployed && "(deploy brain first)"
2542
3153
  },
2543
3154
  {
2544
3155
  name: "Connect Projects",
2545
3156
  value: "connect-projects",
2546
- description: "Add brain references to project config files",
3157
+ description: "Manage brain references in project config files",
2547
3158
  disabled: !isDeployed && "(deploy brain first)"
2548
3159
  },
3160
+ new Separator(),
2549
3161
  {
2550
- name: "Disconnect Projects",
2551
- value: "disconnect-projects",
2552
- description: "Remove brain references from project configs",
2553
- 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)"
2554
3166
  },
2555
3167
  {
2556
3168
  name: "View Brain",
@@ -2563,6 +3175,7 @@ async function interactiveMenu() {
2563
3175
  value: "status",
2564
3176
  description: "Show brain, deployment, and connected tools/projects"
2565
3177
  },
3178
+ new Separator(),
2566
3179
  {
2567
3180
  name: "Logout",
2568
3181
  value: "logout",
@@ -2582,7 +3195,11 @@ async function interactiveMenu() {
2582
3195
  try {
2583
3196
  switch (action) {
2584
3197
  case "build":
2585
- await buildCommand({ key: apiKey });
3198
+ if (hasBrain) {
3199
+ await handleResyncBrain(apiKey, currentValidation);
3200
+ } else {
3201
+ await buildCommand({ key: apiKey });
3202
+ }
2586
3203
  break;
2587
3204
  case "deploy":
2588
3205
  if (isDeployed) {
@@ -2594,14 +3211,11 @@ async function interactiveMenu() {
2594
3211
  case "connect-tools":
2595
3212
  await handleConnectTools(apiKey, currentValidation);
2596
3213
  break;
2597
- case "disconnect-tools":
2598
- await handleDisconnectTools();
2599
- break;
2600
3214
  case "connect-projects":
2601
- await connectCommand({ key: apiKey });
3215
+ await handleManageProjects(apiKey, currentValidation);
2602
3216
  break;
2603
- case "disconnect-projects":
2604
- await disconnectCommand({});
3217
+ case "edit-brain":
3218
+ handleEditBrain();
2605
3219
  break;
2606
3220
  case "view-brain":
2607
3221
  await handleViewBrain(apiKey);
@@ -2627,7 +3241,7 @@ async function interactiveMenu() {
2627
3241
  } else if (err instanceof CliError) {
2628
3242
  console.log();
2629
3243
  } else {
2630
- console.log(chalk10.red(` Error: ${err.message}`));
3244
+ console.log(chalk11.red(` Error: ${err.message}`));
2631
3245
  console.log();
2632
3246
  }
2633
3247
  }
@@ -2650,115 +3264,291 @@ async function handleUndeploy(apiKey) {
2650
3264
  console.log(dim(" Run ") + brand("piut deploy") + dim(" to re-deploy anytime."));
2651
3265
  console.log();
2652
3266
  } catch (err) {
2653
- console.log(chalk10.red(` \u2717 ${err.message}`));
3267
+ console.log(chalk11.red(` \u2717 ${err.message}`));
2654
3268
  }
2655
3269
  }
2656
3270
  async function handleConnectTools(apiKey, validation) {
2657
3271
  const { slug } = validation;
2658
- const unconfigured = [];
2659
- const alreadyConnected = [];
3272
+ const detected = [];
2660
3273
  for (const tool of TOOLS) {
2661
3274
  const paths = resolveConfigPaths(tool.configPaths);
2662
3275
  for (const configPath of paths) {
2663
- const exists = fs12.existsSync(configPath);
2664
- const parentExists = fs12.existsSync(path12.dirname(configPath));
3276
+ const exists = fs14.existsSync(configPath);
3277
+ const parentExists = fs14.existsSync(path15.dirname(configPath));
2665
3278
  if (exists || parentExists) {
2666
- if (exists && isPiutConfigured(configPath, tool.configKey)) {
2667
- alreadyConnected.push(tool.name);
2668
- } else {
2669
- unconfigured.push({ tool, configPath });
2670
- }
3279
+ const connected = exists && isPiutConfigured(configPath, tool.configKey);
3280
+ detected.push({ tool, configPath, connected });
2671
3281
  break;
2672
3282
  }
2673
3283
  }
2674
3284
  }
2675
- if (unconfigured.length === 0) {
2676
- if (alreadyConnected.length > 0) {
2677
- console.log(dim(" All detected tools are already connected."));
2678
- } else {
2679
- console.log(warning(" No supported AI tools detected."));
2680
- console.log(dim(" Supported: Claude Code, Claude Desktop, Cursor, Windsurf, GitHub Copilot, Amazon Q, Zed"));
2681
- }
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"));
2682
3288
  console.log();
2683
3289
  return;
2684
3290
  }
2685
- if (alreadyConnected.length > 0) {
2686
- console.log(dim(` Already connected: ${alreadyConnected.join(", ")}`));
2687
- 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(", ")}`));
2688
3298
  }
2689
- const choices = unconfigured.map((u) => ({
2690
- name: u.tool.name,
2691
- value: u,
2692
- 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
2693
3304
  }));
2694
3305
  const selected = await checkbox6({
2695
- message: "Select tools to connect:",
3306
+ message: "Select tools to keep connected (toggle with space):",
2696
3307
  choices
2697
3308
  });
2698
- if (selected.length === 0) {
2699
- 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();
2700
3314
  return;
2701
3315
  }
2702
3316
  console.log();
2703
- for (const { tool, configPath } of selected) {
3317
+ for (const { tool, configPath } of toConnect) {
2704
3318
  const serverConfig = tool.generateConfig(slug, apiKey);
2705
3319
  mergeConfig(configPath, tool.configKey, serverConfig);
2706
3320
  toolLine(tool.name, success("connected"), "\u2714");
2707
3321
  }
2708
- 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) {
2709
3331
  await Promise.all(
2710
- selected.map(({ tool }) => pingMcp(validation.serverUrl, apiKey, tool.name))
3332
+ toConnect.map(({ tool }) => pingMcp(validation.serverUrl, apiKey, tool.name))
2711
3333
  );
2712
3334
  }
3335
+ if (removedNames.length > 0) {
3336
+ deleteConnections(apiKey, removedNames).catch(() => {
3337
+ });
3338
+ }
2713
3339
  console.log();
2714
3340
  console.log(dim(" Restart your AI tools for changes to take effect."));
2715
3341
  console.log();
2716
3342
  }
2717
- async function handleDisconnectTools() {
2718
- const configured = [];
2719
- for (const tool of TOOLS) {
2720
- const paths = resolveConfigPaths(tool.configPaths);
2721
- for (const configPath of paths) {
2722
- if (fs12.existsSync(configPath) && isPiutConfigured(configPath, tool.configKey)) {
2723
- configured.push({ tool, configPath });
2724
- break;
2725
- }
2726
- }
2727
- }
2728
- if (configured.length === 0) {
2729
- 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."));
2730
3350
  console.log();
2731
3351
  return;
2732
3352
  }
2733
- const choices = configured.map((c) => ({
2734
- name: c.tool.name,
2735
- 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
2736
3368
  }));
2737
3369
  const selected = await checkbox6({
2738
- message: "Select tools to disconnect:",
3370
+ message: "Select projects to keep connected (toggle with space):",
2739
3371
  choices
2740
3372
  });
2741
- if (selected.length === 0) {
2742
- 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();
2743
3378
  return;
2744
3379
  }
2745
- const proceed = await confirm7({
2746
- message: `Disconnect p\u0131ut from ${selected.length} tool(s)?`,
2747
- default: false
2748
- });
2749
- if (!proceed) return;
2750
3380
  console.log();
2751
- for (const { tool, configPath } of selected) {
2752
- const removed = removeFromConfig(configPath, tool.configKey);
2753
- if (removed) {
2754
- toolLine(tool.name, success("disconnected"), "\u2714");
2755
- } else {
2756
- 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");
2757
3440
  }
3441
+ removePiutDir(project.path);
3442
+ toolLine(projectName, warning("disconnected"), "\u2714");
3443
+ const machineId = getMachineId();
3444
+ unregisterProject(apiKey, project.path, machineId).catch(() => {
3445
+ });
2758
3446
  }
2759
3447
  console.log();
2760
- 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`));
2761
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
+ }
2762
3552
  }
2763
3553
  async function handleViewBrain(apiKey) {
2764
3554
  console.log(dim(" Loading brain..."));
@@ -2802,11 +3592,11 @@ async function handleViewBrain(apiKey) {
2802
3592
  console.log();
2803
3593
  const msg = err.message;
2804
3594
  if (msg === "REQUIRES_SUBSCRIPTION") {
2805
- console.log(chalk10.yellow(" Deploy requires an active subscription ($10/mo)."));
3595
+ console.log(chalk11.yellow(" Deploy requires an active subscription ($10/mo)."));
2806
3596
  console.log(` Subscribe at: ${brand("https://piut.com/dashboard/billing")}`);
2807
3597
  console.log(dim(" 14-day free trial included."));
2808
3598
  } else {
2809
- console.log(chalk10.red(` \u2717 ${msg}`));
3599
+ console.log(chalk11.red(` \u2717 ${msg}`));
2810
3600
  }
2811
3601
  console.log();
2812
3602
  }
@@ -2815,7 +3605,7 @@ async function handleViewBrain(apiKey) {
2815
3605
  }
2816
3606
 
2817
3607
  // src/cli.ts
2818
- var VERSION = "3.5.1";
3608
+ var VERSION = "3.6.0";
2819
3609
  function withExit(fn) {
2820
3610
  return async (...args2) => {
2821
3611
  try {
@@ -2838,6 +3628,7 @@ program.command("disconnect").description("Remove brain references from project
2838
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));
2839
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));
2840
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));
2841
3632
  program.command("logout").description("Remove saved API key").action(logoutCommand);
2842
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));
2843
3634
  program.command("update").description("Check for and install CLI updates").action(() => updateCommand(VERSION));