@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.
- package/dist/cli.js +1236 -445
- 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
|
|
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(
|
|
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
|
|
926
|
-
import
|
|
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
|
|
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(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'").replace(/ /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
|
|
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 =
|
|
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(
|
|
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 =
|
|
1025
|
-
if (!description &&
|
|
1026
|
-
|
|
1027
|
-
|
|
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:
|
|
1498
|
+
name: path10.basename(projectPath),
|
|
1044
1499
|
path: projectPath,
|
|
1045
1500
|
description,
|
|
1046
|
-
hasClaudeMd:
|
|
1047
|
-
hasCursorRules:
|
|
1048
|
-
hasWindsurfRules:
|
|
1049
|
-
hasCopilotInstructions:
|
|
1050
|
-
hasConventionsMd:
|
|
1051
|
-
hasZedRules:
|
|
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 =
|
|
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 =
|
|
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(
|
|
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
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
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
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
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
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
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
|
|
1115
|
-
|
|
1116
|
-
const
|
|
1117
|
-
|
|
1118
|
-
|
|
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
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
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
|
-
|
|
1595
|
+
function walk(dir, depth) {
|
|
1596
|
+
if (depth > MAX_SCAN_DEPTH) return [];
|
|
1597
|
+
const found = [];
|
|
1130
1598
|
try {
|
|
1131
|
-
const items =
|
|
1599
|
+
const items = fs7.readdirSync(dir, { withFileTypes: true });
|
|
1132
1600
|
for (const item of items) {
|
|
1133
|
-
if (
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
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
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
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
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
const
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
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
|
-
|
|
1204
|
-
dirs.push(home);
|
|
1205
|
-
}
|
|
1206
|
-
return dirs;
|
|
1645
|
+
return files;
|
|
1207
1646
|
}
|
|
1208
|
-
function
|
|
1209
|
-
const
|
|
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
|
|
1212
|
-
|
|
1213
|
-
|
|
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
|
|
1216
|
-
|
|
1217
|
-
|
|
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(
|
|
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
|
|
1238
|
-
import
|
|
1239
|
-
import
|
|
1240
|
-
var CONFIG_DIR =
|
|
1241
|
-
var CONFIG_FILE2 =
|
|
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 =
|
|
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
|
-
|
|
1254
|
-
|
|
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
|
-
|
|
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 =
|
|
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 (!
|
|
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 =
|
|
1317
|
-
if (
|
|
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 (!
|
|
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
|
|
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 (
|
|
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
|
|
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 (
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
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
|
-
|
|
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
|
|
2034
|
+
let scanDirs;
|
|
1494
2035
|
if (options.folders) {
|
|
1495
|
-
|
|
1496
|
-
}
|
|
1497
|
-
|
|
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
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
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
|
-
|
|
1544
|
-
|
|
1545
|
-
let
|
|
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 === "
|
|
1548
|
-
|
|
1549
|
-
|
|
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
|
-
|
|
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
|
|
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(`
|
|
2067
|
+
console.log(success(` \u2713 Scan complete: ${scanResult.totalFiles} files found across ${scanResult.folders.length} folders (local only)`));
|
|
1564
2068
|
console.log();
|
|
1565
|
-
if (
|
|
1566
|
-
console.log(chalk5.yellow(" No
|
|
1567
|
-
console.log(dim(" Try
|
|
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
|
|
1715
|
-
import
|
|
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 ||
|
|
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 ||
|
|
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 ||
|
|
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 ||
|
|
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 ||
|
|
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 =
|
|
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
|
|
2420
|
+
let scanFolders2;
|
|
1811
2421
|
if (options.folders) {
|
|
1812
|
-
|
|
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(
|
|
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 =
|
|
1828
|
-
if (
|
|
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" || !
|
|
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 =
|
|
1840
|
-
if (!
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
1928
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
|
1959
|
-
import
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
|
2614
|
+
let scanFolders2;
|
|
2005
2615
|
if (options.folders) {
|
|
2006
|
-
|
|
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(
|
|
2619
|
+
const projects = scanForProjects(scanFolders2);
|
|
2010
2620
|
const actions = [];
|
|
2011
2621
|
for (const project of projects) {
|
|
2012
|
-
const projectName =
|
|
2622
|
+
const projectName = path14.basename(project.path);
|
|
2013
2623
|
for (const dedicatedFile of DEDICATED_FILES) {
|
|
2014
|
-
const absPath =
|
|
2015
|
-
if (
|
|
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 =
|
|
2027
|
-
if (
|
|
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:
|
|
2652
|
+
absPath: path14.join(project.path, ".piut"),
|
|
2043
2653
|
action: "remove-dir"
|
|
2044
2654
|
});
|
|
2045
2655
|
}
|
|
2046
|
-
const vscodeMcpPath =
|
|
2047
|
-
if (
|
|
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 =
|
|
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 =
|
|
2708
|
+
const projectName = path14.basename(projectPath);
|
|
2099
2709
|
for (const action of projectActions) {
|
|
2100
2710
|
if (action.action === "delete") {
|
|
2101
2711
|
try {
|
|
2102
|
-
|
|
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
|
|
2787
|
+
import chalk9 from "chalk";
|
|
2163
2788
|
|
|
2164
2789
|
// src/lib/update-check.ts
|
|
2165
2790
|
import { execFile } from "child_process";
|
|
2166
|
-
import
|
|
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 ${
|
|
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(
|
|
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(
|
|
2220
|
-
console.log(
|
|
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(
|
|
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(
|
|
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(
|
|
2258
|
-
console.log(
|
|
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
|
|
2264
|
-
import
|
|
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 (!
|
|
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: ") +
|
|
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: ") +
|
|
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
|
|
2432
|
-
import
|
|
2433
|
-
import
|
|
2434
|
-
import
|
|
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
|
-
|
|
3064
|
+
const apiKey = config.apiKey;
|
|
2438
3065
|
if (apiKey) {
|
|
2439
3066
|
try {
|
|
2440
|
-
const
|
|
2441
|
-
console.log(success(` Connected as ${
|
|
2442
|
-
return { apiKey, validation:
|
|
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
|
-
|
|
2449
|
-
|
|
2450
|
-
console.log(
|
|
2451
|
-
|
|
2452
|
-
apiKey
|
|
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
|
|
3133
|
+
action = await select3({
|
|
2519
3134
|
message: "What would you like to do?",
|
|
3135
|
+
loop: false,
|
|
2520
3136
|
choices: [
|
|
2521
3137
|
{
|
|
2522
|
-
name: hasBrain ? "
|
|
3138
|
+
name: hasBrain ? "Resync Brain" : "Build Brain",
|
|
2523
3139
|
value: "build",
|
|
2524
|
-
description: hasBrain ? "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
2551
|
-
value: "
|
|
2552
|
-
description: "
|
|
2553
|
-
disabled: !
|
|
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
|
-
|
|
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
|
|
3215
|
+
await handleManageProjects(apiKey, currentValidation);
|
|
2602
3216
|
break;
|
|
2603
|
-
case "
|
|
2604
|
-
|
|
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(
|
|
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(
|
|
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
|
|
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 =
|
|
2664
|
-
const parentExists =
|
|
3276
|
+
const exists = fs14.existsSync(configPath);
|
|
3277
|
+
const parentExists = fs14.existsSync(path15.dirname(configPath));
|
|
2665
3278
|
if (exists || parentExists) {
|
|
2666
|
-
|
|
2667
|
-
|
|
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 (
|
|
2676
|
-
|
|
2677
|
-
|
|
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
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
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
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
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
|
|
3306
|
+
message: "Select tools to keep connected (toggle with space):",
|
|
2696
3307
|
choices
|
|
2697
3308
|
});
|
|
2698
|
-
|
|
2699
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
2718
|
-
const
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
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
|
|
2734
|
-
|
|
2735
|
-
|
|
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
|
|
3370
|
+
message: "Select projects to keep connected (toggle with space):",
|
|
2739
3371
|
choices
|
|
2740
3372
|
});
|
|
2741
|
-
|
|
2742
|
-
|
|
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
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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.
|
|
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));
|