@piut/cli 3.6.0 → 3.8.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 +1200 -972
  2. package/package.json +3 -6
package/dist/cli.js CHANGED
@@ -1,16 +1,338 @@
1
1
  #!/usr/bin/env node
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __esm = (fn, res) => function __init() {
5
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
6
+ };
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+
12
+ // node_modules/tsup/assets/esm_shims.js
13
+ import path from "path";
14
+ import { fileURLToPath } from "url";
15
+ var init_esm_shims = __esm({
16
+ "node_modules/tsup/assets/esm_shims.js"() {
17
+ "use strict";
18
+ }
19
+ });
20
+
21
+ // src/lib/tree-prompt.ts
22
+ var tree_prompt_exports = {};
23
+ __export(tree_prompt_exports, {
24
+ collapseNode: () => collapseNode,
25
+ default: () => tree_prompt_default,
26
+ expandNode: () => expandNode,
27
+ getParentIndex: () => getParentIndex,
28
+ loadChildren: () => loadChildren,
29
+ shouldShowInTree: () => shouldShowInTree
30
+ });
31
+ import fs13 from "fs";
32
+ import path14 from "path";
33
+ import os8 from "os";
34
+ import {
35
+ createPrompt,
36
+ useState,
37
+ useKeypress,
38
+ usePagination,
39
+ useRef,
40
+ isUpKey,
41
+ isDownKey,
42
+ isSpaceKey,
43
+ isEnterKey
44
+ } from "@inquirer/core";
45
+ import chalk12 from "chalk";
46
+ function shouldShowInTree(name) {
47
+ if (TREE_SKIP.has(name)) return false;
48
+ if (name.startsWith(".") && !TREE_INCLUDE_DOT_DIRS.has(name)) return false;
49
+ return true;
50
+ }
51
+ function shouldShowFile(name) {
52
+ if (name.startsWith(".")) return false;
53
+ const ext = path14.extname(name).toLowerCase();
54
+ return !HIDDEN_FILE_EXTENSIONS.has(ext);
55
+ }
56
+ function loadChildren(parentPath, parentDepth, includeFiles = false) {
57
+ try {
58
+ const entries = fs13.readdirSync(parentPath, { withFileTypes: true });
59
+ const dirs = [];
60
+ const files = [];
61
+ for (const entry of entries) {
62
+ if (entry.isDirectory()) {
63
+ if (!shouldShowInTree(entry.name)) continue;
64
+ dirs.push({
65
+ path: path14.join(parentPath, entry.name),
66
+ name: entry.name,
67
+ depth: parentDepth + 1,
68
+ expanded: false,
69
+ selected: false,
70
+ children: null
71
+ });
72
+ } else if (includeFiles && entry.isFile()) {
73
+ if (!shouldShowFile(entry.name)) continue;
74
+ files.push({
75
+ path: path14.join(parentPath, entry.name),
76
+ name: entry.name,
77
+ depth: parentDepth + 1,
78
+ expanded: false,
79
+ selected: false,
80
+ children: null,
81
+ isFile: true
82
+ });
83
+ }
84
+ }
85
+ dirs.sort((a, b) => a.name.localeCompare(b.name));
86
+ files.sort((a, b) => a.name.localeCompare(b.name));
87
+ return [...dirs, ...files];
88
+ } catch {
89
+ return [];
90
+ }
91
+ }
92
+ function expandNode(items, index, children) {
93
+ const result = [...items];
94
+ result[index] = { ...result[index], expanded: true, children, empty: children.length === 0 };
95
+ result.splice(index + 1, 0, ...children);
96
+ return result;
97
+ }
98
+ function collapseNode(items, index) {
99
+ const node = items[index];
100
+ const result = [...items];
101
+ result[index] = { ...result[index], expanded: false };
102
+ let removeCount = 0;
103
+ for (let i = index + 1; i < result.length; i++) {
104
+ if (result[i].depth > node.depth) {
105
+ removeCount++;
106
+ } else {
107
+ break;
108
+ }
109
+ }
110
+ if (removeCount > 0) {
111
+ result.splice(index + 1, removeCount);
112
+ }
113
+ return result;
114
+ }
115
+ function getParentIndex(items, index) {
116
+ const targetDepth = items[index].depth - 1;
117
+ for (let i = index - 1; i >= 0; i--) {
118
+ if (items[i].depth === targetDepth) return i;
119
+ }
120
+ return index;
121
+ }
122
+ var TREE_SKIP, TREE_INCLUDE_DOT_DIRS, HIDDEN_FILE_EXTENSIONS, treePrompt, tree_prompt_default;
123
+ var init_tree_prompt = __esm({
124
+ "src/lib/tree-prompt.ts"() {
125
+ "use strict";
126
+ init_esm_shims();
127
+ TREE_SKIP = /* @__PURE__ */ new Set([
128
+ "node_modules",
129
+ ".git",
130
+ "__pycache__",
131
+ ".venv",
132
+ "venv",
133
+ "dist",
134
+ "build",
135
+ ".next",
136
+ ".nuxt",
137
+ ".output",
138
+ ".Trash",
139
+ "Library",
140
+ ".cache",
141
+ ".npm",
142
+ ".yarn",
143
+ ".pnpm-store",
144
+ "Caches",
145
+ "Cache",
146
+ ".piut",
147
+ "Applications",
148
+ "Public",
149
+ "Movies",
150
+ "Music",
151
+ "Pictures",
152
+ "Templates"
153
+ ]);
154
+ TREE_INCLUDE_DOT_DIRS = /* @__PURE__ */ new Set([
155
+ ".cursor",
156
+ ".windsurf",
157
+ ".openclaw",
158
+ ".zed",
159
+ ".github",
160
+ ".amazonq",
161
+ ".gemini",
162
+ ".mcporter",
163
+ ".paperclip",
164
+ ".vscode"
165
+ ]);
166
+ HIDDEN_FILE_EXTENSIONS = /* @__PURE__ */ new Set([
167
+ ".png",
168
+ ".jpg",
169
+ ".jpeg",
170
+ ".gif",
171
+ ".svg",
172
+ ".ico",
173
+ ".webp",
174
+ ".bmp",
175
+ ".tiff",
176
+ ".heic",
177
+ ".mp3",
178
+ ".mp4",
179
+ ".wav",
180
+ ".aac",
181
+ ".flac",
182
+ ".ogg",
183
+ ".avi",
184
+ ".mov",
185
+ ".mkv",
186
+ ".wmv",
187
+ ".webm",
188
+ ".zip",
189
+ ".tar",
190
+ ".gz",
191
+ ".bz2",
192
+ ".xz",
193
+ ".7z",
194
+ ".rar",
195
+ ".dmg",
196
+ ".iso",
197
+ ".exe",
198
+ ".dll",
199
+ ".so",
200
+ ".dylib",
201
+ ".o",
202
+ ".a",
203
+ ".wasm",
204
+ ".class",
205
+ ".jar",
206
+ ".pyc",
207
+ ".pyo",
208
+ ".ttf",
209
+ ".otf",
210
+ ".woff",
211
+ ".woff2",
212
+ ".eot",
213
+ ".lock",
214
+ ".map"
215
+ ]);
216
+ treePrompt = createPrompt((config, done) => {
217
+ const root = config.root ?? os8.homedir();
218
+ const pageSize = config.pageSize ?? 15;
219
+ const mode = config.mode ?? "folders";
220
+ const includeFiles = mode === "files";
221
+ const prefix = chalk12.green("?");
222
+ const childrenCache = useRef(/* @__PURE__ */ new Map()).current;
223
+ const [items, setItems] = useState(() => {
224
+ const rootChildren = loadChildren(root, -1, includeFiles);
225
+ childrenCache.set(root, rootChildren);
226
+ return rootChildren;
227
+ });
228
+ const [active, setActive] = useState(0);
229
+ const [done_, setDone] = useState(false);
230
+ useKeypress((event) => {
231
+ if (done_) return;
232
+ if (isEnterKey(event)) {
233
+ const selected = items.filter((n) => n.selected).map((n) => n.path);
234
+ setDone(true);
235
+ done(selected);
236
+ return;
237
+ }
238
+ if (isUpKey(event)) {
239
+ setActive(Math.max(0, active - 1));
240
+ return;
241
+ }
242
+ if (isDownKey(event)) {
243
+ setActive(Math.min(items.length - 1, active + 1));
244
+ return;
245
+ }
246
+ if (isSpaceKey(event)) {
247
+ const node = items[active];
248
+ if (includeFiles && !node.isFile) return;
249
+ if (!includeFiles && node.isFile) return;
250
+ const updated = [...items];
251
+ updated[active] = { ...updated[active], selected: !updated[active].selected };
252
+ setItems(updated);
253
+ return;
254
+ }
255
+ if (event.name === "right") {
256
+ const node = items[active];
257
+ if (node.isFile) return;
258
+ if (node.expanded) return;
259
+ let children;
260
+ if (childrenCache.has(node.path)) {
261
+ children = childrenCache.get(node.path);
262
+ } else {
263
+ children = loadChildren(node.path, node.depth, includeFiles);
264
+ childrenCache.set(node.path, children);
265
+ }
266
+ setItems(expandNode(items, active, children));
267
+ return;
268
+ }
269
+ if (event.name === "left") {
270
+ const node = items[active];
271
+ if (!node.isFile && node.expanded) {
272
+ setItems(collapseNode(items, active));
273
+ } else {
274
+ const parentIdx = getParentIndex(items, active);
275
+ setActive(parentIdx);
276
+ }
277
+ return;
278
+ }
279
+ });
280
+ if (done_) {
281
+ const selected = items.filter((n) => n.selected);
282
+ const label = includeFiles ? "file" : "folder";
283
+ const summary = selected.length === 0 ? chalk12.dim(`no ${label}s selected`) : selected.map((n) => n.isFile ? n.name : n.path).join(", ");
284
+ return `${prefix} ${config.message} ${chalk12.cyan(summary)}`;
285
+ }
286
+ const page = usePagination({
287
+ items,
288
+ active,
289
+ pageSize,
290
+ loop: false,
291
+ renderItem({ item, isActive }) {
292
+ const indent = " ".repeat(item.depth + 1);
293
+ if (item.isFile) {
294
+ const marker2 = item.selected ? chalk12.green("\u25CF ") : "\u25CB ";
295
+ const line2 = `${indent} ${marker2}${item.name}`;
296
+ return isActive ? chalk12.cyan(line2) : line2;
297
+ }
298
+ const icon = item.expanded ? "\u25BE" : "\u25B8";
299
+ const name = `${item.name}/`;
300
+ const suffix = item.error ? chalk12.dim(" (permission denied)") : item.empty ? chalk12.dim(" (empty)") : "";
301
+ if (includeFiles) {
302
+ const line2 = `${indent}${icon} ${name}${suffix}`;
303
+ return isActive ? chalk12.cyan(line2) : chalk12.dim(line2);
304
+ }
305
+ const marker = item.selected ? chalk12.green("\u25CF ") : "\u25CB ";
306
+ const line = `${indent}${icon} ${marker}${name}${suffix}`;
307
+ return isActive ? chalk12.cyan(line) : line;
308
+ }
309
+ });
310
+ const selectHint = includeFiles ? "space select file" : "space select";
311
+ const help = chalk12.dim(` \u2191\u2193 navigate \u2192 expand \u2190 collapse ${selectHint} enter done`);
312
+ const header = chalk12.dim(` ${root}`);
313
+ return `${prefix} ${config.message}
314
+ ${help}
315
+ ${header}
316
+ ${page}`;
317
+ });
318
+ tree_prompt_default = treePrompt;
319
+ }
320
+ });
2
321
 
3
322
  // src/cli.ts
323
+ init_esm_shims();
4
324
  import { Command } from "commander";
5
325
 
6
326
  // src/commands/setup.ts
327
+ init_esm_shims();
7
328
  import fs4 from "fs";
8
- import path6 from "path";
329
+ import path7 from "path";
9
330
  import { execSync } from "child_process";
10
331
  import { password, confirm, checkbox } from "@inquirer/prompts";
11
332
  import chalk2 from "chalk";
12
333
 
13
334
  // src/lib/api.ts
335
+ init_esm_shims();
14
336
  import os from "os";
15
337
  import crypto from "crypto";
16
338
  var API_BASE = process.env.PIUT_API_BASE || "https://piut.com";
@@ -36,7 +358,7 @@ async function loginWithEmail(email, password3) {
36
358
  }
37
359
  return res.json();
38
360
  }
39
- async function* buildBrainStreaming(key, input3) {
361
+ async function* buildBrainStreaming(key, input2) {
40
362
  const res = await fetch(`${API_BASE}/api/cli/build-brain`, {
41
363
  method: "POST",
42
364
  headers: {
@@ -44,7 +366,7 @@ async function* buildBrainStreaming(key, input3) {
44
366
  "Content-Type": "application/json",
45
367
  Accept: "text/event-stream"
46
368
  },
47
- body: JSON.stringify(input3)
369
+ body: JSON.stringify(input2)
48
370
  });
49
371
  if (!res.ok) {
50
372
  const body = await res.json().catch(() => ({ error: "Unknown error" }));
@@ -122,7 +444,10 @@ async function pingMcp(serverUrl, key, toolName) {
122
444
  headers: {
123
445
  Authorization: `Bearer ${key}`,
124
446
  "Content-Type": "application/json",
125
- "User-Agent": `piut-cli (configured: ${toolName})`
447
+ "User-Agent": `piut-cli (configured: ${toolName})`,
448
+ "X-Piut-Hostname": os.hostname(),
449
+ "X-Piut-Machine-Id": getMachineId(),
450
+ "X-Piut-Tool": toolName
126
451
  },
127
452
  body: JSON.stringify({
128
453
  jsonrpc: "2.0",
@@ -167,6 +492,9 @@ function getMachineId() {
167
492
  const hostname = os.hostname();
168
493
  return crypto.createHash("sha256").update(hostname).digest("hex").slice(0, 16);
169
494
  }
495
+ function getHostname() {
496
+ return os.hostname();
497
+ }
170
498
  async function registerProject(key, project) {
171
499
  const res = await fetch(`${API_BASE}/api/cli/projects`, {
172
500
  method: "POST",
@@ -209,57 +537,93 @@ async function deleteConnections(key, toolNames) {
209
537
  } catch {
210
538
  }
211
539
  }
212
- async function resyncBrain(serverUrl, key, content) {
213
- const res = await fetch(serverUrl, {
540
+ async function unpublishServer(key) {
541
+ const res = await fetch(`${API_BASE}/api/mcp/publish`, {
214
542
  method: "POST",
215
543
  headers: {
216
544
  Authorization: `Bearer ${key}`,
217
545
  "Content-Type": "application/json"
218
546
  },
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
- })
547
+ body: JSON.stringify({ published: false })
228
548
  });
229
549
  if (!res.ok) {
230
- throw new Error(`Resync failed (HTTP ${res.status})`);
550
+ const body = await res.json().catch(() => ({ error: "Unknown error" }));
551
+ throw new Error(body.error || `Unpublish failed (HTTP ${res.status})`);
231
552
  }
232
- const data = await res.json();
233
- if (data.error) {
234
- throw new Error(data.error.message || "Resync failed");
553
+ return res.json();
554
+ }
555
+ async function listVaultFiles(key) {
556
+ const res = await fetch(`${API_BASE}/api/cli/vault`, {
557
+ headers: { Authorization: `Bearer ${key}` }
558
+ });
559
+ if (!res.ok) {
560
+ const body = await res.json().catch(() => ({ error: "Unknown error" }));
561
+ throw new Error(body.error || `Failed to list vault files (HTTP ${res.status})`);
235
562
  }
236
- const resultContent = data.result?.content;
237
- const text = Array.isArray(resultContent) && resultContent[0]?.text ? resultContent[0].text : "Brain updated";
238
- return { summary: text };
563
+ return res.json();
239
564
  }
240
- async function unpublishServer(key) {
241
- const res = await fetch(`${API_BASE}/api/mcp/publish`, {
565
+ async function uploadVaultFile(key, filename, content, encoding) {
566
+ const payload = { filename, content };
567
+ if (encoding) payload.encoding = encoding;
568
+ const res = await fetch(`${API_BASE}/api/cli/vault/upload`, {
242
569
  method: "POST",
243
570
  headers: {
244
571
  Authorization: `Bearer ${key}`,
245
572
  "Content-Type": "application/json"
246
573
  },
247
- body: JSON.stringify({ published: false })
574
+ body: JSON.stringify(payload)
248
575
  });
249
576
  if (!res.ok) {
250
577
  const body = await res.json().catch(() => ({ error: "Unknown error" }));
251
- throw new Error(body.error || `Unpublish failed (HTTP ${res.status})`);
578
+ throw new Error(body.error || `Upload failed (HTTP ${res.status})`);
579
+ }
580
+ return res.json();
581
+ }
582
+ async function readVaultFile(key, filename) {
583
+ const encoded = encodeURIComponent(filename);
584
+ const res = await fetch(`${API_BASE}/api/cli/vault/${encoded}`, {
585
+ headers: { Authorization: `Bearer ${key}` }
586
+ });
587
+ if (!res.ok) {
588
+ const body = await res.json().catch(() => ({ error: "Unknown error" }));
589
+ throw new Error(body.error || `Failed to read vault file (HTTP ${res.status})`);
252
590
  }
253
591
  return res.json();
254
592
  }
593
+ async function deleteVaultFile(key, filename) {
594
+ const res = await fetch(`${API_BASE}/api/cli/vault`, {
595
+ method: "DELETE",
596
+ headers: {
597
+ Authorization: `Bearer ${key}`,
598
+ "Content-Type": "application/json"
599
+ },
600
+ body: JSON.stringify({ filename })
601
+ });
602
+ if (!res.ok) {
603
+ const body = await res.json().catch(() => ({ error: "Unknown error" }));
604
+ throw new Error(body.error || `Delete failed (HTTP ${res.status})`);
605
+ }
606
+ }
255
607
 
256
608
  // src/lib/tools.ts
609
+ init_esm_shims();
257
610
  import os2 from "os";
258
- import path from "path";
611
+ import path2 from "path";
612
+ import crypto2 from "crypto";
259
613
  var MCP_URL = (slug) => `https://piut.com/api/mcp/${slug}`;
260
614
  var AUTH_HEADER = (key) => ({ Authorization: `Bearer ${key}` });
615
+ function getMachineId2() {
616
+ return crypto2.createHash("sha256").update(os2.hostname()).digest("hex").slice(0, 16);
617
+ }
618
+ function machineHeaders(toolName) {
619
+ return {
620
+ "X-Piut-Hostname": os2.hostname(),
621
+ "X-Piut-Machine-Id": getMachineId2(),
622
+ "X-Piut-Tool": toolName
623
+ };
624
+ }
261
625
  function appData() {
262
- return process.env.APPDATA || path.join(os2.homedir(), "AppData", "Roaming");
626
+ return process.env.APPDATA || path2.join(os2.homedir(), "AppData", "Roaming");
263
627
  }
264
628
  var TOOLS = [
265
629
  {
@@ -269,18 +633,19 @@ var TOOLS = [
269
633
  configPaths: {
270
634
  darwin: ["~/.claude.json"],
271
635
  win32: ["~/.claude.json"],
272
- linux: ["~/.claude.json"]
636
+ linux: ["~/.claude.json"],
637
+ project: [".mcp.json"]
273
638
  },
274
639
  skillFilePath: "CLAUDE.md",
275
640
  quickCommand: (slug, key) => `claude mcp add-json piut-context '${JSON.stringify({
276
641
  type: "http",
277
642
  url: MCP_URL(slug),
278
- headers: AUTH_HEADER(key)
643
+ headers: { ...AUTH_HEADER(key), ...machineHeaders("Claude Code") }
279
644
  })}'`,
280
645
  generateConfig: (slug, key) => ({
281
646
  type: "http",
282
647
  url: MCP_URL(slug),
283
- headers: AUTH_HEADER(key)
648
+ headers: { ...AUTH_HEADER(key), ...machineHeaders("Claude Code") }
284
649
  })
285
650
  },
286
651
  {
@@ -289,7 +654,7 @@ var TOOLS = [
289
654
  configKey: "mcpServers",
290
655
  configPaths: {
291
656
  darwin: ["~/Library/Application Support/Claude/claude_desktop_config.json"],
292
- win32: [path.join(appData(), "Claude", "claude_desktop_config.json")],
657
+ win32: [path2.join(appData(), "Claude", "claude_desktop_config.json")],
293
658
  linux: ["~/.config/Claude/claude_desktop_config.json"]
294
659
  },
295
660
  generateConfig: (slug, key) => ({
@@ -316,7 +681,7 @@ var TOOLS = [
316
681
  skillFilePath: ".cursor/rules/piut.mdc",
317
682
  generateConfig: (slug, key) => ({
318
683
  url: MCP_URL(slug),
319
- headers: AUTH_HEADER(key)
684
+ headers: { ...AUTH_HEADER(key), ...machineHeaders("Cursor") }
320
685
  })
321
686
  },
322
687
  {
@@ -331,21 +696,25 @@ var TOOLS = [
331
696
  skillFilePath: ".windsurf/rules/piut.md",
332
697
  generateConfig: (slug, key) => ({
333
698
  serverUrl: MCP_URL(slug),
334
- headers: AUTH_HEADER(key)
699
+ headers: { ...AUTH_HEADER(key), ...machineHeaders("Windsurf") }
335
700
  })
336
701
  },
337
702
  {
338
703
  id: "copilot",
339
704
  name: "GitHub Copilot",
340
705
  configKey: "servers",
706
+ globalConfigKey: "mcp.servers",
341
707
  configPaths: {
708
+ darwin: ["~/Library/Application Support/Code/User/settings.json"],
709
+ win32: [path2.join(appData(), "Code", "User", "settings.json")],
710
+ linux: ["~/.config/Code/User/settings.json"],
342
711
  project: [".vscode/mcp.json"]
343
712
  },
344
713
  skillFilePath: ".github/copilot-instructions.md",
345
714
  generateConfig: (slug, key) => ({
346
715
  type: "http",
347
716
  url: MCP_URL(slug),
348
- headers: AUTH_HEADER(key)
717
+ headers: { ...AUTH_HEADER(key), ...machineHeaders("GitHub Copilot") }
349
718
  })
350
719
  },
351
720
  {
@@ -361,7 +730,7 @@ var TOOLS = [
361
730
  generateConfig: (slug, key) => ({
362
731
  type: "http",
363
732
  url: MCP_URL(slug),
364
- headers: AUTH_HEADER(key)
733
+ headers: { ...AUTH_HEADER(key), ...machineHeaders("Amazon Q") }
365
734
  })
366
735
  },
367
736
  {
@@ -376,35 +745,78 @@ var TOOLS = [
376
745
  generateConfig: (slug, key) => ({
377
746
  settings: {
378
747
  url: MCP_URL(slug),
379
- headers: AUTH_HEADER(key)
748
+ headers: { ...AUTH_HEADER(key), ...machineHeaders("Zed") }
380
749
  }
381
750
  })
751
+ },
752
+ {
753
+ id: "gemini-cli",
754
+ name: "Gemini CLI",
755
+ configKey: "mcpServers",
756
+ configPaths: {
757
+ darwin: ["~/.gemini/settings.json"],
758
+ win32: ["~/.gemini/settings.json"],
759
+ linux: ["~/.gemini/settings.json"],
760
+ project: [".gemini/settings.json"]
761
+ },
762
+ generateConfig: (slug, key) => ({
763
+ httpUrl: MCP_URL(slug),
764
+ headers: { ...AUTH_HEADER(key), ...machineHeaders("Gemini CLI") }
765
+ })
766
+ },
767
+ {
768
+ id: "openclaw",
769
+ name: "OpenClaw",
770
+ configKey: "mcpServers",
771
+ configPaths: {
772
+ darwin: ["~/.mcporter/mcporter.json", "~/.openclaw/workspace/config/mcporter.json"],
773
+ win32: ["~/.mcporter/mcporter.json", "~/.openclaw/workspace/config/mcporter.json"],
774
+ linux: ["~/.mcporter/mcporter.json", "~/.openclaw/workspace/config/mcporter.json"]
775
+ },
776
+ quickCommand: (slug, key) => `npx mcporter config add piut-context ${MCP_URL(slug)} --header "Authorization=Bearer ${key}"`,
777
+ generateConfig: (slug, key) => ({
778
+ url: MCP_URL(slug),
779
+ headers: { ...AUTH_HEADER(key), ...machineHeaders("OpenClaw") }
780
+ })
781
+ },
782
+ {
783
+ id: "paperclip",
784
+ name: "Paperclip",
785
+ skillOnly: true,
786
+ configPaths: {
787
+ darwin: ["~/.paperclip/config.json"],
788
+ win32: ["~/.paperclip/config.json"],
789
+ linux: ["~/.paperclip/config.json"]
790
+ }
382
791
  }
383
792
  ];
384
793
 
385
794
  // src/lib/paths.ts
795
+ init_esm_shims();
386
796
  import os3 from "os";
387
- import path2 from "path";
797
+ import path3 from "path";
388
798
  function expandPath(p) {
389
799
  return p.replace(/^~/, os3.homedir());
390
800
  }
391
- function resolveConfigPaths(configPaths) {
801
+ function resolveConfigPaths(tool) {
392
802
  const resolved = [];
803
+ const configKey = tool.configKey || "";
393
804
  const platformKey = process.platform;
394
- const globalPaths = configPaths[platformKey] || [];
805
+ const globalPaths = tool.configPaths[platformKey] || [];
395
806
  for (const p of globalPaths) {
396
- resolved.push(expandPath(p));
807
+ resolved.push({ filePath: expandPath(p), configKey: tool.globalConfigKey || configKey });
397
808
  }
398
- const projectPaths = configPaths.project || [];
809
+ const projectPaths = tool.configPaths.project || [];
399
810
  for (const p of projectPaths) {
400
- resolved.push(path2.resolve(process.cwd(), p));
811
+ resolved.push({ filePath: path3.resolve(process.cwd(), p), configKey });
401
812
  }
402
813
  return resolved;
403
814
  }
404
815
 
405
816
  // src/lib/config.ts
817
+ init_esm_shims();
406
818
  import fs from "fs";
407
- import path3 from "path";
819
+ import path4 from "path";
408
820
  function readConfig(filePath) {
409
821
  let raw;
410
822
  try {
@@ -425,26 +837,46 @@ function readConfig(filePath) {
425
837
  }
426
838
  }
427
839
  function writeConfig(filePath, data) {
428
- fs.mkdirSync(path3.dirname(filePath), { recursive: true });
840
+ fs.mkdirSync(path4.dirname(filePath), { recursive: true });
429
841
  fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n", "utf-8");
430
842
  }
843
+ function resolveKeyPath(config, keyPath) {
844
+ const parts = keyPath.split(".");
845
+ let current = config;
846
+ for (const part of parts) {
847
+ if (current === void 0 || current === null || typeof current !== "object") return void 0;
848
+ current = current[part];
849
+ }
850
+ return current;
851
+ }
852
+ function setAtKeyPath(config, keyPath, value) {
853
+ const parts = keyPath.split(".");
854
+ let current = config;
855
+ for (let i = 0; i < parts.length - 1; i++) {
856
+ if (!current[parts[i]] || typeof current[parts[i]] !== "object") {
857
+ current[parts[i]] = {};
858
+ }
859
+ current = current[parts[i]];
860
+ }
861
+ current[parts[parts.length - 1]] = value;
862
+ }
431
863
  function isPiutConfigured(filePath, configKey) {
432
864
  const config = readConfig(filePath);
433
865
  if (!config) return false;
434
- const servers = config[configKey];
866
+ const servers = resolveKeyPath(config, configKey);
435
867
  return !!servers?.["piut-context"];
436
868
  }
437
869
  function mergeConfig(filePath, configKey, serverConfig) {
438
870
  const existing = readConfig(filePath) || {};
439
- const servers = existing[configKey] || {};
871
+ const servers = resolveKeyPath(existing, configKey) || {};
440
872
  servers["piut-context"] = serverConfig;
441
- existing[configKey] = servers;
873
+ setAtKeyPath(existing, configKey, servers);
442
874
  writeConfig(filePath, existing);
443
875
  }
444
876
  function getPiutConfig(filePath, configKey) {
445
877
  const config = readConfig(filePath);
446
878
  if (!config) return null;
447
- const servers = config[configKey];
879
+ const servers = resolveKeyPath(config, configKey);
448
880
  const piut = servers?.["piut-context"];
449
881
  return piut ?? null;
450
882
  }
@@ -471,13 +903,35 @@ function extractKeyFromConfig(piutConfig) {
471
903
  }
472
904
  return null;
473
905
  }
906
+ function extractSlugFromConfig(piutConfig) {
907
+ const slugFromUrl = (u) => {
908
+ if (typeof u !== "string") return null;
909
+ const m = u.match(/\/api\/mcp\/([^/?#]+)/);
910
+ return m ? m[1] : null;
911
+ };
912
+ const fromUrl = slugFromUrl(piutConfig.url) || slugFromUrl(piutConfig.serverUrl) || slugFromUrl(piutConfig.httpUrl);
913
+ if (fromUrl) return fromUrl;
914
+ const settings = piutConfig.settings;
915
+ if (settings) {
916
+ const fromSettings = slugFromUrl(settings.url);
917
+ if (fromSettings) return fromSettings;
918
+ }
919
+ const args2 = piutConfig.args;
920
+ if (Array.isArray(args2)) {
921
+ for (const arg of args2) {
922
+ const fromArg = slugFromUrl(arg);
923
+ if (fromArg) return fromArg;
924
+ }
925
+ }
926
+ return null;
927
+ }
474
928
  function removeFromConfig(filePath, configKey) {
475
929
  const config = readConfig(filePath);
476
930
  if (!config) return false;
477
- const servers = config[configKey];
931
+ const servers = resolveKeyPath(config, configKey);
478
932
  if (!servers?.["piut-context"]) return false;
479
933
  delete servers["piut-context"];
480
- if (Object.keys(servers).length === 0) {
934
+ if (Object.keys(servers).length === 0 && !configKey.includes(".")) {
481
935
  delete config[configKey];
482
936
  }
483
937
  writeConfig(filePath, config);
@@ -485,8 +939,9 @@ function removeFromConfig(filePath, configKey) {
485
939
  }
486
940
 
487
941
  // src/lib/skill.ts
942
+ init_esm_shims();
488
943
  import fs2 from "fs";
489
- import path4 from "path";
944
+ import path5 from "path";
490
945
  var SKILL_SNIPPET = `## p\u0131ut Context (MCP Server: piut-context)
491
946
 
492
947
  This project uses p\u0131ut for persistent personal context via MCP (Model Context Protocol).
@@ -529,7 +984,7 @@ p\u0131ut provides MCP tools \u2014 do NOT read local .piut/ files directly. Use
529
984
  Full skill reference: .piut/skill.md`;
530
985
  var SEPARATOR = "\n\n---\n\n";
531
986
  function placeSkillFile(filePath) {
532
- const absPath = path4.isAbsolute(filePath) ? filePath : path4.resolve(process.cwd(), filePath);
987
+ const absPath = path5.isAbsolute(filePath) ? filePath : path5.resolve(process.cwd(), filePath);
533
988
  try {
534
989
  const existing = fs2.readFileSync(absPath, "utf-8");
535
990
  if (existing.includes("p\u0131ut Context")) {
@@ -539,7 +994,7 @@ function placeSkillFile(filePath) {
539
994
  return { created: false, appended: true };
540
995
  } catch (err) {
541
996
  if (err.code === "ENOENT") {
542
- fs2.mkdirSync(path4.dirname(absPath), { recursive: true });
997
+ fs2.mkdirSync(path5.dirname(absPath), { recursive: true });
543
998
  fs2.writeFileSync(absPath, SKILL_SNIPPET + "\n", "utf-8");
544
999
  return { created: true, appended: false };
545
1000
  }
@@ -548,8 +1003,9 @@ function placeSkillFile(filePath) {
548
1003
  }
549
1004
 
550
1005
  // src/lib/piut-dir.ts
1006
+ init_esm_shims();
551
1007
  import fs3 from "fs";
552
- import path5 from "path";
1008
+ import path6 from "path";
553
1009
  var API_BASE2 = process.env.PIUT_API_BASE || "https://piut.com";
554
1010
  var PIUT_DIR = ".piut";
555
1011
  var CONFIG_FILE = "config.json";
@@ -591,13 +1047,13 @@ Protocol: JSON-RPC 2.0 over HTTPS
591
1047
  4. Use \`append_brain\` for quick notes and facts
592
1048
  `;
593
1049
  function piutDir(projectPath) {
594
- return path5.join(projectPath, PIUT_DIR);
1050
+ return path6.join(projectPath, PIUT_DIR);
595
1051
  }
596
1052
  function writePiutConfig(projectPath, config) {
597
1053
  const dir = piutDir(projectPath);
598
1054
  fs3.mkdirSync(dir, { recursive: true });
599
1055
  fs3.writeFileSync(
600
- path5.join(dir, CONFIG_FILE),
1056
+ path6.join(dir, CONFIG_FILE),
601
1057
  JSON.stringify(config, null, 2) + "\n",
602
1058
  "utf-8"
603
1059
  );
@@ -614,10 +1070,10 @@ async function writePiutSkill(projectPath, slug, apiKey) {
614
1070
  content = MINIMAL_SKILL_CONTENT;
615
1071
  }
616
1072
  content = content.replaceAll("{{slug}}", slug).replaceAll("{{key}}", apiKey);
617
- fs3.writeFileSync(path5.join(dir, SKILL_FILE), content, "utf-8");
1073
+ fs3.writeFileSync(path6.join(dir, SKILL_FILE), content, "utf-8");
618
1074
  }
619
1075
  function ensureGitignored(projectPath) {
620
- const gitignorePath = path5.join(projectPath, ".gitignore");
1076
+ const gitignorePath = path6.join(projectPath, ".gitignore");
621
1077
  let content = "";
622
1078
  try {
623
1079
  content = fs3.readFileSync(gitignorePath, "utf-8");
@@ -642,10 +1098,11 @@ function removePiutDir(projectPath) {
642
1098
  return true;
643
1099
  }
644
1100
  function hasPiutDir(projectPath) {
645
- return fs3.existsSync(path5.join(piutDir(projectPath), CONFIG_FILE));
1101
+ return fs3.existsSync(path6.join(piutDir(projectPath), CONFIG_FILE));
646
1102
  }
647
1103
 
648
1104
  // src/lib/ui.ts
1105
+ init_esm_shims();
649
1106
  import chalk from "chalk";
650
1107
  var brand = chalk.hex("#8B5CF6");
651
1108
  var success = chalk.green;
@@ -731,6 +1188,7 @@ var Spinner = class {
731
1188
  };
732
1189
 
733
1190
  // src/types.ts
1191
+ init_esm_shims();
734
1192
  var CliError = class extends Error {
735
1193
  constructor(message) {
736
1194
  super(message || "");
@@ -781,25 +1239,31 @@ async function setupCommand(options) {
781
1239
  const toolFilter = options.tool;
782
1240
  for (const tool of TOOLS) {
783
1241
  if (toolFilter && tool.id !== toolFilter) continue;
784
- const paths = resolveConfigPaths(tool.configPaths);
785
- for (const configPath of paths) {
786
- const exists = fs4.existsSync(configPath);
787
- const parentExists = fs4.existsSync(path6.dirname(configPath));
1242
+ if (tool.skillOnly) continue;
1243
+ const paths = resolveConfigPaths(tool);
1244
+ for (const { filePath, configKey } of paths) {
1245
+ const exists = fs4.existsSync(filePath);
1246
+ const parentExists = fs4.existsSync(path7.dirname(filePath));
788
1247
  if (exists || parentExists) {
789
- const configured2 = exists && isPiutConfigured(configPath, tool.configKey);
1248
+ const configured2 = exists && !!configKey && isPiutConfigured(filePath, configKey);
790
1249
  let staleKey = false;
791
- if (configured2) {
792
- const piutConfig = getPiutConfig(configPath, tool.configKey);
1250
+ if (configured2 && configKey) {
1251
+ const piutConfig = getPiutConfig(filePath, configKey);
793
1252
  if (piutConfig) {
794
1253
  const existingKey = extractKeyFromConfig(piutConfig);
795
1254
  if (existingKey && existingKey !== apiKey) {
796
1255
  staleKey = true;
797
1256
  }
1257
+ const existingSlug = extractSlugFromConfig(piutConfig);
1258
+ if (existingSlug && existingSlug !== slug) {
1259
+ staleKey = true;
1260
+ }
798
1261
  }
799
1262
  }
800
1263
  detected.push({
801
1264
  tool,
802
- configPath,
1265
+ configPath: filePath,
1266
+ resolvedConfigKey: configKey,
803
1267
  exists,
804
1268
  alreadyConfigured: configured2 && !staleKey,
805
1269
  staleKey
@@ -810,7 +1274,6 @@ async function setupCommand(options) {
810
1274
  }
811
1275
  if (detected.length === 0) {
812
1276
  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
1277
  console.log(dim(" See https://piut.com/docs for manual setup."));
815
1278
  console.log();
816
1279
  return;
@@ -862,7 +1325,7 @@ async function setupCommand(options) {
862
1325
  try {
863
1326
  execSync(tool.quickCommand(slug, apiKey), { stdio: "pipe" });
864
1327
  const claudeJson = expandPath("~/.claude.json");
865
- const written = getPiutConfig(claudeJson, tool.configKey);
1328
+ const written = tool.configKey ? getPiutConfig(claudeJson, tool.configKey) : null;
866
1329
  if (written) {
867
1330
  quickSuccess = true;
868
1331
  configured.push(tool.name);
@@ -871,7 +1334,7 @@ async function setupCommand(options) {
871
1334
  }
872
1335
  try {
873
1336
  execSync(tool.quickCommand(slug, apiKey) + " --scope user", { stdio: "pipe" });
874
- const retryCheck = getPiutConfig(claudeJson, tool.configKey);
1337
+ const retryCheck = tool.configKey ? getPiutConfig(claudeJson, tool.configKey) : null;
875
1338
  if (retryCheck) {
876
1339
  quickSuccess = true;
877
1340
  configured.push(tool.name);
@@ -890,10 +1353,12 @@ async function setupCommand(options) {
890
1353
  }
891
1354
  if (quickSuccess) continue;
892
1355
  }
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");
1356
+ if (tool.generateConfig && det.resolvedConfigKey) {
1357
+ const serverConfig = tool.generateConfig(slug, apiKey);
1358
+ mergeConfig(configPath, det.resolvedConfigKey, serverConfig);
1359
+ configured.push(tool.name);
1360
+ toolLine(tool.name, success("configured"), "\u2714");
1361
+ }
897
1362
  }
898
1363
  if (!options.skipSkill && configured.length > 0) {
899
1364
  const addSkill = options.yes ? true : await confirm({
@@ -918,7 +1383,7 @@ async function setupCommand(options) {
918
1383
  }
919
1384
  if (configured.length > 0) {
920
1385
  const cwd = process.cwd();
921
- const isProject2 = fs4.existsSync(path6.join(cwd, ".git")) || fs4.existsSync(path6.join(cwd, "package.json"));
1386
+ const isProject2 = fs4.existsSync(path7.join(cwd, ".git")) || fs4.existsSync(path7.join(cwd, "package.json"));
922
1387
  if (isProject2) {
923
1388
  const { serverUrl } = validationResult;
924
1389
  writePiutConfig(cwd, { slug, apiKey, serverUrl });
@@ -934,7 +1399,8 @@ async function setupCommand(options) {
934
1399
  console.log(dim(" Verifying..."));
935
1400
  for (const det of selected) {
936
1401
  if (!configured.includes(det.tool.name)) continue;
937
- const piutConfig = getPiutConfig(det.configPath, det.tool.configKey);
1402
+ if (!det.resolvedConfigKey) continue;
1403
+ const piutConfig = getPiutConfig(det.configPath, det.resolvedConfigKey);
938
1404
  if (piutConfig) {
939
1405
  toolLine(det.tool.name, success("config verified"), "\u2714");
940
1406
  } else {
@@ -975,50 +1441,20 @@ function isCommandAvailable(cmd) {
975
1441
  }
976
1442
 
977
1443
  // src/commands/status.ts
978
- import fs9 from "fs";
979
- import path12 from "path";
1444
+ init_esm_shims();
1445
+ import fs7 from "fs";
1446
+ import os6 from "os";
1447
+ import path10 from "path";
980
1448
  import chalk3 from "chalk";
981
1449
 
982
1450
  // src/lib/brain-scanner.ts
983
- import fs7 from "fs";
984
- import path10 from "path";
985
- import os5 from "os";
1451
+ init_esm_shims();
1452
+ import fs5 from "fs";
1453
+ import path8 from "path";
1454
+ import os4 from "os";
986
1455
 
987
1456
  // src/lib/file-types.ts
988
- import path7 from "path";
989
- var DOCUMENT_EXTENSIONS = /* @__PURE__ */ new Set([
990
- ".pdf",
991
- ".docx",
992
- ".doc",
993
- ".pptx",
994
- ".pages",
995
- ".key",
996
- ".rtf",
997
- ".odt",
998
- ".odp",
999
- ".eml",
1000
- ".mbox"
1001
- ]);
1002
- var PLAIN_TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
1003
- ".md",
1004
- ".markdown",
1005
- ".txt",
1006
- ".csv",
1007
- ".xml",
1008
- ".html",
1009
- ".htm",
1010
- ".json",
1011
- ".yaml",
1012
- ".yml",
1013
- ".toml",
1014
- ".rst",
1015
- ".adoc",
1016
- ".tex",
1017
- ".ini",
1018
- ".cfg",
1019
- ".conf",
1020
- ".log"
1021
- ]);
1457
+ init_esm_shims();
1022
1458
  var AI_CONFIG_FILENAMES = /* @__PURE__ */ new Set([
1023
1459
  "CLAUDE.md",
1024
1460
  ".cursorrules",
@@ -1032,350 +1468,46 @@ var AI_CONFIG_FILENAMES = /* @__PURE__ */ new Set([
1032
1468
  "IDENTITY.md",
1033
1469
  "copilot-instructions.md"
1034
1470
  ]);
1035
- var PARSEABLE_EXTENSIONS = /* @__PURE__ */ new Set([
1036
- ...DOCUMENT_EXTENSIONS,
1037
- ...PLAIN_TEXT_EXTENSIONS
1038
- ]);
1039
- var EXCLUDED_EXTENSIONS = /* @__PURE__ */ new Set([
1040
- ".js",
1041
- ".ts",
1042
- ".jsx",
1043
- ".tsx",
1044
- ".mjs",
1045
- ".cjs",
1046
- ".py",
1047
- ".pyw",
1048
- ".pyi",
1049
- ".rs",
1050
- ".go",
1051
- ".java",
1052
- ".kt",
1053
- ".scala",
1054
- ".clj",
1055
- ".c",
1056
- ".cpp",
1057
- ".cc",
1058
- ".h",
1059
- ".hpp",
1060
- ".cs",
1061
- ".rb",
1062
- ".php",
1063
- ".swift",
1064
- ".m",
1065
- ".mm",
1066
- ".lua",
1067
- ".r",
1068
- ".jl",
1069
- ".zig",
1070
- ".nim",
1071
- ".v",
1072
- ".sh",
1073
- ".bash",
1074
- ".zsh",
1075
- ".fish",
1076
- ".ps1",
1077
- ".bat",
1078
- ".cmd",
1079
- ".sql",
1080
- ".graphql",
1081
- ".gql",
1082
- ".proto",
1083
- ".css",
1084
- ".scss",
1085
- ".sass",
1086
- ".less",
1087
- ".styl",
1088
- ".xls",
1089
- ".xlsx",
1090
- ".numbers",
1091
- ".sqlite",
1092
- ".db",
1093
- ".dat",
1094
- ".parquet",
1095
- ".avro",
1096
- ".png",
1097
- ".jpg",
1098
- ".jpeg",
1099
- ".gif",
1100
- ".svg",
1101
- ".ico",
1102
- ".webp",
1103
- ".bmp",
1104
- ".tiff",
1105
- ".heic",
1106
- ".mp3",
1107
- ".mp4",
1108
- ".wav",
1109
- ".aac",
1110
- ".flac",
1111
- ".ogg",
1112
- ".avi",
1113
- ".mov",
1114
- ".mkv",
1115
- ".wmv",
1116
- ".webm",
1117
- ".zip",
1118
- ".tar",
1119
- ".gz",
1120
- ".bz2",
1121
- ".xz",
1122
- ".7z",
1123
- ".rar",
1124
- ".dmg",
1125
- ".iso",
1126
- ".exe",
1127
- ".dll",
1128
- ".so",
1129
- ".dylib",
1130
- ".o",
1131
- ".a",
1132
- ".wasm",
1133
- ".class",
1134
- ".jar",
1135
- ".pyc",
1136
- ".pyo",
1137
- ".ttf",
1138
- ".otf",
1139
- ".woff",
1140
- ".woff2",
1141
- ".eot",
1142
- ".lock",
1143
- ".map",
1144
- ".min.js",
1145
- ".min.css"
1146
- ]);
1147
- function canParse(filename) {
1148
- return PARSEABLE_EXTENSIONS.has(path7.extname(filename).toLowerCase());
1149
- }
1150
- function isAiConfigFile(filename) {
1151
- return AI_CONFIG_FILENAMES.has(path7.basename(filename));
1152
- }
1153
- function getFileCategory(filename) {
1154
- const base = path7.basename(filename);
1155
- if (AI_CONFIG_FILENAMES.has(base)) return "config";
1156
- const ext = path7.extname(filename).toLowerCase();
1157
- if (DOCUMENT_EXTENSIONS.has(ext)) return "document";
1158
- if (PLAIN_TEXT_EXTENSIONS.has(ext)) return "text";
1159
- if (EXCLUDED_EXTENSIONS.has(ext)) return "excluded";
1160
- return "unknown";
1161
- }
1162
1471
 
1163
- // src/lib/file-parsers.ts
1164
- import fs5 from "fs";
1165
- import path8 from "path";
1166
- var MAX_RAW_SIZE = 500 * 1024;
1167
- var MAX_EXTRACTED_TEXT = 100 * 1024;
1168
- function truncate(text) {
1169
- if (text.length <= MAX_EXTRACTED_TEXT) return text;
1170
- return text.slice(0, MAX_EXTRACTED_TEXT);
1171
- }
1172
- function stripHtml(html) {
1173
- return html.replace(/<br\s*\/?>/gi, "\n").replace(/<\/p>/gi, "\n").replace(/<\/div>/gi, "\n").replace(/<\/li>/gi, "\n").replace(/<[^>]+>/g, "").replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&nbsp;/g, " ").replace(/\n{3,}/g, "\n\n").trim();
1174
- }
1175
- async function parsePdf(buffer) {
1176
- const { PDFParse } = await import("pdf-parse");
1177
- const parser = new PDFParse({ data: new Uint8Array(buffer) });
1178
- const result = await parser.getText();
1179
- return result.text;
1180
- }
1181
- async function parseDocx(buffer) {
1182
- const { extractRawText } = await import("mammoth");
1183
- const result = await extractRawText({ buffer });
1184
- return result.value;
1185
- }
1186
- async function parsePptx(buffer) {
1187
- const JSZip = (await import("jszip")).default;
1188
- const zip = await JSZip.loadAsync(buffer);
1189
- const texts = [];
1190
- const slideFiles = Object.keys(zip.files).filter((name) => /^ppt\/slides\/slide\d+\.xml$/i.test(name)).sort((a, b) => {
1191
- const numA = parseInt(a.match(/slide(\d+)/)?.[1] || "0");
1192
- const numB = parseInt(b.match(/slide(\d+)/)?.[1] || "0");
1193
- return numA - numB;
1194
- });
1195
- for (const slidePath of slideFiles) {
1196
- const xml = await zip.files[slidePath].async("string");
1197
- const matches = xml.match(/<a:t[^>]*>([^<]*)<\/a:t>/g);
1198
- if (matches) {
1199
- texts.push(matches.map((m) => m.replace(/<[^>]+>/g, "")).join(" "));
1200
- }
1201
- }
1202
- return texts.join("\n\n");
1203
- }
1204
- async function parseAppleDoc(buffer) {
1205
- const JSZip = (await import("jszip")).default;
1206
- const zip = await JSZip.loadAsync(buffer);
1207
- if (zip.files["preview.pdf"]) {
1208
- const pdfBuf = await zip.files["preview.pdf"].async("nodebuffer");
1209
- try {
1210
- return await parsePdf(pdfBuf);
1211
- } catch {
1212
- }
1213
- }
1214
- const texts = [];
1215
- for (const [name, file] of Object.entries(zip.files)) {
1216
- if (name.endsWith(".iwa") && !file.dir) {
1217
- try {
1218
- const buf = await file.async("nodebuffer");
1219
- const str = buf.toString("utf-8");
1220
- const readable = str.match(/[\x20-\x7E\xC0-\xFF]{4,}/g);
1221
- if (readable) {
1222
- texts.push(...readable.filter((s) => s.length > 10));
1223
- }
1224
- } catch {
1225
- }
1226
- }
1227
- }
1228
- return texts.join("\n\n");
1229
- }
1230
- async function parseRtf(buffer) {
1231
- const { fromString } = await import("@iarna/rtf-to-html");
1232
- return new Promise((resolve, reject) => {
1233
- fromString(buffer.toString("utf-8"), (err, html) => {
1234
- if (err) {
1235
- reject(err);
1236
- return;
1237
- }
1238
- resolve(stripHtml(html));
1239
- });
1240
- });
1241
- }
1242
- async function parseOpenDocument(buffer) {
1243
- const JSZip = (await import("jszip")).default;
1244
- const zip = await JSZip.loadAsync(buffer);
1245
- const contentXml = zip.files["content.xml"];
1246
- if (!contentXml) return "";
1247
- return stripHtml(await contentXml.async("string"));
1248
- }
1249
- function parseEml(buffer) {
1250
- const content = buffer.toString("utf-8");
1251
- const boundaryMatch = content.match(/boundary="?([^\s"]+)"?/i);
1252
- if (boundaryMatch) {
1253
- const parts = content.split(`--${boundaryMatch[1]}`);
1254
- for (const part of parts) {
1255
- if (/content-type:\s*text\/plain/i.test(part)) {
1256
- const bodyStart = part.indexOf("\n\n");
1257
- if (bodyStart !== -1) return part.slice(bodyStart + 2).trim();
1258
- }
1259
- }
1260
- }
1261
- const headerEnd = content.indexOf("\n\n");
1262
- if (headerEnd !== -1) {
1263
- const body = content.slice(headerEnd + 2);
1264
- if (!/<html/i.test(body.slice(0, 200))) return body.trim();
1265
- return stripHtml(body);
1266
- }
1267
- return content.trim();
1268
- }
1269
- function parseMbox(buffer) {
1270
- const content = buffer.toString("utf-8");
1271
- const messages = content.split(/^From /m).filter(Boolean);
1272
- const texts = [];
1273
- for (const msg of messages.slice(0, 50)) {
1274
- const headerEnd = msg.indexOf("\n\n");
1275
- if (headerEnd !== -1) {
1276
- const body = msg.slice(headerEnd + 2);
1277
- texts.push(!/<html/i.test(body.slice(0, 200)) ? body.trim() : stripHtml(body));
1278
- }
1279
- }
1280
- return texts.join("\n\n---\n\n");
1281
- }
1282
- async function extractTextFromFile(filePath) {
1283
- const ext = path8.extname(filePath).toLowerCase();
1284
- try {
1285
- const stat = fs5.statSync(filePath);
1286
- if (!stat.isFile()) return null;
1287
- if (stat.size > MAX_RAW_SIZE) return null;
1288
- } catch {
1289
- return null;
1290
- }
1291
- if (PLAIN_TEXT_EXTENSIONS.has(ext)) {
1292
- try {
1293
- return truncate(fs5.readFileSync(filePath, "utf-8"));
1294
- } catch {
1295
- return null;
1296
- }
1297
- }
1298
- if (!DOCUMENT_EXTENSIONS.has(ext)) return null;
1299
- try {
1300
- const buffer = fs5.readFileSync(filePath);
1301
- let text;
1302
- switch (ext) {
1303
- case ".pdf":
1304
- text = await parsePdf(buffer);
1305
- break;
1306
- case ".docx":
1307
- case ".doc":
1308
- text = await parseDocx(buffer);
1309
- break;
1310
- case ".pptx":
1311
- text = await parsePptx(buffer);
1312
- break;
1313
- case ".pages":
1314
- case ".key":
1315
- text = await parseAppleDoc(buffer);
1316
- break;
1317
- case ".rtf":
1318
- text = await parseRtf(buffer);
1319
- break;
1320
- case ".odt":
1321
- case ".odp":
1322
- text = await parseOpenDocument(buffer);
1323
- break;
1324
- case ".eml":
1325
- text = parseEml(buffer);
1326
- break;
1327
- case ".mbox":
1328
- text = parseMbox(buffer);
1329
- break;
1330
- default:
1331
- return null;
1332
- }
1333
- return truncate(text);
1334
- } catch {
1335
- return null;
1336
- }
1337
- }
1338
- function formatSize(bytes) {
1339
- if (bytes < 1024) return `${bytes} B`;
1340
- if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
1341
- return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
1342
- }
1343
-
1344
- // src/lib/folder-tree.ts
1345
- import fs6 from "fs";
1346
- import path9 from "path";
1347
- import os4 from "os";
1472
+ // src/lib/brain-scanner.ts
1348
1473
  var home = os4.homedir();
1349
- function displayPath(p) {
1350
- return p.startsWith(home) ? "~" + p.slice(home.length) : p;
1351
- }
1352
- function groupFilesByFolder(files) {
1353
- const map = /* @__PURE__ */ new Map();
1354
- for (const file of files) {
1355
- const folder = file.folder;
1356
- if (!map.has(folder)) map.set(folder, []);
1357
- map.get(folder).push(file);
1358
- }
1359
- const results = [];
1360
- for (const [folderPath, folderFiles] of map) {
1361
- results.push({
1362
- path: folderPath,
1363
- displayPath: displayPath(folderPath),
1364
- files: folderFiles,
1365
- fileCount: folderFiles.length,
1366
- totalBytes: folderFiles.reduce((sum, f) => sum + f.sizeBytes, 0)
1367
- });
1368
- }
1369
- results.sort((a, b) => a.displayPath.localeCompare(b.displayPath));
1370
- return results;
1371
- }
1372
- function formatFolderChoice(folder) {
1373
- return `${folder.displayPath} \u2014 ${folder.fileCount} file${folder.fileCount === 1 ? "" : "s"} (${formatSize(folder.totalBytes)})`;
1374
- }
1375
- function formatSelectionSummary(folders) {
1376
- const totalFiles = folders.reduce((sum, f) => sum + f.fileCount, 0);
1377
- const totalBytes = folders.reduce((sum, f) => sum + f.totalBytes, 0);
1378
- return `${folders.length} folder${folders.length === 1 ? "" : "s"} selected (${totalFiles} file${totalFiles === 1 ? "" : "s"}, ${formatSize(totalBytes)})`;
1474
+ var SKIP_DIRS = /* @__PURE__ */ new Set([
1475
+ "node_modules",
1476
+ ".git",
1477
+ "__pycache__",
1478
+ ".venv",
1479
+ "venv",
1480
+ "dist",
1481
+ "build",
1482
+ ".next",
1483
+ ".nuxt",
1484
+ ".output",
1485
+ ".Trash",
1486
+ "Library",
1487
+ ".cache",
1488
+ ".npm",
1489
+ ".yarn",
1490
+ ".pnpm-store",
1491
+ "Caches",
1492
+ "Cache",
1493
+ ".piut"
1494
+ ]);
1495
+ var SCAN_DOT_DIRS = /* @__PURE__ */ new Set([
1496
+ ".cursor",
1497
+ ".windsurf",
1498
+ ".github",
1499
+ ".zed",
1500
+ ".amazonq",
1501
+ ".vscode",
1502
+ ".gemini",
1503
+ ".openclaw",
1504
+ ".mcporter",
1505
+ ".paperclip"
1506
+ // .claude intentionally excluded — useful files collected by collectGlobalConfigFiles()
1507
+ ]);
1508
+ function shouldSkipDir(name) {
1509
+ if (name.startsWith(".") && !SCAN_DOT_DIRS.has(name)) return true;
1510
+ return SKIP_DIRS.has(name);
1379
1511
  }
1380
1512
  var SKIP_HOME_DIRS = /* @__PURE__ */ new Set([
1381
1513
  "Library",
@@ -1388,33 +1520,36 @@ var SKIP_HOME_DIRS = /* @__PURE__ */ new Set([
1388
1520
  ".Trash"
1389
1521
  ]);
1390
1522
  var INCLUDE_DOT_DIRS = /* @__PURE__ */ new Set([
1391
- ".claude",
1392
1523
  ".cursor",
1393
1524
  ".windsurf",
1394
1525
  ".openclaw",
1395
1526
  ".zed",
1396
1527
  ".github",
1397
- ".amazonq"
1528
+ ".amazonq",
1529
+ ".gemini",
1530
+ ".mcporter",
1531
+ ".paperclip"
1532
+ // .claude excluded — useful files collected by collectGlobalConfigFiles()
1398
1533
  ]);
1399
1534
  function getDefaultScanDirs() {
1400
1535
  const dirs = [];
1401
1536
  try {
1402
- const entries = fs6.readdirSync(home, { withFileTypes: true });
1537
+ const entries = fs5.readdirSync(home, { withFileTypes: true });
1403
1538
  for (const entry of entries) {
1404
1539
  if (!entry.isDirectory()) continue;
1405
1540
  if (entry.name.startsWith(".") && !INCLUDE_DOT_DIRS.has(entry.name)) continue;
1406
1541
  if (SKIP_HOME_DIRS.has(entry.name)) continue;
1407
- dirs.push(path9.join(home, entry.name));
1542
+ dirs.push(path8.join(home, entry.name));
1408
1543
  }
1409
1544
  } catch {
1410
1545
  }
1411
- const cloudStorage = path9.join(home, "Library", "CloudStorage");
1546
+ const cloudStorage = path8.join(home, "Library", "CloudStorage");
1412
1547
  try {
1413
- if (fs6.existsSync(cloudStorage) && fs6.statSync(cloudStorage).isDirectory()) {
1414
- const entries = fs6.readdirSync(cloudStorage, { withFileTypes: true });
1548
+ if (fs5.existsSync(cloudStorage) && fs5.statSync(cloudStorage).isDirectory()) {
1549
+ const entries = fs5.readdirSync(cloudStorage, { withFileTypes: true });
1415
1550
  for (const entry of entries) {
1416
1551
  if (!entry.isDirectory()) continue;
1417
- const fullPath = path9.join(cloudStorage, entry.name);
1552
+ const fullPath = path8.join(cloudStorage, entry.name);
1418
1553
  if (!dirs.includes(fullPath)) {
1419
1554
  dirs.push(fullPath);
1420
1555
  }
@@ -1425,60 +1560,23 @@ function getDefaultScanDirs() {
1425
1560
  if (dirs.length === 0) dirs.push(home);
1426
1561
  return dirs;
1427
1562
  }
1428
-
1429
- // src/lib/brain-scanner.ts
1430
- var home2 = os5.homedir();
1431
- var SKIP_DIRS = /* @__PURE__ */ new Set([
1432
- "node_modules",
1433
- ".git",
1434
- "__pycache__",
1435
- ".venv",
1436
- "venv",
1437
- "dist",
1438
- "build",
1439
- ".next",
1440
- ".nuxt",
1441
- ".output",
1442
- ".Trash",
1443
- "Library",
1444
- ".cache",
1445
- ".npm",
1446
- ".yarn",
1447
- ".pnpm-store",
1448
- "Caches",
1449
- "Cache",
1450
- ".piut"
1451
- ]);
1452
- var SCAN_DOT_DIRS = /* @__PURE__ */ new Set([
1453
- ".claude",
1454
- ".cursor",
1455
- ".windsurf",
1456
- ".github",
1457
- ".zed",
1458
- ".amazonq",
1459
- ".vscode"
1460
- ]);
1461
- function shouldSkipDir(name) {
1462
- if (name.startsWith(".") && !SCAN_DOT_DIRS.has(name)) return true;
1463
- return SKIP_DIRS.has(name);
1464
- }
1465
1563
  function isProject(dirPath) {
1466
- return fs7.existsSync(path10.join(dirPath, ".git")) || fs7.existsSync(path10.join(dirPath, "package.json")) || fs7.existsSync(path10.join(dirPath, "Cargo.toml")) || fs7.existsSync(path10.join(dirPath, "pyproject.toml")) || fs7.existsSync(path10.join(dirPath, "go.mod"));
1564
+ return fs5.existsSync(path8.join(dirPath, ".git")) || fs5.existsSync(path8.join(dirPath, "package.json")) || fs5.existsSync(path8.join(dirPath, "Cargo.toml")) || fs5.existsSync(path8.join(dirPath, "pyproject.toml")) || fs5.existsSync(path8.join(dirPath, "go.mod"));
1467
1565
  }
1468
1566
  function buildProjectInfo(projectPath) {
1469
- const hasPkgJson = fs7.existsSync(path10.join(projectPath, "package.json"));
1567
+ const hasPkgJson = fs5.existsSync(path8.join(projectPath, "package.json"));
1470
1568
  let description = "";
1471
1569
  if (hasPkgJson) {
1472
1570
  try {
1473
- const pkg = JSON.parse(fs7.readFileSync(path10.join(projectPath, "package.json"), "utf-8"));
1571
+ const pkg = JSON.parse(fs5.readFileSync(path8.join(projectPath, "package.json"), "utf-8"));
1474
1572
  description = pkg.description || "";
1475
1573
  } catch {
1476
1574
  }
1477
1575
  }
1478
- const readmePath = path10.join(projectPath, "README.md");
1479
- if (!description && fs7.existsSync(readmePath)) {
1576
+ const readmePath = path8.join(projectPath, "README.md");
1577
+ if (!description && fs5.existsSync(readmePath)) {
1480
1578
  try {
1481
- const content = fs7.readFileSync(readmePath, "utf-8");
1579
+ const content = fs5.readFileSync(readmePath, "utf-8");
1482
1580
  const lines = content.split("\n");
1483
1581
  let foundHeading = false;
1484
1582
  for (const line of lines) {
@@ -1495,15 +1593,15 @@ function buildProjectInfo(projectPath) {
1495
1593
  }
1496
1594
  }
1497
1595
  return {
1498
- name: path10.basename(projectPath),
1596
+ name: path8.basename(projectPath),
1499
1597
  path: projectPath,
1500
1598
  description,
1501
- hasClaudeMd: fs7.existsSync(path10.join(projectPath, "CLAUDE.md")) || fs7.existsSync(path10.join(projectPath, ".claude", "rules")),
1502
- hasCursorRules: fs7.existsSync(path10.join(projectPath, ".cursorrules")) || fs7.existsSync(path10.join(projectPath, ".cursor", "rules")),
1503
- hasWindsurfRules: fs7.existsSync(path10.join(projectPath, ".windsurfrules")) || fs7.existsSync(path10.join(projectPath, ".windsurf", "rules")),
1504
- hasCopilotInstructions: fs7.existsSync(path10.join(projectPath, ".github", "copilot-instructions.md")) || fs7.existsSync(path10.join(projectPath, ".github", "instructions")),
1505
- hasConventionsMd: fs7.existsSync(path10.join(projectPath, "CONVENTIONS.md")) || fs7.existsSync(path10.join(projectPath, ".amazonq", "rules")),
1506
- hasZedRules: fs7.existsSync(path10.join(projectPath, ".rules"))
1599
+ hasClaudeMd: fs5.existsSync(path8.join(projectPath, "CLAUDE.md")) || fs5.existsSync(path8.join(projectPath, ".claude", "rules")),
1600
+ hasCursorRules: fs5.existsSync(path8.join(projectPath, ".cursorrules")) || fs5.existsSync(path8.join(projectPath, ".cursor", "rules")),
1601
+ hasWindsurfRules: fs5.existsSync(path8.join(projectPath, ".windsurfrules")) || fs5.existsSync(path8.join(projectPath, ".windsurf", "rules")),
1602
+ hasCopilotInstructions: fs5.existsSync(path8.join(projectPath, ".github", "copilot-instructions.md")) || fs5.existsSync(path8.join(projectPath, ".github", "instructions")),
1603
+ hasConventionsMd: fs5.existsSync(path8.join(projectPath, "CONVENTIONS.md")) || fs5.existsSync(path8.join(projectPath, ".amazonq", "rules")),
1604
+ hasZedRules: fs5.existsSync(path8.join(projectPath, ".rules"))
1507
1605
  };
1508
1606
  }
1509
1607
  var MAX_PROJECT_DEPTH = 4;
@@ -1513,17 +1611,17 @@ function detectProjects(scanDirs, onProgress) {
1513
1611
  function walk(dir, depth) {
1514
1612
  if (depth > MAX_PROJECT_DEPTH) return;
1515
1613
  try {
1516
- const items = fs7.readdirSync(dir, { withFileTypes: true });
1614
+ const items = fs5.readdirSync(dir, { withFileTypes: true });
1517
1615
  for (const item of items) {
1518
1616
  if (!item.isDirectory()) continue;
1519
1617
  if (shouldSkipDir(item.name)) continue;
1520
- const fullPath = path10.join(dir, item.name);
1618
+ const fullPath = path8.join(dir, item.name);
1521
1619
  if (seen.has(fullPath)) continue;
1522
1620
  seen.add(fullPath);
1523
1621
  if (isProject(fullPath)) {
1524
1622
  const info = buildProjectInfo(fullPath);
1525
1623
  projects.push(info);
1526
- onProgress?.({ phase: "projects", message: `${info.name} (${fullPath.replace(home2, "~")})` });
1624
+ onProgress?.({ phase: "projects", message: `${info.name} (${fullPath})` });
1527
1625
  } else {
1528
1626
  walk(fullPath, depth + 1);
1529
1627
  }
@@ -1537,34 +1635,41 @@ function detectProjects(scanDirs, onProgress) {
1537
1635
  return projects;
1538
1636
  }
1539
1637
  var MAX_CONFIG_SIZE = 100 * 1024;
1540
- function collectConfigFiles(projects, onProgress) {
1638
+ var MAX_BRAIN_INPUT_BYTES = 1e6;
1639
+ function collectGlobalConfigFiles(onProgress) {
1541
1640
  const configs = [];
1542
1641
  const globalPaths = [
1543
- path10.join(home2, ".claude", "MEMORY.md"),
1544
- path10.join(home2, ".claude", "CLAUDE.md"),
1545
- path10.join(home2, ".openclaw", "workspace", "SOUL.md"),
1546
- path10.join(home2, ".openclaw", "workspace", "MEMORY.md")
1642
+ path8.join(home, ".claude", "MEMORY.md"),
1643
+ path8.join(home, ".claude", "CLAUDE.md"),
1644
+ path8.join(home, ".openclaw", "workspace", "SOUL.md"),
1645
+ path8.join(home, ".openclaw", "workspace", "MEMORY.md"),
1646
+ path8.join(home, ".gemini", "MEMORY.md"),
1647
+ path8.join(home, ".paperclip", "IDENTITY.md")
1547
1648
  ];
1548
1649
  for (const gp of globalPaths) {
1549
1650
  try {
1550
- const stat = fs7.statSync(gp);
1651
+ const stat = fs5.statSync(gp);
1551
1652
  if (!stat.isFile() || stat.size > MAX_CONFIG_SIZE) continue;
1552
- const content = fs7.readFileSync(gp, "utf-8");
1653
+ const content = fs5.readFileSync(gp, "utf-8");
1553
1654
  if (content.trim()) {
1554
- const name = `~/${path10.relative(home2, gp)}`;
1655
+ const name = path8.relative(home, gp);
1555
1656
  configs.push({ name, content });
1556
1657
  onProgress?.({ phase: "configs", message: name });
1557
1658
  }
1558
1659
  } catch {
1559
1660
  }
1560
1661
  }
1662
+ return configs;
1663
+ }
1664
+ function collectProjectConfigFiles(projects, onProgress) {
1665
+ const configs = [];
1561
1666
  for (const project of projects) {
1562
1667
  for (const fileName of AI_CONFIG_FILENAMES) {
1563
- const filePath = path10.join(project.path, fileName);
1668
+ const filePath = path8.join(project.path, fileName);
1564
1669
  try {
1565
- const stat = fs7.statSync(filePath);
1670
+ const stat = fs5.statSync(filePath);
1566
1671
  if (!stat.isFile() || stat.size > MAX_CONFIG_SIZE) continue;
1567
- const content = fs7.readFileSync(filePath, "utf-8");
1672
+ const content = fs5.readFileSync(filePath, "utf-8");
1568
1673
  if (content.trim()) {
1569
1674
  const name = `${project.name}/${fileName}`;
1570
1675
  configs.push({ name, content });
@@ -1573,128 +1678,40 @@ function collectConfigFiles(projects, onProgress) {
1573
1678
  } catch {
1574
1679
  }
1575
1680
  }
1576
- const pkgPath = path10.join(project.path, "package.json");
1681
+ const pkgPath = path8.join(project.path, "package.json");
1577
1682
  try {
1578
- const stat = fs7.statSync(pkgPath);
1683
+ const stat = fs5.statSync(pkgPath);
1579
1684
  if (stat.isFile() && stat.size <= MAX_CONFIG_SIZE) {
1580
- const pkg = JSON.parse(fs7.readFileSync(pkgPath, "utf-8"));
1685
+ const pkg = JSON.parse(fs5.readFileSync(pkgPath, "utf-8"));
1581
1686
  const summary = JSON.stringify({ name: pkg.name, description: pkg.description }, null, 2);
1582
- configs.push({ name: `${project.name}/package.json`, content: summary });
1583
- onProgress?.({ phase: "configs", message: `${project.name}/package.json` });
1584
- }
1585
- } catch {
1586
- }
1587
- }
1588
- return configs;
1589
- }
1590
- var MAX_SCAN_DEPTH = 6;
1591
- var MAX_FILES = 500;
1592
- async function scanFilesInDirs(dirs, onProgress) {
1593
- const files = [];
1594
- const seen = /* @__PURE__ */ new Set();
1595
- function walk(dir, depth) {
1596
- if (depth > MAX_SCAN_DEPTH) return [];
1597
- const found = [];
1598
- try {
1599
- const items = fs7.readdirSync(dir, { withFileTypes: true });
1600
- for (const item of items) {
1601
- if (item.isDirectory()) {
1602
- if (!shouldSkipDir(item.name)) {
1603
- found.push(...walk(path10.join(dir, item.name), depth + 1));
1604
- }
1605
- } else if (item.isFile()) {
1606
- if (canParse(item.name) && !isAiConfigFile(item.name)) {
1607
- const fullPath = path10.join(dir, item.name);
1608
- if (!seen.has(fullPath)) {
1609
- seen.add(fullPath);
1610
- found.push(fullPath);
1611
- }
1612
- }
1613
- }
1614
- }
1615
- } catch {
1616
- }
1617
- return found;
1618
- }
1619
- const allPaths = [];
1620
- for (const dir of dirs) {
1621
- onProgress?.({ phase: "scanning", message: displayPath(dir) });
1622
- allPaths.push(...walk(dir, 0));
1623
- if (allPaths.length > MAX_FILES) break;
1624
- }
1625
- const pathsToProcess = allPaths.slice(0, MAX_FILES);
1626
- for (const filePath of pathsToProcess) {
1627
- try {
1628
- const stat = fs7.statSync(filePath);
1629
- onProgress?.({ phase: "parsing", message: displayPath(filePath) });
1630
- const content = await extractTextFromFile(filePath);
1631
- if (content && content.trim()) {
1632
- const category = getFileCategory(filePath);
1633
- files.push({
1634
- path: filePath,
1635
- displayPath: displayPath(filePath),
1636
- content,
1637
- format: category === "document" ? path10.extname(filePath).slice(1) : "text",
1638
- sizeBytes: stat.size,
1639
- folder: path10.dirname(filePath)
1640
- });
1687
+ configs.push({ name: `${project.name}/package.json`, content: summary });
1688
+ onProgress?.({ phase: "configs", message: `${project.name}/package.json` });
1641
1689
  }
1642
1690
  } catch {
1643
1691
  }
1644
1692
  }
1645
- return files;
1646
- }
1647
- async function scanFolders(dirs, onProgress) {
1648
- const allFiles = await scanFilesInDirs(dirs, onProgress);
1649
- const folders = groupFilesByFolder(allFiles);
1650
- const projects = detectProjects(dirs, onProgress);
1651
- const configFiles = collectConfigFiles(projects, onProgress);
1652
- const totalFiles = allFiles.length;
1653
- const totalBytes = allFiles.reduce((sum, f) => sum + f.sizeBytes, 0);
1654
- return { folders, configFiles, projects, allFiles, totalFiles, totalBytes };
1655
- }
1656
- function buildBrainInput(scanResult, selectedFolderPaths) {
1657
- const selectedSet = new Set(selectedFolderPaths);
1658
- const selectedFiles = scanResult.allFiles.filter((f) => selectedSet.has(f.folder));
1659
- const folderTree = [];
1660
- for (const folder of scanResult.folders) {
1661
- if (selectedSet.has(folder.path)) {
1662
- folderTree.push(`${folder.displayPath}/ (${folder.fileCount} files)`);
1663
- }
1664
- }
1665
- const personalDocuments = selectedFiles.map((f) => ({
1666
- name: f.displayPath,
1667
- content: f.content,
1668
- format: f.format
1669
- }));
1670
- return {
1671
- summary: {
1672
- folders: folderTree,
1673
- projects: scanResult.projects.map((p) => ({
1674
- name: p.name,
1675
- path: p.path.replace(home2, "~"),
1676
- description: p.description
1677
- })),
1678
- configFiles: scanResult.configFiles,
1679
- recentDocuments: [],
1680
- personalDocuments
1681
- }
1682
- };
1693
+ return configs;
1683
1694
  }
1684
1695
  function scanForProjects(folders) {
1685
1696
  const scanDirs = folders || getDefaultScanDirs();
1686
1697
  return detectProjects(scanDirs);
1687
1698
  }
1699
+ function formatSize(bytes) {
1700
+ if (bytes < 1024) return `${bytes} B`;
1701
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
1702
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
1703
+ }
1688
1704
 
1689
1705
  // src/lib/store.ts
1690
- import fs8 from "fs";
1691
- import path11 from "path";
1692
- import os6 from "os";
1693
- var CONFIG_DIR = path11.join(os6.homedir(), ".piut");
1694
- var CONFIG_FILE2 = path11.join(CONFIG_DIR, "config.json");
1706
+ init_esm_shims();
1707
+ import fs6 from "fs";
1708
+ import path9 from "path";
1709
+ import os5 from "os";
1710
+ var CONFIG_DIR = path9.join(os5.homedir(), ".piut");
1711
+ var CONFIG_FILE2 = path9.join(CONFIG_DIR, "config.json");
1695
1712
  function readStore() {
1696
1713
  try {
1697
- const raw = fs8.readFileSync(CONFIG_FILE2, "utf-8");
1714
+ const raw = fs6.readFileSync(CONFIG_FILE2, "utf-8");
1698
1715
  return JSON.parse(raw);
1699
1716
  } catch {
1700
1717
  return {};
@@ -1703,18 +1720,19 @@ function readStore() {
1703
1720
  function updateStore(updates) {
1704
1721
  const config = readStore();
1705
1722
  const updated = { ...config, ...updates };
1706
- fs8.mkdirSync(CONFIG_DIR, { recursive: true });
1707
- fs8.writeFileSync(CONFIG_FILE2, JSON.stringify(updated, null, 2) + "\n", "utf-8");
1723
+ fs6.mkdirSync(CONFIG_DIR, { recursive: true });
1724
+ fs6.writeFileSync(CONFIG_FILE2, JSON.stringify(updated, null, 2) + "\n", "utf-8");
1708
1725
  return updated;
1709
1726
  }
1710
1727
  function clearStore() {
1711
1728
  try {
1712
- fs8.unlinkSync(CONFIG_FILE2);
1729
+ fs6.unlinkSync(CONFIG_FILE2);
1713
1730
  } catch {
1714
1731
  }
1715
1732
  }
1716
1733
 
1717
1734
  // src/commands/status.ts
1735
+ var API_BASE3 = process.env.PIUT_API_BASE || "https://piut.com";
1718
1736
  var PIUT_FILES = [
1719
1737
  "CLAUDE.md",
1720
1738
  ".cursor/rules/piut.mdc",
@@ -1725,27 +1743,69 @@ var PIUT_FILES = [
1725
1743
  ];
1726
1744
  function hasPiutReference(filePath) {
1727
1745
  try {
1728
- const content = fs9.readFileSync(filePath, "utf-8");
1746
+ const content = fs7.readFileSync(filePath, "utf-8");
1729
1747
  return content.includes("p\u0131ut Context") || content.includes("piut Context");
1730
1748
  } catch {
1731
1749
  return false;
1732
1750
  }
1733
1751
  }
1752
+ async function fetchRemoteConnections(key) {
1753
+ try {
1754
+ const res = await fetch(`${API_BASE3}/api/mcp/connections`, {
1755
+ headers: { Authorization: `Bearer ${key}` }
1756
+ });
1757
+ if (!res.ok) return [];
1758
+ const data = await res.json();
1759
+ return data.connections || [];
1760
+ } catch {
1761
+ return [];
1762
+ }
1763
+ }
1764
+ async function fetchRemoteProjects(key) {
1765
+ try {
1766
+ const res = await fetch(`${API_BASE3}/api/cli/projects`, {
1767
+ headers: { Authorization: `Bearer ${key}` }
1768
+ });
1769
+ if (!res.ok) return [];
1770
+ const data = await res.json();
1771
+ return data.projects || [];
1772
+ } catch {
1773
+ return [];
1774
+ }
1775
+ }
1776
+ function machineLabel(hostname, machineId) {
1777
+ if (hostname) return hostname;
1778
+ if (machineId && machineId !== "unknown") return machineId.slice(0, 8);
1779
+ return "unknown";
1780
+ }
1781
+ function timeAgo(dateStr) {
1782
+ const diff = Date.now() - new Date(dateStr).getTime();
1783
+ const minutes = Math.floor(diff / 6e4);
1784
+ if (minutes < 1) return "just now";
1785
+ if (minutes < 60) return `${minutes}m ago`;
1786
+ const hours = Math.floor(minutes / 60);
1787
+ if (hours < 24) return `${hours}h ago`;
1788
+ const days = Math.floor(hours / 24);
1789
+ if (days < 7) return `${days}d ago`;
1790
+ return new Date(dateStr).toLocaleDateString();
1791
+ }
1734
1792
  async function statusCommand(options = {}) {
1735
1793
  banner();
1736
1794
  if (options.verify) {
1737
1795
  await verifyStatus();
1738
1796
  return;
1739
1797
  }
1740
- console.log(" AI tool configuration:");
1798
+ const thisHostname = os6.hostname();
1799
+ const thisMachineId = getMachineId2();
1800
+ console.log(` AI tools on this machine ${dim(`(${thisHostname})`)}:`);
1741
1801
  console.log();
1742
1802
  let foundAny = false;
1743
1803
  for (const tool of TOOLS) {
1744
- const paths = resolveConfigPaths(tool.configPaths);
1745
- for (const configPath of paths) {
1746
- if (!fs9.existsSync(configPath)) continue;
1804
+ const paths = resolveConfigPaths(tool);
1805
+ for (const { filePath, configKey } of paths) {
1806
+ if (!fs7.existsSync(filePath)) continue;
1747
1807
  foundAny = true;
1748
- const configured = isPiutConfigured(configPath, tool.configKey);
1808
+ const configured = isPiutConfigured(filePath, configKey);
1749
1809
  if (configured) {
1750
1810
  toolLine(tool.name, success("connected"), "\u2714");
1751
1811
  } else {
@@ -1759,15 +1819,15 @@ async function statusCommand(options = {}) {
1759
1819
  console.log(dim(" Run ") + brand("piut setup") + dim(" to configure your AI tools."));
1760
1820
  }
1761
1821
  console.log();
1762
- console.log(" Connected projects:");
1822
+ console.log(` Connected projects on this machine:`);
1763
1823
  console.log();
1764
1824
  const projects = scanForProjects();
1765
1825
  let connectedCount = 0;
1766
1826
  for (const project of projects) {
1767
1827
  const connectedFiles = [];
1768
1828
  for (const file of PIUT_FILES) {
1769
- const absPath = path12.join(project.path, file);
1770
- if (fs9.existsSync(absPath) && hasPiutReference(absPath)) {
1829
+ const absPath = path10.join(project.path, file);
1830
+ if (fs7.existsSync(absPath) && hasPiutReference(absPath)) {
1771
1831
  connectedFiles.push(file);
1772
1832
  }
1773
1833
  }
@@ -1784,6 +1844,33 @@ async function statusCommand(options = {}) {
1784
1844
  console.log(dim(` ${connectedCount} project(s) connected to your brain.`));
1785
1845
  }
1786
1846
  console.log();
1847
+ const store = readStore();
1848
+ if (store.apiKey) {
1849
+ const [remoteConnections, remoteProjects] = await Promise.all([
1850
+ fetchRemoteConnections(store.apiKey),
1851
+ fetchRemoteProjects(store.apiKey)
1852
+ ]);
1853
+ const otherMachineConns = remoteConnections.filter((c) => c.machine_id !== thisMachineId);
1854
+ const otherMachineProjects = remoteProjects.filter((p) => p.machineId !== thisMachineId);
1855
+ if (otherMachineConns.length > 0 || otherMachineProjects.length > 0) {
1856
+ console.log(` Other machines:`);
1857
+ console.log();
1858
+ if (otherMachineConns.length > 0) {
1859
+ for (const conn of otherMachineConns) {
1860
+ const machine = machineLabel(conn.hostname, conn.machine_id);
1861
+ const age = timeAgo(conn.last_connected_at);
1862
+ console.log(dim(` ${conn.tool_name}`) + dim(` @${machine}`) + dim(` \u2014 ${conn.request_count} requests, ${age}`));
1863
+ }
1864
+ }
1865
+ if (otherMachineProjects.length > 0) {
1866
+ for (const proj of otherMachineProjects) {
1867
+ const machine = machineLabel(proj.hostname, proj.machineId);
1868
+ console.log(dim(` ${proj.projectName}`) + dim(` @${machine}:${proj.projectPath}`));
1869
+ }
1870
+ }
1871
+ console.log();
1872
+ }
1873
+ }
1787
1874
  }
1788
1875
  async function verifyStatus() {
1789
1876
  const store = readStore();
@@ -1811,19 +1898,19 @@ async function verifyStatus() {
1811
1898
  console.log();
1812
1899
  console.log(" Tool Configurations");
1813
1900
  for (const tool of TOOLS) {
1814
- const paths = resolveConfigPaths(tool.configPaths);
1815
- for (const configPath of paths) {
1816
- if (!fs9.existsSync(configPath)) continue;
1817
- const piutConfig = getPiutConfig(configPath, tool.configKey);
1901
+ const paths = resolveConfigPaths(tool);
1902
+ for (const { filePath, configKey } of paths) {
1903
+ if (!fs7.existsSync(filePath)) continue;
1904
+ const piutConfig = getPiutConfig(filePath, configKey);
1818
1905
  if (!piutConfig) {
1819
1906
  toolLine(tool.name, dim("installed, not connected"), "\u25CB");
1820
1907
  break;
1821
1908
  }
1822
- const configKey = extractKeyFromConfig(piutConfig);
1823
- if (configKey && configKey === store.apiKey) {
1909
+ const extractedKey = extractKeyFromConfig(piutConfig);
1910
+ if (extractedKey && extractedKey === store.apiKey) {
1824
1911
  toolLine(tool.name, success("key matches"), "\u2714");
1825
- } else if (configKey) {
1826
- const masked = configKey.slice(0, 6) + "...";
1912
+ } else if (extractedKey) {
1913
+ const masked = extractedKey.slice(0, 6) + "...";
1827
1914
  toolLine(tool.name, chalk3.red(`key STALE (${masked})`), "\u2717");
1828
1915
  issues++;
1829
1916
  } else {
@@ -1856,16 +1943,18 @@ async function verifyStatus() {
1856
1943
  }
1857
1944
 
1858
1945
  // src/commands/remove.ts
1859
- import fs10 from "fs";
1946
+ init_esm_shims();
1947
+ import fs8 from "fs";
1860
1948
  import { checkbox as checkbox2, confirm as confirm2 } from "@inquirer/prompts";
1861
1949
  async function removeCommand() {
1862
1950
  banner();
1863
1951
  const configured = [];
1864
1952
  for (const tool of TOOLS) {
1865
- const paths = resolveConfigPaths(tool.configPaths);
1866
- for (const configPath of paths) {
1867
- if (fs10.existsSync(configPath) && isPiutConfigured(configPath, tool.configKey)) {
1868
- configured.push({ tool, configPath });
1953
+ if (!tool.configKey) continue;
1954
+ const paths = resolveConfigPaths(tool);
1955
+ for (const { filePath, configKey } of paths) {
1956
+ if (fs8.existsSync(filePath) && isPiutConfigured(filePath, configKey)) {
1957
+ configured.push({ tool, configPath: filePath, resolvedConfigKey: configKey });
1869
1958
  break;
1870
1959
  }
1871
1960
  }
@@ -1894,8 +1983,9 @@ async function removeCommand() {
1894
1983
  if (!proceed) return;
1895
1984
  console.log();
1896
1985
  const removedNames = [];
1897
- for (const { tool, configPath } of selected) {
1898
- const removed = removeFromConfig(configPath, tool.configKey);
1986
+ for (const { tool, configPath, resolvedConfigKey } of selected) {
1987
+ if (!resolvedConfigKey) continue;
1988
+ const removed = removeFromConfig(configPath, resolvedConfigKey);
1899
1989
  if (removed) {
1900
1990
  removedNames.push(tool.name);
1901
1991
  toolLine(tool.name, success("removed"), "\u2714");
@@ -1914,11 +2004,13 @@ async function removeCommand() {
1914
2004
  }
1915
2005
 
1916
2006
  // src/commands/build.ts
1917
- import { select as select2, checkbox as checkbox3, input as input2, confirm as confirm3 } from "@inquirer/prompts";
2007
+ init_esm_shims();
2008
+ import { confirm as confirm3 } from "@inquirer/prompts";
1918
2009
  import chalk5 from "chalk";
1919
2010
  import os7 from "os";
1920
2011
 
1921
2012
  // src/lib/auth.ts
2013
+ init_esm_shims();
1922
2014
  import { select, input, password as password2 } from "@inquirer/prompts";
1923
2015
  import { exec } from "child_process";
1924
2016
  import chalk4 from "chalk";
@@ -2031,66 +2123,53 @@ async function resolveApiKeyWithResult(keyOption) {
2031
2123
  async function buildCommand(options) {
2032
2124
  banner();
2033
2125
  const { apiKey, serverUrl } = await resolveApiKeyWithResult(options.key);
2034
- let scanDirs;
2035
- if (options.folders) {
2036
- scanDirs = options.folders.split(",").map((f) => expandPath(f.trim()));
2037
- } else {
2038
- console.log(dim(" \u2501\u2501\u2501 BUILD YOUR BRAIN \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"));
2039
- console.log();
2040
- console.log(dim(" This is a local scan only \u2014 no files leave your machine"));
2041
- console.log(dim(" until you review and explicitly approve."));
2042
- console.log();
2043
- scanDirs = await selectFolders();
2044
- }
2045
- if (scanDirs.length === 0) {
2046
- console.log(chalk5.yellow(" No folders selected."));
2047
- return;
2048
- }
2126
+ console.log(dim(" \u2501\u2501\u2501 BUILD YOUR BRAIN \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"));
2049
2127
  console.log();
2050
- console.log(dim(" Scanning locally \u2014 no data is shared..."));
2128
+ console.log(dim(" Scanning for AI config files..."));
2051
2129
  console.log();
2052
- let fileCount = 0;
2130
+ const cwd = process.cwd();
2131
+ const homeDirs = getDefaultScanDirs();
2132
+ const allScanDirs = [cwd, ...homeDirs.filter((d) => !d.startsWith(cwd) && !cwd.startsWith(d))];
2053
2133
  const onProgress = (progress) => {
2054
- if (progress.phase === "scanning") {
2055
- console.log(dim(` ${progress.message}`));
2056
- } else if (progress.phase === "parsing") {
2057
- fileCount++;
2058
- console.log(dim(` [${fileCount}] ${progress.message}`));
2059
- } else if (progress.phase === "projects") {
2134
+ if (progress.phase === "projects") {
2060
2135
  console.log(dim(` [project] ${progress.message}`));
2061
2136
  } else if (progress.phase === "configs") {
2062
2137
  console.log(dim(` [config] ${progress.message}`));
2063
2138
  }
2064
2139
  };
2065
- const scanResult = await scanFolders(scanDirs, onProgress);
2066
- console.log();
2067
- console.log(success(` \u2713 Scan complete: ${scanResult.totalFiles} files found across ${scanResult.folders.length} folders (local only)`));
2140
+ const projects = detectProjects(allScanDirs, onProgress);
2141
+ const globalConfigs = collectGlobalConfigFiles(onProgress);
2142
+ const projectConfigs = collectProjectConfigFiles(projects, onProgress);
2143
+ const allConfigs = [...globalConfigs, ...projectConfigs];
2068
2144
  console.log();
2069
- if (scanResult.totalFiles === 0 && scanResult.configFiles.length === 0) {
2070
- console.log(chalk5.yellow(" No parseable files found in the selected folders."));
2071
- console.log(dim(" Try scanning a different directory, or use --folders to specify paths."));
2145
+ if (allConfigs.length > 0) {
2146
+ console.log(success(` \u2713 Found ${allConfigs.length} config file${allConfigs.length === 1 ? "" : "s"} in ${projects.length} project${projects.length === 1 ? "" : "s"}`));
2147
+ } else {
2148
+ console.log(dim(" No AI config files found."));
2149
+ }
2150
+ const totalBytes = allConfigs.reduce((sum, c) => sum + Buffer.byteLength(c.content, "utf-8"), 0);
2151
+ const totalFiles = allConfigs.length;
2152
+ if (totalFiles === 0) {
2153
+ console.log();
2154
+ console.log(chalk5.yellow(" No config files found to build your brain."));
2155
+ console.log(dim(" Add AI config files (CLAUDE.md, .cursorrules, etc.) to your projects,"));
2156
+ console.log(dim(" or upload documents via: piut vault upload <file>"));
2072
2157
  console.log();
2073
2158
  return;
2074
2159
  }
2075
- let selectedFolderPaths;
2076
- if (options.yes || scanResult.folders.length === 0) {
2077
- selectedFolderPaths = scanResult.folders.map((f) => f.path);
2078
- } else {
2079
- selectedFolderPaths = await reviewFolders(scanResult);
2080
- }
2081
- if (selectedFolderPaths.length === 0 && scanResult.configFiles.length === 0) {
2082
- console.log(chalk5.yellow(" No folders selected."));
2160
+ if (totalBytes > MAX_BRAIN_INPUT_BYTES) {
2161
+ console.log();
2162
+ console.log(chalk5.yellow(` Total data: ${formatSize(totalBytes)} exceeds the 1MB limit.`));
2163
+ console.log();
2083
2164
  return;
2084
2165
  }
2085
- const selectedFolders = scanResult.folders.filter((f) => selectedFolderPaths.includes(f.path));
2086
- const totalSelectedFiles = selectedFolders.reduce((sum, f) => sum + f.fileCount, 0) + scanResult.configFiles.length;
2087
2166
  if (!options.yes) {
2088
2167
  console.log();
2089
2168
  console.log(dim(" \u2501\u2501\u2501 READY TO BUILD \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"));
2090
2169
  console.log();
2091
- console.log(dim(` ${totalSelectedFiles} files will be sent to p\u0131ut and processed by Claude`));
2092
- console.log(dim(" Sonnet to design your brain. File contents are used for"));
2093
- console.log(dim(" brain generation only and are not retained."));
2170
+ console.log(dim(` ${totalFiles} file${totalFiles === 1 ? "" : "s"} (${formatSize(totalBytes)}) will be processed by Claude Sonnet to design your brain.`));
2171
+ console.log(dim(" File contents are used for brain generation only and"));
2172
+ console.log(dim(` are not retained by p\u0131ut.`));
2094
2173
  console.log();
2095
2174
  console.log(dim(` Privacy policy: ${brand("https://piut.com/privacy")}`));
2096
2175
  console.log();
@@ -2105,7 +2184,20 @@ async function buildCommand(options) {
2105
2184
  return;
2106
2185
  }
2107
2186
  }
2108
- const brainInput = buildBrainInput(scanResult, selectedFolderPaths);
2187
+ const home2 = os7.homedir();
2188
+ const brainInput = {
2189
+ summary: {
2190
+ projects: projects.map((p) => ({
2191
+ name: p.name,
2192
+ path: p.path.replace(home2, "~"),
2193
+ description: p.description
2194
+ })),
2195
+ configFiles: allConfigs
2196
+ }
2197
+ };
2198
+ await streamBuild(apiKey, serverUrl, brainInput, options);
2199
+ }
2200
+ async function streamBuild(apiKey, serverUrl, brainInput, options) {
2109
2201
  const spinner = new Spinner();
2110
2202
  spinner.start("Generating brain...");
2111
2203
  try {
@@ -2205,85 +2297,13 @@ async function buildCommand(options) {
2205
2297
  spinner.stop();
2206
2298
  if (err instanceof CliError) throw err;
2207
2299
  const msg = err.message || "Unknown error";
2208
- const hint = msg === "terminated" || msg.includes("network") || msg.includes("fetch") ? "The build was interrupted. This can happen if your scan data is very large. Try using --folders to limit which directories are scanned." : msg;
2209
- console.log(chalk5.red(` \u2717 ${hint}`));
2210
- throw new CliError(hint);
2211
- }
2212
- }
2213
- async function selectFolders() {
2214
- const defaults = getDefaultScanDirs();
2215
- const homeDir = os7.homedir();
2216
- const ALL_VALUE = "__all__";
2217
- const CUSTOM_VALUE = "__custom__";
2218
- const choices = [
2219
- { name: `All home folders (~) ${chalk5.dim("(Recommended)")}`, value: ALL_VALUE },
2220
- { name: chalk5.dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"), value: "__sep__", disabled: true },
2221
- ...defaults.map((d) => ({ name: displayPath(d), value: d })),
2222
- { name: chalk5.dim("Browse to a folder..."), value: CUSTOM_VALUE }
2223
- ];
2224
- const selected = await checkbox3({
2225
- message: "Select folders to scan:",
2226
- choices,
2227
- required: true
2228
- });
2229
- let scanDirs;
2230
- if (selected.includes(ALL_VALUE)) {
2231
- scanDirs = defaults;
2232
- } else {
2233
- scanDirs = selected.filter((v) => v !== CUSTOM_VALUE);
2234
- if (selected.includes(CUSTOM_VALUE)) {
2235
- const custom = await input2({
2236
- message: "Enter folder path(s), comma-separated:"
2237
- });
2238
- const customPaths = custom.split(",").map((f) => expandPath(f.trim())).filter(Boolean);
2239
- scanDirs = [...scanDirs, ...customPaths];
2240
- }
2241
- }
2242
- if (scanDirs.length > 0 && !selected.includes(ALL_VALUE)) {
2243
- console.log();
2244
- console.log(dim(" Selected:"));
2245
- for (const d of scanDirs) {
2246
- console.log(dim(` ${displayPath(d)}`));
2247
- }
2248
- const addMore = await select2({
2249
- message: "Add more folders or continue?",
2250
- choices: [
2251
- { name: "Continue with scan", value: "continue" },
2252
- { name: "Add another folder...", value: "add" }
2253
- ]
2254
- });
2255
- if (addMore === "add") {
2256
- const extra = await input2({
2257
- message: "Enter folder path(s), comma-separated:"
2258
- });
2259
- const extraPaths = extra.split(",").map((f) => expandPath(f.trim())).filter(Boolean);
2260
- scanDirs = [...scanDirs, ...extraPaths];
2261
- }
2300
+ console.log(chalk5.red(` \u2717 ${msg}`));
2301
+ throw new CliError(msg);
2262
2302
  }
2263
- return scanDirs;
2264
- }
2265
- async function reviewFolders(scanResult) {
2266
- if (scanResult.folders.length === 0) return [];
2267
- console.log(dim(" \u2501\u2501\u2501 REVIEW SCANNED FOLDERS \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"));
2268
- console.log();
2269
- console.log(dim(" All folders selected by default. Deselect any you want to exclude."));
2270
- console.log();
2271
- const choices = scanResult.folders.map((folder) => ({
2272
- name: formatFolderChoice(folder),
2273
- value: folder.path,
2274
- checked: true
2275
- }));
2276
- const selected = await checkbox3({
2277
- message: "Select folders to include in your brain:",
2278
- choices
2279
- });
2280
- const selectedFolders = scanResult.folders.filter((f) => selected.includes(f.path));
2281
- console.log();
2282
- console.log(dim(` ${formatSelectionSummary(selectedFolders)}`));
2283
- return selected;
2284
2303
  }
2285
2304
 
2286
2305
  // src/commands/deploy.ts
2306
+ init_esm_shims();
2287
2307
  import chalk6 from "chalk";
2288
2308
  async function deployCommand(options) {
2289
2309
  banner();
@@ -2321,33 +2341,34 @@ async function deployCommand(options) {
2321
2341
  }
2322
2342
 
2323
2343
  // src/commands/connect.ts
2324
- import fs11 from "fs";
2325
- import path13 from "path";
2326
- import { checkbox as checkbox4 } from "@inquirer/prompts";
2344
+ init_esm_shims();
2345
+ import fs9 from "fs";
2346
+ import path11 from "path";
2347
+ import { checkbox as checkbox3 } from "@inquirer/prompts";
2327
2348
  var RULE_FILES = [
2328
2349
  {
2329
2350
  tool: "Claude Code",
2330
2351
  filePath: "CLAUDE.md",
2331
2352
  strategy: "append",
2332
- detect: (p) => p.hasClaudeMd || fs11.existsSync(path13.join(p.path, ".claude"))
2353
+ detect: (p) => p.hasClaudeMd || fs9.existsSync(path11.join(p.path, ".claude"))
2333
2354
  },
2334
2355
  {
2335
2356
  tool: "Cursor",
2336
2357
  filePath: ".cursor/rules/piut.mdc",
2337
2358
  strategy: "create",
2338
- detect: (p) => p.hasCursorRules || fs11.existsSync(path13.join(p.path, ".cursor"))
2359
+ detect: (p) => p.hasCursorRules || fs9.existsSync(path11.join(p.path, ".cursor"))
2339
2360
  },
2340
2361
  {
2341
2362
  tool: "Windsurf",
2342
2363
  filePath: ".windsurf/rules/piut.md",
2343
2364
  strategy: "create",
2344
- detect: (p) => p.hasWindsurfRules || fs11.existsSync(path13.join(p.path, ".windsurf"))
2365
+ detect: (p) => p.hasWindsurfRules || fs9.existsSync(path11.join(p.path, ".windsurf"))
2345
2366
  },
2346
2367
  {
2347
2368
  tool: "GitHub Copilot",
2348
2369
  filePath: ".github/copilot-instructions.md",
2349
2370
  strategy: "append",
2350
- detect: (p) => p.hasCopilotInstructions || fs11.existsSync(path13.join(p.path, ".github"))
2371
+ detect: (p) => p.hasCopilotInstructions || fs9.existsSync(path11.join(p.path, ".github"))
2351
2372
  },
2352
2373
  {
2353
2374
  tool: "Amazon Q",
@@ -2359,7 +2380,19 @@ var RULE_FILES = [
2359
2380
  tool: "Zed",
2360
2381
  filePath: ".zed/rules.md",
2361
2382
  strategy: "create",
2362
- detect: (p) => p.hasZedRules || fs11.existsSync(path13.join(p.path, ".zed"))
2383
+ detect: (p) => p.hasZedRules || fs9.existsSync(path11.join(p.path, ".zed"))
2384
+ },
2385
+ {
2386
+ tool: "Gemini CLI",
2387
+ filePath: "GEMINI.md",
2388
+ strategy: "append",
2389
+ detect: (p) => fs9.existsSync(path11.join(p.path, ".gemini"))
2390
+ },
2391
+ {
2392
+ tool: "Paperclip",
2393
+ filePath: "AGENTS.md",
2394
+ strategy: "append",
2395
+ detect: (p) => fs9.existsSync(path11.join(p.path, ".paperclip"))
2363
2396
  }
2364
2397
  ];
2365
2398
  var DEDICATED_FILE_CONTENT = `## p\u0131ut Context (MCP Server: piut-context)
@@ -2394,7 +2427,7 @@ Full skill reference: .piut/skill.md
2394
2427
  `;
2395
2428
  function hasPiutReference2(filePath) {
2396
2429
  try {
2397
- const content = fs11.readFileSync(filePath, "utf-8");
2430
+ const content = fs9.readFileSync(filePath, "utf-8");
2398
2431
  return content.includes("p\u0131ut Context") || content.includes("piut Context");
2399
2432
  } catch {
2400
2433
  return false;
@@ -2417,13 +2450,13 @@ async function connectCommand(options) {
2417
2450
  console.log();
2418
2451
  return;
2419
2452
  }
2420
- let scanFolders2;
2453
+ let scanFolders;
2421
2454
  if (options.folders) {
2422
- scanFolders2 = options.folders.split(",").map((f) => expandPath(f.trim()));
2455
+ scanFolders = options.folders.split(",").map((f) => expandPath(f.trim()));
2423
2456
  }
2424
2457
  console.log();
2425
2458
  console.log(dim(" Scanning for projects..."));
2426
- const projects = scanForProjects(scanFolders2);
2459
+ const projects = scanForProjects(scanFolders);
2427
2460
  if (projects.length === 0) {
2428
2461
  console.log(warning(" No projects found."));
2429
2462
  console.log(dim(" Try running from a directory with your projects, or use --folders."));
@@ -2434,20 +2467,20 @@ async function connectCommand(options) {
2434
2467
  for (const project of projects) {
2435
2468
  for (const rule of RULE_FILES) {
2436
2469
  if (!rule.detect(project)) continue;
2437
- const absPath = path13.join(project.path, rule.filePath);
2438
- if (fs11.existsSync(absPath) && hasPiutReference2(absPath)) continue;
2470
+ const absPath = path11.join(project.path, rule.filePath);
2471
+ if (fs9.existsSync(absPath) && hasPiutReference2(absPath)) continue;
2439
2472
  actions.push({
2440
2473
  project,
2441
2474
  tool: rule.tool,
2442
2475
  filePath: rule.filePath,
2443
2476
  absPath,
2444
- action: rule.strategy === "create" || !fs11.existsSync(absPath) ? "create" : "append"
2477
+ action: rule.strategy === "create" || !fs9.existsSync(absPath) ? "create" : "append"
2445
2478
  });
2446
2479
  }
2447
2480
  const hasAnyAction = actions.some((a) => a.project === project);
2448
2481
  if (!hasAnyAction) {
2449
- const claudeMdPath = path13.join(project.path, "CLAUDE.md");
2450
- if (!fs11.existsSync(claudeMdPath)) {
2482
+ const claudeMdPath = path11.join(project.path, "CLAUDE.md");
2483
+ if (!fs9.existsSync(claudeMdPath)) {
2451
2484
  actions.push({
2452
2485
  project,
2453
2486
  tool: "Claude Code",
@@ -2487,7 +2520,7 @@ async function connectCommand(options) {
2487
2520
  console.log();
2488
2521
  const projectChoices = [];
2489
2522
  for (const [projectPath, projectActions] of byProject) {
2490
- const projectName = path13.basename(projectPath);
2523
+ const projectName = path11.basename(projectPath);
2491
2524
  const desc = projectActions.map((a) => {
2492
2525
  const verb = a.action === "create" ? "will create" : "will append to";
2493
2526
  return `${verb} ${a.filePath}`;
@@ -2502,7 +2535,7 @@ async function connectCommand(options) {
2502
2535
  if (options.yes) {
2503
2536
  selectedPaths = Array.from(byProject.keys());
2504
2537
  } else {
2505
- selectedPaths = await checkbox4({
2538
+ selectedPaths = await checkbox3({
2506
2539
  message: "Select projects to connect:",
2507
2540
  choices: projectChoices
2508
2541
  });
@@ -2516,15 +2549,15 @@ async function connectCommand(options) {
2516
2549
  const copilotTool = TOOLS.find((t) => t.id === "copilot");
2517
2550
  for (const projectPath of selectedPaths) {
2518
2551
  const projectActions = byProject.get(projectPath) || [];
2519
- const projectName = path13.basename(projectPath);
2552
+ const projectName = path11.basename(projectPath);
2520
2553
  writePiutConfig(projectPath, { slug, apiKey, serverUrl });
2521
2554
  await writePiutSkill(projectPath, slug, apiKey);
2522
2555
  ensureGitignored(projectPath);
2523
2556
  console.log(success(` \u2713 ${projectName}/.piut/`) + dim(" \u2014 credentials + skill"));
2524
2557
  if (copilotTool) {
2525
- const hasCopilot = fs11.existsSync(path13.join(projectPath, ".github", "copilot-instructions.md")) || fs11.existsSync(path13.join(projectPath, ".github"));
2558
+ const hasCopilot = fs9.existsSync(path11.join(projectPath, ".github", "copilot-instructions.md")) || fs9.existsSync(path11.join(projectPath, ".github"));
2526
2559
  if (hasCopilot) {
2527
- const vscodeMcpPath = path13.join(projectPath, ".vscode", "mcp.json");
2560
+ const vscodeMcpPath = path11.join(projectPath, ".vscode", "mcp.json");
2528
2561
  const serverConfig = copilotTool.generateConfig(slug, apiKey);
2529
2562
  mergeConfig(vscodeMcpPath, copilotTool.configKey, serverConfig);
2530
2563
  console.log(success(` \u2713 ${projectName}/.vscode/mcp.json`) + dim(" \u2014 Copilot MCP"));
@@ -2534,26 +2567,28 @@ async function connectCommand(options) {
2534
2567
  if (action.action === "create") {
2535
2568
  const isAppendType = RULE_FILES.find((r) => r.filePath === action.filePath)?.strategy === "append";
2536
2569
  const content = isAppendType ? PROJECT_SKILL_SNIPPET + "\n" : DEDICATED_FILE_CONTENT;
2537
- fs11.mkdirSync(path13.dirname(action.absPath), { recursive: true });
2538
- fs11.writeFileSync(action.absPath, content, "utf-8");
2570
+ fs9.mkdirSync(path11.dirname(action.absPath), { recursive: true });
2571
+ fs9.writeFileSync(action.absPath, content, "utf-8");
2539
2572
  console.log(success(` \u2713 ${projectName}/${action.filePath}`) + dim(" \u2014 created"));
2540
2573
  } else {
2541
- fs11.appendFileSync(action.absPath, APPEND_SECTION);
2574
+ fs9.appendFileSync(action.absPath, APPEND_SECTION);
2542
2575
  console.log(success(` \u2713 ${projectName}/${action.filePath}`) + dim(" \u2014 appended"));
2543
2576
  }
2544
2577
  connected++;
2545
2578
  }
2546
2579
  }
2547
2580
  const machineId = getMachineId();
2581
+ const hostname = getHostname();
2548
2582
  for (const projectPath of selectedPaths) {
2549
2583
  const projectActions = byProject.get(projectPath) || [];
2550
- const projectName = path13.basename(projectPath);
2584
+ const projectName = path11.basename(projectPath);
2551
2585
  const toolsDetected = [...new Set(projectActions.map((a) => a.tool))];
2552
2586
  const configFilesWritten = projectActions.map((a) => a.filePath);
2553
2587
  registerProject(apiKey, {
2554
2588
  projectName,
2555
2589
  projectPath,
2556
2590
  machineId,
2591
+ hostname,
2557
2592
  toolsDetected,
2558
2593
  configFiles: configFilesWritten
2559
2594
  }).catch(() => {
@@ -2565,9 +2600,10 @@ async function connectCommand(options) {
2565
2600
  }
2566
2601
 
2567
2602
  // src/commands/disconnect.ts
2568
- import fs12 from "fs";
2569
- import path14 from "path";
2570
- import { checkbox as checkbox5, confirm as confirm5 } from "@inquirer/prompts";
2603
+ init_esm_shims();
2604
+ import fs10 from "fs";
2605
+ import path12 from "path";
2606
+ import { checkbox as checkbox4, confirm as confirm5 } from "@inquirer/prompts";
2571
2607
  var DEDICATED_FILES = /* @__PURE__ */ new Set([
2572
2608
  ".cursor/rules/piut.mdc",
2573
2609
  ".windsurf/rules/piut.md",
@@ -2576,11 +2612,13 @@ var DEDICATED_FILES = /* @__PURE__ */ new Set([
2576
2612
  var APPEND_FILES = [
2577
2613
  "CLAUDE.md",
2578
2614
  ".github/copilot-instructions.md",
2579
- "CONVENTIONS.md"
2615
+ "CONVENTIONS.md",
2616
+ "GEMINI.md",
2617
+ "AGENTS.md"
2580
2618
  ];
2581
2619
  function hasPiutReference3(filePath) {
2582
2620
  try {
2583
- const content = fs12.readFileSync(filePath, "utf-8");
2621
+ const content = fs10.readFileSync(filePath, "utf-8");
2584
2622
  return content.includes("p\u0131ut Context") || content.includes("piut Context");
2585
2623
  } catch {
2586
2624
  return false;
@@ -2588,7 +2626,7 @@ function hasPiutReference3(filePath) {
2588
2626
  }
2589
2627
  function removePiutSection(filePath) {
2590
2628
  try {
2591
- let content = fs12.readFileSync(filePath, "utf-8");
2629
+ let content = fs10.readFileSync(filePath, "utf-8");
2592
2630
  const patterns = [
2593
2631
  /\n*## p[ıi]ut Context[\s\S]*?(?=\n## |\n---\n|$)/g
2594
2632
  ];
@@ -2602,7 +2640,7 @@ function removePiutSection(filePath) {
2602
2640
  }
2603
2641
  if (changed) {
2604
2642
  content = content.replace(/\n{3,}/g, "\n\n").trimEnd() + "\n";
2605
- fs12.writeFileSync(filePath, content, "utf-8");
2643
+ fs10.writeFileSync(filePath, content, "utf-8");
2606
2644
  }
2607
2645
  return changed;
2608
2646
  } catch {
@@ -2611,18 +2649,18 @@ function removePiutSection(filePath) {
2611
2649
  }
2612
2650
  async function disconnectCommand(options) {
2613
2651
  banner();
2614
- let scanFolders2;
2652
+ let scanFolders;
2615
2653
  if (options.folders) {
2616
- scanFolders2 = options.folders.split(",").map((f) => expandPath(f.trim()));
2654
+ scanFolders = options.folders.split(",").map((f) => expandPath(f.trim()));
2617
2655
  }
2618
2656
  console.log(dim(" Scanning for connected projects..."));
2619
- const projects = scanForProjects(scanFolders2);
2657
+ const projects = scanForProjects(scanFolders);
2620
2658
  const actions = [];
2621
2659
  for (const project of projects) {
2622
- const projectName = path14.basename(project.path);
2660
+ const projectName = path12.basename(project.path);
2623
2661
  for (const dedicatedFile of DEDICATED_FILES) {
2624
- const absPath = path14.join(project.path, dedicatedFile);
2625
- if (fs12.existsSync(absPath) && hasPiutReference3(absPath)) {
2662
+ const absPath = path12.join(project.path, dedicatedFile);
2663
+ if (fs10.existsSync(absPath) && hasPiutReference3(absPath)) {
2626
2664
  actions.push({
2627
2665
  projectPath: project.path,
2628
2666
  projectName,
@@ -2633,8 +2671,8 @@ async function disconnectCommand(options) {
2633
2671
  }
2634
2672
  }
2635
2673
  for (const appendFile of APPEND_FILES) {
2636
- const absPath = path14.join(project.path, appendFile);
2637
- if (fs12.existsSync(absPath) && hasPiutReference3(absPath)) {
2674
+ const absPath = path12.join(project.path, appendFile);
2675
+ if (fs10.existsSync(absPath) && hasPiutReference3(absPath)) {
2638
2676
  actions.push({
2639
2677
  projectPath: project.path,
2640
2678
  projectName,
@@ -2649,12 +2687,12 @@ async function disconnectCommand(options) {
2649
2687
  projectPath: project.path,
2650
2688
  projectName,
2651
2689
  filePath: ".piut/",
2652
- absPath: path14.join(project.path, ".piut"),
2690
+ absPath: path12.join(project.path, ".piut"),
2653
2691
  action: "remove-dir"
2654
2692
  });
2655
2693
  }
2656
- const vscodeMcpPath = path14.join(project.path, ".vscode", "mcp.json");
2657
- if (fs12.existsSync(vscodeMcpPath) && isPiutConfigured(vscodeMcpPath, "servers")) {
2694
+ const vscodeMcpPath = path12.join(project.path, ".vscode", "mcp.json");
2695
+ if (fs10.existsSync(vscodeMcpPath) && isPiutConfigured(vscodeMcpPath, "servers")) {
2658
2696
  actions.push({
2659
2697
  projectPath: project.path,
2660
2698
  projectName,
@@ -2676,7 +2714,7 @@ async function disconnectCommand(options) {
2676
2714
  }
2677
2715
  console.log();
2678
2716
  const projectChoices = Array.from(byProject.entries()).map(([projectPath, projectActions]) => {
2679
- const name = path14.basename(projectPath);
2717
+ const name = path12.basename(projectPath);
2680
2718
  const files = projectActions.map((a) => a.filePath).join(", ");
2681
2719
  return {
2682
2720
  name: `${name} ${dim(`(${files})`)}`,
@@ -2687,7 +2725,7 @@ async function disconnectCommand(options) {
2687
2725
  if (options.yes) {
2688
2726
  selectedPaths = Array.from(byProject.keys());
2689
2727
  } else {
2690
- selectedPaths = await checkbox5({
2728
+ selectedPaths = await checkbox4({
2691
2729
  message: "Select projects to disconnect:",
2692
2730
  choices: projectChoices
2693
2731
  });
@@ -2705,11 +2743,11 @@ async function disconnectCommand(options) {
2705
2743
  let disconnected = 0;
2706
2744
  for (const projectPath of selectedPaths) {
2707
2745
  const projectActions = byProject.get(projectPath) || [];
2708
- const projectName = path14.basename(projectPath);
2746
+ const projectName = path12.basename(projectPath);
2709
2747
  for (const action of projectActions) {
2710
2748
  if (action.action === "delete") {
2711
2749
  try {
2712
- fs12.unlinkSync(action.absPath);
2750
+ fs10.unlinkSync(action.absPath);
2713
2751
  console.log(success(` \u2713 ${projectName}/${action.filePath}`) + dim(" \u2014 deleted"));
2714
2752
  disconnected++;
2715
2753
  } catch {
@@ -2753,6 +2791,7 @@ async function disconnectCommand(options) {
2753
2791
  }
2754
2792
 
2755
2793
  // src/commands/login.ts
2794
+ init_esm_shims();
2756
2795
  import chalk7 from "chalk";
2757
2796
  async function loginCommand() {
2758
2797
  try {
@@ -2768,6 +2807,7 @@ async function loginCommand() {
2768
2807
  }
2769
2808
 
2770
2809
  // src/commands/logout.ts
2810
+ init_esm_shims();
2771
2811
  async function logoutCommand() {
2772
2812
  banner();
2773
2813
  const config = readStore();
@@ -2784,9 +2824,11 @@ async function logoutCommand() {
2784
2824
  }
2785
2825
 
2786
2826
  // src/commands/update.ts
2827
+ init_esm_shims();
2787
2828
  import chalk9 from "chalk";
2788
2829
 
2789
2830
  // src/lib/update-check.ts
2831
+ init_esm_shims();
2790
2832
  import { execFile } from "child_process";
2791
2833
  import chalk8 from "chalk";
2792
2834
  import { confirm as confirm6 } from "@inquirer/prompts";
@@ -2885,7 +2927,8 @@ async function updateCommand(currentVersion) {
2885
2927
  }
2886
2928
 
2887
2929
  // src/commands/doctor.ts
2888
- import fs13 from "fs";
2930
+ init_esm_shims();
2931
+ import fs11 from "fs";
2889
2932
  import chalk10 from "chalk";
2890
2933
  async function doctorCommand(options) {
2891
2934
  if (!options.json) banner();
@@ -2935,10 +2978,10 @@ async function doctorCommand(options) {
2935
2978
  }
2936
2979
  let toolsFixed = 0;
2937
2980
  for (const tool of TOOLS) {
2938
- const paths = resolveConfigPaths(tool.configPaths);
2939
- for (const configPath of paths) {
2940
- if (!fs13.existsSync(configPath)) continue;
2941
- const piutConfig = getPiutConfig(configPath, tool.configKey);
2981
+ const paths = resolveConfigPaths(tool);
2982
+ for (const { filePath: configPath, configKey: resolvedKey } of paths) {
2983
+ if (!fs11.existsSync(configPath)) continue;
2984
+ const piutConfig = getPiutConfig(configPath, resolvedKey);
2942
2985
  if (!piutConfig) {
2943
2986
  result.tools.push({
2944
2987
  name: tool.name,
@@ -2953,13 +2996,13 @@ async function doctorCommand(options) {
2953
2996
  }
2954
2997
  break;
2955
2998
  }
2956
- const configKey = extractKeyFromConfig(piutConfig);
2957
- const configPrefix = configKey ? configKey.slice(0, 7) + "..." : "(none)";
2999
+ const extractedKey = extractKeyFromConfig(piutConfig);
3000
+ const configPrefix = extractedKey ? extractedKey.slice(0, 7) + "..." : "(none)";
2958
3001
  let keyMatch = "missing";
2959
- if (!configKey) {
3002
+ if (!extractedKey) {
2960
3003
  keyMatch = "missing";
2961
3004
  result.issues++;
2962
- } else if (apiKey && configKey === apiKey) {
3005
+ } else if (apiKey && extractedKey === apiKey) {
2963
3006
  keyMatch = "match";
2964
3007
  } else {
2965
3008
  keyMatch = "stale";
@@ -2977,7 +3020,7 @@ async function doctorCommand(options) {
2977
3020
  };
2978
3021
  if (keyMatch === "stale" && options.fix && apiKey && result.key.valid && result.key.slug) {
2979
3022
  const serverConfig = tool.generateConfig(result.key.slug, apiKey);
2980
- mergeConfig(configPath, tool.configKey, serverConfig);
3023
+ mergeConfig(configPath, resolvedKey, serverConfig);
2981
3024
  toolResult.fixed = true;
2982
3025
  toolResult.keyMatch = "match";
2983
3026
  result.issues--;
@@ -3052,13 +3095,156 @@ async function doctorCommand(options) {
3052
3095
  if (result.issues > 0) throw new CliError(`${result.issues} issue(s) found`);
3053
3096
  }
3054
3097
 
3098
+ // src/commands/vault.ts
3099
+ init_esm_shims();
3100
+ import fs12 from "fs";
3101
+ import path13 from "path";
3102
+ import chalk11 from "chalk";
3103
+ import { confirm as confirm7 } from "@inquirer/prompts";
3104
+ var DOCUMENT_EXTENSIONS = /* @__PURE__ */ new Set([
3105
+ "pdf",
3106
+ "docx",
3107
+ "doc",
3108
+ "pptx",
3109
+ "pages",
3110
+ "key",
3111
+ "rtf",
3112
+ "odt",
3113
+ "odp",
3114
+ "eml",
3115
+ "mbox"
3116
+ ]);
3117
+ function formatSize2(bytes) {
3118
+ if (bytes < 1024) return `${bytes} B`;
3119
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
3120
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
3121
+ }
3122
+ function resolveApiKey(options) {
3123
+ const key = options.key || readStore().apiKey;
3124
+ if (!key) {
3125
+ console.log(warning(" No API key found."));
3126
+ console.log(dim(" Run ") + brand("piut login") + dim(" first, or pass --key."));
3127
+ throw new CliError();
3128
+ }
3129
+ return key;
3130
+ }
3131
+ async function vaultListCommand(options) {
3132
+ const key = resolveApiKey(options);
3133
+ const data = await listVaultFiles(key);
3134
+ if (data.files.length === 0) {
3135
+ console.log(dim(" No files in vault."));
3136
+ console.log(dim(" Upload with: ") + brand("piut vault upload <file>"));
3137
+ console.log();
3138
+ return;
3139
+ }
3140
+ console.log();
3141
+ for (const file of data.files) {
3142
+ const size = dim(`(${formatSize2(file.sizeBytes)})`);
3143
+ console.log(` ${file.filename} ${size}`);
3144
+ if (file.summary) {
3145
+ console.log(dim(` ${file.summary}`));
3146
+ }
3147
+ }
3148
+ console.log();
3149
+ console.log(dim(` ${data.usage.fileCount} file(s), ${formatSize2(data.usage.totalBytes)} / ${formatSize2(data.usage.maxBytes)} used`));
3150
+ console.log();
3151
+ }
3152
+ async function vaultUploadCommand(filePath, options) {
3153
+ const key = resolveApiKey(options);
3154
+ const resolved = path13.resolve(filePath);
3155
+ if (!fs12.existsSync(resolved)) {
3156
+ console.log(chalk11.red(` File not found: ${filePath}`));
3157
+ throw new CliError();
3158
+ }
3159
+ const stat = fs12.statSync(resolved);
3160
+ if (!stat.isFile()) {
3161
+ console.log(chalk11.red(` Not a file: ${filePath}`));
3162
+ throw new CliError();
3163
+ }
3164
+ const filename = path13.basename(resolved);
3165
+ const ext = filename.includes(".") ? filename.split(".").pop()?.toLowerCase() || "" : "";
3166
+ const isDocument = DOCUMENT_EXTENSIONS.has(ext);
3167
+ let content;
3168
+ let encoding;
3169
+ if (isDocument) {
3170
+ content = fs12.readFileSync(resolved).toString("base64");
3171
+ encoding = "base64";
3172
+ } else {
3173
+ content = fs12.readFileSync(resolved, "utf-8");
3174
+ }
3175
+ const spinner = new Spinner();
3176
+ spinner.start(`Uploading ${filename}...`);
3177
+ try {
3178
+ const result = await uploadVaultFile(key, filename, content, encoding);
3179
+ spinner.stop();
3180
+ console.log(success(` Uploaded ${result.filename}`) + dim(` (${formatSize2(result.sizeBytes)})`));
3181
+ if (result.summary) {
3182
+ console.log(dim(` ${result.summary}`));
3183
+ }
3184
+ console.log();
3185
+ } catch (err) {
3186
+ spinner.stop();
3187
+ console.log(chalk11.red(` ${err.message}`));
3188
+ throw new CliError();
3189
+ }
3190
+ }
3191
+ async function vaultReadCommand(filename, options) {
3192
+ const key = resolveApiKey(options);
3193
+ try {
3194
+ const file = await readVaultFile(key, filename);
3195
+ if (options.output) {
3196
+ const outPath = path13.resolve(options.output);
3197
+ fs12.writeFileSync(outPath, file.content, "utf-8");
3198
+ console.log(success(` Saved to ${outPath}`));
3199
+ console.log();
3200
+ } else {
3201
+ console.log();
3202
+ console.log(file.content);
3203
+ }
3204
+ } catch (err) {
3205
+ console.log(chalk11.red(` ${err.message}`));
3206
+ throw new CliError();
3207
+ }
3208
+ }
3209
+ async function vaultDeleteCommand(filename, options) {
3210
+ const key = resolveApiKey(options);
3211
+ if (!options.yes) {
3212
+ const confirmed = await confirm7({
3213
+ message: `Delete "${filename}" from vault? This cannot be undone.`,
3214
+ default: false
3215
+ });
3216
+ if (!confirmed) return;
3217
+ }
3218
+ try {
3219
+ await deleteVaultFile(key, filename);
3220
+ console.log(success(` Deleted ${filename}`));
3221
+ console.log();
3222
+ } catch (err) {
3223
+ console.log(chalk11.red(` ${err.message}`));
3224
+ throw new CliError();
3225
+ }
3226
+ }
3227
+
3055
3228
  // src/commands/interactive.ts
3056
- import { select as select3, confirm as confirm7, checkbox as checkbox6, Separator } from "@inquirer/prompts";
3229
+ init_esm_shims();
3230
+ import { select as select2, confirm as confirm8, checkbox as checkbox5, Separator } from "@inquirer/prompts";
3057
3231
  import fs14 from "fs";
3058
3232
  import path15 from "path";
3059
3233
  import { exec as exec2 } from "child_process";
3060
- import os8 from "os";
3061
- import chalk11 from "chalk";
3234
+ import chalk13 from "chalk";
3235
+ var DOCUMENT_EXTENSIONS2 = /* @__PURE__ */ new Set([
3236
+ "pdf",
3237
+ "docx",
3238
+ "doc",
3239
+ "pptx",
3240
+ "pages",
3241
+ "key",
3242
+ "rtf",
3243
+ "odt",
3244
+ "odp",
3245
+ "eml",
3246
+ "mbox"
3247
+ ]);
3062
3248
  async function authenticate() {
3063
3249
  const config = readStore();
3064
3250
  const apiKey = config.apiKey;
@@ -3097,7 +3283,7 @@ async function interactiveMenu() {
3097
3283
  console.log(warning(" You haven\u2019t built a brain yet."));
3098
3284
  console.log(dim(" Your brain is how AI tools learn about you \u2014 your projects, preferences, and context."));
3099
3285
  console.log();
3100
- const wantBuild = await confirm7({
3286
+ const wantBuild = await confirm8({
3101
3287
  message: "Build your brain now?",
3102
3288
  default: true
3103
3289
  });
@@ -3113,7 +3299,7 @@ async function interactiveMenu() {
3113
3299
  console.log(warning(" Your brain is built but not deployed yet."));
3114
3300
  console.log(dim(" Deploy it to make your MCP server live so AI tools can read your brain."));
3115
3301
  console.log();
3116
- const wantDeploy = await confirm7({
3302
+ const wantDeploy = await confirm8({
3117
3303
  message: "Deploy your brain now?",
3118
3304
  default: true
3119
3305
  });
@@ -3130,15 +3316,28 @@ async function interactiveMenu() {
3130
3316
  const isDeployed = currentValidation.status === "active";
3131
3317
  let action;
3132
3318
  try {
3133
- action = await select3({
3319
+ action = await select2({
3134
3320
  message: "What would you like to do?",
3135
3321
  loop: false,
3136
3322
  choices: [
3137
3323
  {
3138
- name: hasBrain ? "Resync Brain" : "Build Brain",
3324
+ name: "My Brain",
3325
+ value: "view-brain",
3326
+ description: "View all 5 brain sections",
3327
+ disabled: !hasBrain && "(build brain first)"
3328
+ },
3329
+ {
3330
+ name: "Build Brain",
3139
3331
  value: "build",
3140
- description: hasBrain ? "Rescan your files and merge updates into your brain" : "Build your brain from your files"
3332
+ description: hasBrain ? "Rebuild your brain from your files" : "Build your brain from your files"
3333
+ },
3334
+ {
3335
+ name: "Edit Brain",
3336
+ value: "edit-brain",
3337
+ description: "Open piut.com to edit your brain",
3338
+ disabled: !hasBrain && "(build brain first)"
3141
3339
  },
3340
+ new Separator(),
3142
3341
  {
3143
3342
  name: isDeployed ? "Undeploy Brain" : "Deploy Brain",
3144
3343
  value: "deploy",
@@ -3159,23 +3358,21 @@ async function interactiveMenu() {
3159
3358
  },
3160
3359
  new Separator(),
3161
3360
  {
3162
- name: "Edit Brain",
3163
- value: "edit-brain",
3164
- description: "Open piut.com to edit your brain",
3165
- disabled: !hasBrain && "(build brain first)"
3361
+ name: "View Files",
3362
+ value: "vault-view",
3363
+ description: "List and manage files in your vault"
3166
3364
  },
3167
3365
  {
3168
- name: "View Brain",
3169
- value: "view-brain",
3170
- description: "View all 5 brain sections",
3171
- disabled: !hasBrain && "(build brain first)"
3366
+ name: "Upload Files",
3367
+ value: "vault-upload",
3368
+ description: "Upload a file to your vault"
3172
3369
  },
3370
+ new Separator(),
3173
3371
  {
3174
3372
  name: "Status",
3175
3373
  value: "status",
3176
3374
  description: "Show brain, deployment, and connected tools/projects"
3177
3375
  },
3178
- new Separator(),
3179
3376
  {
3180
3377
  name: "Logout",
3181
3378
  value: "logout",
@@ -3194,12 +3391,14 @@ async function interactiveMenu() {
3194
3391
  if (action === "exit") return;
3195
3392
  try {
3196
3393
  switch (action) {
3394
+ case "view-brain":
3395
+ await handleViewBrain(apiKey);
3396
+ break;
3197
3397
  case "build":
3198
- if (hasBrain) {
3199
- await handleResyncBrain(apiKey, currentValidation);
3200
- } else {
3201
- await buildCommand({ key: apiKey });
3202
- }
3398
+ await buildCommand({ key: apiKey });
3399
+ break;
3400
+ case "edit-brain":
3401
+ handleEditBrain();
3203
3402
  break;
3204
3403
  case "deploy":
3205
3404
  if (isDeployed) {
@@ -3214,11 +3413,11 @@ async function interactiveMenu() {
3214
3413
  case "connect-projects":
3215
3414
  await handleManageProjects(apiKey, currentValidation);
3216
3415
  break;
3217
- case "edit-brain":
3218
- handleEditBrain();
3416
+ case "vault-view":
3417
+ await handleVaultView(apiKey);
3219
3418
  break;
3220
- case "view-brain":
3221
- await handleViewBrain(apiKey);
3419
+ case "vault-upload":
3420
+ await handleVaultUpload(apiKey);
3222
3421
  break;
3223
3422
  case "status":
3224
3423
  statusCommand();
@@ -3241,7 +3440,7 @@ async function interactiveMenu() {
3241
3440
  } else if (err instanceof CliError) {
3242
3441
  console.log();
3243
3442
  } else {
3244
- console.log(chalk11.red(` Error: ${err.message}`));
3443
+ console.log(chalk13.red(` Error: ${err.message}`));
3245
3444
  console.log();
3246
3445
  }
3247
3446
  }
@@ -3252,7 +3451,7 @@ async function interactiveMenu() {
3252
3451
  }
3253
3452
  }
3254
3453
  async function handleUndeploy(apiKey) {
3255
- const confirmed = await confirm7({
3454
+ const confirmed = await confirm8({
3256
3455
  message: "Undeploy your brain? AI tools will lose access to your MCP server.",
3257
3456
  default: false
3258
3457
  });
@@ -3264,69 +3463,86 @@ async function handleUndeploy(apiKey) {
3264
3463
  console.log(dim(" Run ") + brand("piut deploy") + dim(" to re-deploy anytime."));
3265
3464
  console.log();
3266
3465
  } catch (err) {
3267
- console.log(chalk11.red(` \u2717 ${err.message}`));
3466
+ console.log(chalk13.red(` \u2717 ${err.message}`));
3268
3467
  }
3269
3468
  }
3270
3469
  async function handleConnectTools(apiKey, validation) {
3271
3470
  const { slug } = validation;
3272
3471
  const detected = [];
3273
3472
  for (const tool of TOOLS) {
3274
- const paths = resolveConfigPaths(tool.configPaths);
3275
- for (const configPath of paths) {
3276
- const exists = fs14.existsSync(configPath);
3277
- const parentExists = fs14.existsSync(path15.dirname(configPath));
3473
+ const paths = resolveConfigPaths(tool);
3474
+ for (const { filePath, configKey } of paths) {
3475
+ const exists = fs14.existsSync(filePath);
3476
+ const parentExists = fs14.existsSync(path15.dirname(filePath));
3278
3477
  if (exists || parentExists) {
3279
- const connected = exists && isPiutConfigured(configPath, tool.configKey);
3280
- detected.push({ tool, configPath, connected });
3478
+ const connected = exists && !!configKey && isPiutConfigured(filePath, configKey);
3479
+ detected.push({ tool, configPath: filePath, resolvedConfigKey: configKey, connected });
3281
3480
  break;
3282
3481
  }
3283
3482
  }
3284
3483
  }
3285
3484
  if (detected.length === 0) {
3286
3485
  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
3486
  console.log();
3289
3487
  return;
3290
3488
  }
3291
- const connectedCount = detected.filter((d) => d.connected).length;
3292
- const availableCount = detected.length - connectedCount;
3489
+ const mcpTools = detected.filter((d) => !d.tool.skillOnly);
3490
+ const skillOnlyTools = detected.filter((d) => d.tool.skillOnly);
3491
+ const connectedCount = mcpTools.filter((d) => d.connected).length;
3492
+ const availableCount = mcpTools.length - connectedCount;
3293
3493
  if (connectedCount > 0 || availableCount > 0) {
3294
3494
  const parts = [];
3295
3495
  if (connectedCount > 0) parts.push(`${connectedCount} connected`);
3296
3496
  if (availableCount > 0) parts.push(`${availableCount} available`);
3497
+ if (skillOnlyTools.length > 0) parts.push(`${skillOnlyTools.length} skill-only`);
3297
3498
  console.log(dim(` ${parts.join(", ")}`));
3298
3499
  }
3299
3500
  console.log();
3300
- const choices = detected.map((d) => ({
3501
+ if (mcpTools.length === 0) {
3502
+ console.log(dim(" Detected tools are skill-only (no MCP config to manage)."));
3503
+ if (skillOnlyTools.length > 0) {
3504
+ console.log(dim(` Skill-only: ${skillOnlyTools.map((d) => d.tool.name).join(", ")}`));
3505
+ }
3506
+ console.log(dim(' Use "Connect Projects" to add skill files to your projects.'));
3507
+ console.log();
3508
+ return;
3509
+ }
3510
+ const choices = mcpTools.map((d) => ({
3301
3511
  name: `${d.tool.name}${d.connected ? dim(" (connected)") : ""}`,
3302
3512
  value: d,
3303
3513
  checked: d.connected
3304
3514
  }));
3305
- const selected = await checkbox6({
3515
+ const selected = await checkbox5({
3306
3516
  message: "Select tools to keep connected (toggle with space):",
3307
3517
  choices
3308
3518
  });
3309
3519
  const toConnect = selected.filter((s) => !s.connected);
3310
- const toDisconnect = detected.filter((d) => d.connected && !selected.includes(d));
3520
+ const toDisconnect = mcpTools.filter((d) => d.connected && !selected.includes(d));
3311
3521
  if (toConnect.length === 0 && toDisconnect.length === 0) {
3312
3522
  console.log(dim(" No changes."));
3313
3523
  console.log();
3314
3524
  return;
3315
3525
  }
3316
3526
  console.log();
3317
- 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");
3527
+ for (const { tool, configPath, resolvedConfigKey } of toConnect) {
3528
+ if (tool.generateConfig && resolvedConfigKey) {
3529
+ const serverConfig = tool.generateConfig(slug, apiKey);
3530
+ mergeConfig(configPath, resolvedConfigKey, serverConfig);
3531
+ toolLine(tool.name, success("connected"), "\u2714");
3532
+ }
3321
3533
  }
3322
3534
  const removedNames = [];
3323
- for (const { tool, configPath } of toDisconnect) {
3324
- const removed = removeFromConfig(configPath, tool.configKey);
3535
+ for (const { tool, configPath, resolvedConfigKey } of toDisconnect) {
3536
+ if (!resolvedConfigKey) continue;
3537
+ const removed = removeFromConfig(configPath, resolvedConfigKey);
3325
3538
  if (removed) {
3326
3539
  removedNames.push(tool.name);
3327
3540
  toolLine(tool.name, warning("disconnected"), "\u2714");
3328
3541
  }
3329
3542
  }
3543
+ if (skillOnlyTools.length > 0) {
3544
+ console.log(dim(` Skill-only (use "Connect Projects"): ${skillOnlyTools.map((d) => d.tool.name).join(", ")}`));
3545
+ }
3330
3546
  if (toConnect.length > 0 && validation.serverUrl) {
3331
3547
  await Promise.all(
3332
3548
  toConnect.map(({ tool }) => pingMcp(validation.serverUrl, apiKey, tool.name))
@@ -3366,7 +3582,7 @@ async function handleManageProjects(apiKey, validation) {
3366
3582
  value: i,
3367
3583
  checked: i.connected
3368
3584
  }));
3369
- const selected = await checkbox6({
3585
+ const selected = await checkbox5({
3370
3586
  message: "Select projects to keep connected (toggle with space):",
3371
3587
  choices
3372
3588
  });
@@ -3459,96 +3675,103 @@ function handleEditBrain() {
3459
3675
  console.log(success(" \u2713 Opened in browser."));
3460
3676
  console.log();
3461
3677
  }
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")}`);
3678
+ function formatSize3(bytes) {
3679
+ if (bytes < 1024) return `${bytes} B`;
3680
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
3681
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
3682
+ }
3683
+ async function handleVaultView(apiKey) {
3684
+ const data = await listVaultFiles(apiKey);
3685
+ if (data.files.length === 0) {
3686
+ console.log(dim(' Vault is empty. Use "Upload Files" to add files.'));
3687
+ console.log();
3688
+ return;
3497
3689
  }
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")}`);
3690
+ console.log();
3691
+ for (const file of data.files) {
3692
+ const size = dim(`(${formatSize3(file.sizeBytes)})`);
3693
+ console.log(` ${file.filename} ${size}`);
3694
+ if (file.summary) console.log(dim(` ${file.summary}`));
3505
3695
  }
3506
- let result = parts.join("\n\n");
3507
- if (result.length > 4e5) {
3508
- result = result.slice(0, 4e5);
3696
+ console.log();
3697
+ console.log(dim(` ${data.usage.fileCount} file(s), ${formatSize3(data.usage.totalBytes)} / ${formatSize3(data.usage.maxBytes)} used`));
3698
+ console.log();
3699
+ const action = await select2({
3700
+ message: "Actions:",
3701
+ choices: [
3702
+ { name: "Delete a file", value: "delete" },
3703
+ { name: "Back", value: "back" }
3704
+ ]
3705
+ });
3706
+ if (action === "back") return;
3707
+ if (action === "delete") {
3708
+ const fileChoices = data.files.map((f) => ({
3709
+ name: `${f.filename} ${dim(`(${formatSize3(f.sizeBytes)})`)}`,
3710
+ value: f.filename
3711
+ }));
3712
+ const filename = await select2({
3713
+ message: "Which file to delete?",
3714
+ choices: fileChoices
3715
+ });
3716
+ const confirmed = await confirm8({
3717
+ message: `Delete "${filename}"? This cannot be undone.`,
3718
+ default: false
3719
+ });
3720
+ if (confirmed) {
3721
+ try {
3722
+ await deleteVaultFile(apiKey, filename);
3723
+ console.log(success(` Deleted ${filename}`));
3724
+ console.log();
3725
+ } catch (err) {
3726
+ console.log(chalk13.red(` ${err.message}`));
3727
+ console.log();
3728
+ }
3729
+ }
3509
3730
  }
3510
- return result;
3511
3731
  }
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."));
3732
+ async function handleVaultUpload(apiKey) {
3733
+ const treePrompt2 = (await Promise.resolve().then(() => (init_tree_prompt(), tree_prompt_exports))).default;
3734
+ const files = await treePrompt2({
3735
+ message: "Select files to upload:",
3736
+ mode: "files"
3737
+ });
3738
+ if (files.length === 0) {
3739
+ console.log(dim(" No files selected."));
3516
3740
  console.log();
3517
3741
  return;
3518
3742
  }
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
3743
  console.log();
3530
- if (projCount === 0 && cfgCount === 0) {
3531
- console.log(chalk11.yellow(" No projects or config files found to resync from."));
3532
- console.log();
3533
- return;
3744
+ let uploaded = 0;
3745
+ for (const filePath of files) {
3746
+ const filename = path15.basename(filePath);
3747
+ const ext = filename.includes(".") ? filename.split(".").pop()?.toLowerCase() || "" : "";
3748
+ const isDocument = DOCUMENT_EXTENSIONS2.has(ext);
3749
+ let content;
3750
+ let encoding;
3751
+ if (isDocument) {
3752
+ content = fs14.readFileSync(filePath).toString("base64");
3753
+ encoding = "base64";
3754
+ } else {
3755
+ content = fs14.readFileSync(filePath, "utf-8");
3756
+ }
3757
+ const spinner = new Spinner();
3758
+ spinner.start(`Uploading ${filename}...`);
3759
+ try {
3760
+ const result = await uploadVaultFile(apiKey, filename, content, encoding);
3761
+ spinner.stop();
3762
+ console.log(success(` Uploaded ${result.filename}`) + dim(` (${formatSize3(result.sizeBytes)})`));
3763
+ if (result.summary) console.log(dim(` ${result.summary}`));
3764
+ uploaded++;
3765
+ } catch (err) {
3766
+ spinner.stop();
3767
+ console.log(chalk13.red(` ${err.message}`));
3768
+ }
3534
3769
  }
3535
- const content = formatScanContent(brainInput);
3536
- const spinner = new Spinner();
3537
- spinner.start("Resyncing brain...");
3538
- try {
3539
- const result = await resyncBrain(validation.serverUrl, apiKey, content);
3540
- spinner.stop();
3541
- console.log();
3542
- console.log(success(" \u2713 Brain resynced."));
3543
- console.log(dim(` ${result.summary}`));
3544
- console.log();
3545
- } catch (err) {
3546
- spinner.stop();
3547
- const msg = err.message;
3548
- console.log(chalk11.red(` \u2717 ${msg}`));
3770
+ if (uploaded > 0) {
3549
3771
  console.log();
3550
- throw new CliError(msg);
3772
+ console.log(success(` ${uploaded} file${uploaded === 1 ? "" : "s"} uploaded to vault.`));
3551
3773
  }
3774
+ console.log();
3552
3775
  }
3553
3776
  async function handleViewBrain(apiKey) {
3554
3777
  console.log(dim(" Loading brain..."));
@@ -3578,7 +3801,7 @@ async function handleViewBrain(apiKey) {
3578
3801
  if (hasUnpublishedChanges) {
3579
3802
  console.log(warning(" You have unpublished changes."));
3580
3803
  console.log();
3581
- const wantPublish = await confirm7({
3804
+ const wantPublish = await confirm8({
3582
3805
  message: "Publish now?",
3583
3806
  default: true
3584
3807
  });
@@ -3592,11 +3815,11 @@ async function handleViewBrain(apiKey) {
3592
3815
  console.log();
3593
3816
  const msg = err.message;
3594
3817
  if (msg === "REQUIRES_SUBSCRIPTION") {
3595
- console.log(chalk11.yellow(" Deploy requires an active subscription ($10/mo)."));
3818
+ console.log(chalk13.yellow(" Deploy requires an active subscription ($10/mo)."));
3596
3819
  console.log(` Subscribe at: ${brand("https://piut.com/dashboard/billing")}`);
3597
3820
  console.log(dim(" 14-day free trial included."));
3598
3821
  } else {
3599
- console.log(chalk11.red(` \u2717 ${msg}`));
3822
+ console.log(chalk13.red(` \u2717 ${msg}`));
3600
3823
  }
3601
3824
  console.log();
3602
3825
  }
@@ -3605,7 +3828,7 @@ async function handleViewBrain(apiKey) {
3605
3828
  }
3606
3829
 
3607
3830
  // src/cli.ts
3608
- var VERSION = "3.6.0";
3831
+ var VERSION = "3.8.0";
3609
3832
  function withExit(fn) {
3610
3833
  return async (...args2) => {
3611
3834
  try {
@@ -3621,7 +3844,7 @@ program.name("piut").description("Build your AI brain instantly. Deploy it as an
3621
3844
  if (actionCommand.name() === "update") return;
3622
3845
  return checkForUpdate(VERSION);
3623
3846
  }).action(interactiveMenu);
3624
- program.command("build").description("Build or rebuild your brain from your files").option("-k, --key <key>", "API key").option("--folders <paths>", "Comma-separated folder paths to scan").option("-y, --yes", "Auto-publish after build").option("--no-publish", "Skip publish prompt after build").action(withExit(buildCommand));
3847
+ program.command("build").description("Build or rebuild your brain from your AI config files").option("-k, --key <key>", "API key").option("-y, --yes", "Auto-publish after build").option("--no-publish", "Skip publish prompt after build").action(withExit(buildCommand));
3625
3848
  program.command("deploy").description("Publish your MCP server (requires paid account)").option("-k, --key <key>", "API key").action(withExit(deployCommand));
3626
3849
  program.command("connect").description("Add brain references to project config files").option("-k, --key <key>", "API key").option("-y, --yes", "Skip interactive prompts").option("--folders <paths>", "Comma-separated folder paths to scan").action(withExit(connectCommand));
3627
3850
  program.command("disconnect").description("Remove brain references from project config files").option("-y, --yes", "Skip interactive prompts").option("--folders <paths>", "Comma-separated folder paths to scan").action(withExit(disconnectCommand));
@@ -3631,6 +3854,11 @@ program.command("remove").description("Remove all p\u0131ut configurations").act
3631
3854
  program.command("login").description("Authenticate with p\u0131ut (email, browser, or API key)").action(withExit(loginCommand));
3632
3855
  program.command("logout").description("Remove saved API key").action(logoutCommand);
3633
3856
  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));
3857
+ var vault = program.command("vault").description("Manage your file vault (upload, list, read, delete)");
3858
+ vault.command("list").description("List all files in your vault").option("-k, --key <key>", "API key").action(withExit(vaultListCommand));
3859
+ vault.command("upload <file>").description("Upload a file to your vault").option("-k, --key <key>", "API key").action(withExit(vaultUploadCommand));
3860
+ 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));
3861
+ 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
3862
  program.command("update").description("Check for and install CLI updates").action(() => updateCommand(VERSION));
3635
3863
  var args = process.argv.slice(2);
3636
3864
  if (args.includes("--version") || args.includes("-V")) {