@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.
Files changed (2) hide show
  1. package/dist/cli.js +520 -183
  2. 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 resyncBrain(serverUrl, key, content) {
213
- const res = await fetch(serverUrl, {
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
- throw new Error(`Resync failed (HTTP ${res.status})`);
228
+ const body = await res.json().catch(() => ({ error: "Unknown error" }));
229
+ throw new Error(body.error || `Unpublish failed (HTTP ${res.status})`);
231
230
  }
232
- const data = await res.json();
233
- if (data.error) {
234
- throw new Error(data.error.message || "Resync failed");
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
- const resultContent = data.result?.content;
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 unpublishServer(key) {
241
- const res = await fetch(`${API_BASE}/api/mcp/publish`, {
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({ published: false })
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 || `Unpublish failed (HTTP ${res.status})`);
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
- const serverConfig = tool.generateConfig(slug, apiKey);
894
- mergeConfig(configPath, tool.configKey, serverConfig);
895
- configured.push(tool.name);
896
- toolLine(tool.name, success("configured"), "\u2714");
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
- console.log(" AI tool configuration:");
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(" Connected projects:");
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 os7 from "os";
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 = os7.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/interactive.ts
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 confirm7({
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 confirm7({
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: hasBrain ? "Resync Brain" : "Build Brain",
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 ? "Rescan your files and merge updates into your brain" : "Build your brain from your files"
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: "Edit Brain",
3163
- value: "edit-brain",
3164
- description: "Open piut.com to edit your brain",
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: "View Brain",
3169
- value: "view-brain",
3170
- description: "View all 5 brain sections",
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
- if (hasBrain) {
3199
- await handleResyncBrain(apiKey, currentValidation);
3200
- } else {
3201
- await buildCommand({ key: apiKey });
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 "edit-brain":
3218
- handleEditBrain();
3543
+ case "vault-view":
3544
+ await handleVaultView(apiKey);
3219
3545
  break;
3220
- case "view-brain":
3221
- await handleViewBrain(apiKey);
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(chalk11.red(` Error: ${err.message}`));
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 confirm7({
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(chalk11.red(` \u2717 ${err.message}`));
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 = fs14.existsSync(configPath);
3277
- const parentExists = fs14.existsSync(path15.dirname(configPath));
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 connectedCount = detected.filter((d) => d.connected).length;
3292
- const availableCount = detected.length - connectedCount;
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
- const choices = detected.map((d) => ({
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 = detected.filter((d) => d.connected && !selected.includes(d));
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
- const serverConfig = tool.generateConfig(slug, apiKey);
3319
- mergeConfig(configPath, tool.configKey, serverConfig);
3320
- toolLine(tool.name, success("connected"), "\u2714");
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 = path15.basename(project.path);
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 = fs14.existsSync(path15.join(project.path, ".github", "copilot-instructions.md")) || fs14.existsSync(path15.join(project.path, ".github"));
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 = path15.join(project.path, ".vscode", "mcp.json");
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 = path15.join(project.path, rule.filePath);
3398
- if (fs14.existsSync(absPath) && hasPiutReference2(absPath)) continue;
3399
- if (rule.strategy === "create" || !fs14.existsSync(absPath)) {
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
- fs14.mkdirSync(path15.dirname(absPath), { recursive: true });
3403
- fs14.writeFileSync(absPath, content, "utf-8");
3745
+ fs15.mkdirSync(path16.dirname(absPath), { recursive: true });
3746
+ fs15.writeFileSync(absPath, content, "utf-8");
3404
3747
  } else {
3405
- fs14.appendFileSync(absPath, APPEND_SECTION);
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 = path15.basename(project.path);
3764
+ const projectName = path16.basename(project.path);
3422
3765
  for (const dedicatedFile of DEDICATED_FILES) {
3423
- const absPath = path15.join(project.path, dedicatedFile);
3424
- if (fs14.existsSync(absPath) && hasPiutReference2(absPath)) {
3766
+ const absPath = path16.join(project.path, dedicatedFile);
3767
+ if (fs15.existsSync(absPath) && hasPiutReference2(absPath)) {
3425
3768
  try {
3426
- fs14.unlinkSync(absPath);
3769
+ fs15.unlinkSync(absPath);
3427
3770
  } catch {
3428
3771
  }
3429
3772
  }
3430
3773
  }
3431
3774
  for (const appendFile of APPEND_FILES) {
3432
- const absPath = path15.join(project.path, appendFile);
3433
- if (fs14.existsSync(absPath) && hasPiutReference2(absPath)) {
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 = path15.join(project.path, ".vscode", "mcp.json");
3438
- if (fs14.existsSync(vscodeMcpPath) && isPiutConfigured(vscodeMcpPath, "servers")) {
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 formatScanContent(input3) {
3463
- const { summary } = input3;
3464
- const parts = [];
3465
- parts.push("The user has re-scanned their filesystem. Below is updated information about their projects, config files, and documents. Please merge this into the existing brain sections, preserving existing content and updating what has changed.");
3466
- parts.push("");
3467
- if (summary.folders.length > 0) {
3468
- parts.push(`## Folder Structure
3469
- ${summary.folders.join("\n")}`);
3470
- }
3471
- if (summary.projects.length > 0) {
3472
- const projectLines = summary.projects.map(
3473
- (p) => `- **${p.name}** (${p.path})${p.description ? ` \u2014 ${p.description}` : ""}`
3474
- );
3475
- parts.push(`## Projects Found
3476
- ${projectLines.join("\n")}`);
3477
- }
3478
- const totalFiles = (summary.configFiles?.length || 0) + (summary.recentDocuments?.length || 0) + (summary.personalDocuments?.length || 0);
3479
- const fileLimit = totalFiles <= 10 ? 1e4 : totalFiles <= 30 ? 5e3 : totalFiles <= 60 ? 3e3 : 2e3;
3480
- if (summary.configFiles.length > 0) {
3481
- const configBlocks = summary.configFiles.map(
3482
- (f) => `### ${f.name}
3483
- \`\`\`
3484
- ${f.content.slice(0, fileLimit)}
3485
- \`\`\``
3486
- );
3487
- parts.push(`## AI Config Files
3488
- ${configBlocks.join("\n\n")}`);
3489
- }
3490
- if (summary.recentDocuments.length > 0) {
3491
- const docBlocks = summary.recentDocuments.map(
3492
- (f) => `### ${f.name}
3493
- ${f.content.slice(0, fileLimit)}`
3494
- );
3495
- parts.push(`## Recent Documents
3496
- ${docBlocks.join("\n\n")}`);
3497
- }
3498
- if (summary.personalDocuments && summary.personalDocuments.length > 0) {
3499
- const docBlocks = summary.personalDocuments.map(
3500
- (f) => `### ${f.name}
3501
- ${f.content.slice(0, fileLimit)}`
3502
- );
3503
- parts.push(`## Personal Documents
3504
- ${docBlocks.join("\n\n")}`);
3505
- }
3506
- let result = parts.join("\n\n");
3507
- if (result.length > 4e5) {
3508
- result = result.slice(0, 4e5);
3509
- }
3510
- return result;
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 handleResyncBrain(apiKey, validation) {
3513
- if (!validation.serverUrl) {
3514
- console.log(warning(" Brain must be deployed before resyncing."));
3515
- console.log(dim(" Run ") + brand("piut deploy") + dim(" first."));
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
- if (projCount === 0 && cfgCount === 0) {
3531
- console.log(chalk11.yellow(" No projects or config files found to resync from."));
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 content = formatScanContent(brainInput);
3869
+ const filename = path16.basename(resolved);
3870
+ const content = fs15.readFileSync(resolved, "utf-8");
3536
3871
  const spinner = new Spinner();
3537
- spinner.start("Resyncing brain...");
3872
+ spinner.start(`Uploading ${filename}...`);
3538
3873
  try {
3539
- const result = await resyncBrain(validation.serverUrl, apiKey, content);
3874
+ const result = await uploadVaultFile(apiKey, filename, content);
3540
3875
  spinner.stop();
3541
- console.log();
3542
- console.log(success(" \u2713 Brain resynced."));
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
- const msg = err.message;
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 confirm7({
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(chalk11.yellow(" Deploy requires an active subscription ($10/mo)."));
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(chalk11.red(` \u2717 ${msg}`));
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.6.0";
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")) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@piut/cli",
3
- "version": "3.6.0",
3
+ "version": "3.7.0",
4
4
  "description": "Build your AI brain instantly. Deploy it as an MCP server. Connect it to every project.",
5
5
  "type": "module",
6
6
  "bin": {