@piut/cli 3.6.0 → 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 +520 -183
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -122,7 +122,10 @@ async function pingMcp(serverUrl, key, toolName) {
|
|
|
122
122
|
headers: {
|
|
123
123
|
Authorization: `Bearer ${key}`,
|
|
124
124
|
"Content-Type": "application/json",
|
|
125
|
-
"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
|
|
126
129
|
},
|
|
127
130
|
body: JSON.stringify({
|
|
128
131
|
jsonrpc: "2.0",
|
|
@@ -167,6 +170,9 @@ function getMachineId() {
|
|
|
167
170
|
const hostname = os.hostname();
|
|
168
171
|
return crypto.createHash("sha256").update(hostname).digest("hex").slice(0, 16);
|
|
169
172
|
}
|
|
173
|
+
function getHostname() {
|
|
174
|
+
return os.hostname();
|
|
175
|
+
}
|
|
170
176
|
async function registerProject(key, project) {
|
|
171
177
|
const res = await fetch(`${API_BASE}/api/cli/projects`, {
|
|
172
178
|
method: "POST",
|
|
@@ -209,55 +215,88 @@ async function deleteConnections(key, toolNames) {
|
|
|
209
215
|
} catch {
|
|
210
216
|
}
|
|
211
217
|
}
|
|
212
|
-
async function
|
|
213
|
-
const res = await fetch(
|
|
218
|
+
async function unpublishServer(key) {
|
|
219
|
+
const res = await fetch(`${API_BASE}/api/mcp/publish`, {
|
|
214
220
|
method: "POST",
|
|
215
221
|
headers: {
|
|
216
222
|
Authorization: `Bearer ${key}`,
|
|
217
223
|
"Content-Type": "application/json"
|
|
218
224
|
},
|
|
219
|
-
body: JSON.stringify({
|
|
220
|
-
jsonrpc: "2.0",
|
|
221
|
-
id: 1,
|
|
222
|
-
method: "tools/call",
|
|
223
|
-
params: {
|
|
224
|
-
name: "update_brain",
|
|
225
|
-
arguments: { content }
|
|
226
|
-
}
|
|
227
|
-
})
|
|
225
|
+
body: JSON.stringify({ published: false })
|
|
228
226
|
});
|
|
229
227
|
if (!res.ok) {
|
|
230
|
-
|
|
228
|
+
const body = await res.json().catch(() => ({ error: "Unknown error" }));
|
|
229
|
+
throw new Error(body.error || `Unpublish failed (HTTP ${res.status})`);
|
|
231
230
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
231
|
+
return res.json();
|
|
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})`);
|
|
235
240
|
}
|
|
236
|
-
|
|
237
|
-
const text = Array.isArray(resultContent) && resultContent[0]?.text ? resultContent[0].text : "Brain updated";
|
|
238
|
-
return { summary: text };
|
|
241
|
+
return res.json();
|
|
239
242
|
}
|
|
240
|
-
async function
|
|
241
|
-
const res = await fetch(`${API_BASE}/api/
|
|
243
|
+
async function uploadVaultFile(key, filename, content) {
|
|
244
|
+
const res = await fetch(`${API_BASE}/api/cli/vault/upload`, {
|
|
242
245
|
method: "POST",
|
|
243
246
|
headers: {
|
|
244
247
|
Authorization: `Bearer ${key}`,
|
|
245
248
|
"Content-Type": "application/json"
|
|
246
249
|
},
|
|
247
|
-
body: JSON.stringify({
|
|
250
|
+
body: JSON.stringify({ filename, content })
|
|
248
251
|
});
|
|
249
252
|
if (!res.ok) {
|
|
250
253
|
const body = await res.json().catch(() => ({ error: "Unknown error" }));
|
|
251
|
-
throw new Error(body.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})`);
|
|
252
266
|
}
|
|
253
267
|
return res.json();
|
|
254
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
|
+
}
|
|
255
283
|
|
|
256
284
|
// src/lib/tools.ts
|
|
257
285
|
import os2 from "os";
|
|
258
286
|
import path from "path";
|
|
287
|
+
import crypto2 from "crypto";
|
|
259
288
|
var MCP_URL = (slug) => `https://piut.com/api/mcp/${slug}`;
|
|
260
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
|
+
}
|
|
261
300
|
function appData() {
|
|
262
301
|
return process.env.APPDATA || path.join(os2.homedir(), "AppData", "Roaming");
|
|
263
302
|
}
|
|
@@ -269,18 +308,19 @@ var TOOLS = [
|
|
|
269
308
|
configPaths: {
|
|
270
309
|
darwin: ["~/.claude.json"],
|
|
271
310
|
win32: ["~/.claude.json"],
|
|
272
|
-
linux: ["~/.claude.json"]
|
|
311
|
+
linux: ["~/.claude.json"],
|
|
312
|
+
project: [".mcp.json"]
|
|
273
313
|
},
|
|
274
314
|
skillFilePath: "CLAUDE.md",
|
|
275
315
|
quickCommand: (slug, key) => `claude mcp add-json piut-context '${JSON.stringify({
|
|
276
316
|
type: "http",
|
|
277
317
|
url: MCP_URL(slug),
|
|
278
|
-
headers: AUTH_HEADER(key)
|
|
318
|
+
headers: { ...AUTH_HEADER(key), ...machineHeaders("Claude Code") }
|
|
279
319
|
})}'`,
|
|
280
320
|
generateConfig: (slug, key) => ({
|
|
281
321
|
type: "http",
|
|
282
322
|
url: MCP_URL(slug),
|
|
283
|
-
headers: AUTH_HEADER(key)
|
|
323
|
+
headers: { ...AUTH_HEADER(key), ...machineHeaders("Claude Code") }
|
|
284
324
|
})
|
|
285
325
|
},
|
|
286
326
|
{
|
|
@@ -316,7 +356,7 @@ var TOOLS = [
|
|
|
316
356
|
skillFilePath: ".cursor/rules/piut.mdc",
|
|
317
357
|
generateConfig: (slug, key) => ({
|
|
318
358
|
url: MCP_URL(slug),
|
|
319
|
-
headers: AUTH_HEADER(key)
|
|
359
|
+
headers: { ...AUTH_HEADER(key), ...machineHeaders("Cursor") }
|
|
320
360
|
})
|
|
321
361
|
},
|
|
322
362
|
{
|
|
@@ -331,7 +371,7 @@ var TOOLS = [
|
|
|
331
371
|
skillFilePath: ".windsurf/rules/piut.md",
|
|
332
372
|
generateConfig: (slug, key) => ({
|
|
333
373
|
serverUrl: MCP_URL(slug),
|
|
334
|
-
headers: AUTH_HEADER(key)
|
|
374
|
+
headers: { ...AUTH_HEADER(key), ...machineHeaders("Windsurf") }
|
|
335
375
|
})
|
|
336
376
|
},
|
|
337
377
|
{
|
|
@@ -345,7 +385,7 @@ var TOOLS = [
|
|
|
345
385
|
generateConfig: (slug, key) => ({
|
|
346
386
|
type: "http",
|
|
347
387
|
url: MCP_URL(slug),
|
|
348
|
-
headers: AUTH_HEADER(key)
|
|
388
|
+
headers: { ...AUTH_HEADER(key), ...machineHeaders("GitHub Copilot") }
|
|
349
389
|
})
|
|
350
390
|
},
|
|
351
391
|
{
|
|
@@ -361,7 +401,7 @@ var TOOLS = [
|
|
|
361
401
|
generateConfig: (slug, key) => ({
|
|
362
402
|
type: "http",
|
|
363
403
|
url: MCP_URL(slug),
|
|
364
|
-
headers: AUTH_HEADER(key)
|
|
404
|
+
headers: { ...AUTH_HEADER(key), ...machineHeaders("Amazon Q") }
|
|
365
405
|
})
|
|
366
406
|
},
|
|
367
407
|
{
|
|
@@ -376,9 +416,49 @@ var TOOLS = [
|
|
|
376
416
|
generateConfig: (slug, key) => ({
|
|
377
417
|
settings: {
|
|
378
418
|
url: MCP_URL(slug),
|
|
379
|
-
headers: AUTH_HEADER(key)
|
|
419
|
+
headers: { ...AUTH_HEADER(key), ...machineHeaders("Zed") }
|
|
380
420
|
}
|
|
381
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
|
+
}
|
|
382
462
|
}
|
|
383
463
|
];
|
|
384
464
|
|
|
@@ -471,6 +551,28 @@ function extractKeyFromConfig(piutConfig) {
|
|
|
471
551
|
}
|
|
472
552
|
return null;
|
|
473
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
|
+
}
|
|
474
576
|
function removeFromConfig(filePath, configKey) {
|
|
475
577
|
const config = readConfig(filePath);
|
|
476
578
|
if (!config) return false;
|
|
@@ -781,20 +883,25 @@ async function setupCommand(options) {
|
|
|
781
883
|
const toolFilter = options.tool;
|
|
782
884
|
for (const tool of TOOLS) {
|
|
783
885
|
if (toolFilter && tool.id !== toolFilter) continue;
|
|
886
|
+
if (tool.skillOnly) continue;
|
|
784
887
|
const paths = resolveConfigPaths(tool.configPaths);
|
|
785
888
|
for (const configPath of paths) {
|
|
786
889
|
const exists = fs4.existsSync(configPath);
|
|
787
890
|
const parentExists = fs4.existsSync(path6.dirname(configPath));
|
|
788
891
|
if (exists || parentExists) {
|
|
789
|
-
const configured2 = exists && isPiutConfigured(configPath, tool.configKey);
|
|
892
|
+
const configured2 = exists && !!tool.configKey && isPiutConfigured(configPath, tool.configKey);
|
|
790
893
|
let staleKey = false;
|
|
791
|
-
if (configured2) {
|
|
894
|
+
if (configured2 && tool.configKey) {
|
|
792
895
|
const piutConfig = getPiutConfig(configPath, tool.configKey);
|
|
793
896
|
if (piutConfig) {
|
|
794
897
|
const existingKey = extractKeyFromConfig(piutConfig);
|
|
795
898
|
if (existingKey && existingKey !== apiKey) {
|
|
796
899
|
staleKey = true;
|
|
797
900
|
}
|
|
901
|
+
const existingSlug = extractSlugFromConfig(piutConfig);
|
|
902
|
+
if (existingSlug && existingSlug !== slug) {
|
|
903
|
+
staleKey = true;
|
|
904
|
+
}
|
|
798
905
|
}
|
|
799
906
|
}
|
|
800
907
|
detected.push({
|
|
@@ -810,7 +917,6 @@ async function setupCommand(options) {
|
|
|
810
917
|
}
|
|
811
918
|
if (detected.length === 0) {
|
|
812
919
|
console.log(warning(" No supported AI tools detected."));
|
|
813
|
-
console.log(dim(" Supported: Claude Code, Claude Desktop, Cursor, Windsurf, GitHub Copilot, Amazon Q, Zed"));
|
|
814
920
|
console.log(dim(" See https://piut.com/docs for manual setup."));
|
|
815
921
|
console.log();
|
|
816
922
|
return;
|
|
@@ -862,7 +968,7 @@ async function setupCommand(options) {
|
|
|
862
968
|
try {
|
|
863
969
|
execSync(tool.quickCommand(slug, apiKey), { stdio: "pipe" });
|
|
864
970
|
const claudeJson = expandPath("~/.claude.json");
|
|
865
|
-
const written = getPiutConfig(claudeJson, tool.configKey);
|
|
971
|
+
const written = tool.configKey ? getPiutConfig(claudeJson, tool.configKey) : null;
|
|
866
972
|
if (written) {
|
|
867
973
|
quickSuccess = true;
|
|
868
974
|
configured.push(tool.name);
|
|
@@ -871,7 +977,7 @@ async function setupCommand(options) {
|
|
|
871
977
|
}
|
|
872
978
|
try {
|
|
873
979
|
execSync(tool.quickCommand(slug, apiKey) + " --scope user", { stdio: "pipe" });
|
|
874
|
-
const retryCheck = getPiutConfig(claudeJson, tool.configKey);
|
|
980
|
+
const retryCheck = tool.configKey ? getPiutConfig(claudeJson, tool.configKey) : null;
|
|
875
981
|
if (retryCheck) {
|
|
876
982
|
quickSuccess = true;
|
|
877
983
|
configured.push(tool.name);
|
|
@@ -890,10 +996,12 @@ async function setupCommand(options) {
|
|
|
890
996
|
}
|
|
891
997
|
if (quickSuccess) continue;
|
|
892
998
|
}
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
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
|
+
}
|
|
897
1005
|
}
|
|
898
1006
|
if (!options.skipSkill && configured.length > 0) {
|
|
899
1007
|
const addSkill = options.yes ? true : await confirm({
|
|
@@ -934,6 +1042,7 @@ async function setupCommand(options) {
|
|
|
934
1042
|
console.log(dim(" Verifying..."));
|
|
935
1043
|
for (const det of selected) {
|
|
936
1044
|
if (!configured.includes(det.tool.name)) continue;
|
|
1045
|
+
if (!det.tool.configKey) continue;
|
|
937
1046
|
const piutConfig = getPiutConfig(det.configPath, det.tool.configKey);
|
|
938
1047
|
if (piutConfig) {
|
|
939
1048
|
toolLine(det.tool.name, success("config verified"), "\u2714");
|
|
@@ -976,6 +1085,7 @@ function isCommandAvailable(cmd) {
|
|
|
976
1085
|
|
|
977
1086
|
// src/commands/status.ts
|
|
978
1087
|
import fs9 from "fs";
|
|
1088
|
+
import os7 from "os";
|
|
979
1089
|
import path12 from "path";
|
|
980
1090
|
import chalk3 from "chalk";
|
|
981
1091
|
|
|
@@ -1394,7 +1504,10 @@ var INCLUDE_DOT_DIRS = /* @__PURE__ */ new Set([
|
|
|
1394
1504
|
".openclaw",
|
|
1395
1505
|
".zed",
|
|
1396
1506
|
".github",
|
|
1397
|
-
".amazonq"
|
|
1507
|
+
".amazonq",
|
|
1508
|
+
".gemini",
|
|
1509
|
+
".mcporter",
|
|
1510
|
+
".paperclip"
|
|
1398
1511
|
]);
|
|
1399
1512
|
function getDefaultScanDirs() {
|
|
1400
1513
|
const dirs = [];
|
|
@@ -1456,7 +1569,11 @@ var SCAN_DOT_DIRS = /* @__PURE__ */ new Set([
|
|
|
1456
1569
|
".github",
|
|
1457
1570
|
".zed",
|
|
1458
1571
|
".amazonq",
|
|
1459
|
-
".vscode"
|
|
1572
|
+
".vscode",
|
|
1573
|
+
".gemini",
|
|
1574
|
+
".openclaw",
|
|
1575
|
+
".mcporter",
|
|
1576
|
+
".paperclip"
|
|
1460
1577
|
]);
|
|
1461
1578
|
function shouldSkipDir(name) {
|
|
1462
1579
|
if (name.startsWith(".") && !SCAN_DOT_DIRS.has(name)) return true;
|
|
@@ -1543,7 +1660,9 @@ function collectConfigFiles(projects, onProgress) {
|
|
|
1543
1660
|
path10.join(home2, ".claude", "MEMORY.md"),
|
|
1544
1661
|
path10.join(home2, ".claude", "CLAUDE.md"),
|
|
1545
1662
|
path10.join(home2, ".openclaw", "workspace", "SOUL.md"),
|
|
1546
|
-
path10.join(home2, ".openclaw", "workspace", "MEMORY.md")
|
|
1663
|
+
path10.join(home2, ".openclaw", "workspace", "MEMORY.md"),
|
|
1664
|
+
path10.join(home2, ".gemini", "MEMORY.md"),
|
|
1665
|
+
path10.join(home2, ".paperclip", "IDENTITY.md")
|
|
1547
1666
|
];
|
|
1548
1667
|
for (const gp of globalPaths) {
|
|
1549
1668
|
try {
|
|
@@ -1715,6 +1834,7 @@ function clearStore() {
|
|
|
1715
1834
|
}
|
|
1716
1835
|
|
|
1717
1836
|
// src/commands/status.ts
|
|
1837
|
+
var API_BASE3 = process.env.PIUT_API_BASE || "https://piut.com";
|
|
1718
1838
|
var PIUT_FILES = [
|
|
1719
1839
|
"CLAUDE.md",
|
|
1720
1840
|
".cursor/rules/piut.mdc",
|
|
@@ -1731,13 +1851,55 @@ function hasPiutReference(filePath) {
|
|
|
1731
1851
|
return false;
|
|
1732
1852
|
}
|
|
1733
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
|
+
}
|
|
1734
1894
|
async function statusCommand(options = {}) {
|
|
1735
1895
|
banner();
|
|
1736
1896
|
if (options.verify) {
|
|
1737
1897
|
await verifyStatus();
|
|
1738
1898
|
return;
|
|
1739
1899
|
}
|
|
1740
|
-
|
|
1900
|
+
const thisHostname = os7.hostname();
|
|
1901
|
+
const thisMachineId = getMachineId2();
|
|
1902
|
+
console.log(` AI tools on this machine ${dim(`(${thisHostname})`)}:`);
|
|
1741
1903
|
console.log();
|
|
1742
1904
|
let foundAny = false;
|
|
1743
1905
|
for (const tool of TOOLS) {
|
|
@@ -1759,7 +1921,7 @@ async function statusCommand(options = {}) {
|
|
|
1759
1921
|
console.log(dim(" Run ") + brand("piut setup") + dim(" to configure your AI tools."));
|
|
1760
1922
|
}
|
|
1761
1923
|
console.log();
|
|
1762
|
-
console.log(
|
|
1924
|
+
console.log(` Connected projects on this machine:`);
|
|
1763
1925
|
console.log();
|
|
1764
1926
|
const projects = scanForProjects();
|
|
1765
1927
|
let connectedCount = 0;
|
|
@@ -1784,6 +1946,33 @@ async function statusCommand(options = {}) {
|
|
|
1784
1946
|
console.log(dim(` ${connectedCount} project(s) connected to your brain.`));
|
|
1785
1947
|
}
|
|
1786
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
|
+
}
|
|
1787
1976
|
}
|
|
1788
1977
|
async function verifyStatus() {
|
|
1789
1978
|
const store = readStore();
|
|
@@ -1862,6 +2051,7 @@ async function removeCommand() {
|
|
|
1862
2051
|
banner();
|
|
1863
2052
|
const configured = [];
|
|
1864
2053
|
for (const tool of TOOLS) {
|
|
2054
|
+
if (!tool.configKey) continue;
|
|
1865
2055
|
const paths = resolveConfigPaths(tool.configPaths);
|
|
1866
2056
|
for (const configPath of paths) {
|
|
1867
2057
|
if (fs10.existsSync(configPath) && isPiutConfigured(configPath, tool.configKey)) {
|
|
@@ -1895,6 +2085,7 @@ async function removeCommand() {
|
|
|
1895
2085
|
console.log();
|
|
1896
2086
|
const removedNames = [];
|
|
1897
2087
|
for (const { tool, configPath } of selected) {
|
|
2088
|
+
if (!tool.configKey) continue;
|
|
1898
2089
|
const removed = removeFromConfig(configPath, tool.configKey);
|
|
1899
2090
|
if (removed) {
|
|
1900
2091
|
removedNames.push(tool.name);
|
|
@@ -1916,7 +2107,7 @@ async function removeCommand() {
|
|
|
1916
2107
|
// src/commands/build.ts
|
|
1917
2108
|
import { select as select2, checkbox as checkbox3, input as input2, confirm as confirm3 } from "@inquirer/prompts";
|
|
1918
2109
|
import chalk5 from "chalk";
|
|
1919
|
-
import
|
|
2110
|
+
import os8 from "os";
|
|
1920
2111
|
|
|
1921
2112
|
// src/lib/auth.ts
|
|
1922
2113
|
import { select, input, password as password2 } from "@inquirer/prompts";
|
|
@@ -2212,7 +2403,7 @@ async function buildCommand(options) {
|
|
|
2212
2403
|
}
|
|
2213
2404
|
async function selectFolders() {
|
|
2214
2405
|
const defaults = getDefaultScanDirs();
|
|
2215
|
-
const homeDir =
|
|
2406
|
+
const homeDir = os8.homedir();
|
|
2216
2407
|
const ALL_VALUE = "__all__";
|
|
2217
2408
|
const CUSTOM_VALUE = "__custom__";
|
|
2218
2409
|
const choices = [
|
|
@@ -2360,6 +2551,18 @@ var RULE_FILES = [
|
|
|
2360
2551
|
filePath: ".zed/rules.md",
|
|
2361
2552
|
strategy: "create",
|
|
2362
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"))
|
|
2363
2566
|
}
|
|
2364
2567
|
];
|
|
2365
2568
|
var DEDICATED_FILE_CONTENT = `## p\u0131ut Context (MCP Server: piut-context)
|
|
@@ -2545,6 +2748,7 @@ async function connectCommand(options) {
|
|
|
2545
2748
|
}
|
|
2546
2749
|
}
|
|
2547
2750
|
const machineId = getMachineId();
|
|
2751
|
+
const hostname = getHostname();
|
|
2548
2752
|
for (const projectPath of selectedPaths) {
|
|
2549
2753
|
const projectActions = byProject.get(projectPath) || [];
|
|
2550
2754
|
const projectName = path13.basename(projectPath);
|
|
@@ -2554,6 +2758,7 @@ async function connectCommand(options) {
|
|
|
2554
2758
|
projectName,
|
|
2555
2759
|
projectPath,
|
|
2556
2760
|
machineId,
|
|
2761
|
+
hostname,
|
|
2557
2762
|
toolsDetected,
|
|
2558
2763
|
configFiles: configFilesWritten
|
|
2559
2764
|
}).catch(() => {
|
|
@@ -2576,7 +2781,9 @@ var DEDICATED_FILES = /* @__PURE__ */ new Set([
|
|
|
2576
2781
|
var APPEND_FILES = [
|
|
2577
2782
|
"CLAUDE.md",
|
|
2578
2783
|
".github/copilot-instructions.md",
|
|
2579
|
-
"CONVENTIONS.md"
|
|
2784
|
+
"CONVENTIONS.md",
|
|
2785
|
+
"GEMINI.md",
|
|
2786
|
+
"AGENTS.md"
|
|
2580
2787
|
];
|
|
2581
2788
|
function hasPiutReference3(filePath) {
|
|
2582
2789
|
try {
|
|
@@ -3052,13 +3259,119 @@ async function doctorCommand(options) {
|
|
|
3052
3259
|
if (result.issues > 0) throw new CliError(`${result.issues} issue(s) found`);
|
|
3053
3260
|
}
|
|
3054
3261
|
|
|
3055
|
-
// src/commands/
|
|
3056
|
-
import { select as select3, confirm as confirm7, checkbox as checkbox6, Separator } from "@inquirer/prompts";
|
|
3262
|
+
// src/commands/vault.ts
|
|
3057
3263
|
import fs14 from "fs";
|
|
3058
3264
|
import path15 from "path";
|
|
3059
|
-
import { exec as exec2 } from "child_process";
|
|
3060
|
-
import os8 from "os";
|
|
3061
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
|
+
|
|
3369
|
+
// src/commands/interactive.ts
|
|
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";
|
|
3062
3375
|
async function authenticate() {
|
|
3063
3376
|
const config = readStore();
|
|
3064
3377
|
const apiKey = config.apiKey;
|
|
@@ -3097,7 +3410,7 @@ async function interactiveMenu() {
|
|
|
3097
3410
|
console.log(warning(" You haven\u2019t built a brain yet."));
|
|
3098
3411
|
console.log(dim(" Your brain is how AI tools learn about you \u2014 your projects, preferences, and context."));
|
|
3099
3412
|
console.log();
|
|
3100
|
-
const wantBuild = await
|
|
3413
|
+
const wantBuild = await confirm8({
|
|
3101
3414
|
message: "Build your brain now?",
|
|
3102
3415
|
default: true
|
|
3103
3416
|
});
|
|
@@ -3113,7 +3426,7 @@ async function interactiveMenu() {
|
|
|
3113
3426
|
console.log(warning(" Your brain is built but not deployed yet."));
|
|
3114
3427
|
console.log(dim(" Deploy it to make your MCP server live so AI tools can read your brain."));
|
|
3115
3428
|
console.log();
|
|
3116
|
-
const wantDeploy = await
|
|
3429
|
+
const wantDeploy = await confirm8({
|
|
3117
3430
|
message: "Deploy your brain now?",
|
|
3118
3431
|
default: true
|
|
3119
3432
|
});
|
|
@@ -3135,10 +3448,23 @@ async function interactiveMenu() {
|
|
|
3135
3448
|
loop: false,
|
|
3136
3449
|
choices: [
|
|
3137
3450
|
{
|
|
3138
|
-
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",
|
|
3139
3458
|
value: "build",
|
|
3140
|
-
description: hasBrain ? "
|
|
3459
|
+
description: hasBrain ? "Rebuild your brain from your files" : "Build your brain from your files"
|
|
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)"
|
|
3141
3466
|
},
|
|
3467
|
+
new Separator(),
|
|
3142
3468
|
{
|
|
3143
3469
|
name: isDeployed ? "Undeploy Brain" : "Deploy Brain",
|
|
3144
3470
|
value: "deploy",
|
|
@@ -3159,23 +3485,21 @@ async function interactiveMenu() {
|
|
|
3159
3485
|
},
|
|
3160
3486
|
new Separator(),
|
|
3161
3487
|
{
|
|
3162
|
-
name: "
|
|
3163
|
-
value: "
|
|
3164
|
-
description: "
|
|
3165
|
-
disabled: !hasBrain && "(build brain first)"
|
|
3488
|
+
name: "View Files",
|
|
3489
|
+
value: "vault-view",
|
|
3490
|
+
description: "List and manage files in your vault"
|
|
3166
3491
|
},
|
|
3167
3492
|
{
|
|
3168
|
-
name: "
|
|
3169
|
-
value: "
|
|
3170
|
-
description: "
|
|
3171
|
-
disabled: !hasBrain && "(build brain first)"
|
|
3493
|
+
name: "Upload Files",
|
|
3494
|
+
value: "vault-upload",
|
|
3495
|
+
description: "Upload a file to your vault"
|
|
3172
3496
|
},
|
|
3497
|
+
new Separator(),
|
|
3173
3498
|
{
|
|
3174
3499
|
name: "Status",
|
|
3175
3500
|
value: "status",
|
|
3176
3501
|
description: "Show brain, deployment, and connected tools/projects"
|
|
3177
3502
|
},
|
|
3178
|
-
new Separator(),
|
|
3179
3503
|
{
|
|
3180
3504
|
name: "Logout",
|
|
3181
3505
|
value: "logout",
|
|
@@ -3194,12 +3518,14 @@ async function interactiveMenu() {
|
|
|
3194
3518
|
if (action === "exit") return;
|
|
3195
3519
|
try {
|
|
3196
3520
|
switch (action) {
|
|
3521
|
+
case "view-brain":
|
|
3522
|
+
await handleViewBrain(apiKey);
|
|
3523
|
+
break;
|
|
3197
3524
|
case "build":
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
}
|
|
3525
|
+
await buildCommand({ key: apiKey });
|
|
3526
|
+
break;
|
|
3527
|
+
case "edit-brain":
|
|
3528
|
+
handleEditBrain();
|
|
3203
3529
|
break;
|
|
3204
3530
|
case "deploy":
|
|
3205
3531
|
if (isDeployed) {
|
|
@@ -3214,11 +3540,11 @@ async function interactiveMenu() {
|
|
|
3214
3540
|
case "connect-projects":
|
|
3215
3541
|
await handleManageProjects(apiKey, currentValidation);
|
|
3216
3542
|
break;
|
|
3217
|
-
case "
|
|
3218
|
-
|
|
3543
|
+
case "vault-view":
|
|
3544
|
+
await handleVaultView(apiKey);
|
|
3219
3545
|
break;
|
|
3220
|
-
case "
|
|
3221
|
-
await
|
|
3546
|
+
case "vault-upload":
|
|
3547
|
+
await handleVaultUpload(apiKey);
|
|
3222
3548
|
break;
|
|
3223
3549
|
case "status":
|
|
3224
3550
|
statusCommand();
|
|
@@ -3241,7 +3567,7 @@ async function interactiveMenu() {
|
|
|
3241
3567
|
} else if (err instanceof CliError) {
|
|
3242
3568
|
console.log();
|
|
3243
3569
|
} else {
|
|
3244
|
-
console.log(
|
|
3570
|
+
console.log(chalk12.red(` Error: ${err.message}`));
|
|
3245
3571
|
console.log();
|
|
3246
3572
|
}
|
|
3247
3573
|
}
|
|
@@ -3252,7 +3578,7 @@ async function interactiveMenu() {
|
|
|
3252
3578
|
}
|
|
3253
3579
|
}
|
|
3254
3580
|
async function handleUndeploy(apiKey) {
|
|
3255
|
-
const confirmed = await
|
|
3581
|
+
const confirmed = await confirm8({
|
|
3256
3582
|
message: "Undeploy your brain? AI tools will lose access to your MCP server.",
|
|
3257
3583
|
default: false
|
|
3258
3584
|
});
|
|
@@ -3264,7 +3590,7 @@ async function handleUndeploy(apiKey) {
|
|
|
3264
3590
|
console.log(dim(" Run ") + brand("piut deploy") + dim(" to re-deploy anytime."));
|
|
3265
3591
|
console.log();
|
|
3266
3592
|
} catch (err) {
|
|
3267
|
-
console.log(
|
|
3593
|
+
console.log(chalk12.red(` \u2717 ${err.message}`));
|
|
3268
3594
|
}
|
|
3269
3595
|
}
|
|
3270
3596
|
async function handleConnectTools(apiKey, validation) {
|
|
@@ -3273,10 +3599,10 @@ async function handleConnectTools(apiKey, validation) {
|
|
|
3273
3599
|
for (const tool of TOOLS) {
|
|
3274
3600
|
const paths = resolveConfigPaths(tool.configPaths);
|
|
3275
3601
|
for (const configPath of paths) {
|
|
3276
|
-
const exists =
|
|
3277
|
-
const parentExists =
|
|
3602
|
+
const exists = fs15.existsSync(configPath);
|
|
3603
|
+
const parentExists = fs15.existsSync(path16.dirname(configPath));
|
|
3278
3604
|
if (exists || parentExists) {
|
|
3279
|
-
const connected = exists && isPiutConfigured(configPath, tool.configKey);
|
|
3605
|
+
const connected = exists && !!tool.configKey && isPiutConfigured(configPath, tool.configKey);
|
|
3280
3606
|
detected.push({ tool, configPath, connected });
|
|
3281
3607
|
break;
|
|
3282
3608
|
}
|
|
@@ -3284,20 +3610,31 @@ async function handleConnectTools(apiKey, validation) {
|
|
|
3284
3610
|
}
|
|
3285
3611
|
if (detected.length === 0) {
|
|
3286
3612
|
console.log(warning(" No supported AI tools detected."));
|
|
3287
|
-
console.log(dim(" Supported: Claude Code, Claude Desktop, Cursor, Windsurf, GitHub Copilot, Amazon Q, Zed"));
|
|
3288
3613
|
console.log();
|
|
3289
3614
|
return;
|
|
3290
3615
|
}
|
|
3291
|
-
const
|
|
3292
|
-
const
|
|
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;
|
|
3293
3620
|
if (connectedCount > 0 || availableCount > 0) {
|
|
3294
3621
|
const parts = [];
|
|
3295
3622
|
if (connectedCount > 0) parts.push(`${connectedCount} connected`);
|
|
3296
3623
|
if (availableCount > 0) parts.push(`${availableCount} available`);
|
|
3624
|
+
if (skillOnlyTools.length > 0) parts.push(`${skillOnlyTools.length} skill-only`);
|
|
3297
3625
|
console.log(dim(` ${parts.join(", ")}`));
|
|
3298
3626
|
}
|
|
3299
3627
|
console.log();
|
|
3300
|
-
|
|
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.'));
|
|
3634
|
+
console.log();
|
|
3635
|
+
return;
|
|
3636
|
+
}
|
|
3637
|
+
const choices = mcpTools.map((d) => ({
|
|
3301
3638
|
name: `${d.tool.name}${d.connected ? dim(" (connected)") : ""}`,
|
|
3302
3639
|
value: d,
|
|
3303
3640
|
checked: d.connected
|
|
@@ -3307,7 +3644,7 @@ async function handleConnectTools(apiKey, validation) {
|
|
|
3307
3644
|
choices
|
|
3308
3645
|
});
|
|
3309
3646
|
const toConnect = selected.filter((s) => !s.connected);
|
|
3310
|
-
const toDisconnect =
|
|
3647
|
+
const toDisconnect = mcpTools.filter((d) => d.connected && !selected.includes(d));
|
|
3311
3648
|
if (toConnect.length === 0 && toDisconnect.length === 0) {
|
|
3312
3649
|
console.log(dim(" No changes."));
|
|
3313
3650
|
console.log();
|
|
@@ -3315,18 +3652,24 @@ async function handleConnectTools(apiKey, validation) {
|
|
|
3315
3652
|
}
|
|
3316
3653
|
console.log();
|
|
3317
3654
|
for (const { tool, configPath } of toConnect) {
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
|
|
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
|
+
}
|
|
3321
3660
|
}
|
|
3322
3661
|
const removedNames = [];
|
|
3323
3662
|
for (const { tool, configPath } of toDisconnect) {
|
|
3663
|
+
if (!tool.configKey) continue;
|
|
3324
3664
|
const removed = removeFromConfig(configPath, tool.configKey);
|
|
3325
3665
|
if (removed) {
|
|
3326
3666
|
removedNames.push(tool.name);
|
|
3327
3667
|
toolLine(tool.name, warning("disconnected"), "\u2714");
|
|
3328
3668
|
}
|
|
3329
3669
|
}
|
|
3670
|
+
if (skillOnlyTools.length > 0) {
|
|
3671
|
+
console.log(dim(` Skill-only (use "Connect Projects"): ${skillOnlyTools.map((d) => d.tool.name).join(", ")}`));
|
|
3672
|
+
}
|
|
3330
3673
|
if (toConnect.length > 0 && validation.serverUrl) {
|
|
3331
3674
|
await Promise.all(
|
|
3332
3675
|
toConnect.map(({ tool }) => pingMcp(validation.serverUrl, apiKey, tool.name))
|
|
@@ -3380,29 +3723,29 @@ async function handleManageProjects(apiKey, validation) {
|
|
|
3380
3723
|
console.log();
|
|
3381
3724
|
const copilotTool = TOOLS.find((t) => t.id === "copilot");
|
|
3382
3725
|
for (const { project } of toConnect) {
|
|
3383
|
-
const projectName =
|
|
3726
|
+
const projectName = path16.basename(project.path);
|
|
3384
3727
|
writePiutConfig(project.path, { slug, apiKey, serverUrl });
|
|
3385
3728
|
await writePiutSkill(project.path, slug, apiKey);
|
|
3386
3729
|
ensureGitignored(project.path);
|
|
3387
3730
|
if (copilotTool) {
|
|
3388
|
-
const hasCopilot =
|
|
3731
|
+
const hasCopilot = fs15.existsSync(path16.join(project.path, ".github", "copilot-instructions.md")) || fs15.existsSync(path16.join(project.path, ".github"));
|
|
3389
3732
|
if (hasCopilot) {
|
|
3390
|
-
const vscodeMcpPath =
|
|
3733
|
+
const vscodeMcpPath = path16.join(project.path, ".vscode", "mcp.json");
|
|
3391
3734
|
const serverConfig = copilotTool.generateConfig(slug, apiKey);
|
|
3392
3735
|
mergeConfig(vscodeMcpPath, copilotTool.configKey, serverConfig);
|
|
3393
3736
|
}
|
|
3394
3737
|
}
|
|
3395
3738
|
for (const rule of RULE_FILES) {
|
|
3396
3739
|
if (!rule.detect(project)) continue;
|
|
3397
|
-
const absPath =
|
|
3398
|
-
if (
|
|
3399
|
-
if (rule.strategy === "create" || !
|
|
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)) {
|
|
3400
3743
|
const isAppendType = rule.strategy === "append";
|
|
3401
3744
|
const content = isAppendType ? PROJECT_SKILL_SNIPPET + "\n" : DEDICATED_FILE_CONTENT;
|
|
3402
|
-
|
|
3403
|
-
|
|
3745
|
+
fs15.mkdirSync(path16.dirname(absPath), { recursive: true });
|
|
3746
|
+
fs15.writeFileSync(absPath, content, "utf-8");
|
|
3404
3747
|
} else {
|
|
3405
|
-
|
|
3748
|
+
fs15.appendFileSync(absPath, APPEND_SECTION);
|
|
3406
3749
|
}
|
|
3407
3750
|
}
|
|
3408
3751
|
toolLine(projectName, success("connected"), "\u2714");
|
|
@@ -3418,24 +3761,24 @@ async function handleManageProjects(apiKey, validation) {
|
|
|
3418
3761
|
});
|
|
3419
3762
|
}
|
|
3420
3763
|
for (const { project } of toDisconnect) {
|
|
3421
|
-
const projectName =
|
|
3764
|
+
const projectName = path16.basename(project.path);
|
|
3422
3765
|
for (const dedicatedFile of DEDICATED_FILES) {
|
|
3423
|
-
const absPath =
|
|
3424
|
-
if (
|
|
3766
|
+
const absPath = path16.join(project.path, dedicatedFile);
|
|
3767
|
+
if (fs15.existsSync(absPath) && hasPiutReference2(absPath)) {
|
|
3425
3768
|
try {
|
|
3426
|
-
|
|
3769
|
+
fs15.unlinkSync(absPath);
|
|
3427
3770
|
} catch {
|
|
3428
3771
|
}
|
|
3429
3772
|
}
|
|
3430
3773
|
}
|
|
3431
3774
|
for (const appendFile of APPEND_FILES) {
|
|
3432
|
-
const absPath =
|
|
3433
|
-
if (
|
|
3775
|
+
const absPath = path16.join(project.path, appendFile);
|
|
3776
|
+
if (fs15.existsSync(absPath) && hasPiutReference2(absPath)) {
|
|
3434
3777
|
removePiutSection(absPath);
|
|
3435
3778
|
}
|
|
3436
3779
|
}
|
|
3437
|
-
const vscodeMcpPath =
|
|
3438
|
-
if (
|
|
3780
|
+
const vscodeMcpPath = path16.join(project.path, ".vscode", "mcp.json");
|
|
3781
|
+
if (fs15.existsSync(vscodeMcpPath) && isPiutConfigured(vscodeMcpPath, "servers")) {
|
|
3439
3782
|
removeFromConfig(vscodeMcpPath, "servers");
|
|
3440
3783
|
}
|
|
3441
3784
|
removePiutDir(project.path);
|
|
@@ -3459,95 +3802,84 @@ function handleEditBrain() {
|
|
|
3459
3802
|
console.log(success(" \u2713 Opened in browser."));
|
|
3460
3803
|
console.log();
|
|
3461
3804
|
}
|
|
3462
|
-
function
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
parts.push("");
|
|
3467
|
-
if (summary.folders.length > 0) {
|
|
3468
|
-
parts.push(`## Folder Structure
|
|
3469
|
-
${summary.folders.join("\n")}`);
|
|
3470
|
-
}
|
|
3471
|
-
if (summary.projects.length > 0) {
|
|
3472
|
-
const projectLines = summary.projects.map(
|
|
3473
|
-
(p) => `- **${p.name}** (${p.path})${p.description ? ` \u2014 ${p.description}` : ""}`
|
|
3474
|
-
);
|
|
3475
|
-
parts.push(`## Projects Found
|
|
3476
|
-
${projectLines.join("\n")}`);
|
|
3477
|
-
}
|
|
3478
|
-
const totalFiles = (summary.configFiles?.length || 0) + (summary.recentDocuments?.length || 0) + (summary.personalDocuments?.length || 0);
|
|
3479
|
-
const fileLimit = totalFiles <= 10 ? 1e4 : totalFiles <= 30 ? 5e3 : totalFiles <= 60 ? 3e3 : 2e3;
|
|
3480
|
-
if (summary.configFiles.length > 0) {
|
|
3481
|
-
const configBlocks = summary.configFiles.map(
|
|
3482
|
-
(f) => `### ${f.name}
|
|
3483
|
-
\`\`\`
|
|
3484
|
-
${f.content.slice(0, fileLimit)}
|
|
3485
|
-
\`\`\``
|
|
3486
|
-
);
|
|
3487
|
-
parts.push(`## AI Config Files
|
|
3488
|
-
${configBlocks.join("\n\n")}`);
|
|
3489
|
-
}
|
|
3490
|
-
if (summary.recentDocuments.length > 0) {
|
|
3491
|
-
const docBlocks = summary.recentDocuments.map(
|
|
3492
|
-
(f) => `### ${f.name}
|
|
3493
|
-
${f.content.slice(0, fileLimit)}`
|
|
3494
|
-
);
|
|
3495
|
-
parts.push(`## Recent Documents
|
|
3496
|
-
${docBlocks.join("\n\n")}`);
|
|
3497
|
-
}
|
|
3498
|
-
if (summary.personalDocuments && summary.personalDocuments.length > 0) {
|
|
3499
|
-
const docBlocks = summary.personalDocuments.map(
|
|
3500
|
-
(f) => `### ${f.name}
|
|
3501
|
-
${f.content.slice(0, fileLimit)}`
|
|
3502
|
-
);
|
|
3503
|
-
parts.push(`## Personal Documents
|
|
3504
|
-
${docBlocks.join("\n\n")}`);
|
|
3505
|
-
}
|
|
3506
|
-
let result = parts.join("\n\n");
|
|
3507
|
-
if (result.length > 4e5) {
|
|
3508
|
-
result = result.slice(0, 4e5);
|
|
3509
|
-
}
|
|
3510
|
-
return result;
|
|
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`;
|
|
3511
3809
|
}
|
|
3512
|
-
async function
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
console.log(dim(
|
|
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.'));
|
|
3516
3814
|
console.log();
|
|
3517
3815
|
return;
|
|
3518
3816
|
}
|
|
3519
|
-
const cwd = process.cwd();
|
|
3520
|
-
const cwdDisplay = cwd.replace(os8.homedir(), "~");
|
|
3521
|
-
console.log(dim(` Scanning ${cwdDisplay}...`));
|
|
3522
|
-
const scanResult = await scanFolders([cwd]);
|
|
3523
|
-
const allFolderPaths = scanResult.folders.map((f) => f.path);
|
|
3524
|
-
const brainInput = buildBrainInput(scanResult, allFolderPaths);
|
|
3525
|
-
const projCount = brainInput.summary.projects.length;
|
|
3526
|
-
const cfgCount = brainInput.summary.configFiles.length;
|
|
3527
|
-
const dcCount = (brainInput.summary.personalDocuments?.length || 0) + brainInput.summary.recentDocuments.length;
|
|
3528
|
-
console.log(success(` Scanned: ${projCount} projects, ${cfgCount} config files, ${dcCount} docs`));
|
|
3529
3817
|
console.log();
|
|
3530
|
-
|
|
3531
|
-
|
|
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`));
|
|
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}`));
|
|
3532
3866
|
console.log();
|
|
3533
3867
|
return;
|
|
3534
3868
|
}
|
|
3535
|
-
const
|
|
3869
|
+
const filename = path16.basename(resolved);
|
|
3870
|
+
const content = fs15.readFileSync(resolved, "utf-8");
|
|
3536
3871
|
const spinner = new Spinner();
|
|
3537
|
-
spinner.start(
|
|
3872
|
+
spinner.start(`Uploading ${filename}...`);
|
|
3538
3873
|
try {
|
|
3539
|
-
const result = await
|
|
3874
|
+
const result = await uploadVaultFile(apiKey, filename, content);
|
|
3540
3875
|
spinner.stop();
|
|
3541
|
-
console.log();
|
|
3542
|
-
console.log(
|
|
3543
|
-
console.log(dim(` ${result.summary}`));
|
|
3876
|
+
console.log(success(` Uploaded ${result.filename}`) + dim(` (${formatSize3(result.sizeBytes)})`));
|
|
3877
|
+
if (result.summary) console.log(dim(` ${result.summary}`));
|
|
3544
3878
|
console.log();
|
|
3545
3879
|
} catch (err) {
|
|
3546
3880
|
spinner.stop();
|
|
3547
|
-
|
|
3548
|
-
console.log(chalk11.red(` \u2717 ${msg}`));
|
|
3881
|
+
console.log(chalk12.red(` ${err.message}`));
|
|
3549
3882
|
console.log();
|
|
3550
|
-
throw new CliError(msg);
|
|
3551
3883
|
}
|
|
3552
3884
|
}
|
|
3553
3885
|
async function handleViewBrain(apiKey) {
|
|
@@ -3578,7 +3910,7 @@ async function handleViewBrain(apiKey) {
|
|
|
3578
3910
|
if (hasUnpublishedChanges) {
|
|
3579
3911
|
console.log(warning(" You have unpublished changes."));
|
|
3580
3912
|
console.log();
|
|
3581
|
-
const wantPublish = await
|
|
3913
|
+
const wantPublish = await confirm8({
|
|
3582
3914
|
message: "Publish now?",
|
|
3583
3915
|
default: true
|
|
3584
3916
|
});
|
|
@@ -3592,11 +3924,11 @@ async function handleViewBrain(apiKey) {
|
|
|
3592
3924
|
console.log();
|
|
3593
3925
|
const msg = err.message;
|
|
3594
3926
|
if (msg === "REQUIRES_SUBSCRIPTION") {
|
|
3595
|
-
console.log(
|
|
3927
|
+
console.log(chalk12.yellow(" Deploy requires an active subscription ($10/mo)."));
|
|
3596
3928
|
console.log(` Subscribe at: ${brand("https://piut.com/dashboard/billing")}`);
|
|
3597
3929
|
console.log(dim(" 14-day free trial included."));
|
|
3598
3930
|
} else {
|
|
3599
|
-
console.log(
|
|
3931
|
+
console.log(chalk12.red(` \u2717 ${msg}`));
|
|
3600
3932
|
}
|
|
3601
3933
|
console.log();
|
|
3602
3934
|
}
|
|
@@ -3605,7 +3937,7 @@ async function handleViewBrain(apiKey) {
|
|
|
3605
3937
|
}
|
|
3606
3938
|
|
|
3607
3939
|
// src/cli.ts
|
|
3608
|
-
var VERSION = "3.
|
|
3940
|
+
var VERSION = "3.7.0";
|
|
3609
3941
|
function withExit(fn) {
|
|
3610
3942
|
return async (...args2) => {
|
|
3611
3943
|
try {
|
|
@@ -3631,6 +3963,11 @@ program.command("remove").description("Remove all p\u0131ut configurations").act
|
|
|
3631
3963
|
program.command("login").description("Authenticate with p\u0131ut (email, browser, or API key)").action(withExit(loginCommand));
|
|
3632
3964
|
program.command("logout").description("Remove saved API key").action(logoutCommand);
|
|
3633
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));
|
|
3634
3971
|
program.command("update").description("Check for and install CLI updates").action(() => updateCommand(VERSION));
|
|
3635
3972
|
var args = process.argv.slice(2);
|
|
3636
3973
|
if (args.includes("--version") || args.includes("-V")) {
|