@piut/cli 3.5.1 → 3.7.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 +1610 -482
- 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" }));
|
|
@@ -110,7 +122,10 @@ async function pingMcp(serverUrl, key, toolName) {
|
|
|
110
122
|
headers: {
|
|
111
123
|
Authorization: `Bearer ${key}`,
|
|
112
124
|
"Content-Type": "application/json",
|
|
113
|
-
"User-Agent": `piut-cli (configured: ${toolName})
|
|
125
|
+
"User-Agent": `piut-cli (configured: ${toolName})`,
|
|
126
|
+
"X-Piut-Hostname": os.hostname(),
|
|
127
|
+
"X-Piut-Machine-Id": getMachineId(),
|
|
128
|
+
"X-Piut-Tool": toolName
|
|
114
129
|
},
|
|
115
130
|
body: JSON.stringify({
|
|
116
131
|
jsonrpc: "2.0",
|
|
@@ -155,6 +170,9 @@ function getMachineId() {
|
|
|
155
170
|
const hostname = os.hostname();
|
|
156
171
|
return crypto.createHash("sha256").update(hostname).digest("hex").slice(0, 16);
|
|
157
172
|
}
|
|
173
|
+
function getHostname() {
|
|
174
|
+
return os.hostname();
|
|
175
|
+
}
|
|
158
176
|
async function registerProject(key, project) {
|
|
159
177
|
const res = await fetch(`${API_BASE}/api/cli/projects`, {
|
|
160
178
|
method: "POST",
|
|
@@ -184,6 +202,19 @@ async function unregisterProject(key, projectPath, machineId) {
|
|
|
184
202
|
throw new Error(body.error || `Unregister project failed (HTTP ${res.status})`);
|
|
185
203
|
}
|
|
186
204
|
}
|
|
205
|
+
async function deleteConnections(key, toolNames) {
|
|
206
|
+
try {
|
|
207
|
+
await fetch(`${API_BASE}/api/mcp/connections`, {
|
|
208
|
+
method: "DELETE",
|
|
209
|
+
headers: {
|
|
210
|
+
Authorization: `Bearer ${key}`,
|
|
211
|
+
"Content-Type": "application/json"
|
|
212
|
+
},
|
|
213
|
+
body: JSON.stringify({ toolNames })
|
|
214
|
+
});
|
|
215
|
+
} catch {
|
|
216
|
+
}
|
|
217
|
+
}
|
|
187
218
|
async function unpublishServer(key) {
|
|
188
219
|
const res = await fetch(`${API_BASE}/api/mcp/publish`, {
|
|
189
220
|
method: "POST",
|
|
@@ -199,12 +230,73 @@ async function unpublishServer(key) {
|
|
|
199
230
|
}
|
|
200
231
|
return res.json();
|
|
201
232
|
}
|
|
233
|
+
async function listVaultFiles(key) {
|
|
234
|
+
const res = await fetch(`${API_BASE}/api/cli/vault`, {
|
|
235
|
+
headers: { Authorization: `Bearer ${key}` }
|
|
236
|
+
});
|
|
237
|
+
if (!res.ok) {
|
|
238
|
+
const body = await res.json().catch(() => ({ error: "Unknown error" }));
|
|
239
|
+
throw new Error(body.error || `Failed to list vault files (HTTP ${res.status})`);
|
|
240
|
+
}
|
|
241
|
+
return res.json();
|
|
242
|
+
}
|
|
243
|
+
async function uploadVaultFile(key, filename, content) {
|
|
244
|
+
const res = await fetch(`${API_BASE}/api/cli/vault/upload`, {
|
|
245
|
+
method: "POST",
|
|
246
|
+
headers: {
|
|
247
|
+
Authorization: `Bearer ${key}`,
|
|
248
|
+
"Content-Type": "application/json"
|
|
249
|
+
},
|
|
250
|
+
body: JSON.stringify({ filename, content })
|
|
251
|
+
});
|
|
252
|
+
if (!res.ok) {
|
|
253
|
+
const body = await res.json().catch(() => ({ error: "Unknown error" }));
|
|
254
|
+
throw new Error(body.error || `Upload failed (HTTP ${res.status})`);
|
|
255
|
+
}
|
|
256
|
+
return res.json();
|
|
257
|
+
}
|
|
258
|
+
async function readVaultFile(key, filename) {
|
|
259
|
+
const encoded = encodeURIComponent(filename);
|
|
260
|
+
const res = await fetch(`${API_BASE}/api/cli/vault/${encoded}`, {
|
|
261
|
+
headers: { Authorization: `Bearer ${key}` }
|
|
262
|
+
});
|
|
263
|
+
if (!res.ok) {
|
|
264
|
+
const body = await res.json().catch(() => ({ error: "Unknown error" }));
|
|
265
|
+
throw new Error(body.error || `Failed to read vault file (HTTP ${res.status})`);
|
|
266
|
+
}
|
|
267
|
+
return res.json();
|
|
268
|
+
}
|
|
269
|
+
async function deleteVaultFile(key, filename) {
|
|
270
|
+
const res = await fetch(`${API_BASE}/api/cli/vault`, {
|
|
271
|
+
method: "DELETE",
|
|
272
|
+
headers: {
|
|
273
|
+
Authorization: `Bearer ${key}`,
|
|
274
|
+
"Content-Type": "application/json"
|
|
275
|
+
},
|
|
276
|
+
body: JSON.stringify({ filename })
|
|
277
|
+
});
|
|
278
|
+
if (!res.ok) {
|
|
279
|
+
const body = await res.json().catch(() => ({ error: "Unknown error" }));
|
|
280
|
+
throw new Error(body.error || `Delete failed (HTTP ${res.status})`);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
202
283
|
|
|
203
284
|
// src/lib/tools.ts
|
|
204
285
|
import os2 from "os";
|
|
205
286
|
import path from "path";
|
|
287
|
+
import crypto2 from "crypto";
|
|
206
288
|
var MCP_URL = (slug) => `https://piut.com/api/mcp/${slug}`;
|
|
207
289
|
var AUTH_HEADER = (key) => ({ Authorization: `Bearer ${key}` });
|
|
290
|
+
function getMachineId2() {
|
|
291
|
+
return crypto2.createHash("sha256").update(os2.hostname()).digest("hex").slice(0, 16);
|
|
292
|
+
}
|
|
293
|
+
function machineHeaders(toolName) {
|
|
294
|
+
return {
|
|
295
|
+
"X-Piut-Hostname": os2.hostname(),
|
|
296
|
+
"X-Piut-Machine-Id": getMachineId2(),
|
|
297
|
+
"X-Piut-Tool": toolName
|
|
298
|
+
};
|
|
299
|
+
}
|
|
208
300
|
function appData() {
|
|
209
301
|
return process.env.APPDATA || path.join(os2.homedir(), "AppData", "Roaming");
|
|
210
302
|
}
|
|
@@ -216,18 +308,19 @@ var TOOLS = [
|
|
|
216
308
|
configPaths: {
|
|
217
309
|
darwin: ["~/.claude.json"],
|
|
218
310
|
win32: ["~/.claude.json"],
|
|
219
|
-
linux: ["~/.claude.json"]
|
|
311
|
+
linux: ["~/.claude.json"],
|
|
312
|
+
project: [".mcp.json"]
|
|
220
313
|
},
|
|
221
314
|
skillFilePath: "CLAUDE.md",
|
|
222
315
|
quickCommand: (slug, key) => `claude mcp add-json piut-context '${JSON.stringify({
|
|
223
316
|
type: "http",
|
|
224
317
|
url: MCP_URL(slug),
|
|
225
|
-
headers: AUTH_HEADER(key)
|
|
318
|
+
headers: { ...AUTH_HEADER(key), ...machineHeaders("Claude Code") }
|
|
226
319
|
})}'`,
|
|
227
320
|
generateConfig: (slug, key) => ({
|
|
228
321
|
type: "http",
|
|
229
322
|
url: MCP_URL(slug),
|
|
230
|
-
headers: AUTH_HEADER(key)
|
|
323
|
+
headers: { ...AUTH_HEADER(key), ...machineHeaders("Claude Code") }
|
|
231
324
|
})
|
|
232
325
|
},
|
|
233
326
|
{
|
|
@@ -263,7 +356,7 @@ var TOOLS = [
|
|
|
263
356
|
skillFilePath: ".cursor/rules/piut.mdc",
|
|
264
357
|
generateConfig: (slug, key) => ({
|
|
265
358
|
url: MCP_URL(slug),
|
|
266
|
-
headers: AUTH_HEADER(key)
|
|
359
|
+
headers: { ...AUTH_HEADER(key), ...machineHeaders("Cursor") }
|
|
267
360
|
})
|
|
268
361
|
},
|
|
269
362
|
{
|
|
@@ -278,7 +371,7 @@ var TOOLS = [
|
|
|
278
371
|
skillFilePath: ".windsurf/rules/piut.md",
|
|
279
372
|
generateConfig: (slug, key) => ({
|
|
280
373
|
serverUrl: MCP_URL(slug),
|
|
281
|
-
headers: AUTH_HEADER(key)
|
|
374
|
+
headers: { ...AUTH_HEADER(key), ...machineHeaders("Windsurf") }
|
|
282
375
|
})
|
|
283
376
|
},
|
|
284
377
|
{
|
|
@@ -292,7 +385,7 @@ var TOOLS = [
|
|
|
292
385
|
generateConfig: (slug, key) => ({
|
|
293
386
|
type: "http",
|
|
294
387
|
url: MCP_URL(slug),
|
|
295
|
-
headers: AUTH_HEADER(key)
|
|
388
|
+
headers: { ...AUTH_HEADER(key), ...machineHeaders("GitHub Copilot") }
|
|
296
389
|
})
|
|
297
390
|
},
|
|
298
391
|
{
|
|
@@ -308,7 +401,7 @@ var TOOLS = [
|
|
|
308
401
|
generateConfig: (slug, key) => ({
|
|
309
402
|
type: "http",
|
|
310
403
|
url: MCP_URL(slug),
|
|
311
|
-
headers: AUTH_HEADER(key)
|
|
404
|
+
headers: { ...AUTH_HEADER(key), ...machineHeaders("Amazon Q") }
|
|
312
405
|
})
|
|
313
406
|
},
|
|
314
407
|
{
|
|
@@ -323,9 +416,49 @@ var TOOLS = [
|
|
|
323
416
|
generateConfig: (slug, key) => ({
|
|
324
417
|
settings: {
|
|
325
418
|
url: MCP_URL(slug),
|
|
326
|
-
headers: AUTH_HEADER(key)
|
|
419
|
+
headers: { ...AUTH_HEADER(key), ...machineHeaders("Zed") }
|
|
327
420
|
}
|
|
328
421
|
})
|
|
422
|
+
},
|
|
423
|
+
{
|
|
424
|
+
id: "gemini-cli",
|
|
425
|
+
name: "Gemini CLI",
|
|
426
|
+
configKey: "mcpServers",
|
|
427
|
+
configPaths: {
|
|
428
|
+
darwin: ["~/.gemini/settings.json"],
|
|
429
|
+
win32: ["~/.gemini/settings.json"],
|
|
430
|
+
linux: ["~/.gemini/settings.json"],
|
|
431
|
+
project: [".gemini/settings.json"]
|
|
432
|
+
},
|
|
433
|
+
generateConfig: (slug, key) => ({
|
|
434
|
+
httpUrl: MCP_URL(slug),
|
|
435
|
+
headers: { ...AUTH_HEADER(key), ...machineHeaders("Gemini CLI") }
|
|
436
|
+
})
|
|
437
|
+
},
|
|
438
|
+
{
|
|
439
|
+
id: "openclaw",
|
|
440
|
+
name: "OpenClaw",
|
|
441
|
+
configKey: "mcpServers",
|
|
442
|
+
configPaths: {
|
|
443
|
+
darwin: ["~/.mcporter/mcporter.json", "~/.openclaw/workspace/config/mcporter.json"],
|
|
444
|
+
win32: ["~/.mcporter/mcporter.json", "~/.openclaw/workspace/config/mcporter.json"],
|
|
445
|
+
linux: ["~/.mcporter/mcporter.json", "~/.openclaw/workspace/config/mcporter.json"]
|
|
446
|
+
},
|
|
447
|
+
quickCommand: (slug, key) => `npx mcporter config add piut-context ${MCP_URL(slug)} --header "Authorization=Bearer ${key}"`,
|
|
448
|
+
generateConfig: (slug, key) => ({
|
|
449
|
+
url: MCP_URL(slug),
|
|
450
|
+
headers: { ...AUTH_HEADER(key), ...machineHeaders("OpenClaw") }
|
|
451
|
+
})
|
|
452
|
+
},
|
|
453
|
+
{
|
|
454
|
+
id: "paperclip",
|
|
455
|
+
name: "Paperclip",
|
|
456
|
+
skillOnly: true,
|
|
457
|
+
configPaths: {
|
|
458
|
+
darwin: ["~/.paperclip/config.json"],
|
|
459
|
+
win32: ["~/.paperclip/config.json"],
|
|
460
|
+
linux: ["~/.paperclip/config.json"]
|
|
461
|
+
}
|
|
329
462
|
}
|
|
330
463
|
];
|
|
331
464
|
|
|
@@ -418,6 +551,28 @@ function extractKeyFromConfig(piutConfig) {
|
|
|
418
551
|
}
|
|
419
552
|
return null;
|
|
420
553
|
}
|
|
554
|
+
function extractSlugFromConfig(piutConfig) {
|
|
555
|
+
const slugFromUrl = (u) => {
|
|
556
|
+
if (typeof u !== "string") return null;
|
|
557
|
+
const m = u.match(/\/api\/mcp\/([^/?#]+)/);
|
|
558
|
+
return m ? m[1] : null;
|
|
559
|
+
};
|
|
560
|
+
const fromUrl = slugFromUrl(piutConfig.url) || slugFromUrl(piutConfig.serverUrl) || slugFromUrl(piutConfig.httpUrl);
|
|
561
|
+
if (fromUrl) return fromUrl;
|
|
562
|
+
const settings = piutConfig.settings;
|
|
563
|
+
if (settings) {
|
|
564
|
+
const fromSettings = slugFromUrl(settings.url);
|
|
565
|
+
if (fromSettings) return fromSettings;
|
|
566
|
+
}
|
|
567
|
+
const args2 = piutConfig.args;
|
|
568
|
+
if (Array.isArray(args2)) {
|
|
569
|
+
for (const arg of args2) {
|
|
570
|
+
const fromArg = slugFromUrl(arg);
|
|
571
|
+
if (fromArg) return fromArg;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
return null;
|
|
575
|
+
}
|
|
421
576
|
function removeFromConfig(filePath, configKey) {
|
|
422
577
|
const config = readConfig(filePath);
|
|
423
578
|
if (!config) return false;
|
|
@@ -728,20 +883,25 @@ async function setupCommand(options) {
|
|
|
728
883
|
const toolFilter = options.tool;
|
|
729
884
|
for (const tool of TOOLS) {
|
|
730
885
|
if (toolFilter && tool.id !== toolFilter) continue;
|
|
886
|
+
if (tool.skillOnly) continue;
|
|
731
887
|
const paths = resolveConfigPaths(tool.configPaths);
|
|
732
888
|
for (const configPath of paths) {
|
|
733
889
|
const exists = fs4.existsSync(configPath);
|
|
734
890
|
const parentExists = fs4.existsSync(path6.dirname(configPath));
|
|
735
891
|
if (exists || parentExists) {
|
|
736
|
-
const configured2 = exists && isPiutConfigured(configPath, tool.configKey);
|
|
892
|
+
const configured2 = exists && !!tool.configKey && isPiutConfigured(configPath, tool.configKey);
|
|
737
893
|
let staleKey = false;
|
|
738
|
-
if (configured2) {
|
|
894
|
+
if (configured2 && tool.configKey) {
|
|
739
895
|
const piutConfig = getPiutConfig(configPath, tool.configKey);
|
|
740
896
|
if (piutConfig) {
|
|
741
897
|
const existingKey = extractKeyFromConfig(piutConfig);
|
|
742
898
|
if (existingKey && existingKey !== apiKey) {
|
|
743
899
|
staleKey = true;
|
|
744
900
|
}
|
|
901
|
+
const existingSlug = extractSlugFromConfig(piutConfig);
|
|
902
|
+
if (existingSlug && existingSlug !== slug) {
|
|
903
|
+
staleKey = true;
|
|
904
|
+
}
|
|
745
905
|
}
|
|
746
906
|
}
|
|
747
907
|
detected.push({
|
|
@@ -757,7 +917,6 @@ async function setupCommand(options) {
|
|
|
757
917
|
}
|
|
758
918
|
if (detected.length === 0) {
|
|
759
919
|
console.log(warning(" No supported AI tools detected."));
|
|
760
|
-
console.log(dim(" Supported: Claude Code, Claude Desktop, Cursor, Windsurf, GitHub Copilot, Amazon Q, Zed"));
|
|
761
920
|
console.log(dim(" See https://piut.com/docs for manual setup."));
|
|
762
921
|
console.log();
|
|
763
922
|
return;
|
|
@@ -809,7 +968,7 @@ async function setupCommand(options) {
|
|
|
809
968
|
try {
|
|
810
969
|
execSync(tool.quickCommand(slug, apiKey), { stdio: "pipe" });
|
|
811
970
|
const claudeJson = expandPath("~/.claude.json");
|
|
812
|
-
const written = getPiutConfig(claudeJson, tool.configKey);
|
|
971
|
+
const written = tool.configKey ? getPiutConfig(claudeJson, tool.configKey) : null;
|
|
813
972
|
if (written) {
|
|
814
973
|
quickSuccess = true;
|
|
815
974
|
configured.push(tool.name);
|
|
@@ -818,7 +977,7 @@ async function setupCommand(options) {
|
|
|
818
977
|
}
|
|
819
978
|
try {
|
|
820
979
|
execSync(tool.quickCommand(slug, apiKey) + " --scope user", { stdio: "pipe" });
|
|
821
|
-
const retryCheck = getPiutConfig(claudeJson, tool.configKey);
|
|
980
|
+
const retryCheck = tool.configKey ? getPiutConfig(claudeJson, tool.configKey) : null;
|
|
822
981
|
if (retryCheck) {
|
|
823
982
|
quickSuccess = true;
|
|
824
983
|
configured.push(tool.name);
|
|
@@ -837,10 +996,12 @@ async function setupCommand(options) {
|
|
|
837
996
|
}
|
|
838
997
|
if (quickSuccess) continue;
|
|
839
998
|
}
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
999
|
+
if (tool.generateConfig && tool.configKey) {
|
|
1000
|
+
const serverConfig = tool.generateConfig(slug, apiKey);
|
|
1001
|
+
mergeConfig(configPath, tool.configKey, serverConfig);
|
|
1002
|
+
configured.push(tool.name);
|
|
1003
|
+
toolLine(tool.name, success("configured"), "\u2714");
|
|
1004
|
+
}
|
|
844
1005
|
}
|
|
845
1006
|
if (!options.skipSkill && configured.length > 0) {
|
|
846
1007
|
const addSkill = options.yes ? true : await confirm({
|
|
@@ -881,6 +1042,7 @@ async function setupCommand(options) {
|
|
|
881
1042
|
console.log(dim(" Verifying..."));
|
|
882
1043
|
for (const det of selected) {
|
|
883
1044
|
if (!configured.includes(det.tool.name)) continue;
|
|
1045
|
+
if (!det.tool.configKey) continue;
|
|
884
1046
|
const piutConfig = getPiutConfig(det.configPath, det.tool.configKey);
|
|
885
1047
|
if (piutConfig) {
|
|
886
1048
|
toolLine(det.tool.name, success("config verified"), "\u2714");
|
|
@@ -922,15 +1084,463 @@ function isCommandAvailable(cmd) {
|
|
|
922
1084
|
}
|
|
923
1085
|
|
|
924
1086
|
// src/commands/status.ts
|
|
925
|
-
import
|
|
926
|
-
import
|
|
1087
|
+
import fs9 from "fs";
|
|
1088
|
+
import os7 from "os";
|
|
1089
|
+
import path12 from "path";
|
|
927
1090
|
import chalk3 from "chalk";
|
|
928
1091
|
|
|
929
1092
|
// src/lib/brain-scanner.ts
|
|
930
|
-
import
|
|
1093
|
+
import fs7 from "fs";
|
|
1094
|
+
import path10 from "path";
|
|
1095
|
+
import os5 from "os";
|
|
1096
|
+
|
|
1097
|
+
// src/lib/file-types.ts
|
|
931
1098
|
import path7 from "path";
|
|
1099
|
+
var DOCUMENT_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
1100
|
+
".pdf",
|
|
1101
|
+
".docx",
|
|
1102
|
+
".doc",
|
|
1103
|
+
".pptx",
|
|
1104
|
+
".pages",
|
|
1105
|
+
".key",
|
|
1106
|
+
".rtf",
|
|
1107
|
+
".odt",
|
|
1108
|
+
".odp",
|
|
1109
|
+
".eml",
|
|
1110
|
+
".mbox"
|
|
1111
|
+
]);
|
|
1112
|
+
var PLAIN_TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
1113
|
+
".md",
|
|
1114
|
+
".markdown",
|
|
1115
|
+
".txt",
|
|
1116
|
+
".csv",
|
|
1117
|
+
".xml",
|
|
1118
|
+
".html",
|
|
1119
|
+
".htm",
|
|
1120
|
+
".json",
|
|
1121
|
+
".yaml",
|
|
1122
|
+
".yml",
|
|
1123
|
+
".toml",
|
|
1124
|
+
".rst",
|
|
1125
|
+
".adoc",
|
|
1126
|
+
".tex",
|
|
1127
|
+
".ini",
|
|
1128
|
+
".cfg",
|
|
1129
|
+
".conf",
|
|
1130
|
+
".log"
|
|
1131
|
+
]);
|
|
1132
|
+
var AI_CONFIG_FILENAMES = /* @__PURE__ */ new Set([
|
|
1133
|
+
"CLAUDE.md",
|
|
1134
|
+
".cursorrules",
|
|
1135
|
+
".windsurfrules",
|
|
1136
|
+
".rules",
|
|
1137
|
+
".clinerules",
|
|
1138
|
+
"AGENTS.md",
|
|
1139
|
+
"CONVENTIONS.md",
|
|
1140
|
+
"MEMORY.md",
|
|
1141
|
+
"SOUL.md",
|
|
1142
|
+
"IDENTITY.md",
|
|
1143
|
+
"copilot-instructions.md"
|
|
1144
|
+
]);
|
|
1145
|
+
var PARSEABLE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
1146
|
+
...DOCUMENT_EXTENSIONS,
|
|
1147
|
+
...PLAIN_TEXT_EXTENSIONS
|
|
1148
|
+
]);
|
|
1149
|
+
var EXCLUDED_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
1150
|
+
".js",
|
|
1151
|
+
".ts",
|
|
1152
|
+
".jsx",
|
|
1153
|
+
".tsx",
|
|
1154
|
+
".mjs",
|
|
1155
|
+
".cjs",
|
|
1156
|
+
".py",
|
|
1157
|
+
".pyw",
|
|
1158
|
+
".pyi",
|
|
1159
|
+
".rs",
|
|
1160
|
+
".go",
|
|
1161
|
+
".java",
|
|
1162
|
+
".kt",
|
|
1163
|
+
".scala",
|
|
1164
|
+
".clj",
|
|
1165
|
+
".c",
|
|
1166
|
+
".cpp",
|
|
1167
|
+
".cc",
|
|
1168
|
+
".h",
|
|
1169
|
+
".hpp",
|
|
1170
|
+
".cs",
|
|
1171
|
+
".rb",
|
|
1172
|
+
".php",
|
|
1173
|
+
".swift",
|
|
1174
|
+
".m",
|
|
1175
|
+
".mm",
|
|
1176
|
+
".lua",
|
|
1177
|
+
".r",
|
|
1178
|
+
".jl",
|
|
1179
|
+
".zig",
|
|
1180
|
+
".nim",
|
|
1181
|
+
".v",
|
|
1182
|
+
".sh",
|
|
1183
|
+
".bash",
|
|
1184
|
+
".zsh",
|
|
1185
|
+
".fish",
|
|
1186
|
+
".ps1",
|
|
1187
|
+
".bat",
|
|
1188
|
+
".cmd",
|
|
1189
|
+
".sql",
|
|
1190
|
+
".graphql",
|
|
1191
|
+
".gql",
|
|
1192
|
+
".proto",
|
|
1193
|
+
".css",
|
|
1194
|
+
".scss",
|
|
1195
|
+
".sass",
|
|
1196
|
+
".less",
|
|
1197
|
+
".styl",
|
|
1198
|
+
".xls",
|
|
1199
|
+
".xlsx",
|
|
1200
|
+
".numbers",
|
|
1201
|
+
".sqlite",
|
|
1202
|
+
".db",
|
|
1203
|
+
".dat",
|
|
1204
|
+
".parquet",
|
|
1205
|
+
".avro",
|
|
1206
|
+
".png",
|
|
1207
|
+
".jpg",
|
|
1208
|
+
".jpeg",
|
|
1209
|
+
".gif",
|
|
1210
|
+
".svg",
|
|
1211
|
+
".ico",
|
|
1212
|
+
".webp",
|
|
1213
|
+
".bmp",
|
|
1214
|
+
".tiff",
|
|
1215
|
+
".heic",
|
|
1216
|
+
".mp3",
|
|
1217
|
+
".mp4",
|
|
1218
|
+
".wav",
|
|
1219
|
+
".aac",
|
|
1220
|
+
".flac",
|
|
1221
|
+
".ogg",
|
|
1222
|
+
".avi",
|
|
1223
|
+
".mov",
|
|
1224
|
+
".mkv",
|
|
1225
|
+
".wmv",
|
|
1226
|
+
".webm",
|
|
1227
|
+
".zip",
|
|
1228
|
+
".tar",
|
|
1229
|
+
".gz",
|
|
1230
|
+
".bz2",
|
|
1231
|
+
".xz",
|
|
1232
|
+
".7z",
|
|
1233
|
+
".rar",
|
|
1234
|
+
".dmg",
|
|
1235
|
+
".iso",
|
|
1236
|
+
".exe",
|
|
1237
|
+
".dll",
|
|
1238
|
+
".so",
|
|
1239
|
+
".dylib",
|
|
1240
|
+
".o",
|
|
1241
|
+
".a",
|
|
1242
|
+
".wasm",
|
|
1243
|
+
".class",
|
|
1244
|
+
".jar",
|
|
1245
|
+
".pyc",
|
|
1246
|
+
".pyo",
|
|
1247
|
+
".ttf",
|
|
1248
|
+
".otf",
|
|
1249
|
+
".woff",
|
|
1250
|
+
".woff2",
|
|
1251
|
+
".eot",
|
|
1252
|
+
".lock",
|
|
1253
|
+
".map",
|
|
1254
|
+
".min.js",
|
|
1255
|
+
".min.css"
|
|
1256
|
+
]);
|
|
1257
|
+
function canParse(filename) {
|
|
1258
|
+
return PARSEABLE_EXTENSIONS.has(path7.extname(filename).toLowerCase());
|
|
1259
|
+
}
|
|
1260
|
+
function isAiConfigFile(filename) {
|
|
1261
|
+
return AI_CONFIG_FILENAMES.has(path7.basename(filename));
|
|
1262
|
+
}
|
|
1263
|
+
function getFileCategory(filename) {
|
|
1264
|
+
const base = path7.basename(filename);
|
|
1265
|
+
if (AI_CONFIG_FILENAMES.has(base)) return "config";
|
|
1266
|
+
const ext = path7.extname(filename).toLowerCase();
|
|
1267
|
+
if (DOCUMENT_EXTENSIONS.has(ext)) return "document";
|
|
1268
|
+
if (PLAIN_TEXT_EXTENSIONS.has(ext)) return "text";
|
|
1269
|
+
if (EXCLUDED_EXTENSIONS.has(ext)) return "excluded";
|
|
1270
|
+
return "unknown";
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
// src/lib/file-parsers.ts
|
|
1274
|
+
import fs5 from "fs";
|
|
1275
|
+
import path8 from "path";
|
|
1276
|
+
var MAX_RAW_SIZE = 500 * 1024;
|
|
1277
|
+
var MAX_EXTRACTED_TEXT = 100 * 1024;
|
|
1278
|
+
function truncate(text) {
|
|
1279
|
+
if (text.length <= MAX_EXTRACTED_TEXT) return text;
|
|
1280
|
+
return text.slice(0, MAX_EXTRACTED_TEXT);
|
|
1281
|
+
}
|
|
1282
|
+
function stripHtml(html) {
|
|
1283
|
+
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();
|
|
1284
|
+
}
|
|
1285
|
+
async function parsePdf(buffer) {
|
|
1286
|
+
const { PDFParse } = await import("pdf-parse");
|
|
1287
|
+
const parser = new PDFParse({ data: new Uint8Array(buffer) });
|
|
1288
|
+
const result = await parser.getText();
|
|
1289
|
+
return result.text;
|
|
1290
|
+
}
|
|
1291
|
+
async function parseDocx(buffer) {
|
|
1292
|
+
const { extractRawText } = await import("mammoth");
|
|
1293
|
+
const result = await extractRawText({ buffer });
|
|
1294
|
+
return result.value;
|
|
1295
|
+
}
|
|
1296
|
+
async function parsePptx(buffer) {
|
|
1297
|
+
const JSZip = (await import("jszip")).default;
|
|
1298
|
+
const zip = await JSZip.loadAsync(buffer);
|
|
1299
|
+
const texts = [];
|
|
1300
|
+
const slideFiles = Object.keys(zip.files).filter((name) => /^ppt\/slides\/slide\d+\.xml$/i.test(name)).sort((a, b) => {
|
|
1301
|
+
const numA = parseInt(a.match(/slide(\d+)/)?.[1] || "0");
|
|
1302
|
+
const numB = parseInt(b.match(/slide(\d+)/)?.[1] || "0");
|
|
1303
|
+
return numA - numB;
|
|
1304
|
+
});
|
|
1305
|
+
for (const slidePath of slideFiles) {
|
|
1306
|
+
const xml = await zip.files[slidePath].async("string");
|
|
1307
|
+
const matches = xml.match(/<a:t[^>]*>([^<]*)<\/a:t>/g);
|
|
1308
|
+
if (matches) {
|
|
1309
|
+
texts.push(matches.map((m) => m.replace(/<[^>]+>/g, "")).join(" "));
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
return texts.join("\n\n");
|
|
1313
|
+
}
|
|
1314
|
+
async function parseAppleDoc(buffer) {
|
|
1315
|
+
const JSZip = (await import("jszip")).default;
|
|
1316
|
+
const zip = await JSZip.loadAsync(buffer);
|
|
1317
|
+
if (zip.files["preview.pdf"]) {
|
|
1318
|
+
const pdfBuf = await zip.files["preview.pdf"].async("nodebuffer");
|
|
1319
|
+
try {
|
|
1320
|
+
return await parsePdf(pdfBuf);
|
|
1321
|
+
} catch {
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
const texts = [];
|
|
1325
|
+
for (const [name, file] of Object.entries(zip.files)) {
|
|
1326
|
+
if (name.endsWith(".iwa") && !file.dir) {
|
|
1327
|
+
try {
|
|
1328
|
+
const buf = await file.async("nodebuffer");
|
|
1329
|
+
const str = buf.toString("utf-8");
|
|
1330
|
+
const readable = str.match(/[\x20-\x7E\xC0-\xFF]{4,}/g);
|
|
1331
|
+
if (readable) {
|
|
1332
|
+
texts.push(...readable.filter((s) => s.length > 10));
|
|
1333
|
+
}
|
|
1334
|
+
} catch {
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
return texts.join("\n\n");
|
|
1339
|
+
}
|
|
1340
|
+
async function parseRtf(buffer) {
|
|
1341
|
+
const { fromString } = await import("@iarna/rtf-to-html");
|
|
1342
|
+
return new Promise((resolve, reject) => {
|
|
1343
|
+
fromString(buffer.toString("utf-8"), (err, html) => {
|
|
1344
|
+
if (err) {
|
|
1345
|
+
reject(err);
|
|
1346
|
+
return;
|
|
1347
|
+
}
|
|
1348
|
+
resolve(stripHtml(html));
|
|
1349
|
+
});
|
|
1350
|
+
});
|
|
1351
|
+
}
|
|
1352
|
+
async function parseOpenDocument(buffer) {
|
|
1353
|
+
const JSZip = (await import("jszip")).default;
|
|
1354
|
+
const zip = await JSZip.loadAsync(buffer);
|
|
1355
|
+
const contentXml = zip.files["content.xml"];
|
|
1356
|
+
if (!contentXml) return "";
|
|
1357
|
+
return stripHtml(await contentXml.async("string"));
|
|
1358
|
+
}
|
|
1359
|
+
function parseEml(buffer) {
|
|
1360
|
+
const content = buffer.toString("utf-8");
|
|
1361
|
+
const boundaryMatch = content.match(/boundary="?([^\s"]+)"?/i);
|
|
1362
|
+
if (boundaryMatch) {
|
|
1363
|
+
const parts = content.split(`--${boundaryMatch[1]}`);
|
|
1364
|
+
for (const part of parts) {
|
|
1365
|
+
if (/content-type:\s*text\/plain/i.test(part)) {
|
|
1366
|
+
const bodyStart = part.indexOf("\n\n");
|
|
1367
|
+
if (bodyStart !== -1) return part.slice(bodyStart + 2).trim();
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
const headerEnd = content.indexOf("\n\n");
|
|
1372
|
+
if (headerEnd !== -1) {
|
|
1373
|
+
const body = content.slice(headerEnd + 2);
|
|
1374
|
+
if (!/<html/i.test(body.slice(0, 200))) return body.trim();
|
|
1375
|
+
return stripHtml(body);
|
|
1376
|
+
}
|
|
1377
|
+
return content.trim();
|
|
1378
|
+
}
|
|
1379
|
+
function parseMbox(buffer) {
|
|
1380
|
+
const content = buffer.toString("utf-8");
|
|
1381
|
+
const messages = content.split(/^From /m).filter(Boolean);
|
|
1382
|
+
const texts = [];
|
|
1383
|
+
for (const msg of messages.slice(0, 50)) {
|
|
1384
|
+
const headerEnd = msg.indexOf("\n\n");
|
|
1385
|
+
if (headerEnd !== -1) {
|
|
1386
|
+
const body = msg.slice(headerEnd + 2);
|
|
1387
|
+
texts.push(!/<html/i.test(body.slice(0, 200)) ? body.trim() : stripHtml(body));
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
return texts.join("\n\n---\n\n");
|
|
1391
|
+
}
|
|
1392
|
+
async function extractTextFromFile(filePath) {
|
|
1393
|
+
const ext = path8.extname(filePath).toLowerCase();
|
|
1394
|
+
try {
|
|
1395
|
+
const stat = fs5.statSync(filePath);
|
|
1396
|
+
if (!stat.isFile()) return null;
|
|
1397
|
+
if (stat.size > MAX_RAW_SIZE) return null;
|
|
1398
|
+
} catch {
|
|
1399
|
+
return null;
|
|
1400
|
+
}
|
|
1401
|
+
if (PLAIN_TEXT_EXTENSIONS.has(ext)) {
|
|
1402
|
+
try {
|
|
1403
|
+
return truncate(fs5.readFileSync(filePath, "utf-8"));
|
|
1404
|
+
} catch {
|
|
1405
|
+
return null;
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
if (!DOCUMENT_EXTENSIONS.has(ext)) return null;
|
|
1409
|
+
try {
|
|
1410
|
+
const buffer = fs5.readFileSync(filePath);
|
|
1411
|
+
let text;
|
|
1412
|
+
switch (ext) {
|
|
1413
|
+
case ".pdf":
|
|
1414
|
+
text = await parsePdf(buffer);
|
|
1415
|
+
break;
|
|
1416
|
+
case ".docx":
|
|
1417
|
+
case ".doc":
|
|
1418
|
+
text = await parseDocx(buffer);
|
|
1419
|
+
break;
|
|
1420
|
+
case ".pptx":
|
|
1421
|
+
text = await parsePptx(buffer);
|
|
1422
|
+
break;
|
|
1423
|
+
case ".pages":
|
|
1424
|
+
case ".key":
|
|
1425
|
+
text = await parseAppleDoc(buffer);
|
|
1426
|
+
break;
|
|
1427
|
+
case ".rtf":
|
|
1428
|
+
text = await parseRtf(buffer);
|
|
1429
|
+
break;
|
|
1430
|
+
case ".odt":
|
|
1431
|
+
case ".odp":
|
|
1432
|
+
text = await parseOpenDocument(buffer);
|
|
1433
|
+
break;
|
|
1434
|
+
case ".eml":
|
|
1435
|
+
text = parseEml(buffer);
|
|
1436
|
+
break;
|
|
1437
|
+
case ".mbox":
|
|
1438
|
+
text = parseMbox(buffer);
|
|
1439
|
+
break;
|
|
1440
|
+
default:
|
|
1441
|
+
return null;
|
|
1442
|
+
}
|
|
1443
|
+
return truncate(text);
|
|
1444
|
+
} catch {
|
|
1445
|
+
return null;
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
function formatSize(bytes) {
|
|
1449
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
1450
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
1451
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
// src/lib/folder-tree.ts
|
|
1455
|
+
import fs6 from "fs";
|
|
1456
|
+
import path9 from "path";
|
|
932
1457
|
import os4 from "os";
|
|
933
1458
|
var home = os4.homedir();
|
|
1459
|
+
function displayPath(p) {
|
|
1460
|
+
return p.startsWith(home) ? "~" + p.slice(home.length) : p;
|
|
1461
|
+
}
|
|
1462
|
+
function groupFilesByFolder(files) {
|
|
1463
|
+
const map = /* @__PURE__ */ new Map();
|
|
1464
|
+
for (const file of files) {
|
|
1465
|
+
const folder = file.folder;
|
|
1466
|
+
if (!map.has(folder)) map.set(folder, []);
|
|
1467
|
+
map.get(folder).push(file);
|
|
1468
|
+
}
|
|
1469
|
+
const results = [];
|
|
1470
|
+
for (const [folderPath, folderFiles] of map) {
|
|
1471
|
+
results.push({
|
|
1472
|
+
path: folderPath,
|
|
1473
|
+
displayPath: displayPath(folderPath),
|
|
1474
|
+
files: folderFiles,
|
|
1475
|
+
fileCount: folderFiles.length,
|
|
1476
|
+
totalBytes: folderFiles.reduce((sum, f) => sum + f.sizeBytes, 0)
|
|
1477
|
+
});
|
|
1478
|
+
}
|
|
1479
|
+
results.sort((a, b) => a.displayPath.localeCompare(b.displayPath));
|
|
1480
|
+
return results;
|
|
1481
|
+
}
|
|
1482
|
+
function formatFolderChoice(folder) {
|
|
1483
|
+
return `${folder.displayPath} \u2014 ${folder.fileCount} file${folder.fileCount === 1 ? "" : "s"} (${formatSize(folder.totalBytes)})`;
|
|
1484
|
+
}
|
|
1485
|
+
function formatSelectionSummary(folders) {
|
|
1486
|
+
const totalFiles = folders.reduce((sum, f) => sum + f.fileCount, 0);
|
|
1487
|
+
const totalBytes = folders.reduce((sum, f) => sum + f.totalBytes, 0);
|
|
1488
|
+
return `${folders.length} folder${folders.length === 1 ? "" : "s"} selected (${totalFiles} file${totalFiles === 1 ? "" : "s"}, ${formatSize(totalBytes)})`;
|
|
1489
|
+
}
|
|
1490
|
+
var SKIP_HOME_DIRS = /* @__PURE__ */ new Set([
|
|
1491
|
+
"Library",
|
|
1492
|
+
"Applications",
|
|
1493
|
+
"Public",
|
|
1494
|
+
"Movies",
|
|
1495
|
+
"Music",
|
|
1496
|
+
"Pictures",
|
|
1497
|
+
"Templates",
|
|
1498
|
+
".Trash"
|
|
1499
|
+
]);
|
|
1500
|
+
var INCLUDE_DOT_DIRS = /* @__PURE__ */ new Set([
|
|
1501
|
+
".claude",
|
|
1502
|
+
".cursor",
|
|
1503
|
+
".windsurf",
|
|
1504
|
+
".openclaw",
|
|
1505
|
+
".zed",
|
|
1506
|
+
".github",
|
|
1507
|
+
".amazonq",
|
|
1508
|
+
".gemini",
|
|
1509
|
+
".mcporter",
|
|
1510
|
+
".paperclip"
|
|
1511
|
+
]);
|
|
1512
|
+
function getDefaultScanDirs() {
|
|
1513
|
+
const dirs = [];
|
|
1514
|
+
try {
|
|
1515
|
+
const entries = fs6.readdirSync(home, { withFileTypes: true });
|
|
1516
|
+
for (const entry of entries) {
|
|
1517
|
+
if (!entry.isDirectory()) continue;
|
|
1518
|
+
if (entry.name.startsWith(".") && !INCLUDE_DOT_DIRS.has(entry.name)) continue;
|
|
1519
|
+
if (SKIP_HOME_DIRS.has(entry.name)) continue;
|
|
1520
|
+
dirs.push(path9.join(home, entry.name));
|
|
1521
|
+
}
|
|
1522
|
+
} catch {
|
|
1523
|
+
}
|
|
1524
|
+
const cloudStorage = path9.join(home, "Library", "CloudStorage");
|
|
1525
|
+
try {
|
|
1526
|
+
if (fs6.existsSync(cloudStorage) && fs6.statSync(cloudStorage).isDirectory()) {
|
|
1527
|
+
const entries = fs6.readdirSync(cloudStorage, { withFileTypes: true });
|
|
1528
|
+
for (const entry of entries) {
|
|
1529
|
+
if (!entry.isDirectory()) continue;
|
|
1530
|
+
const fullPath = path9.join(cloudStorage, entry.name);
|
|
1531
|
+
if (!dirs.includes(fullPath)) {
|
|
1532
|
+
dirs.push(fullPath);
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
} catch {
|
|
1537
|
+
}
|
|
1538
|
+
if (dirs.length === 0) dirs.push(home);
|
|
1539
|
+
return dirs;
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
// src/lib/brain-scanner.ts
|
|
1543
|
+
var home2 = os5.homedir();
|
|
934
1544
|
var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
935
1545
|
"node_modules",
|
|
936
1546
|
".git",
|
|
@@ -952,21 +1562,6 @@ var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
|
952
1562
|
"Cache",
|
|
953
1563
|
".piut"
|
|
954
1564
|
]);
|
|
955
|
-
var FULL_READ_FILES = /* @__PURE__ */ new Set([
|
|
956
|
-
"README.md",
|
|
957
|
-
"CLAUDE.md",
|
|
958
|
-
".cursorrules",
|
|
959
|
-
".windsurfrules",
|
|
960
|
-
".rules",
|
|
961
|
-
".clinerules",
|
|
962
|
-
"AGENTS.md",
|
|
963
|
-
"CONVENTIONS.md",
|
|
964
|
-
"MEMORY.md",
|
|
965
|
-
"SOUL.md",
|
|
966
|
-
"IDENTITY.md"
|
|
967
|
-
]);
|
|
968
|
-
var MAX_FILE_SIZE = 100 * 1024;
|
|
969
|
-
var RECENT_DAYS = 30;
|
|
970
1565
|
var SCAN_DOT_DIRS = /* @__PURE__ */ new Set([
|
|
971
1566
|
".claude",
|
|
972
1567
|
".cursor",
|
|
@@ -974,57 +1569,33 @@ var SCAN_DOT_DIRS = /* @__PURE__ */ new Set([
|
|
|
974
1569
|
".github",
|
|
975
1570
|
".zed",
|
|
976
1571
|
".amazonq",
|
|
977
|
-
".vscode"
|
|
1572
|
+
".vscode",
|
|
1573
|
+
".gemini",
|
|
1574
|
+
".openclaw",
|
|
1575
|
+
".mcporter",
|
|
1576
|
+
".paperclip"
|
|
978
1577
|
]);
|
|
979
1578
|
function shouldSkipDir(name) {
|
|
980
|
-
if (name.startsWith(".") && !SCAN_DOT_DIRS.has(name))
|
|
981
|
-
return true;
|
|
982
|
-
}
|
|
1579
|
+
if (name.startsWith(".") && !SCAN_DOT_DIRS.has(name)) return true;
|
|
983
1580
|
return SKIP_DIRS.has(name);
|
|
984
1581
|
}
|
|
985
|
-
function readFileSafe(filePath) {
|
|
986
|
-
try {
|
|
987
|
-
const stat = fs5.statSync(filePath);
|
|
988
|
-
if (stat.size > MAX_FILE_SIZE) return null;
|
|
989
|
-
if (!stat.isFile()) return null;
|
|
990
|
-
return fs5.readFileSync(filePath, "utf-8");
|
|
991
|
-
} catch {
|
|
992
|
-
return null;
|
|
993
|
-
}
|
|
994
|
-
}
|
|
995
|
-
function getFolderTree(dir, depth = 0, maxDepth = 3) {
|
|
996
|
-
if (depth >= maxDepth) return [];
|
|
997
|
-
const entries = [];
|
|
998
|
-
try {
|
|
999
|
-
const items = fs5.readdirSync(dir, { withFileTypes: true });
|
|
1000
|
-
for (const item of items) {
|
|
1001
|
-
if (!item.isDirectory()) continue;
|
|
1002
|
-
if (shouldSkipDir(item.name)) continue;
|
|
1003
|
-
const indent = " ".repeat(depth);
|
|
1004
|
-
entries.push(`${indent}${item.name}/`);
|
|
1005
|
-
entries.push(...getFolderTree(path7.join(dir, item.name), depth + 1, maxDepth));
|
|
1006
|
-
}
|
|
1007
|
-
} catch {
|
|
1008
|
-
}
|
|
1009
|
-
return entries;
|
|
1010
|
-
}
|
|
1011
1582
|
function isProject(dirPath) {
|
|
1012
|
-
return
|
|
1583
|
+
return fs7.existsSync(path10.join(dirPath, ".git")) || fs7.existsSync(path10.join(dirPath, "package.json")) || fs7.existsSync(path10.join(dirPath, "Cargo.toml")) || fs7.existsSync(path10.join(dirPath, "pyproject.toml")) || fs7.existsSync(path10.join(dirPath, "go.mod"));
|
|
1013
1584
|
}
|
|
1014
1585
|
function buildProjectInfo(projectPath) {
|
|
1015
|
-
const hasPkgJson =
|
|
1586
|
+
const hasPkgJson = fs7.existsSync(path10.join(projectPath, "package.json"));
|
|
1016
1587
|
let description = "";
|
|
1017
1588
|
if (hasPkgJson) {
|
|
1018
1589
|
try {
|
|
1019
|
-
const pkg = JSON.parse(
|
|
1590
|
+
const pkg = JSON.parse(fs7.readFileSync(path10.join(projectPath, "package.json"), "utf-8"));
|
|
1020
1591
|
description = pkg.description || "";
|
|
1021
1592
|
} catch {
|
|
1022
1593
|
}
|
|
1023
1594
|
}
|
|
1024
|
-
const readmePath =
|
|
1025
|
-
if (!description &&
|
|
1026
|
-
|
|
1027
|
-
|
|
1595
|
+
const readmePath = path10.join(projectPath, "README.md");
|
|
1596
|
+
if (!description && fs7.existsSync(readmePath)) {
|
|
1597
|
+
try {
|
|
1598
|
+
const content = fs7.readFileSync(readmePath, "utf-8");
|
|
1028
1599
|
const lines = content.split("\n");
|
|
1029
1600
|
let foundHeading = false;
|
|
1030
1601
|
for (const line of lines) {
|
|
@@ -1037,18 +1608,19 @@ function buildProjectInfo(projectPath) {
|
|
|
1037
1608
|
break;
|
|
1038
1609
|
}
|
|
1039
1610
|
}
|
|
1611
|
+
} catch {
|
|
1040
1612
|
}
|
|
1041
1613
|
}
|
|
1042
1614
|
return {
|
|
1043
|
-
name:
|
|
1615
|
+
name: path10.basename(projectPath),
|
|
1044
1616
|
path: projectPath,
|
|
1045
1617
|
description,
|
|
1046
|
-
hasClaudeMd:
|
|
1047
|
-
hasCursorRules:
|
|
1048
|
-
hasWindsurfRules:
|
|
1049
|
-
hasCopilotInstructions:
|
|
1050
|
-
hasConventionsMd:
|
|
1051
|
-
hasZedRules:
|
|
1618
|
+
hasClaudeMd: fs7.existsSync(path10.join(projectPath, "CLAUDE.md")) || fs7.existsSync(path10.join(projectPath, ".claude", "rules")),
|
|
1619
|
+
hasCursorRules: fs7.existsSync(path10.join(projectPath, ".cursorrules")) || fs7.existsSync(path10.join(projectPath, ".cursor", "rules")),
|
|
1620
|
+
hasWindsurfRules: fs7.existsSync(path10.join(projectPath, ".windsurfrules")) || fs7.existsSync(path10.join(projectPath, ".windsurf", "rules")),
|
|
1621
|
+
hasCopilotInstructions: fs7.existsSync(path10.join(projectPath, ".github", "copilot-instructions.md")) || fs7.existsSync(path10.join(projectPath, ".github", "instructions")),
|
|
1622
|
+
hasConventionsMd: fs7.existsSync(path10.join(projectPath, "CONVENTIONS.md")) || fs7.existsSync(path10.join(projectPath, ".amazonq", "rules")),
|
|
1623
|
+
hasZedRules: fs7.existsSync(path10.join(projectPath, ".rules"))
|
|
1052
1624
|
};
|
|
1053
1625
|
}
|
|
1054
1626
|
var MAX_PROJECT_DEPTH = 4;
|
|
@@ -1058,17 +1630,17 @@ function detectProjects(scanDirs, onProgress) {
|
|
|
1058
1630
|
function walk(dir, depth) {
|
|
1059
1631
|
if (depth > MAX_PROJECT_DEPTH) return;
|
|
1060
1632
|
try {
|
|
1061
|
-
const items =
|
|
1633
|
+
const items = fs7.readdirSync(dir, { withFileTypes: true });
|
|
1062
1634
|
for (const item of items) {
|
|
1063
1635
|
if (!item.isDirectory()) continue;
|
|
1064
1636
|
if (shouldSkipDir(item.name)) continue;
|
|
1065
|
-
const fullPath =
|
|
1637
|
+
const fullPath = path10.join(dir, item.name);
|
|
1066
1638
|
if (seen.has(fullPath)) continue;
|
|
1067
1639
|
seen.add(fullPath);
|
|
1068
1640
|
if (isProject(fullPath)) {
|
|
1069
1641
|
const info = buildProjectInfo(fullPath);
|
|
1070
1642
|
projects.push(info);
|
|
1071
|
-
onProgress?.({ phase: "projects", message: `${info.name} (${fullPath.replace(
|
|
1643
|
+
onProgress?.({ phase: "projects", message: `${info.name} (${fullPath.replace(home2, "~")})` });
|
|
1072
1644
|
} else {
|
|
1073
1645
|
walk(fullPath, depth + 1);
|
|
1074
1646
|
}
|
|
@@ -1081,150 +1653,150 @@ function detectProjects(scanDirs, onProgress) {
|
|
|
1081
1653
|
}
|
|
1082
1654
|
return projects;
|
|
1083
1655
|
}
|
|
1656
|
+
var MAX_CONFIG_SIZE = 100 * 1024;
|
|
1084
1657
|
function collectConfigFiles(projects, onProgress) {
|
|
1085
1658
|
const configs = [];
|
|
1086
1659
|
const globalPaths = [
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1660
|
+
path10.join(home2, ".claude", "MEMORY.md"),
|
|
1661
|
+
path10.join(home2, ".claude", "CLAUDE.md"),
|
|
1662
|
+
path10.join(home2, ".openclaw", "workspace", "SOUL.md"),
|
|
1663
|
+
path10.join(home2, ".openclaw", "workspace", "MEMORY.md"),
|
|
1664
|
+
path10.join(home2, ".gemini", "MEMORY.md"),
|
|
1665
|
+
path10.join(home2, ".paperclip", "IDENTITY.md")
|
|
1091
1666
|
];
|
|
1092
1667
|
for (const gp of globalPaths) {
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
}
|
|
1100
|
-
for (const project of projects) {
|
|
1101
|
-
for (const fileName of FULL_READ_FILES) {
|
|
1102
|
-
const filePath = path7.join(project.path, fileName);
|
|
1103
|
-
const content = readFileSafe(filePath);
|
|
1104
|
-
if (content && content.trim()) {
|
|
1105
|
-
const name = `${project.name}/${fileName}`;
|
|
1668
|
+
try {
|
|
1669
|
+
const stat = fs7.statSync(gp);
|
|
1670
|
+
if (!stat.isFile() || stat.size > MAX_CONFIG_SIZE) continue;
|
|
1671
|
+
const content = fs7.readFileSync(gp, "utf-8");
|
|
1672
|
+
if (content.trim()) {
|
|
1673
|
+
const name = `~/${path10.relative(home2, gp)}`;
|
|
1106
1674
|
configs.push({ name, content });
|
|
1107
1675
|
onProgress?.({ phase: "configs", message: name });
|
|
1108
1676
|
}
|
|
1677
|
+
} catch {
|
|
1109
1678
|
}
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1679
|
+
}
|
|
1680
|
+
for (const project of projects) {
|
|
1681
|
+
for (const fileName of AI_CONFIG_FILENAMES) {
|
|
1682
|
+
const filePath = path10.join(project.path, fileName);
|
|
1113
1683
|
try {
|
|
1114
|
-
const
|
|
1115
|
-
|
|
1116
|
-
const
|
|
1117
|
-
|
|
1118
|
-
|
|
1684
|
+
const stat = fs7.statSync(filePath);
|
|
1685
|
+
if (!stat.isFile() || stat.size > MAX_CONFIG_SIZE) continue;
|
|
1686
|
+
const content = fs7.readFileSync(filePath, "utf-8");
|
|
1687
|
+
if (content.trim()) {
|
|
1688
|
+
const name = `${project.name}/${fileName}`;
|
|
1689
|
+
configs.push({ name, content });
|
|
1690
|
+
onProgress?.({ phase: "configs", message: name });
|
|
1691
|
+
}
|
|
1119
1692
|
} catch {
|
|
1120
1693
|
}
|
|
1121
1694
|
}
|
|
1695
|
+
const pkgPath = path10.join(project.path, "package.json");
|
|
1696
|
+
try {
|
|
1697
|
+
const stat = fs7.statSync(pkgPath);
|
|
1698
|
+
if (stat.isFile() && stat.size <= MAX_CONFIG_SIZE) {
|
|
1699
|
+
const pkg = JSON.parse(fs7.readFileSync(pkgPath, "utf-8"));
|
|
1700
|
+
const summary = JSON.stringify({ name: pkg.name, description: pkg.description }, null, 2);
|
|
1701
|
+
configs.push({ name: `${project.name}/package.json`, content: summary });
|
|
1702
|
+
onProgress?.({ phase: "configs", message: `${project.name}/package.json` });
|
|
1703
|
+
}
|
|
1704
|
+
} catch {
|
|
1705
|
+
}
|
|
1122
1706
|
}
|
|
1123
1707
|
return configs;
|
|
1124
1708
|
}
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1709
|
+
var MAX_SCAN_DEPTH = 6;
|
|
1710
|
+
var MAX_FILES = 500;
|
|
1711
|
+
async function scanFilesInDirs(dirs, onProgress) {
|
|
1712
|
+
const files = [];
|
|
1128
1713
|
const seen = /* @__PURE__ */ new Set();
|
|
1129
|
-
|
|
1714
|
+
function walk(dir, depth) {
|
|
1715
|
+
if (depth > MAX_SCAN_DEPTH) return [];
|
|
1716
|
+
const found = [];
|
|
1130
1717
|
try {
|
|
1131
|
-
const items =
|
|
1718
|
+
const items = fs7.readdirSync(dir, { withFileTypes: true });
|
|
1132
1719
|
for (const item of items) {
|
|
1133
|
-
if (
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
const content = fs5.readFileSync(filePath, "utf-8");
|
|
1145
|
-
if (content.trim()) {
|
|
1146
|
-
const name = `${project.name}/${item.name}`;
|
|
1147
|
-
docs.push({ name, content });
|
|
1148
|
-
onProgress?.({ phase: "docs", message: name });
|
|
1720
|
+
if (item.isDirectory()) {
|
|
1721
|
+
if (!shouldSkipDir(item.name)) {
|
|
1722
|
+
found.push(...walk(path10.join(dir, item.name), depth + 1));
|
|
1723
|
+
}
|
|
1724
|
+
} else if (item.isFile()) {
|
|
1725
|
+
if (canParse(item.name) && !isAiConfigFile(item.name)) {
|
|
1726
|
+
const fullPath = path10.join(dir, item.name);
|
|
1727
|
+
if (!seen.has(fullPath)) {
|
|
1728
|
+
seen.add(fullPath);
|
|
1729
|
+
found.push(fullPath);
|
|
1730
|
+
}
|
|
1149
1731
|
}
|
|
1150
|
-
} catch {
|
|
1151
1732
|
}
|
|
1152
1733
|
}
|
|
1153
1734
|
} catch {
|
|
1154
1735
|
}
|
|
1736
|
+
return found;
|
|
1155
1737
|
}
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
"Public",
|
|
1162
|
-
"Movies",
|
|
1163
|
-
"Music",
|
|
1164
|
-
"Pictures",
|
|
1165
|
-
"Templates",
|
|
1166
|
-
".Trash"
|
|
1167
|
-
]);
|
|
1168
|
-
var INCLUDE_DOT_DIRS = /* @__PURE__ */ new Set([
|
|
1169
|
-
".claude",
|
|
1170
|
-
".cursor",
|
|
1171
|
-
".windsurf",
|
|
1172
|
-
".openclaw",
|
|
1173
|
-
".zed",
|
|
1174
|
-
".github",
|
|
1175
|
-
".amazonq"
|
|
1176
|
-
]);
|
|
1177
|
-
function getDefaultScanDirs() {
|
|
1178
|
-
const dirs = [];
|
|
1179
|
-
try {
|
|
1180
|
-
const entries = fs5.readdirSync(home, { withFileTypes: true });
|
|
1181
|
-
for (const entry of entries) {
|
|
1182
|
-
if (!entry.isDirectory()) continue;
|
|
1183
|
-
if (entry.name.startsWith(".") && !INCLUDE_DOT_DIRS.has(entry.name)) continue;
|
|
1184
|
-
if (SKIP_HOME_DIRS.has(entry.name)) continue;
|
|
1185
|
-
dirs.push(path7.join(home, entry.name));
|
|
1186
|
-
}
|
|
1187
|
-
} catch {
|
|
1738
|
+
const allPaths = [];
|
|
1739
|
+
for (const dir of dirs) {
|
|
1740
|
+
onProgress?.({ phase: "scanning", message: displayPath(dir) });
|
|
1741
|
+
allPaths.push(...walk(dir, 0));
|
|
1742
|
+
if (allPaths.length > MAX_FILES) break;
|
|
1188
1743
|
}
|
|
1189
|
-
const
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
const
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1744
|
+
const pathsToProcess = allPaths.slice(0, MAX_FILES);
|
|
1745
|
+
for (const filePath of pathsToProcess) {
|
|
1746
|
+
try {
|
|
1747
|
+
const stat = fs7.statSync(filePath);
|
|
1748
|
+
onProgress?.({ phase: "parsing", message: displayPath(filePath) });
|
|
1749
|
+
const content = await extractTextFromFile(filePath);
|
|
1750
|
+
if (content && content.trim()) {
|
|
1751
|
+
const category = getFileCategory(filePath);
|
|
1752
|
+
files.push({
|
|
1753
|
+
path: filePath,
|
|
1754
|
+
displayPath: displayPath(filePath),
|
|
1755
|
+
content,
|
|
1756
|
+
format: category === "document" ? path10.extname(filePath).slice(1) : "text",
|
|
1757
|
+
sizeBytes: stat.size,
|
|
1758
|
+
folder: path10.dirname(filePath)
|
|
1759
|
+
});
|
|
1199
1760
|
}
|
|
1761
|
+
} catch {
|
|
1200
1762
|
}
|
|
1201
|
-
} catch {
|
|
1202
|
-
}
|
|
1203
|
-
if (dirs.length === 0) {
|
|
1204
|
-
dirs.push(home);
|
|
1205
1763
|
}
|
|
1206
|
-
return
|
|
1764
|
+
return files;
|
|
1207
1765
|
}
|
|
1208
|
-
function
|
|
1209
|
-
const
|
|
1766
|
+
async function scanFolders(dirs, onProgress) {
|
|
1767
|
+
const allFiles = await scanFilesInDirs(dirs, onProgress);
|
|
1768
|
+
const folders = groupFilesByFolder(allFiles);
|
|
1769
|
+
const projects = detectProjects(dirs, onProgress);
|
|
1770
|
+
const configFiles = collectConfigFiles(projects, onProgress);
|
|
1771
|
+
const totalFiles = allFiles.length;
|
|
1772
|
+
const totalBytes = allFiles.reduce((sum, f) => sum + f.sizeBytes, 0);
|
|
1773
|
+
return { folders, configFiles, projects, allFiles, totalFiles, totalBytes };
|
|
1774
|
+
}
|
|
1775
|
+
function buildBrainInput(scanResult, selectedFolderPaths) {
|
|
1776
|
+
const selectedSet = new Set(selectedFolderPaths);
|
|
1777
|
+
const selectedFiles = scanResult.allFiles.filter((f) => selectedSet.has(f.folder));
|
|
1210
1778
|
const folderTree = [];
|
|
1211
|
-
for (const
|
|
1212
|
-
|
|
1213
|
-
|
|
1779
|
+
for (const folder of scanResult.folders) {
|
|
1780
|
+
if (selectedSet.has(folder.path)) {
|
|
1781
|
+
folderTree.push(`${folder.displayPath}/ (${folder.fileCount} files)`);
|
|
1782
|
+
}
|
|
1214
1783
|
}
|
|
1215
|
-
const
|
|
1216
|
-
|
|
1217
|
-
|
|
1784
|
+
const personalDocuments = selectedFiles.map((f) => ({
|
|
1785
|
+
name: f.displayPath,
|
|
1786
|
+
content: f.content,
|
|
1787
|
+
format: f.format
|
|
1788
|
+
}));
|
|
1218
1789
|
return {
|
|
1219
1790
|
summary: {
|
|
1220
1791
|
folders: folderTree,
|
|
1221
|
-
projects: projects.map((p) => ({
|
|
1792
|
+
projects: scanResult.projects.map((p) => ({
|
|
1222
1793
|
name: p.name,
|
|
1223
|
-
path: p.path.replace(
|
|
1794
|
+
path: p.path.replace(home2, "~"),
|
|
1224
1795
|
description: p.description
|
|
1225
1796
|
})),
|
|
1226
|
-
configFiles,
|
|
1227
|
-
recentDocuments
|
|
1797
|
+
configFiles: scanResult.configFiles,
|
|
1798
|
+
recentDocuments: [],
|
|
1799
|
+
personalDocuments
|
|
1228
1800
|
}
|
|
1229
1801
|
};
|
|
1230
1802
|
}
|
|
@@ -1234,14 +1806,14 @@ function scanForProjects(folders) {
|
|
|
1234
1806
|
}
|
|
1235
1807
|
|
|
1236
1808
|
// src/lib/store.ts
|
|
1237
|
-
import
|
|
1238
|
-
import
|
|
1239
|
-
import
|
|
1240
|
-
var CONFIG_DIR =
|
|
1241
|
-
var CONFIG_FILE2 =
|
|
1809
|
+
import fs8 from "fs";
|
|
1810
|
+
import path11 from "path";
|
|
1811
|
+
import os6 from "os";
|
|
1812
|
+
var CONFIG_DIR = path11.join(os6.homedir(), ".piut");
|
|
1813
|
+
var CONFIG_FILE2 = path11.join(CONFIG_DIR, "config.json");
|
|
1242
1814
|
function readStore() {
|
|
1243
1815
|
try {
|
|
1244
|
-
const raw =
|
|
1816
|
+
const raw = fs8.readFileSync(CONFIG_FILE2, "utf-8");
|
|
1245
1817
|
return JSON.parse(raw);
|
|
1246
1818
|
} catch {
|
|
1247
1819
|
return {};
|
|
@@ -1250,18 +1822,19 @@ function readStore() {
|
|
|
1250
1822
|
function updateStore(updates) {
|
|
1251
1823
|
const config = readStore();
|
|
1252
1824
|
const updated = { ...config, ...updates };
|
|
1253
|
-
|
|
1254
|
-
|
|
1825
|
+
fs8.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
1826
|
+
fs8.writeFileSync(CONFIG_FILE2, JSON.stringify(updated, null, 2) + "\n", "utf-8");
|
|
1255
1827
|
return updated;
|
|
1256
1828
|
}
|
|
1257
1829
|
function clearStore() {
|
|
1258
1830
|
try {
|
|
1259
|
-
|
|
1831
|
+
fs8.unlinkSync(CONFIG_FILE2);
|
|
1260
1832
|
} catch {
|
|
1261
1833
|
}
|
|
1262
1834
|
}
|
|
1263
1835
|
|
|
1264
1836
|
// src/commands/status.ts
|
|
1837
|
+
var API_BASE3 = process.env.PIUT_API_BASE || "https://piut.com";
|
|
1265
1838
|
var PIUT_FILES = [
|
|
1266
1839
|
"CLAUDE.md",
|
|
1267
1840
|
".cursor/rules/piut.mdc",
|
|
@@ -1272,25 +1845,67 @@ var PIUT_FILES = [
|
|
|
1272
1845
|
];
|
|
1273
1846
|
function hasPiutReference(filePath) {
|
|
1274
1847
|
try {
|
|
1275
|
-
const content =
|
|
1848
|
+
const content = fs9.readFileSync(filePath, "utf-8");
|
|
1276
1849
|
return content.includes("p\u0131ut Context") || content.includes("piut Context");
|
|
1277
1850
|
} catch {
|
|
1278
1851
|
return false;
|
|
1279
1852
|
}
|
|
1280
1853
|
}
|
|
1854
|
+
async function fetchRemoteConnections(key) {
|
|
1855
|
+
try {
|
|
1856
|
+
const res = await fetch(`${API_BASE3}/api/mcp/connections`, {
|
|
1857
|
+
headers: { Authorization: `Bearer ${key}` }
|
|
1858
|
+
});
|
|
1859
|
+
if (!res.ok) return [];
|
|
1860
|
+
const data = await res.json();
|
|
1861
|
+
return data.connections || [];
|
|
1862
|
+
} catch {
|
|
1863
|
+
return [];
|
|
1864
|
+
}
|
|
1865
|
+
}
|
|
1866
|
+
async function fetchRemoteProjects(key) {
|
|
1867
|
+
try {
|
|
1868
|
+
const res = await fetch(`${API_BASE3}/api/cli/projects`, {
|
|
1869
|
+
headers: { Authorization: `Bearer ${key}` }
|
|
1870
|
+
});
|
|
1871
|
+
if (!res.ok) return [];
|
|
1872
|
+
const data = await res.json();
|
|
1873
|
+
return data.projects || [];
|
|
1874
|
+
} catch {
|
|
1875
|
+
return [];
|
|
1876
|
+
}
|
|
1877
|
+
}
|
|
1878
|
+
function machineLabel(hostname, machineId) {
|
|
1879
|
+
if (hostname) return hostname;
|
|
1880
|
+
if (machineId && machineId !== "unknown") return machineId.slice(0, 8);
|
|
1881
|
+
return "unknown";
|
|
1882
|
+
}
|
|
1883
|
+
function timeAgo(dateStr) {
|
|
1884
|
+
const diff = Date.now() - new Date(dateStr).getTime();
|
|
1885
|
+
const minutes = Math.floor(diff / 6e4);
|
|
1886
|
+
if (minutes < 1) return "just now";
|
|
1887
|
+
if (minutes < 60) return `${minutes}m ago`;
|
|
1888
|
+
const hours = Math.floor(minutes / 60);
|
|
1889
|
+
if (hours < 24) return `${hours}h ago`;
|
|
1890
|
+
const days = Math.floor(hours / 24);
|
|
1891
|
+
if (days < 7) return `${days}d ago`;
|
|
1892
|
+
return new Date(dateStr).toLocaleDateString();
|
|
1893
|
+
}
|
|
1281
1894
|
async function statusCommand(options = {}) {
|
|
1282
1895
|
banner();
|
|
1283
1896
|
if (options.verify) {
|
|
1284
1897
|
await verifyStatus();
|
|
1285
1898
|
return;
|
|
1286
1899
|
}
|
|
1287
|
-
|
|
1900
|
+
const thisHostname = os7.hostname();
|
|
1901
|
+
const thisMachineId = getMachineId2();
|
|
1902
|
+
console.log(` AI tools on this machine ${dim(`(${thisHostname})`)}:`);
|
|
1288
1903
|
console.log();
|
|
1289
1904
|
let foundAny = false;
|
|
1290
1905
|
for (const tool of TOOLS) {
|
|
1291
1906
|
const paths = resolveConfigPaths(tool.configPaths);
|
|
1292
1907
|
for (const configPath of paths) {
|
|
1293
|
-
if (!
|
|
1908
|
+
if (!fs9.existsSync(configPath)) continue;
|
|
1294
1909
|
foundAny = true;
|
|
1295
1910
|
const configured = isPiutConfigured(configPath, tool.configKey);
|
|
1296
1911
|
if (configured) {
|
|
@@ -1306,15 +1921,15 @@ async function statusCommand(options = {}) {
|
|
|
1306
1921
|
console.log(dim(" Run ") + brand("piut setup") + dim(" to configure your AI tools."));
|
|
1307
1922
|
}
|
|
1308
1923
|
console.log();
|
|
1309
|
-
console.log(
|
|
1924
|
+
console.log(` Connected projects on this machine:`);
|
|
1310
1925
|
console.log();
|
|
1311
1926
|
const projects = scanForProjects();
|
|
1312
1927
|
let connectedCount = 0;
|
|
1313
1928
|
for (const project of projects) {
|
|
1314
1929
|
const connectedFiles = [];
|
|
1315
1930
|
for (const file of PIUT_FILES) {
|
|
1316
|
-
const absPath =
|
|
1317
|
-
if (
|
|
1931
|
+
const absPath = path12.join(project.path, file);
|
|
1932
|
+
if (fs9.existsSync(absPath) && hasPiutReference(absPath)) {
|
|
1318
1933
|
connectedFiles.push(file);
|
|
1319
1934
|
}
|
|
1320
1935
|
}
|
|
@@ -1331,6 +1946,33 @@ async function statusCommand(options = {}) {
|
|
|
1331
1946
|
console.log(dim(` ${connectedCount} project(s) connected to your brain.`));
|
|
1332
1947
|
}
|
|
1333
1948
|
console.log();
|
|
1949
|
+
const store = readStore();
|
|
1950
|
+
if (store.apiKey) {
|
|
1951
|
+
const [remoteConnections, remoteProjects] = await Promise.all([
|
|
1952
|
+
fetchRemoteConnections(store.apiKey),
|
|
1953
|
+
fetchRemoteProjects(store.apiKey)
|
|
1954
|
+
]);
|
|
1955
|
+
const otherMachineConns = remoteConnections.filter((c) => c.machine_id !== thisMachineId);
|
|
1956
|
+
const otherMachineProjects = remoteProjects.filter((p) => p.machineId !== thisMachineId);
|
|
1957
|
+
if (otherMachineConns.length > 0 || otherMachineProjects.length > 0) {
|
|
1958
|
+
console.log(` Other machines:`);
|
|
1959
|
+
console.log();
|
|
1960
|
+
if (otherMachineConns.length > 0) {
|
|
1961
|
+
for (const conn of otherMachineConns) {
|
|
1962
|
+
const machine = machineLabel(conn.hostname, conn.machine_id);
|
|
1963
|
+
const age = timeAgo(conn.last_connected_at);
|
|
1964
|
+
console.log(dim(` ${conn.tool_name}`) + dim(` @${machine}`) + dim(` \u2014 ${conn.request_count} requests, ${age}`));
|
|
1965
|
+
}
|
|
1966
|
+
}
|
|
1967
|
+
if (otherMachineProjects.length > 0) {
|
|
1968
|
+
for (const proj of otherMachineProjects) {
|
|
1969
|
+
const machine = machineLabel(proj.hostname, proj.machineId);
|
|
1970
|
+
console.log(dim(` ${proj.projectName}`) + dim(` @${machine}:${proj.projectPath}`));
|
|
1971
|
+
}
|
|
1972
|
+
}
|
|
1973
|
+
console.log();
|
|
1974
|
+
}
|
|
1975
|
+
}
|
|
1334
1976
|
}
|
|
1335
1977
|
async function verifyStatus() {
|
|
1336
1978
|
const store = readStore();
|
|
@@ -1360,7 +2002,7 @@ async function verifyStatus() {
|
|
|
1360
2002
|
for (const tool of TOOLS) {
|
|
1361
2003
|
const paths = resolveConfigPaths(tool.configPaths);
|
|
1362
2004
|
for (const configPath of paths) {
|
|
1363
|
-
if (!
|
|
2005
|
+
if (!fs9.existsSync(configPath)) continue;
|
|
1364
2006
|
const piutConfig = getPiutConfig(configPath, tool.configKey);
|
|
1365
2007
|
if (!piutConfig) {
|
|
1366
2008
|
toolLine(tool.name, dim("installed, not connected"), "\u25CB");
|
|
@@ -1403,15 +2045,16 @@ async function verifyStatus() {
|
|
|
1403
2045
|
}
|
|
1404
2046
|
|
|
1405
2047
|
// src/commands/remove.ts
|
|
1406
|
-
import
|
|
2048
|
+
import fs10 from "fs";
|
|
1407
2049
|
import { checkbox as checkbox2, confirm as confirm2 } from "@inquirer/prompts";
|
|
1408
2050
|
async function removeCommand() {
|
|
1409
2051
|
banner();
|
|
1410
2052
|
const configured = [];
|
|
1411
2053
|
for (const tool of TOOLS) {
|
|
2054
|
+
if (!tool.configKey) continue;
|
|
1412
2055
|
const paths = resolveConfigPaths(tool.configPaths);
|
|
1413
2056
|
for (const configPath of paths) {
|
|
1414
|
-
if (
|
|
2057
|
+
if (fs10.existsSync(configPath) && isPiutConfigured(configPath, tool.configKey)) {
|
|
1415
2058
|
configured.push({ tool, configPath });
|
|
1416
2059
|
break;
|
|
1417
2060
|
}
|
|
@@ -1440,134 +2083,220 @@ async function removeCommand() {
|
|
|
1440
2083
|
});
|
|
1441
2084
|
if (!proceed) return;
|
|
1442
2085
|
console.log();
|
|
2086
|
+
const removedNames = [];
|
|
1443
2087
|
for (const { tool, configPath } of selected) {
|
|
2088
|
+
if (!tool.configKey) continue;
|
|
1444
2089
|
const removed = removeFromConfig(configPath, tool.configKey);
|
|
1445
2090
|
if (removed) {
|
|
2091
|
+
removedNames.push(tool.name);
|
|
1446
2092
|
toolLine(tool.name, success("removed"), "\u2714");
|
|
1447
2093
|
} else {
|
|
1448
2094
|
toolLine(tool.name, warning("not found"), "\xD7");
|
|
1449
2095
|
}
|
|
1450
2096
|
}
|
|
2097
|
+
const store = readStore();
|
|
2098
|
+
if (store.apiKey && removedNames.length > 0) {
|
|
2099
|
+
deleteConnections(store.apiKey, removedNames).catch(() => {
|
|
2100
|
+
});
|
|
2101
|
+
}
|
|
1451
2102
|
console.log();
|
|
1452
2103
|
console.log(dim(" Restart your AI tools for changes to take effect."));
|
|
1453
2104
|
console.log();
|
|
1454
2105
|
}
|
|
1455
2106
|
|
|
1456
2107
|
// src/commands/build.ts
|
|
1457
|
-
import { select, checkbox as checkbox3, input, confirm as confirm3 } from "@inquirer/prompts";
|
|
2108
|
+
import { select as select2, checkbox as checkbox3, input as input2, confirm as confirm3 } from "@inquirer/prompts";
|
|
1458
2109
|
import chalk5 from "chalk";
|
|
1459
|
-
import
|
|
2110
|
+
import os8 from "os";
|
|
1460
2111
|
|
|
1461
2112
|
// src/lib/auth.ts
|
|
1462
|
-
import { password as password2 } from "@inquirer/prompts";
|
|
2113
|
+
import { select, input, password as password2 } from "@inquirer/prompts";
|
|
2114
|
+
import { exec } from "child_process";
|
|
1463
2115
|
import chalk4 from "chalk";
|
|
2116
|
+
function openBrowser(url) {
|
|
2117
|
+
const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
2118
|
+
exec(`${cmd} "${url}"`);
|
|
2119
|
+
}
|
|
2120
|
+
async function loginWithEmailFlow() {
|
|
2121
|
+
const email = await input({
|
|
2122
|
+
message: "Email:",
|
|
2123
|
+
validate: (v) => v.includes("@") || "Enter a valid email address"
|
|
2124
|
+
});
|
|
2125
|
+
const pw = await password2({
|
|
2126
|
+
message: "Password:",
|
|
2127
|
+
mask: "*"
|
|
2128
|
+
});
|
|
2129
|
+
console.log(dim(" Authenticating..."));
|
|
2130
|
+
const result = await loginWithEmail(email, pw);
|
|
2131
|
+
return {
|
|
2132
|
+
apiKey: result.apiKey,
|
|
2133
|
+
validation: {
|
|
2134
|
+
slug: result.slug,
|
|
2135
|
+
displayName: result.displayName,
|
|
2136
|
+
serverUrl: result.serverUrl,
|
|
2137
|
+
planType: result.planType,
|
|
2138
|
+
status: result.status,
|
|
2139
|
+
_contractVersion: result._contractVersion
|
|
2140
|
+
}
|
|
2141
|
+
};
|
|
2142
|
+
}
|
|
2143
|
+
async function pasteKeyFlow() {
|
|
2144
|
+
const apiKey = await password2({
|
|
2145
|
+
message: "Enter your p\u0131ut API key:",
|
|
2146
|
+
mask: "*",
|
|
2147
|
+
validate: (v) => v.startsWith("pb_") || "Key must start with pb_"
|
|
2148
|
+
});
|
|
2149
|
+
console.log(dim(" Validating key..."));
|
|
2150
|
+
const validation = await validateKey(apiKey);
|
|
2151
|
+
return { apiKey, validation };
|
|
2152
|
+
}
|
|
2153
|
+
async function browserFlow() {
|
|
2154
|
+
const url = "https://piut.com/dashboard/keys";
|
|
2155
|
+
console.log(dim(` Opening ${brand(url)}...`));
|
|
2156
|
+
openBrowser(url);
|
|
2157
|
+
console.log(dim(" Copy your API key from the dashboard, then paste it here."));
|
|
2158
|
+
console.log();
|
|
2159
|
+
return pasteKeyFlow();
|
|
2160
|
+
}
|
|
2161
|
+
async function promptLogin() {
|
|
2162
|
+
const method = await select({
|
|
2163
|
+
message: "How would you like to connect?",
|
|
2164
|
+
choices: [
|
|
2165
|
+
{
|
|
2166
|
+
name: "Log in with email",
|
|
2167
|
+
value: "email",
|
|
2168
|
+
description: "Use your p\u0131ut email and password"
|
|
2169
|
+
},
|
|
2170
|
+
{
|
|
2171
|
+
name: "Log in with browser",
|
|
2172
|
+
value: "browser",
|
|
2173
|
+
description: "Open piut.com to get your API key"
|
|
2174
|
+
},
|
|
2175
|
+
{
|
|
2176
|
+
name: "Paste API key",
|
|
2177
|
+
value: "api-key",
|
|
2178
|
+
description: "Enter an API key directly"
|
|
2179
|
+
}
|
|
2180
|
+
]
|
|
2181
|
+
});
|
|
2182
|
+
switch (method) {
|
|
2183
|
+
case "email":
|
|
2184
|
+
return loginWithEmailFlow();
|
|
2185
|
+
case "browser":
|
|
2186
|
+
return browserFlow();
|
|
2187
|
+
case "api-key":
|
|
2188
|
+
return pasteKeyFlow();
|
|
2189
|
+
}
|
|
2190
|
+
}
|
|
1464
2191
|
async function resolveApiKeyWithResult(keyOption) {
|
|
1465
2192
|
const config = readStore();
|
|
1466
2193
|
let apiKey = keyOption || config.apiKey;
|
|
1467
|
-
if (
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
})
|
|
2194
|
+
if (apiKey) {
|
|
2195
|
+
console.log(dim(" Validating key..."));
|
|
2196
|
+
let result;
|
|
2197
|
+
try {
|
|
2198
|
+
result = await validateKey(apiKey);
|
|
2199
|
+
} catch (err) {
|
|
2200
|
+
console.log(chalk4.red(` \u2717 ${err.message}`));
|
|
2201
|
+
console.log(dim(" Get a key at https://piut.com/dashboard/keys"));
|
|
2202
|
+
throw new CliError(err.message);
|
|
2203
|
+
}
|
|
2204
|
+
const label = result.slug ? `${result.displayName} (${result.slug})` : result.displayName;
|
|
2205
|
+
console.log(success(` \u2713 Connected as ${label}`));
|
|
2206
|
+
updateStore({ apiKey });
|
|
2207
|
+
return { apiKey, ...result };
|
|
1473
2208
|
}
|
|
1474
|
-
console.log(dim(" Validating key..."));
|
|
1475
|
-
let result;
|
|
1476
2209
|
try {
|
|
1477
|
-
|
|
2210
|
+
const { apiKey: newKey, validation } = await promptLogin();
|
|
2211
|
+
const label = validation.slug ? `${validation.displayName} (${validation.slug})` : validation.displayName;
|
|
2212
|
+
console.log(success(` \u2713 Connected as ${label}`));
|
|
2213
|
+
updateStore({ apiKey: newKey });
|
|
2214
|
+
return { apiKey: newKey, ...validation };
|
|
1478
2215
|
} catch (err) {
|
|
1479
2216
|
console.log(chalk4.red(` \u2717 ${err.message}`));
|
|
1480
|
-
console.log(dim(" Get a key at https://piut.com/dashboard/keys"));
|
|
1481
2217
|
throw new CliError(err.message);
|
|
1482
2218
|
}
|
|
1483
|
-
const label = result.slug ? `${result.displayName} (${result.slug})` : result.displayName;
|
|
1484
|
-
console.log(success(` \u2713 Connected as ${label}`));
|
|
1485
|
-
updateStore({ apiKey });
|
|
1486
|
-
return { apiKey, ...result };
|
|
1487
2219
|
}
|
|
1488
2220
|
|
|
1489
2221
|
// src/commands/build.ts
|
|
1490
2222
|
async function buildCommand(options) {
|
|
1491
2223
|
banner();
|
|
1492
2224
|
const { apiKey, serverUrl } = await resolveApiKeyWithResult(options.key);
|
|
1493
|
-
let
|
|
2225
|
+
let scanDirs;
|
|
1494
2226
|
if (options.folders) {
|
|
1495
|
-
|
|
1496
|
-
}
|
|
1497
|
-
|
|
1498
|
-
const cwdDisplay = cwd.replace(os6.homedir(), "~");
|
|
1499
|
-
if (!scanFolders) {
|
|
1500
|
-
console.log(dim(` Current directory: `) + cwdDisplay);
|
|
1501
|
-
console.log(dim(` We'll scan for AI config files and projects here.`));
|
|
2227
|
+
scanDirs = options.folders.split(",").map((f) => expandPath(f.trim()));
|
|
2228
|
+
} else {
|
|
2229
|
+
console.log(dim(" \u2501\u2501\u2501 BUILD YOUR BRAIN \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"));
|
|
1502
2230
|
console.log();
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
{ name: "Select folder(s)...", value: "folders", description: "Choose a different directory to scan" }
|
|
1508
|
-
]
|
|
1509
|
-
});
|
|
1510
|
-
if (mode === "cwd") {
|
|
1511
|
-
scanFolders = [cwd];
|
|
1512
|
-
} else {
|
|
1513
|
-
const defaults = getDefaultScanDirs();
|
|
1514
|
-
const CUSTOM_VALUE = "__custom__";
|
|
1515
|
-
const choices = [
|
|
1516
|
-
...defaults.map((d) => ({ name: d.replace(os6.homedir(), "~"), value: d })),
|
|
1517
|
-
{ name: chalk5.dim("Enter a custom path..."), value: CUSTOM_VALUE }
|
|
1518
|
-
];
|
|
1519
|
-
const selected = await checkbox3({
|
|
1520
|
-
message: "Which folders should we scan?",
|
|
1521
|
-
choices,
|
|
1522
|
-
required: true
|
|
1523
|
-
});
|
|
1524
|
-
if (selected.includes(CUSTOM_VALUE)) {
|
|
1525
|
-
const custom = await input({
|
|
1526
|
-
message: "Enter folder path(s), comma-separated:"
|
|
1527
|
-
});
|
|
1528
|
-
const customPaths = custom.split(",").map((f) => expandPath(f.trim())).filter(Boolean);
|
|
1529
|
-
scanFolders = [
|
|
1530
|
-
...selected.filter((v) => v !== CUSTOM_VALUE),
|
|
1531
|
-
...customPaths
|
|
1532
|
-
];
|
|
1533
|
-
} else {
|
|
1534
|
-
scanFolders = selected;
|
|
1535
|
-
}
|
|
1536
|
-
if (scanFolders.length === 0) {
|
|
1537
|
-
console.log(chalk5.yellow(" No folders selected."));
|
|
1538
|
-
return;
|
|
1539
|
-
}
|
|
1540
|
-
}
|
|
2231
|
+
console.log(dim(" This is a local scan only \u2014 no files leave your machine"));
|
|
2232
|
+
console.log(dim(" until you review and explicitly approve."));
|
|
2233
|
+
console.log();
|
|
2234
|
+
scanDirs = await selectFolders();
|
|
1541
2235
|
}
|
|
2236
|
+
if (scanDirs.length === 0) {
|
|
2237
|
+
console.log(chalk5.yellow(" No folders selected."));
|
|
2238
|
+
return;
|
|
2239
|
+
}
|
|
2240
|
+
console.log();
|
|
2241
|
+
console.log(dim(" Scanning locally \u2014 no data is shared..."));
|
|
1542
2242
|
console.log();
|
|
1543
|
-
let
|
|
1544
|
-
let configCount = 0;
|
|
1545
|
-
let docCount = 0;
|
|
2243
|
+
let fileCount = 0;
|
|
1546
2244
|
const onProgress = (progress) => {
|
|
1547
|
-
if (progress.phase === "
|
|
1548
|
-
|
|
1549
|
-
|
|
2245
|
+
if (progress.phase === "scanning") {
|
|
2246
|
+
console.log(dim(` ${progress.message}`));
|
|
2247
|
+
} else if (progress.phase === "parsing") {
|
|
2248
|
+
fileCount++;
|
|
2249
|
+
console.log(dim(` [${fileCount}] ${progress.message}`));
|
|
2250
|
+
} else if (progress.phase === "projects") {
|
|
2251
|
+
console.log(dim(` [project] ${progress.message}`));
|
|
1550
2252
|
} else if (progress.phase === "configs") {
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
2253
|
+
console.log(dim(` [config] ${progress.message}`));
|
|
2254
|
+
}
|
|
2255
|
+
};
|
|
2256
|
+
const scanResult = await scanFolders(scanDirs, onProgress);
|
|
2257
|
+
console.log();
|
|
2258
|
+
console.log(success(` \u2713 Scan complete: ${scanResult.totalFiles} files found across ${scanResult.folders.length} folders (local only)`));
|
|
2259
|
+
console.log();
|
|
2260
|
+
if (scanResult.totalFiles === 0 && scanResult.configFiles.length === 0) {
|
|
2261
|
+
console.log(chalk5.yellow(" No parseable files found in the selected folders."));
|
|
2262
|
+
console.log(dim(" Try scanning a different directory, or use --folders to specify paths."));
|
|
2263
|
+
console.log();
|
|
2264
|
+
return;
|
|
2265
|
+
}
|
|
2266
|
+
let selectedFolderPaths;
|
|
2267
|
+
if (options.yes || scanResult.folders.length === 0) {
|
|
2268
|
+
selectedFolderPaths = scanResult.folders.map((f) => f.path);
|
|
2269
|
+
} else {
|
|
2270
|
+
selectedFolderPaths = await reviewFolders(scanResult);
|
|
2271
|
+
}
|
|
2272
|
+
if (selectedFolderPaths.length === 0 && scanResult.configFiles.length === 0) {
|
|
2273
|
+
console.log(chalk5.yellow(" No folders selected."));
|
|
2274
|
+
return;
|
|
2275
|
+
}
|
|
2276
|
+
const selectedFolders = scanResult.folders.filter((f) => selectedFolderPaths.includes(f.path));
|
|
2277
|
+
const totalSelectedFiles = selectedFolders.reduce((sum, f) => sum + f.fileCount, 0) + scanResult.configFiles.length;
|
|
2278
|
+
if (!options.yes) {
|
|
2279
|
+
console.log();
|
|
2280
|
+
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"));
|
|
2281
|
+
console.log();
|
|
2282
|
+
console.log(dim(` ${totalSelectedFiles} files will be sent to p\u0131ut and processed by Claude`));
|
|
2283
|
+
console.log(dim(" Sonnet to design your brain. File contents are used for"));
|
|
2284
|
+
console.log(dim(" brain generation only and are not retained."));
|
|
2285
|
+
console.log();
|
|
2286
|
+
console.log(dim(` Privacy policy: ${brand("https://piut.com/privacy")}`));
|
|
2287
|
+
console.log();
|
|
2288
|
+
const consent = await confirm3({
|
|
2289
|
+
message: "Send files and build your brain?",
|
|
2290
|
+
default: false
|
|
2291
|
+
});
|
|
2292
|
+
if (!consent) {
|
|
2293
|
+
console.log();
|
|
2294
|
+
console.log(dim(" Build cancelled. No files were sent."));
|
|
2295
|
+
console.log();
|
|
2296
|
+
return;
|
|
1556
2297
|
}
|
|
1557
|
-
};
|
|
1558
|
-
const brainInput = scanForBrain(scanFolders, onProgress);
|
|
1559
|
-
const projCount = brainInput.summary.projects.length;
|
|
1560
|
-
const cfgCount = brainInput.summary.configFiles.length;
|
|
1561
|
-
const dcCount = brainInput.summary.recentDocuments.length;
|
|
1562
|
-
console.log();
|
|
1563
|
-
console.log(success(` Scanned: ${projCount} projects, ${cfgCount} config files, ${dcCount} recent docs`));
|
|
1564
|
-
console.log();
|
|
1565
|
-
if (projCount === 0 && cfgCount === 0) {
|
|
1566
|
-
console.log(chalk5.yellow(" No projects or config files found to build from."));
|
|
1567
|
-
console.log(dim(" Try running from a directory with your projects, or use --folders."));
|
|
1568
|
-
console.log();
|
|
1569
|
-
return;
|
|
1570
2298
|
}
|
|
2299
|
+
const brainInput = buildBrainInput(scanResult, selectedFolderPaths);
|
|
1571
2300
|
const spinner = new Spinner();
|
|
1572
2301
|
spinner.start("Generating brain...");
|
|
1573
2302
|
try {
|
|
@@ -1672,6 +2401,78 @@ async function buildCommand(options) {
|
|
|
1672
2401
|
throw new CliError(hint);
|
|
1673
2402
|
}
|
|
1674
2403
|
}
|
|
2404
|
+
async function selectFolders() {
|
|
2405
|
+
const defaults = getDefaultScanDirs();
|
|
2406
|
+
const homeDir = os8.homedir();
|
|
2407
|
+
const ALL_VALUE = "__all__";
|
|
2408
|
+
const CUSTOM_VALUE = "__custom__";
|
|
2409
|
+
const choices = [
|
|
2410
|
+
{ name: `All home folders (~) ${chalk5.dim("(Recommended)")}`, value: ALL_VALUE },
|
|
2411
|
+
{ name: chalk5.dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"), value: "__sep__", disabled: true },
|
|
2412
|
+
...defaults.map((d) => ({ name: displayPath(d), value: d })),
|
|
2413
|
+
{ name: chalk5.dim("Browse to a folder..."), value: CUSTOM_VALUE }
|
|
2414
|
+
];
|
|
2415
|
+
const selected = await checkbox3({
|
|
2416
|
+
message: "Select folders to scan:",
|
|
2417
|
+
choices,
|
|
2418
|
+
required: true
|
|
2419
|
+
});
|
|
2420
|
+
let scanDirs;
|
|
2421
|
+
if (selected.includes(ALL_VALUE)) {
|
|
2422
|
+
scanDirs = defaults;
|
|
2423
|
+
} else {
|
|
2424
|
+
scanDirs = selected.filter((v) => v !== CUSTOM_VALUE);
|
|
2425
|
+
if (selected.includes(CUSTOM_VALUE)) {
|
|
2426
|
+
const custom = await input2({
|
|
2427
|
+
message: "Enter folder path(s), comma-separated:"
|
|
2428
|
+
});
|
|
2429
|
+
const customPaths = custom.split(",").map((f) => expandPath(f.trim())).filter(Boolean);
|
|
2430
|
+
scanDirs = [...scanDirs, ...customPaths];
|
|
2431
|
+
}
|
|
2432
|
+
}
|
|
2433
|
+
if (scanDirs.length > 0 && !selected.includes(ALL_VALUE)) {
|
|
2434
|
+
console.log();
|
|
2435
|
+
console.log(dim(" Selected:"));
|
|
2436
|
+
for (const d of scanDirs) {
|
|
2437
|
+
console.log(dim(` ${displayPath(d)}`));
|
|
2438
|
+
}
|
|
2439
|
+
const addMore = await select2({
|
|
2440
|
+
message: "Add more folders or continue?",
|
|
2441
|
+
choices: [
|
|
2442
|
+
{ name: "Continue with scan", value: "continue" },
|
|
2443
|
+
{ name: "Add another folder...", value: "add" }
|
|
2444
|
+
]
|
|
2445
|
+
});
|
|
2446
|
+
if (addMore === "add") {
|
|
2447
|
+
const extra = await input2({
|
|
2448
|
+
message: "Enter folder path(s), comma-separated:"
|
|
2449
|
+
});
|
|
2450
|
+
const extraPaths = extra.split(",").map((f) => expandPath(f.trim())).filter(Boolean);
|
|
2451
|
+
scanDirs = [...scanDirs, ...extraPaths];
|
|
2452
|
+
}
|
|
2453
|
+
}
|
|
2454
|
+
return scanDirs;
|
|
2455
|
+
}
|
|
2456
|
+
async function reviewFolders(scanResult) {
|
|
2457
|
+
if (scanResult.folders.length === 0) return [];
|
|
2458
|
+
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"));
|
|
2459
|
+
console.log();
|
|
2460
|
+
console.log(dim(" All folders selected by default. Deselect any you want to exclude."));
|
|
2461
|
+
console.log();
|
|
2462
|
+
const choices = scanResult.folders.map((folder) => ({
|
|
2463
|
+
name: formatFolderChoice(folder),
|
|
2464
|
+
value: folder.path,
|
|
2465
|
+
checked: true
|
|
2466
|
+
}));
|
|
2467
|
+
const selected = await checkbox3({
|
|
2468
|
+
message: "Select folders to include in your brain:",
|
|
2469
|
+
choices
|
|
2470
|
+
});
|
|
2471
|
+
const selectedFolders = scanResult.folders.filter((f) => selected.includes(f.path));
|
|
2472
|
+
console.log();
|
|
2473
|
+
console.log(dim(` ${formatSelectionSummary(selectedFolders)}`));
|
|
2474
|
+
return selected;
|
|
2475
|
+
}
|
|
1675
2476
|
|
|
1676
2477
|
// src/commands/deploy.ts
|
|
1677
2478
|
import chalk6 from "chalk";
|
|
@@ -1711,33 +2512,33 @@ async function deployCommand(options) {
|
|
|
1711
2512
|
}
|
|
1712
2513
|
|
|
1713
2514
|
// src/commands/connect.ts
|
|
1714
|
-
import
|
|
1715
|
-
import
|
|
2515
|
+
import fs11 from "fs";
|
|
2516
|
+
import path13 from "path";
|
|
1716
2517
|
import { checkbox as checkbox4 } from "@inquirer/prompts";
|
|
1717
2518
|
var RULE_FILES = [
|
|
1718
2519
|
{
|
|
1719
2520
|
tool: "Claude Code",
|
|
1720
2521
|
filePath: "CLAUDE.md",
|
|
1721
2522
|
strategy: "append",
|
|
1722
|
-
detect: (p) => p.hasClaudeMd ||
|
|
2523
|
+
detect: (p) => p.hasClaudeMd || fs11.existsSync(path13.join(p.path, ".claude"))
|
|
1723
2524
|
},
|
|
1724
2525
|
{
|
|
1725
2526
|
tool: "Cursor",
|
|
1726
2527
|
filePath: ".cursor/rules/piut.mdc",
|
|
1727
2528
|
strategy: "create",
|
|
1728
|
-
detect: (p) => p.hasCursorRules ||
|
|
2529
|
+
detect: (p) => p.hasCursorRules || fs11.existsSync(path13.join(p.path, ".cursor"))
|
|
1729
2530
|
},
|
|
1730
2531
|
{
|
|
1731
2532
|
tool: "Windsurf",
|
|
1732
2533
|
filePath: ".windsurf/rules/piut.md",
|
|
1733
2534
|
strategy: "create",
|
|
1734
|
-
detect: (p) => p.hasWindsurfRules ||
|
|
2535
|
+
detect: (p) => p.hasWindsurfRules || fs11.existsSync(path13.join(p.path, ".windsurf"))
|
|
1735
2536
|
},
|
|
1736
2537
|
{
|
|
1737
2538
|
tool: "GitHub Copilot",
|
|
1738
2539
|
filePath: ".github/copilot-instructions.md",
|
|
1739
2540
|
strategy: "append",
|
|
1740
|
-
detect: (p) => p.hasCopilotInstructions ||
|
|
2541
|
+
detect: (p) => p.hasCopilotInstructions || fs11.existsSync(path13.join(p.path, ".github"))
|
|
1741
2542
|
},
|
|
1742
2543
|
{
|
|
1743
2544
|
tool: "Amazon Q",
|
|
@@ -1749,7 +2550,19 @@ var RULE_FILES = [
|
|
|
1749
2550
|
tool: "Zed",
|
|
1750
2551
|
filePath: ".zed/rules.md",
|
|
1751
2552
|
strategy: "create",
|
|
1752
|
-
detect: (p) => p.hasZedRules ||
|
|
2553
|
+
detect: (p) => p.hasZedRules || fs11.existsSync(path13.join(p.path, ".zed"))
|
|
2554
|
+
},
|
|
2555
|
+
{
|
|
2556
|
+
tool: "Gemini CLI",
|
|
2557
|
+
filePath: "GEMINI.md",
|
|
2558
|
+
strategy: "append",
|
|
2559
|
+
detect: (p) => fs11.existsSync(path13.join(p.path, ".gemini"))
|
|
2560
|
+
},
|
|
2561
|
+
{
|
|
2562
|
+
tool: "Paperclip",
|
|
2563
|
+
filePath: "AGENTS.md",
|
|
2564
|
+
strategy: "append",
|
|
2565
|
+
detect: (p) => fs11.existsSync(path13.join(p.path, ".paperclip"))
|
|
1753
2566
|
}
|
|
1754
2567
|
];
|
|
1755
2568
|
var DEDICATED_FILE_CONTENT = `## p\u0131ut Context (MCP Server: piut-context)
|
|
@@ -1784,7 +2597,7 @@ Full skill reference: .piut/skill.md
|
|
|
1784
2597
|
`;
|
|
1785
2598
|
function hasPiutReference2(filePath) {
|
|
1786
2599
|
try {
|
|
1787
|
-
const content =
|
|
2600
|
+
const content = fs11.readFileSync(filePath, "utf-8");
|
|
1788
2601
|
return content.includes("p\u0131ut Context") || content.includes("piut Context");
|
|
1789
2602
|
} catch {
|
|
1790
2603
|
return false;
|
|
@@ -1807,13 +2620,13 @@ async function connectCommand(options) {
|
|
|
1807
2620
|
console.log();
|
|
1808
2621
|
return;
|
|
1809
2622
|
}
|
|
1810
|
-
let
|
|
2623
|
+
let scanFolders2;
|
|
1811
2624
|
if (options.folders) {
|
|
1812
|
-
|
|
2625
|
+
scanFolders2 = options.folders.split(",").map((f) => expandPath(f.trim()));
|
|
1813
2626
|
}
|
|
1814
2627
|
console.log();
|
|
1815
2628
|
console.log(dim(" Scanning for projects..."));
|
|
1816
|
-
const projects = scanForProjects(
|
|
2629
|
+
const projects = scanForProjects(scanFolders2);
|
|
1817
2630
|
if (projects.length === 0) {
|
|
1818
2631
|
console.log(warning(" No projects found."));
|
|
1819
2632
|
console.log(dim(" Try running from a directory with your projects, or use --folders."));
|
|
@@ -1824,20 +2637,20 @@ async function connectCommand(options) {
|
|
|
1824
2637
|
for (const project of projects) {
|
|
1825
2638
|
for (const rule of RULE_FILES) {
|
|
1826
2639
|
if (!rule.detect(project)) continue;
|
|
1827
|
-
const absPath =
|
|
1828
|
-
if (
|
|
2640
|
+
const absPath = path13.join(project.path, rule.filePath);
|
|
2641
|
+
if (fs11.existsSync(absPath) && hasPiutReference2(absPath)) continue;
|
|
1829
2642
|
actions.push({
|
|
1830
2643
|
project,
|
|
1831
2644
|
tool: rule.tool,
|
|
1832
2645
|
filePath: rule.filePath,
|
|
1833
2646
|
absPath,
|
|
1834
|
-
action: rule.strategy === "create" || !
|
|
2647
|
+
action: rule.strategy === "create" || !fs11.existsSync(absPath) ? "create" : "append"
|
|
1835
2648
|
});
|
|
1836
2649
|
}
|
|
1837
2650
|
const hasAnyAction = actions.some((a) => a.project === project);
|
|
1838
2651
|
if (!hasAnyAction) {
|
|
1839
|
-
const claudeMdPath =
|
|
1840
|
-
if (!
|
|
2652
|
+
const claudeMdPath = path13.join(project.path, "CLAUDE.md");
|
|
2653
|
+
if (!fs11.existsSync(claudeMdPath)) {
|
|
1841
2654
|
actions.push({
|
|
1842
2655
|
project,
|
|
1843
2656
|
tool: "Claude Code",
|
|
@@ -1877,7 +2690,7 @@ async function connectCommand(options) {
|
|
|
1877
2690
|
console.log();
|
|
1878
2691
|
const projectChoices = [];
|
|
1879
2692
|
for (const [projectPath, projectActions] of byProject) {
|
|
1880
|
-
const projectName =
|
|
2693
|
+
const projectName = path13.basename(projectPath);
|
|
1881
2694
|
const desc = projectActions.map((a) => {
|
|
1882
2695
|
const verb = a.action === "create" ? "will create" : "will append to";
|
|
1883
2696
|
return `${verb} ${a.filePath}`;
|
|
@@ -1906,15 +2719,15 @@ async function connectCommand(options) {
|
|
|
1906
2719
|
const copilotTool = TOOLS.find((t) => t.id === "copilot");
|
|
1907
2720
|
for (const projectPath of selectedPaths) {
|
|
1908
2721
|
const projectActions = byProject.get(projectPath) || [];
|
|
1909
|
-
const projectName =
|
|
2722
|
+
const projectName = path13.basename(projectPath);
|
|
1910
2723
|
writePiutConfig(projectPath, { slug, apiKey, serverUrl });
|
|
1911
2724
|
await writePiutSkill(projectPath, slug, apiKey);
|
|
1912
2725
|
ensureGitignored(projectPath);
|
|
1913
2726
|
console.log(success(` \u2713 ${projectName}/.piut/`) + dim(" \u2014 credentials + skill"));
|
|
1914
2727
|
if (copilotTool) {
|
|
1915
|
-
const hasCopilot =
|
|
2728
|
+
const hasCopilot = fs11.existsSync(path13.join(projectPath, ".github", "copilot-instructions.md")) || fs11.existsSync(path13.join(projectPath, ".github"));
|
|
1916
2729
|
if (hasCopilot) {
|
|
1917
|
-
const vscodeMcpPath =
|
|
2730
|
+
const vscodeMcpPath = path13.join(projectPath, ".vscode", "mcp.json");
|
|
1918
2731
|
const serverConfig = copilotTool.generateConfig(slug, apiKey);
|
|
1919
2732
|
mergeConfig(vscodeMcpPath, copilotTool.configKey, serverConfig);
|
|
1920
2733
|
console.log(success(` \u2713 ${projectName}/.vscode/mcp.json`) + dim(" \u2014 Copilot MCP"));
|
|
@@ -1924,26 +2737,28 @@ async function connectCommand(options) {
|
|
|
1924
2737
|
if (action.action === "create") {
|
|
1925
2738
|
const isAppendType = RULE_FILES.find((r) => r.filePath === action.filePath)?.strategy === "append";
|
|
1926
2739
|
const content = isAppendType ? PROJECT_SKILL_SNIPPET + "\n" : DEDICATED_FILE_CONTENT;
|
|
1927
|
-
|
|
1928
|
-
|
|
2740
|
+
fs11.mkdirSync(path13.dirname(action.absPath), { recursive: true });
|
|
2741
|
+
fs11.writeFileSync(action.absPath, content, "utf-8");
|
|
1929
2742
|
console.log(success(` \u2713 ${projectName}/${action.filePath}`) + dim(" \u2014 created"));
|
|
1930
2743
|
} else {
|
|
1931
|
-
|
|
2744
|
+
fs11.appendFileSync(action.absPath, APPEND_SECTION);
|
|
1932
2745
|
console.log(success(` \u2713 ${projectName}/${action.filePath}`) + dim(" \u2014 appended"));
|
|
1933
2746
|
}
|
|
1934
2747
|
connected++;
|
|
1935
2748
|
}
|
|
1936
2749
|
}
|
|
1937
2750
|
const machineId = getMachineId();
|
|
2751
|
+
const hostname = getHostname();
|
|
1938
2752
|
for (const projectPath of selectedPaths) {
|
|
1939
2753
|
const projectActions = byProject.get(projectPath) || [];
|
|
1940
|
-
const projectName =
|
|
2754
|
+
const projectName = path13.basename(projectPath);
|
|
1941
2755
|
const toolsDetected = [...new Set(projectActions.map((a) => a.tool))];
|
|
1942
2756
|
const configFilesWritten = projectActions.map((a) => a.filePath);
|
|
1943
2757
|
registerProject(apiKey, {
|
|
1944
2758
|
projectName,
|
|
1945
2759
|
projectPath,
|
|
1946
2760
|
machineId,
|
|
2761
|
+
hostname,
|
|
1947
2762
|
toolsDetected,
|
|
1948
2763
|
configFiles: configFilesWritten
|
|
1949
2764
|
}).catch(() => {
|
|
@@ -1955,8 +2770,8 @@ async function connectCommand(options) {
|
|
|
1955
2770
|
}
|
|
1956
2771
|
|
|
1957
2772
|
// src/commands/disconnect.ts
|
|
1958
|
-
import
|
|
1959
|
-
import
|
|
2773
|
+
import fs12 from "fs";
|
|
2774
|
+
import path14 from "path";
|
|
1960
2775
|
import { checkbox as checkbox5, confirm as confirm5 } from "@inquirer/prompts";
|
|
1961
2776
|
var DEDICATED_FILES = /* @__PURE__ */ new Set([
|
|
1962
2777
|
".cursor/rules/piut.mdc",
|
|
@@ -1966,11 +2781,13 @@ var DEDICATED_FILES = /* @__PURE__ */ new Set([
|
|
|
1966
2781
|
var APPEND_FILES = [
|
|
1967
2782
|
"CLAUDE.md",
|
|
1968
2783
|
".github/copilot-instructions.md",
|
|
1969
|
-
"CONVENTIONS.md"
|
|
2784
|
+
"CONVENTIONS.md",
|
|
2785
|
+
"GEMINI.md",
|
|
2786
|
+
"AGENTS.md"
|
|
1970
2787
|
];
|
|
1971
2788
|
function hasPiutReference3(filePath) {
|
|
1972
2789
|
try {
|
|
1973
|
-
const content =
|
|
2790
|
+
const content = fs12.readFileSync(filePath, "utf-8");
|
|
1974
2791
|
return content.includes("p\u0131ut Context") || content.includes("piut Context");
|
|
1975
2792
|
} catch {
|
|
1976
2793
|
return false;
|
|
@@ -1978,7 +2795,7 @@ function hasPiutReference3(filePath) {
|
|
|
1978
2795
|
}
|
|
1979
2796
|
function removePiutSection(filePath) {
|
|
1980
2797
|
try {
|
|
1981
|
-
let content =
|
|
2798
|
+
let content = fs12.readFileSync(filePath, "utf-8");
|
|
1982
2799
|
const patterns = [
|
|
1983
2800
|
/\n*## p[ıi]ut Context[\s\S]*?(?=\n## |\n---\n|$)/g
|
|
1984
2801
|
];
|
|
@@ -1992,7 +2809,7 @@ function removePiutSection(filePath) {
|
|
|
1992
2809
|
}
|
|
1993
2810
|
if (changed) {
|
|
1994
2811
|
content = content.replace(/\n{3,}/g, "\n\n").trimEnd() + "\n";
|
|
1995
|
-
|
|
2812
|
+
fs12.writeFileSync(filePath, content, "utf-8");
|
|
1996
2813
|
}
|
|
1997
2814
|
return changed;
|
|
1998
2815
|
} catch {
|
|
@@ -2001,18 +2818,18 @@ function removePiutSection(filePath) {
|
|
|
2001
2818
|
}
|
|
2002
2819
|
async function disconnectCommand(options) {
|
|
2003
2820
|
banner();
|
|
2004
|
-
let
|
|
2821
|
+
let scanFolders2;
|
|
2005
2822
|
if (options.folders) {
|
|
2006
|
-
|
|
2823
|
+
scanFolders2 = options.folders.split(",").map((f) => expandPath(f.trim()));
|
|
2007
2824
|
}
|
|
2008
2825
|
console.log(dim(" Scanning for connected projects..."));
|
|
2009
|
-
const projects = scanForProjects(
|
|
2826
|
+
const projects = scanForProjects(scanFolders2);
|
|
2010
2827
|
const actions = [];
|
|
2011
2828
|
for (const project of projects) {
|
|
2012
|
-
const projectName =
|
|
2829
|
+
const projectName = path14.basename(project.path);
|
|
2013
2830
|
for (const dedicatedFile of DEDICATED_FILES) {
|
|
2014
|
-
const absPath =
|
|
2015
|
-
if (
|
|
2831
|
+
const absPath = path14.join(project.path, dedicatedFile);
|
|
2832
|
+
if (fs12.existsSync(absPath) && hasPiutReference3(absPath)) {
|
|
2016
2833
|
actions.push({
|
|
2017
2834
|
projectPath: project.path,
|
|
2018
2835
|
projectName,
|
|
@@ -2023,8 +2840,8 @@ async function disconnectCommand(options) {
|
|
|
2023
2840
|
}
|
|
2024
2841
|
}
|
|
2025
2842
|
for (const appendFile of APPEND_FILES) {
|
|
2026
|
-
const absPath =
|
|
2027
|
-
if (
|
|
2843
|
+
const absPath = path14.join(project.path, appendFile);
|
|
2844
|
+
if (fs12.existsSync(absPath) && hasPiutReference3(absPath)) {
|
|
2028
2845
|
actions.push({
|
|
2029
2846
|
projectPath: project.path,
|
|
2030
2847
|
projectName,
|
|
@@ -2039,12 +2856,12 @@ async function disconnectCommand(options) {
|
|
|
2039
2856
|
projectPath: project.path,
|
|
2040
2857
|
projectName,
|
|
2041
2858
|
filePath: ".piut/",
|
|
2042
|
-
absPath:
|
|
2859
|
+
absPath: path14.join(project.path, ".piut"),
|
|
2043
2860
|
action: "remove-dir"
|
|
2044
2861
|
});
|
|
2045
2862
|
}
|
|
2046
|
-
const vscodeMcpPath =
|
|
2047
|
-
if (
|
|
2863
|
+
const vscodeMcpPath = path14.join(project.path, ".vscode", "mcp.json");
|
|
2864
|
+
if (fs12.existsSync(vscodeMcpPath) && isPiutConfigured(vscodeMcpPath, "servers")) {
|
|
2048
2865
|
actions.push({
|
|
2049
2866
|
projectPath: project.path,
|
|
2050
2867
|
projectName,
|
|
@@ -2066,7 +2883,7 @@ async function disconnectCommand(options) {
|
|
|
2066
2883
|
}
|
|
2067
2884
|
console.log();
|
|
2068
2885
|
const projectChoices = Array.from(byProject.entries()).map(([projectPath, projectActions]) => {
|
|
2069
|
-
const name =
|
|
2886
|
+
const name = path14.basename(projectPath);
|
|
2070
2887
|
const files = projectActions.map((a) => a.filePath).join(", ");
|
|
2071
2888
|
return {
|
|
2072
2889
|
name: `${name} ${dim(`(${files})`)}`,
|
|
@@ -2095,11 +2912,11 @@ async function disconnectCommand(options) {
|
|
|
2095
2912
|
let disconnected = 0;
|
|
2096
2913
|
for (const projectPath of selectedPaths) {
|
|
2097
2914
|
const projectActions = byProject.get(projectPath) || [];
|
|
2098
|
-
const projectName =
|
|
2915
|
+
const projectName = path14.basename(projectPath);
|
|
2099
2916
|
for (const action of projectActions) {
|
|
2100
2917
|
if (action.action === "delete") {
|
|
2101
2918
|
try {
|
|
2102
|
-
|
|
2919
|
+
fs12.unlinkSync(action.absPath);
|
|
2103
2920
|
console.log(success(` \u2713 ${projectName}/${action.filePath}`) + dim(" \u2014 deleted"));
|
|
2104
2921
|
disconnected++;
|
|
2105
2922
|
} catch {
|
|
@@ -2142,6 +2959,21 @@ async function disconnectCommand(options) {
|
|
|
2142
2959
|
console.log();
|
|
2143
2960
|
}
|
|
2144
2961
|
|
|
2962
|
+
// src/commands/login.ts
|
|
2963
|
+
import chalk7 from "chalk";
|
|
2964
|
+
async function loginCommand() {
|
|
2965
|
+
try {
|
|
2966
|
+
const { apiKey, validation } = await promptLogin();
|
|
2967
|
+
const label = validation.slug ? `${validation.displayName} (${validation.slug})` : validation.displayName;
|
|
2968
|
+
console.log(success(` \u2713 Connected as ${label}`));
|
|
2969
|
+
updateStore({ apiKey });
|
|
2970
|
+
console.log(dim(" API key saved. You can now use all p\u0131ut commands."));
|
|
2971
|
+
} catch (err) {
|
|
2972
|
+
console.log(chalk7.red(` \u2717 ${err.message}`));
|
|
2973
|
+
throw new CliError(err.message);
|
|
2974
|
+
}
|
|
2975
|
+
}
|
|
2976
|
+
|
|
2145
2977
|
// src/commands/logout.ts
|
|
2146
2978
|
async function logoutCommand() {
|
|
2147
2979
|
banner();
|
|
@@ -2159,11 +2991,11 @@ async function logoutCommand() {
|
|
|
2159
2991
|
}
|
|
2160
2992
|
|
|
2161
2993
|
// src/commands/update.ts
|
|
2162
|
-
import
|
|
2994
|
+
import chalk9 from "chalk";
|
|
2163
2995
|
|
|
2164
2996
|
// src/lib/update-check.ts
|
|
2165
2997
|
import { execFile } from "child_process";
|
|
2166
|
-
import
|
|
2998
|
+
import chalk8 from "chalk";
|
|
2167
2999
|
import { confirm as confirm6 } from "@inquirer/prompts";
|
|
2168
3000
|
var PACKAGE_NAME = "@piut/cli";
|
|
2169
3001
|
function isNpx() {
|
|
@@ -2200,7 +3032,7 @@ async function checkForUpdate(currentVersion) {
|
|
|
2200
3032
|
const updateCmd = npx ? `npx ${PACKAGE_NAME}@latest` : `npm install -g ${PACKAGE_NAME}@latest`;
|
|
2201
3033
|
console.log();
|
|
2202
3034
|
console.log(brand(" Update available!") + dim(` ${currentVersion} \u2192 ${latest}`));
|
|
2203
|
-
console.log(dim(` Run ${
|
|
3035
|
+
console.log(dim(` Run ${chalk8.bold(updateCmd)} to update`));
|
|
2204
3036
|
console.log();
|
|
2205
3037
|
if (npx) return;
|
|
2206
3038
|
try {
|
|
@@ -2212,12 +3044,12 @@ async function checkForUpdate(currentVersion) {
|
|
|
2212
3044
|
console.log(dim(" Updating..."));
|
|
2213
3045
|
const ok = await runUpdate();
|
|
2214
3046
|
if (ok) {
|
|
2215
|
-
console.log(
|
|
3047
|
+
console.log(chalk8.green(` \u2713 Updated to v${latest}`));
|
|
2216
3048
|
console.log(dim(" Restart the CLI to use the new version."));
|
|
2217
3049
|
process.exit(0);
|
|
2218
3050
|
} else {
|
|
2219
|
-
console.log(
|
|
2220
|
-
console.log(
|
|
3051
|
+
console.log(chalk8.yellow(` Could not auto-update. Run manually:`));
|
|
3052
|
+
console.log(chalk8.bold(` npm install -g ${PACKAGE_NAME}@latest`));
|
|
2221
3053
|
console.log();
|
|
2222
3054
|
}
|
|
2223
3055
|
}
|
|
@@ -2232,7 +3064,7 @@ async function updateCommand(currentVersion) {
|
|
|
2232
3064
|
console.log(dim(" Checking for updates..."));
|
|
2233
3065
|
const latest = await getLatestVersion();
|
|
2234
3066
|
if (!latest) {
|
|
2235
|
-
console.log(
|
|
3067
|
+
console.log(chalk9.yellow(" Could not reach the npm registry. Check your connection."));
|
|
2236
3068
|
return;
|
|
2237
3069
|
}
|
|
2238
3070
|
if (!isNewer(currentVersion, latest)) {
|
|
@@ -2244,7 +3076,7 @@ async function updateCommand(currentVersion) {
|
|
|
2244
3076
|
if (isNpx()) {
|
|
2245
3077
|
console.log();
|
|
2246
3078
|
console.log(dim(" You're running via npx. Use the latest version with:"));
|
|
2247
|
-
console.log(
|
|
3079
|
+
console.log(chalk9.bold(` npx ${PACKAGE_NAME2}@latest`));
|
|
2248
3080
|
console.log();
|
|
2249
3081
|
return;
|
|
2250
3082
|
}
|
|
@@ -2254,14 +3086,14 @@ async function updateCommand(currentVersion) {
|
|
|
2254
3086
|
console.log(success(` \u2713 Updated to v${latest}`));
|
|
2255
3087
|
console.log(dim(" Restart the CLI to use the new version."));
|
|
2256
3088
|
} else {
|
|
2257
|
-
console.log(
|
|
2258
|
-
console.log(
|
|
3089
|
+
console.log(chalk9.yellow(" Could not auto-update. Run manually:"));
|
|
3090
|
+
console.log(chalk9.bold(` npm install -g ${PACKAGE_NAME2}@latest`));
|
|
2259
3091
|
}
|
|
2260
3092
|
}
|
|
2261
3093
|
|
|
2262
3094
|
// src/commands/doctor.ts
|
|
2263
|
-
import
|
|
2264
|
-
import
|
|
3095
|
+
import fs13 from "fs";
|
|
3096
|
+
import chalk10 from "chalk";
|
|
2265
3097
|
async function doctorCommand(options) {
|
|
2266
3098
|
if (!options.json) banner();
|
|
2267
3099
|
const result = {
|
|
@@ -2312,7 +3144,7 @@ async function doctorCommand(options) {
|
|
|
2312
3144
|
for (const tool of TOOLS) {
|
|
2313
3145
|
const paths = resolveConfigPaths(tool.configPaths);
|
|
2314
3146
|
for (const configPath of paths) {
|
|
2315
|
-
if (!
|
|
3147
|
+
if (!fs13.existsSync(configPath)) continue;
|
|
2316
3148
|
const piutConfig = getPiutConfig(configPath, tool.configKey);
|
|
2317
3149
|
if (!piutConfig) {
|
|
2318
3150
|
result.tools.push({
|
|
@@ -2417,59 +3249,155 @@ async function doctorCommand(options) {
|
|
|
2417
3249
|
const staleTools = result.tools.filter((t) => t.keyMatch === "stale" && !t.fixed);
|
|
2418
3250
|
if (staleTools.length > 0 && !options.fix) {
|
|
2419
3251
|
console.log();
|
|
2420
|
-
console.log(dim(" Fix stale configs: ") +
|
|
3252
|
+
console.log(dim(" Fix stale configs: ") + chalk10.cyan("piut doctor --fix"));
|
|
2421
3253
|
}
|
|
2422
3254
|
if (!result.key.valid) {
|
|
2423
|
-
console.log(dim(" Set a valid key: ") +
|
|
3255
|
+
console.log(dim(" Set a valid key: ") + chalk10.cyan("piut setup --key pb_YOUR_KEY"));
|
|
2424
3256
|
}
|
|
2425
3257
|
}
|
|
2426
3258
|
console.log();
|
|
2427
3259
|
if (result.issues > 0) throw new CliError(`${result.issues} issue(s) found`);
|
|
2428
3260
|
}
|
|
2429
3261
|
|
|
3262
|
+
// src/commands/vault.ts
|
|
3263
|
+
import fs14 from "fs";
|
|
3264
|
+
import path15 from "path";
|
|
3265
|
+
import chalk11 from "chalk";
|
|
3266
|
+
import { confirm as confirm7 } from "@inquirer/prompts";
|
|
3267
|
+
function formatSize2(bytes) {
|
|
3268
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
3269
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
3270
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
3271
|
+
}
|
|
3272
|
+
function resolveApiKey(options) {
|
|
3273
|
+
const key = options.key || readStore().apiKey;
|
|
3274
|
+
if (!key) {
|
|
3275
|
+
console.log(warning(" No API key found."));
|
|
3276
|
+
console.log(dim(" Run ") + brand("piut login") + dim(" first, or pass --key."));
|
|
3277
|
+
throw new CliError();
|
|
3278
|
+
}
|
|
3279
|
+
return key;
|
|
3280
|
+
}
|
|
3281
|
+
async function vaultListCommand(options) {
|
|
3282
|
+
const key = resolveApiKey(options);
|
|
3283
|
+
const data = await listVaultFiles(key);
|
|
3284
|
+
if (data.files.length === 0) {
|
|
3285
|
+
console.log(dim(" No files in vault."));
|
|
3286
|
+
console.log(dim(" Upload with: ") + brand("piut vault upload <file>"));
|
|
3287
|
+
console.log();
|
|
3288
|
+
return;
|
|
3289
|
+
}
|
|
3290
|
+
console.log();
|
|
3291
|
+
for (const file of data.files) {
|
|
3292
|
+
const size = dim(`(${formatSize2(file.sizeBytes)})`);
|
|
3293
|
+
console.log(` ${file.filename} ${size}`);
|
|
3294
|
+
if (file.summary) {
|
|
3295
|
+
console.log(dim(` ${file.summary}`));
|
|
3296
|
+
}
|
|
3297
|
+
}
|
|
3298
|
+
console.log();
|
|
3299
|
+
console.log(dim(` ${data.usage.fileCount} file(s), ${formatSize2(data.usage.totalBytes)} / ${formatSize2(data.usage.maxBytes)} used`));
|
|
3300
|
+
console.log();
|
|
3301
|
+
}
|
|
3302
|
+
async function vaultUploadCommand(filePath, options) {
|
|
3303
|
+
const key = resolveApiKey(options);
|
|
3304
|
+
const resolved = path15.resolve(filePath);
|
|
3305
|
+
if (!fs14.existsSync(resolved)) {
|
|
3306
|
+
console.log(chalk11.red(` File not found: ${filePath}`));
|
|
3307
|
+
throw new CliError();
|
|
3308
|
+
}
|
|
3309
|
+
const stat = fs14.statSync(resolved);
|
|
3310
|
+
if (!stat.isFile()) {
|
|
3311
|
+
console.log(chalk11.red(` Not a file: ${filePath}`));
|
|
3312
|
+
throw new CliError();
|
|
3313
|
+
}
|
|
3314
|
+
const filename = path15.basename(resolved);
|
|
3315
|
+
const content = fs14.readFileSync(resolved, "utf-8");
|
|
3316
|
+
const spinner = new Spinner();
|
|
3317
|
+
spinner.start(`Uploading ${filename}...`);
|
|
3318
|
+
try {
|
|
3319
|
+
const result = await uploadVaultFile(key, filename, content);
|
|
3320
|
+
spinner.stop();
|
|
3321
|
+
console.log(success(` Uploaded ${result.filename}`) + dim(` (${formatSize2(result.sizeBytes)})`));
|
|
3322
|
+
if (result.summary) {
|
|
3323
|
+
console.log(dim(` ${result.summary}`));
|
|
3324
|
+
}
|
|
3325
|
+
console.log();
|
|
3326
|
+
} catch (err) {
|
|
3327
|
+
spinner.stop();
|
|
3328
|
+
console.log(chalk11.red(` ${err.message}`));
|
|
3329
|
+
throw new CliError();
|
|
3330
|
+
}
|
|
3331
|
+
}
|
|
3332
|
+
async function vaultReadCommand(filename, options) {
|
|
3333
|
+
const key = resolveApiKey(options);
|
|
3334
|
+
try {
|
|
3335
|
+
const file = await readVaultFile(key, filename);
|
|
3336
|
+
if (options.output) {
|
|
3337
|
+
const outPath = path15.resolve(options.output);
|
|
3338
|
+
fs14.writeFileSync(outPath, file.content, "utf-8");
|
|
3339
|
+
console.log(success(` Saved to ${outPath}`));
|
|
3340
|
+
console.log();
|
|
3341
|
+
} else {
|
|
3342
|
+
console.log();
|
|
3343
|
+
console.log(file.content);
|
|
3344
|
+
}
|
|
3345
|
+
} catch (err) {
|
|
3346
|
+
console.log(chalk11.red(` ${err.message}`));
|
|
3347
|
+
throw new CliError();
|
|
3348
|
+
}
|
|
3349
|
+
}
|
|
3350
|
+
async function vaultDeleteCommand(filename, options) {
|
|
3351
|
+
const key = resolveApiKey(options);
|
|
3352
|
+
if (!options.yes) {
|
|
3353
|
+
const confirmed = await confirm7({
|
|
3354
|
+
message: `Delete "${filename}" from vault? This cannot be undone.`,
|
|
3355
|
+
default: false
|
|
3356
|
+
});
|
|
3357
|
+
if (!confirmed) return;
|
|
3358
|
+
}
|
|
3359
|
+
try {
|
|
3360
|
+
await deleteVaultFile(key, filename);
|
|
3361
|
+
console.log(success(` Deleted ${filename}`));
|
|
3362
|
+
console.log();
|
|
3363
|
+
} catch (err) {
|
|
3364
|
+
console.log(chalk11.red(` ${err.message}`));
|
|
3365
|
+
throw new CliError();
|
|
3366
|
+
}
|
|
3367
|
+
}
|
|
3368
|
+
|
|
2430
3369
|
// src/commands/interactive.ts
|
|
2431
|
-
import { select as
|
|
2432
|
-
import
|
|
2433
|
-
import
|
|
2434
|
-
import
|
|
3370
|
+
import { select as select3, confirm as confirm8, checkbox as checkbox6, Separator } from "@inquirer/prompts";
|
|
3371
|
+
import fs15 from "fs";
|
|
3372
|
+
import path16 from "path";
|
|
3373
|
+
import { exec as exec2 } from "child_process";
|
|
3374
|
+
import chalk12 from "chalk";
|
|
2435
3375
|
async function authenticate() {
|
|
2436
3376
|
const config = readStore();
|
|
2437
|
-
|
|
3377
|
+
const apiKey = config.apiKey;
|
|
2438
3378
|
if (apiKey) {
|
|
2439
3379
|
try {
|
|
2440
|
-
const
|
|
2441
|
-
console.log(success(` Connected as ${
|
|
2442
|
-
return { apiKey, validation:
|
|
3380
|
+
const result = await validateKey(apiKey);
|
|
3381
|
+
console.log(success(` Connected as ${result.displayName}`));
|
|
3382
|
+
return { apiKey, validation: result };
|
|
2443
3383
|
} catch {
|
|
2444
3384
|
console.log(dim(" Saved key expired. Please re-authenticate."));
|
|
2445
|
-
apiKey = void 0;
|
|
2446
3385
|
}
|
|
2447
3386
|
}
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
console.log(
|
|
2451
|
-
|
|
2452
|
-
apiKey
|
|
2453
|
-
message: "Enter your p\u0131ut API key:",
|
|
2454
|
-
mask: "*",
|
|
2455
|
-
validate: (v) => v.startsWith("pb_") || "Key must start with pb_"
|
|
2456
|
-
});
|
|
2457
|
-
console.log(dim(" Validating key..."));
|
|
2458
|
-
let result;
|
|
2459
|
-
try {
|
|
2460
|
-
result = await validateKey(apiKey);
|
|
2461
|
-
} catch (err) {
|
|
2462
|
-
console.log(chalk10.red(` \u2717 ${err.message}`));
|
|
2463
|
-
console.log(dim(" Get a key at https://piut.com/dashboard/keys"));
|
|
2464
|
-
process.exit(1);
|
|
2465
|
-
}
|
|
2466
|
-
console.log(success(` \u2713 Connected as ${result.displayName}`));
|
|
2467
|
-
updateStore({ apiKey });
|
|
2468
|
-
return { apiKey, validation: result };
|
|
3387
|
+
const { apiKey: newKey, validation } = await promptLogin();
|
|
3388
|
+
const label = validation.slug ? `${validation.displayName} (${validation.slug})` : validation.displayName;
|
|
3389
|
+
console.log(success(` \u2713 Connected as ${label}`));
|
|
3390
|
+
updateStore({ apiKey: newKey });
|
|
3391
|
+
return { apiKey: newKey, validation };
|
|
2469
3392
|
}
|
|
2470
3393
|
function isPromptCancellation(err) {
|
|
2471
3394
|
return !!(err && typeof err === "object" && "name" in err && err.name === "ExitPromptError");
|
|
2472
3395
|
}
|
|
3396
|
+
function openInBrowser(url) {
|
|
3397
|
+
const platform = process.platform;
|
|
3398
|
+
const cmd = platform === "darwin" ? "open" : platform === "win32" ? "start" : "xdg-open";
|
|
3399
|
+
exec2(`${cmd} ${url}`);
|
|
3400
|
+
}
|
|
2473
3401
|
async function interactiveMenu() {
|
|
2474
3402
|
banner();
|
|
2475
3403
|
let apiKey;
|
|
@@ -2482,7 +3410,7 @@ async function interactiveMenu() {
|
|
|
2482
3410
|
console.log(warning(" You haven\u2019t built a brain yet."));
|
|
2483
3411
|
console.log(dim(" Your brain is how AI tools learn about you \u2014 your projects, preferences, and context."));
|
|
2484
3412
|
console.log();
|
|
2485
|
-
const wantBuild = await
|
|
3413
|
+
const wantBuild = await confirm8({
|
|
2486
3414
|
message: "Build your brain now?",
|
|
2487
3415
|
default: true
|
|
2488
3416
|
});
|
|
@@ -2498,7 +3426,7 @@ async function interactiveMenu() {
|
|
|
2498
3426
|
console.log(warning(" Your brain is built but not deployed yet."));
|
|
2499
3427
|
console.log(dim(" Deploy it to make your MCP server live so AI tools can read your brain."));
|
|
2500
3428
|
console.log();
|
|
2501
|
-
const wantDeploy = await
|
|
3429
|
+
const wantDeploy = await confirm8({
|
|
2502
3430
|
message: "Deploy your brain now?",
|
|
2503
3431
|
default: true
|
|
2504
3432
|
});
|
|
@@ -2515,49 +3443,58 @@ async function interactiveMenu() {
|
|
|
2515
3443
|
const isDeployed = currentValidation.status === "active";
|
|
2516
3444
|
let action;
|
|
2517
3445
|
try {
|
|
2518
|
-
action = await
|
|
3446
|
+
action = await select3({
|
|
2519
3447
|
message: "What would you like to do?",
|
|
3448
|
+
loop: false,
|
|
2520
3449
|
choices: [
|
|
2521
3450
|
{
|
|
2522
|
-
name:
|
|
3451
|
+
name: "My Brain",
|
|
3452
|
+
value: "view-brain",
|
|
3453
|
+
description: "View all 5 brain sections",
|
|
3454
|
+
disabled: !hasBrain && "(build brain first)"
|
|
3455
|
+
},
|
|
3456
|
+
{
|
|
3457
|
+
name: "Build Brain",
|
|
2523
3458
|
value: "build",
|
|
2524
3459
|
description: hasBrain ? "Rebuild your brain from your files" : "Build your brain from your files"
|
|
2525
3460
|
},
|
|
3461
|
+
{
|
|
3462
|
+
name: "Edit Brain",
|
|
3463
|
+
value: "edit-brain",
|
|
3464
|
+
description: "Open piut.com to edit your brain",
|
|
3465
|
+
disabled: !hasBrain && "(build brain first)"
|
|
3466
|
+
},
|
|
3467
|
+
new Separator(),
|
|
2526
3468
|
{
|
|
2527
3469
|
name: isDeployed ? "Undeploy Brain" : "Deploy Brain",
|
|
2528
3470
|
value: "deploy",
|
|
2529
3471
|
description: isDeployed ? "Take your MCP server offline" : "Publish your MCP server (requires paid account)"
|
|
2530
3472
|
},
|
|
3473
|
+
new Separator(),
|
|
2531
3474
|
{
|
|
2532
3475
|
name: "Connect Tools",
|
|
2533
3476
|
value: "connect-tools",
|
|
2534
|
-
description: "
|
|
2535
|
-
disabled: !isDeployed && "(deploy brain first)"
|
|
2536
|
-
},
|
|
2537
|
-
{
|
|
2538
|
-
name: "Disconnect Tools",
|
|
2539
|
-
value: "disconnect-tools",
|
|
2540
|
-
description: "Remove p\u0131ut from AI tool configs",
|
|
3477
|
+
description: "Manage which AI tools use your MCP server",
|
|
2541
3478
|
disabled: !isDeployed && "(deploy brain first)"
|
|
2542
3479
|
},
|
|
2543
3480
|
{
|
|
2544
3481
|
name: "Connect Projects",
|
|
2545
3482
|
value: "connect-projects",
|
|
2546
|
-
description: "
|
|
3483
|
+
description: "Manage brain references in project config files",
|
|
2547
3484
|
disabled: !isDeployed && "(deploy brain first)"
|
|
2548
3485
|
},
|
|
3486
|
+
new Separator(),
|
|
2549
3487
|
{
|
|
2550
|
-
name: "
|
|
2551
|
-
value: "
|
|
2552
|
-
description: "
|
|
2553
|
-
disabled: !isDeployed && "(deploy brain first)"
|
|
3488
|
+
name: "View Files",
|
|
3489
|
+
value: "vault-view",
|
|
3490
|
+
description: "List and manage files in your vault"
|
|
2554
3491
|
},
|
|
2555
3492
|
{
|
|
2556
|
-
name: "
|
|
2557
|
-
value: "
|
|
2558
|
-
description: "
|
|
2559
|
-
disabled: !hasBrain && "(build brain first)"
|
|
3493
|
+
name: "Upload Files",
|
|
3494
|
+
value: "vault-upload",
|
|
3495
|
+
description: "Upload a file to your vault"
|
|
2560
3496
|
},
|
|
3497
|
+
new Separator(),
|
|
2561
3498
|
{
|
|
2562
3499
|
name: "Status",
|
|
2563
3500
|
value: "status",
|
|
@@ -2581,9 +3518,15 @@ async function interactiveMenu() {
|
|
|
2581
3518
|
if (action === "exit") return;
|
|
2582
3519
|
try {
|
|
2583
3520
|
switch (action) {
|
|
3521
|
+
case "view-brain":
|
|
3522
|
+
await handleViewBrain(apiKey);
|
|
3523
|
+
break;
|
|
2584
3524
|
case "build":
|
|
2585
3525
|
await buildCommand({ key: apiKey });
|
|
2586
3526
|
break;
|
|
3527
|
+
case "edit-brain":
|
|
3528
|
+
handleEditBrain();
|
|
3529
|
+
break;
|
|
2587
3530
|
case "deploy":
|
|
2588
3531
|
if (isDeployed) {
|
|
2589
3532
|
await handleUndeploy(apiKey);
|
|
@@ -2594,17 +3537,14 @@ async function interactiveMenu() {
|
|
|
2594
3537
|
case "connect-tools":
|
|
2595
3538
|
await handleConnectTools(apiKey, currentValidation);
|
|
2596
3539
|
break;
|
|
2597
|
-
case "disconnect-tools":
|
|
2598
|
-
await handleDisconnectTools();
|
|
2599
|
-
break;
|
|
2600
3540
|
case "connect-projects":
|
|
2601
|
-
await
|
|
3541
|
+
await handleManageProjects(apiKey, currentValidation);
|
|
2602
3542
|
break;
|
|
2603
|
-
case "
|
|
2604
|
-
await
|
|
3543
|
+
case "vault-view":
|
|
3544
|
+
await handleVaultView(apiKey);
|
|
2605
3545
|
break;
|
|
2606
|
-
case "
|
|
2607
|
-
await
|
|
3546
|
+
case "vault-upload":
|
|
3547
|
+
await handleVaultUpload(apiKey);
|
|
2608
3548
|
break;
|
|
2609
3549
|
case "status":
|
|
2610
3550
|
statusCommand();
|
|
@@ -2627,7 +3567,7 @@ async function interactiveMenu() {
|
|
|
2627
3567
|
} else if (err instanceof CliError) {
|
|
2628
3568
|
console.log();
|
|
2629
3569
|
} else {
|
|
2630
|
-
console.log(
|
|
3570
|
+
console.log(chalk12.red(` Error: ${err.message}`));
|
|
2631
3571
|
console.log();
|
|
2632
3572
|
}
|
|
2633
3573
|
}
|
|
@@ -2638,7 +3578,7 @@ async function interactiveMenu() {
|
|
|
2638
3578
|
}
|
|
2639
3579
|
}
|
|
2640
3580
|
async function handleUndeploy(apiKey) {
|
|
2641
|
-
const confirmed = await
|
|
3581
|
+
const confirmed = await confirm8({
|
|
2642
3582
|
message: "Undeploy your brain? AI tools will lose access to your MCP server.",
|
|
2643
3583
|
default: false
|
|
2644
3584
|
});
|
|
@@ -2650,115 +3590,297 @@ async function handleUndeploy(apiKey) {
|
|
|
2650
3590
|
console.log(dim(" Run ") + brand("piut deploy") + dim(" to re-deploy anytime."));
|
|
2651
3591
|
console.log();
|
|
2652
3592
|
} catch (err) {
|
|
2653
|
-
console.log(
|
|
3593
|
+
console.log(chalk12.red(` \u2717 ${err.message}`));
|
|
2654
3594
|
}
|
|
2655
3595
|
}
|
|
2656
3596
|
async function handleConnectTools(apiKey, validation) {
|
|
2657
3597
|
const { slug } = validation;
|
|
2658
|
-
const
|
|
2659
|
-
const alreadyConnected = [];
|
|
3598
|
+
const detected = [];
|
|
2660
3599
|
for (const tool of TOOLS) {
|
|
2661
3600
|
const paths = resolveConfigPaths(tool.configPaths);
|
|
2662
3601
|
for (const configPath of paths) {
|
|
2663
|
-
const exists =
|
|
2664
|
-
const parentExists =
|
|
3602
|
+
const exists = fs15.existsSync(configPath);
|
|
3603
|
+
const parentExists = fs15.existsSync(path16.dirname(configPath));
|
|
2665
3604
|
if (exists || parentExists) {
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
} else {
|
|
2669
|
-
unconfigured.push({ tool, configPath });
|
|
2670
|
-
}
|
|
3605
|
+
const connected = exists && !!tool.configKey && isPiutConfigured(configPath, tool.configKey);
|
|
3606
|
+
detected.push({ tool, configPath, connected });
|
|
2671
3607
|
break;
|
|
2672
3608
|
}
|
|
2673
3609
|
}
|
|
2674
3610
|
}
|
|
2675
|
-
if (
|
|
2676
|
-
|
|
2677
|
-
console.log(dim(" All detected tools are already connected."));
|
|
2678
|
-
} else {
|
|
2679
|
-
console.log(warning(" No supported AI tools detected."));
|
|
2680
|
-
console.log(dim(" Supported: Claude Code, Claude Desktop, Cursor, Windsurf, GitHub Copilot, Amazon Q, Zed"));
|
|
2681
|
-
}
|
|
3611
|
+
if (detected.length === 0) {
|
|
3612
|
+
console.log(warning(" No supported AI tools detected."));
|
|
2682
3613
|
console.log();
|
|
2683
3614
|
return;
|
|
2684
3615
|
}
|
|
2685
|
-
|
|
2686
|
-
|
|
3616
|
+
const mcpTools = detected.filter((d) => !d.tool.skillOnly);
|
|
3617
|
+
const skillOnlyTools = detected.filter((d) => d.tool.skillOnly);
|
|
3618
|
+
const connectedCount = mcpTools.filter((d) => d.connected).length;
|
|
3619
|
+
const availableCount = mcpTools.length - connectedCount;
|
|
3620
|
+
if (connectedCount > 0 || availableCount > 0) {
|
|
3621
|
+
const parts = [];
|
|
3622
|
+
if (connectedCount > 0) parts.push(`${connectedCount} connected`);
|
|
3623
|
+
if (availableCount > 0) parts.push(`${availableCount} available`);
|
|
3624
|
+
if (skillOnlyTools.length > 0) parts.push(`${skillOnlyTools.length} skill-only`);
|
|
3625
|
+
console.log(dim(` ${parts.join(", ")}`));
|
|
3626
|
+
}
|
|
3627
|
+
console.log();
|
|
3628
|
+
if (mcpTools.length === 0) {
|
|
3629
|
+
console.log(dim(" Detected tools are skill-only (no MCP config to manage)."));
|
|
3630
|
+
if (skillOnlyTools.length > 0) {
|
|
3631
|
+
console.log(dim(` Skill-only: ${skillOnlyTools.map((d) => d.tool.name).join(", ")}`));
|
|
3632
|
+
}
|
|
3633
|
+
console.log(dim(' Use "Connect Projects" to add skill files to your projects.'));
|
|
2687
3634
|
console.log();
|
|
3635
|
+
return;
|
|
2688
3636
|
}
|
|
2689
|
-
const choices =
|
|
2690
|
-
name:
|
|
2691
|
-
value:
|
|
2692
|
-
checked:
|
|
3637
|
+
const choices = mcpTools.map((d) => ({
|
|
3638
|
+
name: `${d.tool.name}${d.connected ? dim(" (connected)") : ""}`,
|
|
3639
|
+
value: d,
|
|
3640
|
+
checked: d.connected
|
|
2693
3641
|
}));
|
|
2694
3642
|
const selected = await checkbox6({
|
|
2695
|
-
message: "Select tools to
|
|
3643
|
+
message: "Select tools to keep connected (toggle with space):",
|
|
2696
3644
|
choices
|
|
2697
3645
|
});
|
|
2698
|
-
|
|
2699
|
-
|
|
3646
|
+
const toConnect = selected.filter((s) => !s.connected);
|
|
3647
|
+
const toDisconnect = mcpTools.filter((d) => d.connected && !selected.includes(d));
|
|
3648
|
+
if (toConnect.length === 0 && toDisconnect.length === 0) {
|
|
3649
|
+
console.log(dim(" No changes."));
|
|
3650
|
+
console.log();
|
|
2700
3651
|
return;
|
|
2701
3652
|
}
|
|
2702
3653
|
console.log();
|
|
2703
|
-
for (const { tool, configPath } of
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
3654
|
+
for (const { tool, configPath } of toConnect) {
|
|
3655
|
+
if (tool.generateConfig && tool.configKey) {
|
|
3656
|
+
const serverConfig = tool.generateConfig(slug, apiKey);
|
|
3657
|
+
mergeConfig(configPath, tool.configKey, serverConfig);
|
|
3658
|
+
toolLine(tool.name, success("connected"), "\u2714");
|
|
3659
|
+
}
|
|
3660
|
+
}
|
|
3661
|
+
const removedNames = [];
|
|
3662
|
+
for (const { tool, configPath } of toDisconnect) {
|
|
3663
|
+
if (!tool.configKey) continue;
|
|
3664
|
+
const removed = removeFromConfig(configPath, tool.configKey);
|
|
3665
|
+
if (removed) {
|
|
3666
|
+
removedNames.push(tool.name);
|
|
3667
|
+
toolLine(tool.name, warning("disconnected"), "\u2714");
|
|
3668
|
+
}
|
|
2707
3669
|
}
|
|
2708
|
-
if (
|
|
3670
|
+
if (skillOnlyTools.length > 0) {
|
|
3671
|
+
console.log(dim(` Skill-only (use "Connect Projects"): ${skillOnlyTools.map((d) => d.tool.name).join(", ")}`));
|
|
3672
|
+
}
|
|
3673
|
+
if (toConnect.length > 0 && validation.serverUrl) {
|
|
2709
3674
|
await Promise.all(
|
|
2710
|
-
|
|
3675
|
+
toConnect.map(({ tool }) => pingMcp(validation.serverUrl, apiKey, tool.name))
|
|
2711
3676
|
);
|
|
2712
3677
|
}
|
|
3678
|
+
if (removedNames.length > 0) {
|
|
3679
|
+
deleteConnections(apiKey, removedNames).catch(() => {
|
|
3680
|
+
});
|
|
3681
|
+
}
|
|
2713
3682
|
console.log();
|
|
2714
3683
|
console.log(dim(" Restart your AI tools for changes to take effect."));
|
|
2715
3684
|
console.log();
|
|
2716
3685
|
}
|
|
2717
|
-
async function
|
|
2718
|
-
const
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
break;
|
|
2725
|
-
}
|
|
2726
|
-
}
|
|
2727
|
-
}
|
|
2728
|
-
if (configured.length === 0) {
|
|
2729
|
-
console.log(dim(" p\u0131ut is not configured in any detected AI tools."));
|
|
3686
|
+
async function handleManageProjects(apiKey, validation) {
|
|
3687
|
+
const { slug, serverUrl } = validation;
|
|
3688
|
+
console.log(dim(" Scanning for projects..."));
|
|
3689
|
+
const projects = scanForProjects();
|
|
3690
|
+
if (projects.length === 0) {
|
|
3691
|
+
console.log(warning(" No projects found."));
|
|
3692
|
+
console.log(dim(" Try running from a directory with your projects."));
|
|
2730
3693
|
console.log();
|
|
2731
3694
|
return;
|
|
2732
3695
|
}
|
|
2733
|
-
const
|
|
2734
|
-
|
|
2735
|
-
|
|
3696
|
+
const items = projects.map((p) => ({
|
|
3697
|
+
project: p,
|
|
3698
|
+
connected: hasPiutDir(p.path)
|
|
3699
|
+
}));
|
|
3700
|
+
const connectedCount = items.filter((i) => i.connected).length;
|
|
3701
|
+
const availableCount = items.length - connectedCount;
|
|
3702
|
+
const parts = [];
|
|
3703
|
+
if (connectedCount > 0) parts.push(`${connectedCount} connected`);
|
|
3704
|
+
if (availableCount > 0) parts.push(`${availableCount} available`);
|
|
3705
|
+
console.log(dim(` ${parts.join(", ")}`));
|
|
3706
|
+
console.log();
|
|
3707
|
+
const choices = items.map((i) => ({
|
|
3708
|
+
name: `${i.project.name}${i.connected ? dim(" (connected)") : ""}`,
|
|
3709
|
+
value: i,
|
|
3710
|
+
checked: i.connected
|
|
2736
3711
|
}));
|
|
2737
3712
|
const selected = await checkbox6({
|
|
2738
|
-
message: "Select
|
|
3713
|
+
message: "Select projects to keep connected (toggle with space):",
|
|
2739
3714
|
choices
|
|
2740
3715
|
});
|
|
2741
|
-
|
|
2742
|
-
|
|
3716
|
+
const toConnect = selected.filter((s) => !s.connected);
|
|
3717
|
+
const toDisconnect = items.filter((i) => i.connected && !selected.includes(i));
|
|
3718
|
+
if (toConnect.length === 0 && toDisconnect.length === 0) {
|
|
3719
|
+
console.log(dim(" No changes."));
|
|
3720
|
+
console.log();
|
|
2743
3721
|
return;
|
|
2744
3722
|
}
|
|
2745
|
-
const proceed = await confirm7({
|
|
2746
|
-
message: `Disconnect p\u0131ut from ${selected.length} tool(s)?`,
|
|
2747
|
-
default: false
|
|
2748
|
-
});
|
|
2749
|
-
if (!proceed) return;
|
|
2750
3723
|
console.log();
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
3724
|
+
const copilotTool = TOOLS.find((t) => t.id === "copilot");
|
|
3725
|
+
for (const { project } of toConnect) {
|
|
3726
|
+
const projectName = path16.basename(project.path);
|
|
3727
|
+
writePiutConfig(project.path, { slug, apiKey, serverUrl });
|
|
3728
|
+
await writePiutSkill(project.path, slug, apiKey);
|
|
3729
|
+
ensureGitignored(project.path);
|
|
3730
|
+
if (copilotTool) {
|
|
3731
|
+
const hasCopilot = fs15.existsSync(path16.join(project.path, ".github", "copilot-instructions.md")) || fs15.existsSync(path16.join(project.path, ".github"));
|
|
3732
|
+
if (hasCopilot) {
|
|
3733
|
+
const vscodeMcpPath = path16.join(project.path, ".vscode", "mcp.json");
|
|
3734
|
+
const serverConfig = copilotTool.generateConfig(slug, apiKey);
|
|
3735
|
+
mergeConfig(vscodeMcpPath, copilotTool.configKey, serverConfig);
|
|
3736
|
+
}
|
|
2757
3737
|
}
|
|
3738
|
+
for (const rule of RULE_FILES) {
|
|
3739
|
+
if (!rule.detect(project)) continue;
|
|
3740
|
+
const absPath = path16.join(project.path, rule.filePath);
|
|
3741
|
+
if (fs15.existsSync(absPath) && hasPiutReference2(absPath)) continue;
|
|
3742
|
+
if (rule.strategy === "create" || !fs15.existsSync(absPath)) {
|
|
3743
|
+
const isAppendType = rule.strategy === "append";
|
|
3744
|
+
const content = isAppendType ? PROJECT_SKILL_SNIPPET + "\n" : DEDICATED_FILE_CONTENT;
|
|
3745
|
+
fs15.mkdirSync(path16.dirname(absPath), { recursive: true });
|
|
3746
|
+
fs15.writeFileSync(absPath, content, "utf-8");
|
|
3747
|
+
} else {
|
|
3748
|
+
fs15.appendFileSync(absPath, APPEND_SECTION);
|
|
3749
|
+
}
|
|
3750
|
+
}
|
|
3751
|
+
toolLine(projectName, success("connected"), "\u2714");
|
|
3752
|
+
const machineId = getMachineId();
|
|
3753
|
+
const toolsDetected = RULE_FILES.filter((r) => r.detect(project)).map((r) => r.tool);
|
|
3754
|
+
registerProject(apiKey, {
|
|
3755
|
+
projectName,
|
|
3756
|
+
projectPath: project.path,
|
|
3757
|
+
machineId,
|
|
3758
|
+
toolsDetected,
|
|
3759
|
+
configFiles: RULE_FILES.filter((r) => r.detect(project)).map((r) => r.filePath)
|
|
3760
|
+
}).catch(() => {
|
|
3761
|
+
});
|
|
3762
|
+
}
|
|
3763
|
+
for (const { project } of toDisconnect) {
|
|
3764
|
+
const projectName = path16.basename(project.path);
|
|
3765
|
+
for (const dedicatedFile of DEDICATED_FILES) {
|
|
3766
|
+
const absPath = path16.join(project.path, dedicatedFile);
|
|
3767
|
+
if (fs15.existsSync(absPath) && hasPiutReference2(absPath)) {
|
|
3768
|
+
try {
|
|
3769
|
+
fs15.unlinkSync(absPath);
|
|
3770
|
+
} catch {
|
|
3771
|
+
}
|
|
3772
|
+
}
|
|
3773
|
+
}
|
|
3774
|
+
for (const appendFile of APPEND_FILES) {
|
|
3775
|
+
const absPath = path16.join(project.path, appendFile);
|
|
3776
|
+
if (fs15.existsSync(absPath) && hasPiutReference2(absPath)) {
|
|
3777
|
+
removePiutSection(absPath);
|
|
3778
|
+
}
|
|
3779
|
+
}
|
|
3780
|
+
const vscodeMcpPath = path16.join(project.path, ".vscode", "mcp.json");
|
|
3781
|
+
if (fs15.existsSync(vscodeMcpPath) && isPiutConfigured(vscodeMcpPath, "servers")) {
|
|
3782
|
+
removeFromConfig(vscodeMcpPath, "servers");
|
|
3783
|
+
}
|
|
3784
|
+
removePiutDir(project.path);
|
|
3785
|
+
toolLine(projectName, warning("disconnected"), "\u2714");
|
|
3786
|
+
const machineId = getMachineId();
|
|
3787
|
+
unregisterProject(apiKey, project.path, machineId).catch(() => {
|
|
3788
|
+
});
|
|
2758
3789
|
}
|
|
2759
3790
|
console.log();
|
|
2760
|
-
|
|
3791
|
+
if (toConnect.length > 0) {
|
|
3792
|
+
console.log(success(` ${toConnect.length} project(s) connected.`));
|
|
3793
|
+
}
|
|
3794
|
+
if (toDisconnect.length > 0) {
|
|
3795
|
+
console.log(success(` ${toDisconnect.length} project(s) disconnected.`));
|
|
3796
|
+
}
|
|
3797
|
+
console.log();
|
|
3798
|
+
}
|
|
3799
|
+
function handleEditBrain() {
|
|
3800
|
+
console.log(dim(" Opening piut.com/dashboard..."));
|
|
3801
|
+
openInBrowser("https://piut.com/dashboard");
|
|
3802
|
+
console.log(success(" \u2713 Opened in browser."));
|
|
3803
|
+
console.log();
|
|
3804
|
+
}
|
|
3805
|
+
function formatSize3(bytes) {
|
|
3806
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
3807
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
3808
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
3809
|
+
}
|
|
3810
|
+
async function handleVaultView(apiKey) {
|
|
3811
|
+
const data = await listVaultFiles(apiKey);
|
|
3812
|
+
if (data.files.length === 0) {
|
|
3813
|
+
console.log(dim(' Vault is empty. Use "Upload Files" to add files.'));
|
|
3814
|
+
console.log();
|
|
3815
|
+
return;
|
|
3816
|
+
}
|
|
3817
|
+
console.log();
|
|
3818
|
+
for (const file of data.files) {
|
|
3819
|
+
const size = dim(`(${formatSize3(file.sizeBytes)})`);
|
|
3820
|
+
console.log(` ${file.filename} ${size}`);
|
|
3821
|
+
if (file.summary) console.log(dim(` ${file.summary}`));
|
|
3822
|
+
}
|
|
3823
|
+
console.log();
|
|
3824
|
+
console.log(dim(` ${data.usage.fileCount} file(s), ${formatSize3(data.usage.totalBytes)} / ${formatSize3(data.usage.maxBytes)} used`));
|
|
2761
3825
|
console.log();
|
|
3826
|
+
const action = await select3({
|
|
3827
|
+
message: "Actions:",
|
|
3828
|
+
choices: [
|
|
3829
|
+
{ name: "Delete a file", value: "delete" },
|
|
3830
|
+
{ name: "Back", value: "back" }
|
|
3831
|
+
]
|
|
3832
|
+
});
|
|
3833
|
+
if (action === "back") return;
|
|
3834
|
+
if (action === "delete") {
|
|
3835
|
+
const fileChoices = data.files.map((f) => ({
|
|
3836
|
+
name: `${f.filename} ${dim(`(${formatSize3(f.sizeBytes)})`)}`,
|
|
3837
|
+
value: f.filename
|
|
3838
|
+
}));
|
|
3839
|
+
const filename = await select3({
|
|
3840
|
+
message: "Which file to delete?",
|
|
3841
|
+
choices: fileChoices
|
|
3842
|
+
});
|
|
3843
|
+
const confirmed = await confirm8({
|
|
3844
|
+
message: `Delete "${filename}"? This cannot be undone.`,
|
|
3845
|
+
default: false
|
|
3846
|
+
});
|
|
3847
|
+
if (confirmed) {
|
|
3848
|
+
try {
|
|
3849
|
+
await deleteVaultFile(apiKey, filename);
|
|
3850
|
+
console.log(success(` Deleted ${filename}`));
|
|
3851
|
+
console.log();
|
|
3852
|
+
} catch (err) {
|
|
3853
|
+
console.log(chalk12.red(` ${err.message}`));
|
|
3854
|
+
console.log();
|
|
3855
|
+
}
|
|
3856
|
+
}
|
|
3857
|
+
}
|
|
3858
|
+
}
|
|
3859
|
+
async function handleVaultUpload(apiKey) {
|
|
3860
|
+
const { input: input3 } = await import("@inquirer/prompts");
|
|
3861
|
+
const filePath = await input3({ message: "File path:" });
|
|
3862
|
+
if (!filePath.trim()) return;
|
|
3863
|
+
const resolved = path16.resolve(filePath);
|
|
3864
|
+
if (!fs15.existsSync(resolved) || !fs15.statSync(resolved).isFile()) {
|
|
3865
|
+
console.log(chalk12.red(` File not found: ${filePath}`));
|
|
3866
|
+
console.log();
|
|
3867
|
+
return;
|
|
3868
|
+
}
|
|
3869
|
+
const filename = path16.basename(resolved);
|
|
3870
|
+
const content = fs15.readFileSync(resolved, "utf-8");
|
|
3871
|
+
const spinner = new Spinner();
|
|
3872
|
+
spinner.start(`Uploading ${filename}...`);
|
|
3873
|
+
try {
|
|
3874
|
+
const result = await uploadVaultFile(apiKey, filename, content);
|
|
3875
|
+
spinner.stop();
|
|
3876
|
+
console.log(success(` Uploaded ${result.filename}`) + dim(` (${formatSize3(result.sizeBytes)})`));
|
|
3877
|
+
if (result.summary) console.log(dim(` ${result.summary}`));
|
|
3878
|
+
console.log();
|
|
3879
|
+
} catch (err) {
|
|
3880
|
+
spinner.stop();
|
|
3881
|
+
console.log(chalk12.red(` ${err.message}`));
|
|
3882
|
+
console.log();
|
|
3883
|
+
}
|
|
2762
3884
|
}
|
|
2763
3885
|
async function handleViewBrain(apiKey) {
|
|
2764
3886
|
console.log(dim(" Loading brain..."));
|
|
@@ -2788,7 +3910,7 @@ async function handleViewBrain(apiKey) {
|
|
|
2788
3910
|
if (hasUnpublishedChanges) {
|
|
2789
3911
|
console.log(warning(" You have unpublished changes."));
|
|
2790
3912
|
console.log();
|
|
2791
|
-
const wantPublish = await
|
|
3913
|
+
const wantPublish = await confirm8({
|
|
2792
3914
|
message: "Publish now?",
|
|
2793
3915
|
default: true
|
|
2794
3916
|
});
|
|
@@ -2802,11 +3924,11 @@ async function handleViewBrain(apiKey) {
|
|
|
2802
3924
|
console.log();
|
|
2803
3925
|
const msg = err.message;
|
|
2804
3926
|
if (msg === "REQUIRES_SUBSCRIPTION") {
|
|
2805
|
-
console.log(
|
|
3927
|
+
console.log(chalk12.yellow(" Deploy requires an active subscription ($10/mo)."));
|
|
2806
3928
|
console.log(` Subscribe at: ${brand("https://piut.com/dashboard/billing")}`);
|
|
2807
3929
|
console.log(dim(" 14-day free trial included."));
|
|
2808
3930
|
} else {
|
|
2809
|
-
console.log(
|
|
3931
|
+
console.log(chalk12.red(` \u2717 ${msg}`));
|
|
2810
3932
|
}
|
|
2811
3933
|
console.log();
|
|
2812
3934
|
}
|
|
@@ -2815,7 +3937,7 @@ async function handleViewBrain(apiKey) {
|
|
|
2815
3937
|
}
|
|
2816
3938
|
|
|
2817
3939
|
// src/cli.ts
|
|
2818
|
-
var VERSION = "3.
|
|
3940
|
+
var VERSION = "3.7.0";
|
|
2819
3941
|
function withExit(fn) {
|
|
2820
3942
|
return async (...args2) => {
|
|
2821
3943
|
try {
|
|
@@ -2838,8 +3960,14 @@ program.command("disconnect").description("Remove brain references from project
|
|
|
2838
3960
|
program.command("setup").description("Auto-detect and configure AI tools (MCP config)").option("-k, --key <key>", "API key (prompts interactively if not provided)").option("-t, --tool <id>", "Configure a single tool (claude-code, cursor, windsurf, etc.)").option("-y, --yes", "Skip interactive prompts (auto-select all detected tools)").option("--project", "Prefer project-local config files").option("--skip-skill", "Skip skill.md file placement").action(withExit(setupCommand));
|
|
2839
3961
|
program.command("status").description("Show brain, deployment, and connected projects").option("--verify", "Validate API key, check tool configs, and test MCP endpoint").action(withExit(statusCommand));
|
|
2840
3962
|
program.command("remove").description("Remove all p\u0131ut configurations").action(withExit(removeCommand));
|
|
3963
|
+
program.command("login").description("Authenticate with p\u0131ut (email, browser, or API key)").action(withExit(loginCommand));
|
|
2841
3964
|
program.command("logout").description("Remove saved API key").action(logoutCommand);
|
|
2842
3965
|
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));
|
|
3966
|
+
var vault = program.command("vault").description("Manage your file vault (upload, list, read, delete)");
|
|
3967
|
+
vault.command("list").description("List all files in your vault").option("-k, --key <key>", "API key").action(withExit(vaultListCommand));
|
|
3968
|
+
vault.command("upload <file>").description("Upload a file to your vault").option("-k, --key <key>", "API key").action(withExit(vaultUploadCommand));
|
|
3969
|
+
vault.command("read <filename>").description("Read a file from your vault").option("-k, --key <key>", "API key").option("-o, --output <path>", "Save to a local file instead of printing").action(withExit(vaultReadCommand));
|
|
3970
|
+
vault.command("delete <filename>").description("Delete a file from your vault").option("-k, --key <key>", "API key").option("-y, --yes", "Skip confirmation prompt").action(withExit(vaultDeleteCommand));
|
|
2843
3971
|
program.command("update").description("Check for and install CLI updates").action(() => updateCommand(VERSION));
|
|
2844
3972
|
var args = process.argv.slice(2);
|
|
2845
3973
|
if (args.includes("--version") || args.includes("-V")) {
|