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