@piut/cli 3.3.0 → 3.5.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 +709 -146
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -11,6 +11,8 @@ import { password, confirm, checkbox } from "@inquirer/prompts";
11
11
  import chalk2 from "chalk";
12
12
 
13
13
  // src/lib/api.ts
14
+ import os from "os";
15
+ import crypto from "crypto";
14
16
  var API_BASE = process.env.PIUT_API_BASE || "https://piut.com";
15
17
  async function validateKey(key) {
16
18
  const res = await fetch(`${API_BASE}/api/cli/validate`, {
@@ -73,6 +75,34 @@ async function* buildBrainStreaming(key, input2) {
73
75
  }
74
76
  }
75
77
  }
78
+ async function verifyMcpEndpoint(serverUrl, key) {
79
+ const start = Date.now();
80
+ try {
81
+ const res = await fetch(serverUrl, {
82
+ method: "POST",
83
+ headers: {
84
+ Authorization: `Bearer ${key}`,
85
+ "Content-Type": "application/json",
86
+ "User-Agent": "piut-cli (verify)"
87
+ },
88
+ body: JSON.stringify({
89
+ jsonrpc: "2.0",
90
+ id: 1,
91
+ method: "tools/list"
92
+ })
93
+ });
94
+ const latencyMs = Date.now() - start;
95
+ if (!res.ok) {
96
+ return { ok: false, tools: [], latencyMs, error: `HTTP ${res.status}` };
97
+ }
98
+ const data = await res.json();
99
+ const result = data?.result;
100
+ const tools = Array.isArray(result?.tools) ? result.tools.map((t) => t.name).filter(Boolean) : [];
101
+ return { ok: true, tools, latencyMs };
102
+ } catch (err) {
103
+ return { ok: false, tools: [], latencyMs: Date.now() - start, error: err.message };
104
+ }
105
+ }
76
106
  async function pingMcp(serverUrl, key, toolName) {
77
107
  try {
78
108
  const res = await fetch(serverUrl, {
@@ -111,6 +141,49 @@ async function publishServer(key) {
111
141
  }
112
142
  return res.json();
113
143
  }
144
+ async function getBrain(key) {
145
+ const res = await fetch(`${API_BASE}/api/cli/brain`, {
146
+ headers: { Authorization: `Bearer ${key}` }
147
+ });
148
+ if (!res.ok) {
149
+ const body = await res.json().catch(() => ({ error: "Unknown error" }));
150
+ throw new Error(body.error || `Failed to fetch brain (HTTP ${res.status})`);
151
+ }
152
+ return res.json();
153
+ }
154
+ function getMachineId() {
155
+ const hostname = os.hostname();
156
+ return crypto.createHash("sha256").update(hostname).digest("hex").slice(0, 16);
157
+ }
158
+ async function registerProject(key, project) {
159
+ const res = await fetch(`${API_BASE}/api/cli/projects`, {
160
+ method: "POST",
161
+ headers: {
162
+ Authorization: `Bearer ${key}`,
163
+ "Content-Type": "application/json"
164
+ },
165
+ body: JSON.stringify(project)
166
+ });
167
+ if (!res.ok) {
168
+ const body = await res.json().catch(() => ({ error: "Unknown error" }));
169
+ throw new Error(body.error || `Register project failed (HTTP ${res.status})`);
170
+ }
171
+ return res.json();
172
+ }
173
+ async function unregisterProject(key, projectPath, machineId) {
174
+ const res = await fetch(`${API_BASE}/api/cli/projects`, {
175
+ method: "DELETE",
176
+ headers: {
177
+ Authorization: `Bearer ${key}`,
178
+ "Content-Type": "application/json"
179
+ },
180
+ body: JSON.stringify({ projectPath, machineId })
181
+ });
182
+ if (!res.ok) {
183
+ const body = await res.json().catch(() => ({ error: "Unknown error" }));
184
+ throw new Error(body.error || `Unregister project failed (HTTP ${res.status})`);
185
+ }
186
+ }
114
187
  async function unpublishServer(key) {
115
188
  const res = await fetch(`${API_BASE}/api/mcp/publish`, {
116
189
  method: "POST",
@@ -128,12 +201,12 @@ async function unpublishServer(key) {
128
201
  }
129
202
 
130
203
  // src/lib/tools.ts
131
- import os from "os";
204
+ import os2 from "os";
132
205
  import path from "path";
133
206
  var MCP_URL = (slug) => `https://piut.com/api/mcp/${slug}`;
134
207
  var AUTH_HEADER = (key) => ({ Authorization: `Bearer ${key}` });
135
208
  function appData() {
136
- return process.env.APPDATA || path.join(os.homedir(), "AppData", "Roaming");
209
+ return process.env.APPDATA || path.join(os2.homedir(), "AppData", "Roaming");
137
210
  }
138
211
  var TOOLS = [
139
212
  {
@@ -257,10 +330,10 @@ var TOOLS = [
257
330
  ];
258
331
 
259
332
  // src/lib/paths.ts
260
- import os2 from "os";
333
+ import os3 from "os";
261
334
  import path2 from "path";
262
335
  function expandPath(p) {
263
- return p.replace(/^~/, os2.homedir());
336
+ return p.replace(/^~/, os3.homedir());
264
337
  }
265
338
  function resolveConfigPaths(configPaths) {
266
339
  const resolved = [];
@@ -315,6 +388,36 @@ function mergeConfig(filePath, configKey, serverConfig) {
315
388
  existing[configKey] = servers;
316
389
  writeConfig(filePath, existing);
317
390
  }
391
+ function getPiutConfig(filePath, configKey) {
392
+ const config = readConfig(filePath);
393
+ if (!config) return null;
394
+ const servers = config[configKey];
395
+ const piut = servers?.["piut-context"];
396
+ return piut ?? null;
397
+ }
398
+ function extractKeyFromConfig(piutConfig) {
399
+ const headers = piutConfig.headers;
400
+ if (headers?.Authorization) {
401
+ const match = headers.Authorization.match(/Bearer\s+(pb_\S+)/);
402
+ if (match) return match[1];
403
+ }
404
+ const settings = piutConfig.settings;
405
+ if (settings) {
406
+ const settingsHeaders = settings.headers;
407
+ if (settingsHeaders?.Authorization) {
408
+ const match = settingsHeaders.Authorization.match(/Bearer\s+(pb_\S+)/);
409
+ if (match) return match[1];
410
+ }
411
+ }
412
+ const args2 = piutConfig.args;
413
+ if (Array.isArray(args2)) {
414
+ for (const arg of args2) {
415
+ const match = arg.match(/Bearer\s+(pb_\S+)/);
416
+ if (match) return match[1];
417
+ }
418
+ }
419
+ return null;
420
+ }
318
421
  function removeFromConfig(filePath, configKey) {
319
422
  const config = readConfig(filePath);
320
423
  if (!config) return false;
@@ -331,21 +434,46 @@ function removeFromConfig(filePath, configKey) {
331
434
  // src/lib/skill.ts
332
435
  import fs2 from "fs";
333
436
  import path4 from "path";
334
- var SKILL_SNIPPET = `## p\u0131ut Context
437
+ var SKILL_SNIPPET = `## p\u0131ut Context (MCP Server: piut-context)
335
438
 
336
- This project uses p\u0131ut for persistent personal context.
337
- Skill reference: https://raw.githubusercontent.com/M-Flat-Inc/piut/main/skill.md
439
+ This project uses p\u0131ut for persistent personal context via MCP (Model Context Protocol).
440
+ p\u0131ut provides MCP tools \u2014 do NOT read local .piut/ files directly. Use the MCP tools.
338
441
 
339
- Always call \`get_context\` at the start of a conversation to understand the user.
340
- Read the \`soul\` section first \u2014 it contains behavioral instructions for how to interact.
341
- Use \`update_brain\` for substantial new information, \`append_brain\` for quick notes.`;
342
- var PROJECT_SKILL_SNIPPET = `## p\u0131ut Context
343
- This project uses p\u0131ut for persistent personal context.
344
- Full skill reference: .piut/skill.md
442
+ ### Available MCP Tools
443
+ - \`get_context\` \u2014 Fetch all 5 brain sections. CALL THIS FIRST in every conversation.
444
+ - \`get_section\` \u2014 Fetch a single section (about, soul, areas, projects, memory)
445
+ - \`search_brain\` \u2014 Search across all sections
446
+ - \`append_brain\` \u2014 Append text to a section (no AI processing)
447
+ - \`update_brain\` \u2014 AI-powered integration of new info into brain
448
+ - \`prompt_brain\` \u2014 Execute natural language commands against context
449
+
450
+ ### Instructions
451
+ 1. Call \`get_context\` at conversation start to load the user's brain
452
+ 2. Read the \`soul\` section first \u2014 it contains behavioral instructions
453
+ 3. Use \`update_brain\` for substantial new info, \`append_brain\` for quick notes
454
+ 4. Never read .piut/config.json directly \u2014 always use the MCP tools
455
+
456
+ Skill reference: https://raw.githubusercontent.com/M-Flat-Inc/piut/main/skill.md`;
457
+ var PROJECT_SKILL_SNIPPET = `## p\u0131ut Context (MCP Server: piut-context)
458
+
459
+ This project uses p\u0131ut for persistent personal context via MCP (Model Context Protocol).
460
+ p\u0131ut provides MCP tools \u2014 do NOT read local .piut/ files directly. Use the MCP tools.
345
461
 
346
- Always call \`get_context\` at the start of every conversation.
347
- Read the \`soul\` section first \u2014 it contains behavioral instructions.
348
- Use \`update_brain\` for substantial new info, \`append_brain\` for quick notes.`;
462
+ ### Available MCP Tools
463
+ - \`get_context\` \u2014 Fetch all 5 brain sections. CALL THIS FIRST in every conversation.
464
+ - \`get_section\` \u2014 Fetch a single section (about, soul, areas, projects, memory)
465
+ - \`search_brain\` \u2014 Search across all sections
466
+ - \`append_brain\` \u2014 Append text to a section (no AI processing)
467
+ - \`update_brain\` \u2014 AI-powered integration of new info into brain
468
+ - \`prompt_brain\` \u2014 Execute natural language commands against context
469
+
470
+ ### Instructions
471
+ 1. Call \`get_context\` at conversation start to load the user's brain
472
+ 2. Read the \`soul\` section first \u2014 it contains behavioral instructions
473
+ 3. Use \`update_brain\` for substantial new info, \`append_brain\` for quick notes
474
+ 4. Never read .piut/config.json directly \u2014 always use the MCP tools
475
+
476
+ Full skill reference: .piut/skill.md`;
349
477
  var SEPARATOR = "\n\n---\n\n";
350
478
  function placeSkillFile(filePath) {
351
479
  const absPath = path4.isAbsolute(filePath) ? filePath : path4.resolve(process.cwd(), filePath);
@@ -485,19 +613,14 @@ var Spinner = class {
485
613
  interval = null;
486
614
  startTime = Date.now();
487
615
  message = "";
488
- tokens = 0;
489
616
  sections = [];
490
617
  start(message) {
491
618
  this.message = message;
492
619
  this.startTime = Date.now();
493
- this.tokens = 0;
494
620
  this.sections = [];
495
621
  this.render();
496
622
  this.interval = setInterval(() => this.render(), 80);
497
623
  }
498
- updateTokens(count) {
499
- this.tokens = count;
500
- }
501
624
  addSection(name) {
502
625
  this.clearLine();
503
626
  const elapsed = this.elapsed();
@@ -521,9 +644,8 @@ var Spinner = class {
521
644
  this.frame = (this.frame + 1) % SPINNER_FRAMES.length;
522
645
  const spinner = brand(SPINNER_FRAMES[this.frame]);
523
646
  const elapsed = dim(this.elapsed());
524
- const tokenStr = this.tokens > 0 ? dim(` ${this.tokens.toLocaleString()} tokens`) : "";
525
647
  this.clearLine();
526
- process.stdout.write(` ${spinner} ${this.message} ${elapsed}${tokenStr}`);
648
+ process.stdout.write(` ${spinner} ${this.message} ${elapsed}`);
527
649
  }
528
650
  elapsed() {
529
651
  const ms = Date.now() - this.startTime;
@@ -591,11 +713,23 @@ async function setupCommand(options) {
591
713
  const exists = fs4.existsSync(configPath);
592
714
  const parentExists = fs4.existsSync(path6.dirname(configPath));
593
715
  if (exists || parentExists) {
716
+ const configured2 = exists && isPiutConfigured(configPath, tool.configKey);
717
+ let staleKey = false;
718
+ if (configured2) {
719
+ const piutConfig = getPiutConfig(configPath, tool.configKey);
720
+ if (piutConfig) {
721
+ const existingKey = extractKeyFromConfig(piutConfig);
722
+ if (existingKey && existingKey !== apiKey) {
723
+ staleKey = true;
724
+ }
725
+ }
726
+ }
594
727
  detected.push({
595
728
  tool,
596
729
  configPath,
597
730
  exists,
598
- alreadyConfigured: exists && isPiutConfigured(configPath, tool.configKey)
731
+ alreadyConfigured: configured2 && !staleKey,
732
+ staleKey
599
733
  });
600
734
  break;
601
735
  }
@@ -610,7 +744,7 @@ async function setupCommand(options) {
610
744
  }
611
745
  let selected;
612
746
  if (options.yes) {
613
- selected = detected.filter((d) => !d.alreadyConfigured);
747
+ selected = detected.filter((d) => !d.alreadyConfigured || d.staleKey);
614
748
  if (selected.length === 0) {
615
749
  console.log(dim(" All detected tools are already configured."));
616
750
  console.log();
@@ -618,9 +752,9 @@ async function setupCommand(options) {
618
752
  }
619
753
  } else {
620
754
  const choices = detected.map((d) => ({
621
- name: d.alreadyConfigured ? `${d.tool.name} ${dim("(already configured)")}` : d.tool.name,
755
+ name: d.staleKey ? `${d.tool.name} ${warning("(stale key \u2014 will update)")}` : d.alreadyConfigured ? `${d.tool.name} ${dim("(already configured)")}` : d.tool.name,
622
756
  value: d,
623
- checked: !d.alreadyConfigured
757
+ checked: !d.alreadyConfigured || d.staleKey
624
758
  }));
625
759
  selected = await checkbox({
626
760
  message: "Select tools to configure:",
@@ -636,7 +770,7 @@ async function setupCommand(options) {
636
770
  const skipped = [];
637
771
  for (const det of selected) {
638
772
  const { tool, configPath, alreadyConfigured } = det;
639
- if (alreadyConfigured) {
773
+ if (alreadyConfigured && !det.staleKey) {
640
774
  if (options.yes) {
641
775
  skipped.push(tool.name);
642
776
  continue;
@@ -651,14 +785,37 @@ async function setupCommand(options) {
651
785
  }
652
786
  }
653
787
  if (tool.id === "claude-code" && tool.quickCommand && isCommandAvailable("claude")) {
788
+ let quickSuccess = false;
654
789
  try {
655
790
  execSync(tool.quickCommand(slug, apiKey), { stdio: "pipe" });
656
- configured.push(tool.name);
657
- toolLine(tool.name, success("configured via CLI"), "\u2714");
658
- continue;
659
- } catch {
791
+ const claudeJson = expandPath("~/.claude.json");
792
+ const written = getPiutConfig(claudeJson, tool.configKey);
793
+ if (written) {
794
+ quickSuccess = true;
795
+ configured.push(tool.name);
796
+ toolLine(tool.name, success("configured via CLI"), "\u2714");
797
+ continue;
798
+ }
799
+ try {
800
+ execSync(tool.quickCommand(slug, apiKey) + " --scope user", { stdio: "pipe" });
801
+ const retryCheck = getPiutConfig(claudeJson, tool.configKey);
802
+ if (retryCheck) {
803
+ quickSuccess = true;
804
+ configured.push(tool.name);
805
+ toolLine(tool.name, success("configured via CLI"), "\u2714");
806
+ continue;
807
+ }
808
+ } catch {
809
+ }
810
+ console.log(dim(" Quick command succeeded but config not found, using config file..."));
811
+ } catch (err) {
812
+ const stderr = err?.stderr?.toString().trim();
813
+ if (stderr) {
814
+ console.log(dim(` Claude CLI: ${stderr}`));
815
+ }
660
816
  console.log(dim(" Claude CLI command failed, using config file..."));
661
817
  }
818
+ if (quickSuccess) continue;
662
819
  }
663
820
  const serverConfig = tool.generateConfig(slug, apiKey);
664
821
  mergeConfig(configPath, tool.configKey, serverConfig);
@@ -701,7 +858,22 @@ async function setupCommand(options) {
701
858
  if (configured.length > 0) {
702
859
  const { serverUrl } = validationResult;
703
860
  console.log();
704
- console.log(dim(" Registering connections..."));
861
+ console.log(dim(" Verifying..."));
862
+ for (const det of selected) {
863
+ if (!configured.includes(det.tool.name)) continue;
864
+ const piutConfig = getPiutConfig(det.configPath, det.tool.configKey);
865
+ if (piutConfig) {
866
+ toolLine(det.tool.name, success("config verified"), "\u2714");
867
+ } else {
868
+ toolLine(det.tool.name, warning("config not found \u2014 run piut doctor"), "\u2717");
869
+ }
870
+ }
871
+ const mcpResult = await verifyMcpEndpoint(serverUrl, apiKey);
872
+ if (mcpResult.ok) {
873
+ toolLine("MCP server", success(`${mcpResult.tools.length} tools available`) + dim(` (${mcpResult.latencyMs}ms)`), "\u2714");
874
+ } else {
875
+ toolLine("MCP server", warning(mcpResult.error || "unreachable") + dim(" \u2014 run piut doctor"), "\u2717");
876
+ }
705
877
  await Promise.all(
706
878
  configured.map((toolName) => pingMcp(serverUrl, apiKey, toolName))
707
879
  );
@@ -717,6 +889,7 @@ async function setupCommand(options) {
717
889
  console.log();
718
890
  console.log(dim(" Restart your AI tools for changes to take effect."));
719
891
  console.log(dim(' Verify: ask any AI "What do you know about me from my context?"'));
892
+ console.log(dim(" Diagnose issues: ") + chalk2.cyan("piut doctor"));
720
893
  console.log();
721
894
  }
722
895
  function isCommandAvailable(cmd) {
@@ -729,14 +902,15 @@ function isCommandAvailable(cmd) {
729
902
  }
730
903
 
731
904
  // src/commands/status.ts
732
- import fs6 from "fs";
733
- import path8 from "path";
905
+ import fs7 from "fs";
906
+ import path9 from "path";
907
+ import chalk3 from "chalk";
734
908
 
735
909
  // src/lib/brain-scanner.ts
736
910
  import fs5 from "fs";
737
911
  import path7 from "path";
738
- import os3 from "os";
739
- var home = os3.homedir();
912
+ import os4 from "os";
913
+ var home = os4.homedir();
740
914
  var SKIP_DIRS = /* @__PURE__ */ new Set([
741
915
  "node_modules",
742
916
  ".git",
@@ -1039,6 +1213,34 @@ function scanForProjects(folders) {
1039
1213
  return detectProjects(scanDirs);
1040
1214
  }
1041
1215
 
1216
+ // src/lib/store.ts
1217
+ import fs6 from "fs";
1218
+ import path8 from "path";
1219
+ import os5 from "os";
1220
+ var CONFIG_DIR = path8.join(os5.homedir(), ".piut");
1221
+ var CONFIG_FILE2 = path8.join(CONFIG_DIR, "config.json");
1222
+ function readStore() {
1223
+ try {
1224
+ const raw = fs6.readFileSync(CONFIG_FILE2, "utf-8");
1225
+ return JSON.parse(raw);
1226
+ } catch {
1227
+ return {};
1228
+ }
1229
+ }
1230
+ function updateStore(updates) {
1231
+ const config = readStore();
1232
+ const updated = { ...config, ...updates };
1233
+ fs6.mkdirSync(CONFIG_DIR, { recursive: true });
1234
+ fs6.writeFileSync(CONFIG_FILE2, JSON.stringify(updated, null, 2) + "\n", "utf-8");
1235
+ return updated;
1236
+ }
1237
+ function clearStore() {
1238
+ try {
1239
+ fs6.unlinkSync(CONFIG_FILE2);
1240
+ } catch {
1241
+ }
1242
+ }
1243
+
1042
1244
  // src/commands/status.ts
1043
1245
  var PIUT_FILES = [
1044
1246
  "CLAUDE.md",
@@ -1050,21 +1252,25 @@ var PIUT_FILES = [
1050
1252
  ];
1051
1253
  function hasPiutReference(filePath) {
1052
1254
  try {
1053
- const content = fs6.readFileSync(filePath, "utf-8");
1255
+ const content = fs7.readFileSync(filePath, "utf-8");
1054
1256
  return content.includes("p\u0131ut Context") || content.includes("piut Context");
1055
1257
  } catch {
1056
1258
  return false;
1057
1259
  }
1058
1260
  }
1059
- function statusCommand() {
1261
+ async function statusCommand(options = {}) {
1060
1262
  banner();
1263
+ if (options.verify) {
1264
+ await verifyStatus();
1265
+ return;
1266
+ }
1061
1267
  console.log(" AI tool configuration:");
1062
1268
  console.log();
1063
1269
  let foundAny = false;
1064
1270
  for (const tool of TOOLS) {
1065
1271
  const paths = resolveConfigPaths(tool.configPaths);
1066
1272
  for (const configPath of paths) {
1067
- if (!fs6.existsSync(configPath)) continue;
1273
+ if (!fs7.existsSync(configPath)) continue;
1068
1274
  foundAny = true;
1069
1275
  const configured = isPiutConfigured(configPath, tool.configKey);
1070
1276
  if (configured) {
@@ -1087,8 +1293,8 @@ function statusCommand() {
1087
1293
  for (const project of projects) {
1088
1294
  const connectedFiles = [];
1089
1295
  for (const file of PIUT_FILES) {
1090
- const absPath = path8.join(project.path, file);
1091
- if (fs6.existsSync(absPath) && hasPiutReference(absPath)) {
1296
+ const absPath = path9.join(project.path, file);
1297
+ if (fs7.existsSync(absPath) && hasPiutReference(absPath)) {
1092
1298
  connectedFiles.push(file);
1093
1299
  }
1094
1300
  }
@@ -1106,9 +1312,78 @@ function statusCommand() {
1106
1312
  }
1107
1313
  console.log();
1108
1314
  }
1315
+ async function verifyStatus() {
1316
+ const store = readStore();
1317
+ let issues = 0;
1318
+ console.log(" API Key");
1319
+ if (!store.apiKey) {
1320
+ console.log(warning(" \u2717 No saved API key"));
1321
+ console.log(dim(" Run ") + brand("piut setup") + dim(" to configure."));
1322
+ issues++;
1323
+ console.log();
1324
+ return;
1325
+ }
1326
+ let slug;
1327
+ let serverUrl;
1328
+ try {
1329
+ const info = await validateKey(store.apiKey);
1330
+ slug = info.slug;
1331
+ serverUrl = info.serverUrl;
1332
+ const masked = store.apiKey.slice(0, 6) + "...";
1333
+ console.log(success(` \u2714 Key valid: ${info.displayName} (${info.slug})`) + dim(` ${masked}`));
1334
+ } catch (err) {
1335
+ console.log(warning(` \u2717 Key invalid: ${err.message}`));
1336
+ issues++;
1337
+ }
1338
+ console.log();
1339
+ console.log(" Tool Configurations");
1340
+ for (const tool of TOOLS) {
1341
+ const paths = resolveConfigPaths(tool.configPaths);
1342
+ for (const configPath of paths) {
1343
+ if (!fs7.existsSync(configPath)) continue;
1344
+ const piutConfig = getPiutConfig(configPath, tool.configKey);
1345
+ if (!piutConfig) {
1346
+ toolLine(tool.name, dim("installed, not connected"), "\u25CB");
1347
+ break;
1348
+ }
1349
+ const configKey = extractKeyFromConfig(piutConfig);
1350
+ if (configKey && configKey === store.apiKey) {
1351
+ toolLine(tool.name, success("key matches"), "\u2714");
1352
+ } else if (configKey) {
1353
+ const masked = configKey.slice(0, 6) + "...";
1354
+ toolLine(tool.name, chalk3.red(`key STALE (${masked})`), "\u2717");
1355
+ issues++;
1356
+ } else {
1357
+ toolLine(tool.name, dim("configured (key not extractable)"), "\u25CB");
1358
+ }
1359
+ break;
1360
+ }
1361
+ }
1362
+ console.log();
1363
+ console.log(" MCP Server");
1364
+ if (serverUrl && store.apiKey) {
1365
+ const result = await verifyMcpEndpoint(serverUrl, store.apiKey);
1366
+ if (result.ok) {
1367
+ console.log(success(` \u2714 ${serverUrl}`) + dim(` ${result.tools.length} tools, ${result.latencyMs}ms`));
1368
+ } else {
1369
+ console.log(warning(` \u2717 ${serverUrl}`) + dim(` ${result.error}`));
1370
+ issues++;
1371
+ }
1372
+ } else if (!serverUrl) {
1373
+ console.log(dim(" Skipped (no server URL)"));
1374
+ }
1375
+ console.log();
1376
+ if (issues > 0) {
1377
+ console.log(warning(` Issues Found: ${issues}`));
1378
+ console.log(dim(" Run ") + brand("piut doctor") + dim(" for detailed diagnostics."));
1379
+ } else {
1380
+ console.log(success(" All checks passed."));
1381
+ }
1382
+ console.log();
1383
+ }
1109
1384
 
1110
1385
  // src/commands/remove.ts
1111
- import fs7 from "fs";
1386
+ import fs8 from "fs";
1112
1387
  import { checkbox as checkbox2, confirm as confirm2 } from "@inquirer/prompts";
1113
1388
  async function removeCommand() {
1114
1389
  banner();
@@ -1116,7 +1391,7 @@ async function removeCommand() {
1116
1391
  for (const tool of TOOLS) {
1117
1392
  const paths = resolveConfigPaths(tool.configPaths);
1118
1393
  for (const configPath of paths) {
1119
- if (fs7.existsSync(configPath) && isPiutConfigured(configPath, tool.configKey)) {
1394
+ if (fs8.existsSync(configPath) && isPiutConfigured(configPath, tool.configKey)) {
1120
1395
  configured.push({ tool, configPath });
1121
1396
  break;
1122
1397
  }
@@ -1159,43 +1434,13 @@ async function removeCommand() {
1159
1434
  }
1160
1435
 
1161
1436
  // src/commands/build.ts
1162
- import { select, checkbox as checkbox3, input } from "@inquirer/prompts";
1163
- import chalk4 from "chalk";
1164
- import os5 from "os";
1437
+ import { select, checkbox as checkbox3, input, confirm as confirm3 } from "@inquirer/prompts";
1438
+ import chalk5 from "chalk";
1439
+ import os6 from "os";
1165
1440
 
1166
1441
  // src/lib/auth.ts
1167
1442
  import { password as password2 } from "@inquirer/prompts";
1168
- import chalk3 from "chalk";
1169
-
1170
- // src/lib/store.ts
1171
- import fs8 from "fs";
1172
- import path9 from "path";
1173
- import os4 from "os";
1174
- var CONFIG_DIR = path9.join(os4.homedir(), ".piut");
1175
- var CONFIG_FILE2 = path9.join(CONFIG_DIR, "config.json");
1176
- function readStore() {
1177
- try {
1178
- const raw = fs8.readFileSync(CONFIG_FILE2, "utf-8");
1179
- return JSON.parse(raw);
1180
- } catch {
1181
- return {};
1182
- }
1183
- }
1184
- function updateStore(updates) {
1185
- const config = readStore();
1186
- const updated = { ...config, ...updates };
1187
- fs8.mkdirSync(CONFIG_DIR, { recursive: true });
1188
- fs8.writeFileSync(CONFIG_FILE2, JSON.stringify(updated, null, 2) + "\n", "utf-8");
1189
- return updated;
1190
- }
1191
- function clearStore() {
1192
- try {
1193
- fs8.unlinkSync(CONFIG_FILE2);
1194
- } catch {
1195
- }
1196
- }
1197
-
1198
- // src/lib/auth.ts
1443
+ import chalk4 from "chalk";
1199
1444
  async function resolveApiKey(keyOption) {
1200
1445
  const config = readStore();
1201
1446
  let apiKey = keyOption || config.apiKey;
@@ -1211,7 +1456,7 @@ async function resolveApiKey(keyOption) {
1211
1456
  try {
1212
1457
  result = await validateKey(apiKey);
1213
1458
  } catch (err) {
1214
- console.log(chalk3.red(` \u2717 ${err.message}`));
1459
+ console.log(chalk4.red(` \u2717 ${err.message}`));
1215
1460
  console.log(dim(" Get a key at https://piut.com/dashboard/keys"));
1216
1461
  throw new CliError(err.message);
1217
1462
  }
@@ -1235,7 +1480,7 @@ async function resolveApiKeyWithResult(keyOption) {
1235
1480
  try {
1236
1481
  result = await validateKey(apiKey);
1237
1482
  } catch (err) {
1238
- console.log(chalk3.red(` \u2717 ${err.message}`));
1483
+ console.log(chalk4.red(` \u2717 ${err.message}`));
1239
1484
  console.log(dim(" Get a key at https://piut.com/dashboard/keys"));
1240
1485
  throw new CliError(err.message);
1241
1486
  }
@@ -1254,7 +1499,7 @@ async function buildCommand(options) {
1254
1499
  scanFolders = options.folders.split(",").map((f) => expandPath(f.trim()));
1255
1500
  }
1256
1501
  const cwd = process.cwd();
1257
- const cwdDisplay = cwd.replace(os5.homedir(), "~");
1502
+ const cwdDisplay = cwd.replace(os6.homedir(), "~");
1258
1503
  if (!scanFolders) {
1259
1504
  console.log(dim(` Current directory: `) + cwdDisplay);
1260
1505
  console.log(dim(` We'll scan for AI config files and projects here.`));
@@ -1272,8 +1517,8 @@ async function buildCommand(options) {
1272
1517
  const defaults = getDefaultScanDirs();
1273
1518
  const CUSTOM_VALUE = "__custom__";
1274
1519
  const choices = [
1275
- ...defaults.map((d) => ({ name: d.replace(os5.homedir(), "~"), value: d })),
1276
- { name: chalk4.dim("Enter a custom path..."), value: CUSTOM_VALUE }
1520
+ ...defaults.map((d) => ({ name: d.replace(os6.homedir(), "~"), value: d })),
1521
+ { name: chalk5.dim("Enter a custom path..."), value: CUSTOM_VALUE }
1277
1522
  ];
1278
1523
  const selected = await checkbox3({
1279
1524
  message: "Which folders should we scan?",
@@ -1293,7 +1538,7 @@ async function buildCommand(options) {
1293
1538
  scanFolders = selected;
1294
1539
  }
1295
1540
  if (scanFolders.length === 0) {
1296
- console.log(chalk4.yellow(" No folders selected."));
1541
+ console.log(chalk5.yellow(" No folders selected."));
1297
1542
  return;
1298
1543
  }
1299
1544
  }
@@ -1322,7 +1567,7 @@ async function buildCommand(options) {
1322
1567
  console.log(success(` Scanned: ${projCount} projects, ${cfgCount} config files, ${dcCount} recent docs`));
1323
1568
  console.log();
1324
1569
  if (projCount === 0 && cfgCount === 0) {
1325
- console.log(chalk4.yellow(" No projects or config files found to build from."));
1570
+ console.log(chalk5.yellow(" No projects or config files found to build from."));
1326
1571
  console.log(dim(" Try running from a directory with your projects, or use --folders."));
1327
1572
  console.log();
1328
1573
  return;
@@ -1337,7 +1582,6 @@ async function buildCommand(options) {
1337
1582
  spinner.updateMessage(String(event.data.message || "Processing..."));
1338
1583
  break;
1339
1584
  case "progress":
1340
- spinner.updateTokens(event.data.tokens);
1341
1585
  break;
1342
1586
  case "section":
1343
1587
  spinner.addSection(String(event.data.name));
@@ -1347,45 +1591,83 @@ async function buildCommand(options) {
1347
1591
  break;
1348
1592
  case "error":
1349
1593
  spinner.stop();
1350
- console.log(chalk4.red(` \u2717 ${event.data.message || "Build failed"}`));
1594
+ console.log(chalk5.red(` \u2717 ${event.data.message || "Build failed"}`));
1351
1595
  throw new CliError(String(event.data.message || "Build failed"));
1352
1596
  }
1353
1597
  }
1354
1598
  spinner.stop();
1355
1599
  if (!sections) {
1356
- console.log(chalk4.red(" \u2717 No response received from server"));
1600
+ console.log(chalk5.red(" \u2717 No response received from server"));
1357
1601
  throw new CliError("No response received from server");
1358
1602
  }
1359
1603
  console.log();
1360
1604
  console.log(success(" Brain built!"));
1361
1605
  console.log();
1362
- const sectionSummary = (content, label) => {
1363
- if (!content || !content.trim()) {
1606
+ const SECTION_LABELS = {
1607
+ about: "About",
1608
+ soul: "Soul",
1609
+ areas: "Areas of Responsibility",
1610
+ projects: "Projects",
1611
+ memory: "Memory"
1612
+ };
1613
+ for (const [key, label] of Object.entries(SECTION_LABELS)) {
1614
+ const content = sections[key] || "";
1615
+ if (!content.trim()) {
1364
1616
  console.log(dim(` ${label} \u2014 (empty)`));
1365
1617
  } else {
1366
- const firstLine = content.split("\n").find((l) => l.trim() && !l.startsWith("#"))?.trim() || "";
1367
- const preview = firstLine.length > 60 ? firstLine.slice(0, 60) + "..." : firstLine;
1368
- console.log(success(` ${label}`) + dim(` \u2014 ${preview}`));
1618
+ console.log(success(` ${label}`));
1619
+ const lines = content.split("\n").filter((l) => l.trim()).slice(0, 5);
1620
+ for (const line of lines) {
1621
+ console.log(dim(` ${line.length > 80 ? line.slice(0, 80) + "..." : line}`));
1622
+ }
1623
+ const totalLines = content.split("\n").filter((l) => l.trim()).length;
1624
+ if (totalLines > 5) {
1625
+ console.log(dim(` ... (${totalLines - 5} more lines)`));
1626
+ }
1369
1627
  }
1370
- };
1371
- sectionSummary(sections.about || "", "About");
1372
- sectionSummary(sections.soul || "", "Soul");
1373
- sectionSummary(sections.areas || "", "Areas of Responsibility");
1374
- sectionSummary(sections.projects || "", "Projects");
1375
- sectionSummary(sections.memory || "", "Memory");
1376
- console.log();
1628
+ console.log();
1629
+ }
1377
1630
  console.log(dim(` Review and edit at ${brand("piut.com/dashboard")}`));
1378
1631
  console.log();
1632
+ const wantPublish = options.publish === false ? false : options.yes ? true : await confirm3({
1633
+ message: "Publish your brain now?",
1634
+ default: true
1635
+ });
1636
+ if (wantPublish) {
1637
+ try {
1638
+ await publishServer(apiKey);
1639
+ console.log();
1640
+ console.log(success(" \u2713 Brain published. MCP server is live."));
1641
+ console.log();
1642
+ } catch (err) {
1643
+ console.log();
1644
+ const msg = err.message;
1645
+ if (msg === "REQUIRES_SUBSCRIPTION") {
1646
+ console.log(chalk5.yellow(" Deploy requires an active subscription ($10/mo)."));
1647
+ console.log(` Subscribe at: ${brand("https://piut.com/dashboard/billing")}`);
1648
+ console.log(dim(" 14-day free trial included."));
1649
+ } else {
1650
+ console.log(chalk5.red(` \u2717 ${msg}`));
1651
+ }
1652
+ console.log();
1653
+ }
1654
+ } else {
1655
+ console.log();
1656
+ console.log(dim(" You can publish anytime with: ") + brand("piut deploy"));
1657
+ console.log();
1658
+ }
1379
1659
  } catch (err) {
1380
1660
  spinner.stop();
1381
1661
  if (err instanceof CliError) throw err;
1382
- console.log(chalk4.red(` \u2717 ${err.message}`));
1383
- throw new CliError(err.message);
1662
+ const msg = err.message || "Unknown error";
1663
+ 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;
1664
+ console.log(chalk5.red(` \u2717 ${hint}`));
1665
+ throw new CliError(hint);
1384
1666
  }
1385
1667
  }
1386
1668
 
1387
1669
  // src/commands/deploy.ts
1388
- import chalk5 from "chalk";
1670
+ import chalk6 from "chalk";
1389
1671
  async function deployCommand(options) {
1390
1672
  banner();
1391
1673
  const { apiKey, slug, serverUrl, status } = await resolveApiKeyWithResult(options.key);
@@ -1409,13 +1691,13 @@ async function deployCommand(options) {
1409
1691
  const msg = err.message;
1410
1692
  if (msg === "REQUIRES_SUBSCRIPTION") {
1411
1693
  console.log();
1412
- console.log(chalk5.yellow(" Deploy requires an active subscription ($10/mo)."));
1694
+ console.log(chalk6.yellow(" Deploy requires an active subscription ($10/mo)."));
1413
1695
  console.log();
1414
1696
  console.log(` Subscribe at: ${brand("https://piut.com/dashboard/billing")}`);
1415
1697
  console.log(dim(" 14-day free trial included."));
1416
1698
  console.log();
1417
1699
  } else {
1418
- console.log(chalk5.red(` \u2717 ${msg}`));
1700
+ console.log(chalk6.red(` \u2717 ${msg}`));
1419
1701
  throw new CliError(msg);
1420
1702
  }
1421
1703
  }
@@ -1463,19 +1745,35 @@ var RULE_FILES = [
1463
1745
  detect: (p) => p.hasZedRules || fs9.existsSync(path10.join(p.path, ".zed"))
1464
1746
  }
1465
1747
  ];
1466
- var DEDICATED_FILE_CONTENT = `## p\u0131ut Context
1467
- This project uses p\u0131ut for persistent personal context.
1468
- Full skill reference: .piut/skill.md
1748
+ var DEDICATED_FILE_CONTENT = `## p\u0131ut Context (MCP Server: piut-context)
1749
+
1750
+ This project uses p\u0131ut for persistent personal context via MCP (Model Context Protocol).
1751
+ p\u0131ut provides MCP tools \u2014 do NOT read local .piut/ files directly. Use the MCP tools.
1469
1752
 
1470
- Always call \`get_context\` at the start of every conversation.
1471
- Read the \`soul\` section first \u2014 it contains behavioral instructions.
1472
- Use \`update_brain\` for substantial new info, \`append_brain\` for quick notes.
1753
+ ### Available MCP Tools
1754
+ - \`get_context\` \u2014 Fetch all 5 brain sections. CALL THIS FIRST in every conversation.
1755
+ - \`get_section\` \u2014 Fetch a single section (about, soul, areas, projects, memory)
1756
+ - \`search_brain\` \u2014 Search across all sections
1757
+ - \`append_brain\` \u2014 Append text to a section (no AI processing)
1758
+ - \`update_brain\` \u2014 AI-powered integration of new info into brain
1759
+ - \`prompt_brain\` \u2014 Execute natural language commands against context
1760
+
1761
+ ### Instructions
1762
+ 1. Call \`get_context\` at conversation start to load the user's brain
1763
+ 2. Read the \`soul\` section first \u2014 it contains behavioral instructions
1764
+ 3. Use \`update_brain\` for substantial new info, \`append_brain\` for quick notes
1765
+ 4. Never read .piut/config.json directly \u2014 always use the MCP tools
1766
+
1767
+ Full skill reference: .piut/skill.md
1473
1768
  `;
1474
1769
  var APPEND_SECTION = `
1475
1770
 
1476
- ## p\u0131ut Context
1477
- Full skill reference: .piut/skill.md
1771
+ ## p\u0131ut Context (MCP Server: piut-context)
1772
+
1773
+ p\u0131ut provides MCP tools for persistent personal context. Do NOT read local .piut/ files.
1774
+ Available tools: \`get_context\`, \`get_section\`, \`search_brain\`, \`append_brain\`, \`update_brain\`, \`prompt_brain\`
1478
1775
  Always call \`get_context\` at the start of every conversation to load personal context.
1776
+ Full skill reference: .piut/skill.md
1479
1777
  `;
1480
1778
  function hasPiutReference2(filePath) {
1481
1779
  try {
@@ -1551,8 +1849,10 @@ async function connectCommand(options) {
1551
1849
  }
1552
1850
  }
1553
1851
  }
1852
+ const projectsWithActions = new Set(actions.map((a) => a.project.path));
1853
+ const alreadyConnectedCount = projects.filter((p) => !projectsWithActions.has(p.path)).length;
1554
1854
  if (actions.length === 0) {
1555
- console.log(dim(" All projects are already connected."));
1855
+ console.log(success(` All ${projects.length} project(s) are already connected.`));
1556
1856
  console.log();
1557
1857
  return;
1558
1858
  }
@@ -1563,7 +1863,10 @@ async function connectCommand(options) {
1563
1863
  byProject.get(key).push(action);
1564
1864
  }
1565
1865
  console.log();
1566
- console.log(` Found ${brand.bold(String(byProject.size))} project(s) to connect:`);
1866
+ if (alreadyConnectedCount > 0) {
1867
+ console.log(dim(` ${alreadyConnectedCount} project(s) already connected.`));
1868
+ }
1869
+ console.log(` Found ${brand.bold(String(byProject.size))} project(s) with new connections available:`);
1567
1870
  console.log();
1568
1871
  const projectChoices = [];
1569
1872
  for (const [projectPath, projectActions] of byProject) {
@@ -1574,7 +1877,8 @@ async function connectCommand(options) {
1574
1877
  }).join(", ");
1575
1878
  projectChoices.push({
1576
1879
  name: `${projectName} ${dim(`(${desc})`)}`,
1577
- value: projectPath
1880
+ value: projectPath,
1881
+ checked: true
1578
1882
  });
1579
1883
  }
1580
1884
  let selectedPaths;
@@ -1623,6 +1927,21 @@ async function connectCommand(options) {
1623
1927
  connected++;
1624
1928
  }
1625
1929
  }
1930
+ const machineId = getMachineId();
1931
+ for (const projectPath of selectedPaths) {
1932
+ const projectActions = byProject.get(projectPath) || [];
1933
+ const projectName = path10.basename(projectPath);
1934
+ const toolsDetected = [...new Set(projectActions.map((a) => a.tool))];
1935
+ const configFilesWritten = projectActions.map((a) => a.filePath);
1936
+ registerProject(apiKey, {
1937
+ projectName,
1938
+ projectPath,
1939
+ machineId,
1940
+ toolsDetected,
1941
+ configFiles: configFilesWritten
1942
+ }).catch(() => {
1943
+ });
1944
+ }
1626
1945
  console.log();
1627
1946
  console.log(success(` Done. ${selectedPaths.length} project(s) connected.`));
1628
1947
  console.log();
@@ -1631,7 +1950,7 @@ async function connectCommand(options) {
1631
1950
  // src/commands/disconnect.ts
1632
1951
  import fs10 from "fs";
1633
1952
  import path11 from "path";
1634
- import { checkbox as checkbox5, confirm as confirm4 } from "@inquirer/prompts";
1953
+ import { checkbox as checkbox5, confirm as confirm5 } from "@inquirer/prompts";
1635
1954
  var DEDICATED_FILES = /* @__PURE__ */ new Set([
1636
1955
  ".cursor/rules/piut.mdc",
1637
1956
  ".windsurf/rules/piut.md",
@@ -1759,7 +2078,7 @@ async function disconnectCommand(options) {
1759
2078
  console.log(dim(" No projects selected."));
1760
2079
  return;
1761
2080
  }
1762
- const proceed = await confirm4({
2081
+ const proceed = await confirm5({
1763
2082
  message: `Disconnect ${selectedPaths.length} project(s)?`,
1764
2083
  default: false
1765
2084
  });
@@ -1803,6 +2122,14 @@ async function disconnectCommand(options) {
1803
2122
  }
1804
2123
  }
1805
2124
  }
2125
+ const store = readStore();
2126
+ if (store.apiKey) {
2127
+ const machineId = getMachineId();
2128
+ for (const projectPath of selectedPaths) {
2129
+ unregisterProject(store.apiKey, projectPath, machineId).catch(() => {
2130
+ });
2131
+ }
2132
+ }
1806
2133
  console.log();
1807
2134
  console.log(success(` Done. ${disconnected} file(s) updated.`));
1808
2135
  console.log();
@@ -1825,12 +2152,12 @@ async function logoutCommand() {
1825
2152
  }
1826
2153
 
1827
2154
  // src/commands/update.ts
1828
- import chalk7 from "chalk";
2155
+ import chalk8 from "chalk";
1829
2156
 
1830
2157
  // src/lib/update-check.ts
1831
2158
  import { execFile } from "child_process";
1832
- import chalk6 from "chalk";
1833
- import { confirm as confirm5 } from "@inquirer/prompts";
2159
+ import chalk7 from "chalk";
2160
+ import { confirm as confirm6 } from "@inquirer/prompts";
1834
2161
  var PACKAGE_NAME = "@piut/cli";
1835
2162
  function isNpx() {
1836
2163
  return process.env.npm_command === "exec" || (process.env._?.includes("npx") ?? false);
@@ -1866,11 +2193,11 @@ async function checkForUpdate(currentVersion) {
1866
2193
  const updateCmd = npx ? `npx ${PACKAGE_NAME}@latest` : `npm install -g ${PACKAGE_NAME}@latest`;
1867
2194
  console.log();
1868
2195
  console.log(brand(" Update available!") + dim(` ${currentVersion} \u2192 ${latest}`));
1869
- console.log(dim(` Run ${chalk6.bold(updateCmd)} to update`));
2196
+ console.log(dim(` Run ${chalk7.bold(updateCmd)} to update`));
1870
2197
  console.log();
1871
2198
  if (npx) return;
1872
2199
  try {
1873
- const shouldUpdate = await confirm5({
2200
+ const shouldUpdate = await confirm6({
1874
2201
  message: `Update to v${latest} now?`,
1875
2202
  default: true
1876
2203
  });
@@ -1878,12 +2205,12 @@ async function checkForUpdate(currentVersion) {
1878
2205
  console.log(dim(" Updating..."));
1879
2206
  const ok = await runUpdate();
1880
2207
  if (ok) {
1881
- console.log(chalk6.green(` \u2713 Updated to v${latest}`));
2208
+ console.log(chalk7.green(` \u2713 Updated to v${latest}`));
1882
2209
  console.log(dim(" Restart the CLI to use the new version."));
1883
2210
  process.exit(0);
1884
2211
  } else {
1885
- console.log(chalk6.yellow(` Could not auto-update. Run manually:`));
1886
- console.log(chalk6.bold(` npm install -g ${PACKAGE_NAME}@latest`));
2212
+ console.log(chalk7.yellow(` Could not auto-update. Run manually:`));
2213
+ console.log(chalk7.bold(` npm install -g ${PACKAGE_NAME}@latest`));
1887
2214
  console.log();
1888
2215
  }
1889
2216
  }
@@ -1898,7 +2225,7 @@ async function updateCommand(currentVersion) {
1898
2225
  console.log(dim(" Checking for updates..."));
1899
2226
  const latest = await getLatestVersion();
1900
2227
  if (!latest) {
1901
- console.log(chalk7.yellow(" Could not reach the npm registry. Check your connection."));
2228
+ console.log(chalk8.yellow(" Could not reach the npm registry. Check your connection."));
1902
2229
  return;
1903
2230
  }
1904
2231
  if (!isNewer(currentVersion, latest)) {
@@ -1910,7 +2237,7 @@ async function updateCommand(currentVersion) {
1910
2237
  if (isNpx()) {
1911
2238
  console.log();
1912
2239
  console.log(dim(" You're running via npx. Use the latest version with:"));
1913
- console.log(chalk7.bold(` npx ${PACKAGE_NAME2}@latest`));
2240
+ console.log(chalk8.bold(` npx ${PACKAGE_NAME2}@latest`));
1914
2241
  console.log();
1915
2242
  return;
1916
2243
  }
@@ -1920,16 +2247,184 @@ async function updateCommand(currentVersion) {
1920
2247
  console.log(success(` \u2713 Updated to v${latest}`));
1921
2248
  console.log(dim(" Restart the CLI to use the new version."));
1922
2249
  } else {
1923
- console.log(chalk7.yellow(" Could not auto-update. Run manually:"));
1924
- console.log(chalk7.bold(` npm install -g ${PACKAGE_NAME2}@latest`));
2250
+ console.log(chalk8.yellow(" Could not auto-update. Run manually:"));
2251
+ console.log(chalk8.bold(` npm install -g ${PACKAGE_NAME2}@latest`));
1925
2252
  }
1926
2253
  }
1927
2254
 
1928
- // src/commands/interactive.ts
1929
- import { select as select2, confirm as confirm6, checkbox as checkbox6, password as password3 } from "@inquirer/prompts";
2255
+ // src/commands/doctor.ts
1930
2256
  import fs11 from "fs";
2257
+ import chalk9 from "chalk";
2258
+ async function doctorCommand(options) {
2259
+ if (!options.json) banner();
2260
+ const result = {
2261
+ key: { valid: false },
2262
+ tools: [],
2263
+ mcp: { ok: false, tools: [], latencyMs: 0 },
2264
+ issues: 0
2265
+ };
2266
+ const store = readStore();
2267
+ const apiKey = options.key || store.apiKey;
2268
+ if (!apiKey) {
2269
+ result.key = { valid: false, error: "No API key found" };
2270
+ result.issues++;
2271
+ if (!options.json) {
2272
+ console.log(" API Key");
2273
+ console.log(error(` \u2717 No API key saved. Run: piut setup --key pb_YOUR_KEY`));
2274
+ console.log();
2275
+ }
2276
+ } else {
2277
+ const prefix = apiKey.slice(0, 7) + "...";
2278
+ try {
2279
+ const validation = await validateKey(apiKey);
2280
+ result.key = {
2281
+ valid: true,
2282
+ slug: validation.slug,
2283
+ displayName: validation.displayName,
2284
+ prefix
2285
+ };
2286
+ if (!options.json) {
2287
+ console.log(" API Key");
2288
+ console.log(success(` \u2714 Key valid: ${validation.displayName} (${validation.slug})`) + dim(` ${prefix}`));
2289
+ console.log();
2290
+ }
2291
+ } catch (err) {
2292
+ result.key = { valid: false, prefix, error: err.message };
2293
+ result.issues++;
2294
+ if (!options.json) {
2295
+ console.log(" API Key");
2296
+ console.log(error(` \u2717 Key invalid: ${err.message}`) + dim(` ${prefix}`));
2297
+ console.log();
2298
+ }
2299
+ }
2300
+ }
2301
+ if (!options.json) {
2302
+ console.log(" Tool Configurations");
2303
+ }
2304
+ let toolsFixed = 0;
2305
+ for (const tool of TOOLS) {
2306
+ const paths = resolveConfigPaths(tool.configPaths);
2307
+ for (const configPath of paths) {
2308
+ if (!fs11.existsSync(configPath)) continue;
2309
+ const piutConfig = getPiutConfig(configPath, tool.configKey);
2310
+ if (!piutConfig) {
2311
+ result.tools.push({
2312
+ name: tool.name,
2313
+ id: tool.id,
2314
+ configPath,
2315
+ found: true,
2316
+ configured: false,
2317
+ keyMatch: "missing"
2318
+ });
2319
+ if (!options.json) {
2320
+ toolLine(tool.name, dim("installed, not configured"), "\u25CB");
2321
+ }
2322
+ break;
2323
+ }
2324
+ const configKey = extractKeyFromConfig(piutConfig);
2325
+ const configPrefix = configKey ? configKey.slice(0, 7) + "..." : "(none)";
2326
+ let keyMatch = "missing";
2327
+ if (!configKey) {
2328
+ keyMatch = "missing";
2329
+ result.issues++;
2330
+ } else if (apiKey && configKey === apiKey) {
2331
+ keyMatch = "match";
2332
+ } else {
2333
+ keyMatch = "stale";
2334
+ result.issues++;
2335
+ }
2336
+ const toolResult = {
2337
+ name: tool.name,
2338
+ id: tool.id,
2339
+ configPath,
2340
+ found: true,
2341
+ configured: true,
2342
+ keyMatch,
2343
+ keyPrefix: configPrefix,
2344
+ fixed: false
2345
+ };
2346
+ if (keyMatch === "stale" && options.fix && apiKey && result.key.valid && result.key.slug) {
2347
+ const serverConfig = tool.generateConfig(result.key.slug, apiKey);
2348
+ mergeConfig(configPath, tool.configKey, serverConfig);
2349
+ toolResult.fixed = true;
2350
+ toolResult.keyMatch = "match";
2351
+ result.issues--;
2352
+ toolsFixed++;
2353
+ }
2354
+ result.tools.push(toolResult);
2355
+ if (!options.json) {
2356
+ if (toolResult.fixed) {
2357
+ toolLine(tool.name, success("fixed") + dim(` \u2192 ${configPath}`), "\u2714");
2358
+ } else if (keyMatch === "match") {
2359
+ toolLine(tool.name, success("key matches") + dim(` ${configPath}`), "\u2714");
2360
+ } else if (keyMatch === "stale") {
2361
+ toolLine(tool.name, warning(`key STALE (${configPrefix})`) + dim(` ${configPath}`), "\u2717");
2362
+ } else {
2363
+ toolLine(tool.name, warning("no key found") + dim(` ${configPath}`), "\u2717");
2364
+ }
2365
+ }
2366
+ break;
2367
+ }
2368
+ }
2369
+ if (result.tools.length === 0 && !options.json) {
2370
+ console.log(dim(" No AI tools detected."));
2371
+ }
2372
+ if (!options.json) console.log();
2373
+ if (apiKey && result.key.valid && result.key.slug) {
2374
+ const serverUrl = `https://piut.com/api/mcp/${result.key.slug}`;
2375
+ const mcpResult = await verifyMcpEndpoint(serverUrl, apiKey);
2376
+ result.mcp = mcpResult;
2377
+ if (!mcpResult.ok) {
2378
+ result.issues++;
2379
+ }
2380
+ if (!options.json) {
2381
+ console.log(" MCP Server");
2382
+ if (mcpResult.ok) {
2383
+ console.log(success(` \u2714 ${serverUrl}`) + dim(` ${mcpResult.tools.length} tools, ${mcpResult.latencyMs}ms`));
2384
+ if (mcpResult.tools.length > 0) {
2385
+ console.log(dim(` ${mcpResult.tools.join(", ")}`));
2386
+ }
2387
+ } else {
2388
+ console.log(error(` \u2717 ${serverUrl}`) + dim(` ${mcpResult.error}`));
2389
+ }
2390
+ console.log();
2391
+ }
2392
+ } else if (!options.json) {
2393
+ console.log(" MCP Server");
2394
+ console.log(dim(" \u2298 Skipped (no valid key)"));
2395
+ console.log();
2396
+ }
2397
+ if (options.json) {
2398
+ console.log(JSON.stringify(result, null, 2));
2399
+ if (result.issues > 0) throw new CliError(`${result.issues} issue(s) found`);
2400
+ return;
2401
+ }
2402
+ if (toolsFixed > 0) {
2403
+ console.log(success(` Fixed ${toolsFixed} stale config(s).`));
2404
+ console.log();
2405
+ }
2406
+ if (result.issues === 0) {
2407
+ console.log(success(" All checks passed."));
2408
+ } else {
2409
+ console.log(warning(` ${result.issues} issue(s) found.`));
2410
+ const staleTools = result.tools.filter((t) => t.keyMatch === "stale" && !t.fixed);
2411
+ if (staleTools.length > 0 && !options.fix) {
2412
+ console.log();
2413
+ console.log(dim(" Fix stale configs: ") + chalk9.cyan("piut doctor --fix"));
2414
+ }
2415
+ if (!result.key.valid) {
2416
+ console.log(dim(" Set a valid key: ") + chalk9.cyan("piut setup --key pb_YOUR_KEY"));
2417
+ }
2418
+ }
2419
+ console.log();
2420
+ if (result.issues > 0) throw new CliError(`${result.issues} issue(s) found`);
2421
+ }
2422
+
2423
+ // src/commands/interactive.ts
2424
+ import { select as select2, confirm as confirm7, checkbox as checkbox6, password as password3 } from "@inquirer/prompts";
2425
+ import fs12 from "fs";
1931
2426
  import path12 from "path";
1932
- import chalk8 from "chalk";
2427
+ import chalk10 from "chalk";
1933
2428
  async function authenticate() {
1934
2429
  const config = readStore();
1935
2430
  let apiKey = config.apiKey;
@@ -1957,7 +2452,7 @@ async function authenticate() {
1957
2452
  try {
1958
2453
  result = await validateKey(apiKey);
1959
2454
  } catch (err) {
1960
- console.log(chalk8.red(` \u2717 ${err.message}`));
2455
+ console.log(chalk10.red(` \u2717 ${err.message}`));
1961
2456
  console.log(dim(" Get a key at https://piut.com/dashboard/keys"));
1962
2457
  process.exit(1);
1963
2458
  }
@@ -1980,7 +2475,7 @@ async function interactiveMenu() {
1980
2475
  console.log(warning(" You haven\u2019t built a brain yet."));
1981
2476
  console.log(dim(" Your brain is how AI tools learn about you \u2014 your projects, preferences, and context."));
1982
2477
  console.log();
1983
- const wantBuild = await confirm6({
2478
+ const wantBuild = await confirm7({
1984
2479
  message: "Build your brain now?",
1985
2480
  default: true
1986
2481
  });
@@ -1996,7 +2491,7 @@ async function interactiveMenu() {
1996
2491
  console.log(warning(" Your brain is built but not deployed yet."));
1997
2492
  console.log(dim(" Deploy it to make your MCP server live so AI tools can read your brain."));
1998
2493
  console.log();
1999
- const wantDeploy = await confirm6({
2494
+ const wantDeploy = await confirm7({
2000
2495
  message: "Deploy your brain now?",
2001
2496
  default: true
2002
2497
  });
@@ -2050,6 +2545,12 @@ async function interactiveMenu() {
2050
2545
  description: "Remove brain references from project configs",
2051
2546
  disabled: !isDeployed && "(deploy brain first)"
2052
2547
  },
2548
+ {
2549
+ name: "View Brain",
2550
+ value: "view-brain",
2551
+ description: "View all 5 brain sections",
2552
+ disabled: !hasBrain && "(build brain first)"
2553
+ },
2053
2554
  {
2054
2555
  name: "Status",
2055
2556
  value: "status",
@@ -2095,6 +2596,9 @@ async function interactiveMenu() {
2095
2596
  case "disconnect-projects":
2096
2597
  await disconnectCommand({});
2097
2598
  break;
2599
+ case "view-brain":
2600
+ await handleViewBrain(apiKey);
2601
+ break;
2098
2602
  case "status":
2099
2603
  statusCommand();
2100
2604
  break;
@@ -2116,7 +2620,7 @@ async function interactiveMenu() {
2116
2620
  } else if (err instanceof CliError) {
2117
2621
  console.log();
2118
2622
  } else {
2119
- console.log(chalk8.red(` Error: ${err.message}`));
2623
+ console.log(chalk10.red(` Error: ${err.message}`));
2120
2624
  console.log();
2121
2625
  }
2122
2626
  }
@@ -2127,7 +2631,7 @@ async function interactiveMenu() {
2127
2631
  }
2128
2632
  }
2129
2633
  async function handleUndeploy(apiKey) {
2130
- const confirmed = await confirm6({
2634
+ const confirmed = await confirm7({
2131
2635
  message: "Undeploy your brain? AI tools will lose access to your MCP server.",
2132
2636
  default: false
2133
2637
  });
@@ -2139,7 +2643,7 @@ async function handleUndeploy(apiKey) {
2139
2643
  console.log(dim(" Run ") + brand("piut deploy") + dim(" to re-deploy anytime."));
2140
2644
  console.log();
2141
2645
  } catch (err) {
2142
- console.log(chalk8.red(` \u2717 ${err.message}`));
2646
+ console.log(chalk10.red(` \u2717 ${err.message}`));
2143
2647
  }
2144
2648
  }
2145
2649
  async function handleConnectTools(apiKey, validation) {
@@ -2149,8 +2653,8 @@ async function handleConnectTools(apiKey, validation) {
2149
2653
  for (const tool of TOOLS) {
2150
2654
  const paths = resolveConfigPaths(tool.configPaths);
2151
2655
  for (const configPath of paths) {
2152
- const exists = fs11.existsSync(configPath);
2153
- const parentExists = fs11.existsSync(path12.dirname(configPath));
2656
+ const exists = fs12.existsSync(configPath);
2657
+ const parentExists = fs12.existsSync(path12.dirname(configPath));
2154
2658
  if (exists || parentExists) {
2155
2659
  if (exists && isPiutConfigured(configPath, tool.configKey)) {
2156
2660
  alreadyConnected.push(tool.name);
@@ -2194,6 +2698,11 @@ async function handleConnectTools(apiKey, validation) {
2194
2698
  mergeConfig(configPath, tool.configKey, serverConfig);
2195
2699
  toolLine(tool.name, success("connected"), "\u2714");
2196
2700
  }
2701
+ if (validation.serverUrl) {
2702
+ await Promise.all(
2703
+ selected.map(({ tool }) => pingMcp(validation.serverUrl, apiKey, tool.name))
2704
+ );
2705
+ }
2197
2706
  console.log();
2198
2707
  console.log(dim(" Restart your AI tools for changes to take effect."));
2199
2708
  console.log();
@@ -2203,7 +2712,7 @@ async function handleDisconnectTools() {
2203
2712
  for (const tool of TOOLS) {
2204
2713
  const paths = resolveConfigPaths(tool.configPaths);
2205
2714
  for (const configPath of paths) {
2206
- if (fs11.existsSync(configPath) && isPiutConfigured(configPath, tool.configKey)) {
2715
+ if (fs12.existsSync(configPath) && isPiutConfigured(configPath, tool.configKey)) {
2207
2716
  configured.push({ tool, configPath });
2208
2717
  break;
2209
2718
  }
@@ -2226,7 +2735,7 @@ async function handleDisconnectTools() {
2226
2735
  console.log(dim(" No tools selected."));
2227
2736
  return;
2228
2737
  }
2229
- const proceed = await confirm6({
2738
+ const proceed = await confirm7({
2230
2739
  message: `Disconnect p\u0131ut from ${selected.length} tool(s)?`,
2231
2740
  default: false
2232
2741
  });
@@ -2244,9 +2753,62 @@ async function handleDisconnectTools() {
2244
2753
  console.log(dim(" Restart your AI tools for changes to take effect."));
2245
2754
  console.log();
2246
2755
  }
2756
+ async function handleViewBrain(apiKey) {
2757
+ console.log(dim(" Loading brain..."));
2758
+ const { sections, hasUnpublishedChanges } = await getBrain(apiKey);
2759
+ const SECTION_LABELS = {
2760
+ about: "About",
2761
+ soul: "Soul",
2762
+ areas: "Areas of Responsibility",
2763
+ projects: "Projects",
2764
+ memory: "Memory"
2765
+ };
2766
+ console.log();
2767
+ for (const [key, label] of Object.entries(SECTION_LABELS)) {
2768
+ const content = sections[key] || "";
2769
+ if (!content.trim()) {
2770
+ console.log(dim(` ${label} \u2014 (empty)`));
2771
+ } else {
2772
+ console.log(success(` ${label}`));
2773
+ for (const line of content.split("\n")) {
2774
+ console.log(` ${line}`);
2775
+ }
2776
+ }
2777
+ console.log();
2778
+ }
2779
+ console.log(dim(` Edit at ${brand("piut.com/dashboard")}`));
2780
+ console.log();
2781
+ if (hasUnpublishedChanges) {
2782
+ console.log(warning(" You have unpublished changes."));
2783
+ console.log();
2784
+ const wantPublish = await confirm7({
2785
+ message: "Publish now?",
2786
+ default: true
2787
+ });
2788
+ if (wantPublish) {
2789
+ try {
2790
+ await publishServer(apiKey);
2791
+ console.log();
2792
+ console.log(success(" \u2713 Brain published."));
2793
+ console.log();
2794
+ } catch (err) {
2795
+ console.log();
2796
+ const msg = err.message;
2797
+ if (msg === "REQUIRES_SUBSCRIPTION") {
2798
+ console.log(chalk10.yellow(" Deploy requires an active subscription ($10/mo)."));
2799
+ console.log(` Subscribe at: ${brand("https://piut.com/dashboard/billing")}`);
2800
+ console.log(dim(" 14-day free trial included."));
2801
+ } else {
2802
+ console.log(chalk10.red(` \u2717 ${msg}`));
2803
+ }
2804
+ console.log();
2805
+ }
2806
+ }
2807
+ }
2808
+ }
2247
2809
 
2248
2810
  // src/cli.ts
2249
- var VERSION = "3.3.0";
2811
+ var VERSION = "3.5.0";
2250
2812
  function withExit(fn) {
2251
2813
  return async (...args2) => {
2252
2814
  try {
@@ -2262,14 +2824,15 @@ program.name("piut").description("Build your AI brain instantly. Deploy it as an
2262
2824
  if (actionCommand.name() === "update") return;
2263
2825
  return checkForUpdate(VERSION);
2264
2826
  }).action(interactiveMenu);
2265
- 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").action(withExit(buildCommand));
2827
+ 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));
2266
2828
  program.command("deploy").description("Publish your MCP server (requires paid account)").option("-k, --key <key>", "API key").action(withExit(deployCommand));
2267
2829
  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));
2268
2830
  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));
2269
2831
  program.command("setup").description("Auto-detect and configure AI tools (MCP config)").option("-k, --key <key>", "API key (prompts interactively if not provided)").option("-t, --tool <id>", "Configure a single tool (claude-code, cursor, windsurf, etc.)").option("-y, --yes", "Skip interactive prompts (auto-select all detected tools)").option("--project", "Prefer project-local config files").option("--skip-skill", "Skip skill.md file placement").action(withExit(setupCommand));
2270
- program.command("status").description("Show brain, deployment, and connected projects").action(statusCommand);
2832
+ program.command("status").description("Show brain, deployment, and connected projects").option("--verify", "Validate API key, check tool configs, and test MCP endpoint").action(withExit(statusCommand));
2271
2833
  program.command("remove").description("Remove all p\u0131ut configurations").action(withExit(removeCommand));
2272
2834
  program.command("logout").description("Remove saved API key").action(logoutCommand);
2835
+ 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));
2273
2836
  program.command("update").description("Check for and install CLI updates").action(() => updateCommand(VERSION));
2274
2837
  var args = process.argv.slice(2);
2275
2838
  if (args.includes("--version") || args.includes("-V")) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@piut/cli",
3
- "version": "3.3.0",
3
+ "version": "3.5.0",
4
4
  "description": "Build your AI brain instantly. Deploy it as an MCP server. Connect it to every project.",
5
5
  "type": "module",
6
6
  "bin": {