@piut/cli 3.3.0 → 3.4.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 +560 -111
  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,39 @@ async function publishServer(key) {
111
141
  }
112
142
  return res.json();
113
143
  }
144
+ function getMachineId() {
145
+ const hostname = os.hostname();
146
+ return crypto.createHash("sha256").update(hostname).digest("hex").slice(0, 16);
147
+ }
148
+ async function registerProject(key, project) {
149
+ const res = await fetch(`${API_BASE}/api/cli/projects`, {
150
+ method: "POST",
151
+ headers: {
152
+ Authorization: `Bearer ${key}`,
153
+ "Content-Type": "application/json"
154
+ },
155
+ body: JSON.stringify(project)
156
+ });
157
+ if (!res.ok) {
158
+ const body = await res.json().catch(() => ({ error: "Unknown error" }));
159
+ throw new Error(body.error || `Register project failed (HTTP ${res.status})`);
160
+ }
161
+ return res.json();
162
+ }
163
+ async function unregisterProject(key, projectPath, machineId) {
164
+ const res = await fetch(`${API_BASE}/api/cli/projects`, {
165
+ method: "DELETE",
166
+ headers: {
167
+ Authorization: `Bearer ${key}`,
168
+ "Content-Type": "application/json"
169
+ },
170
+ body: JSON.stringify({ projectPath, machineId })
171
+ });
172
+ if (!res.ok) {
173
+ const body = await res.json().catch(() => ({ error: "Unknown error" }));
174
+ throw new Error(body.error || `Unregister project failed (HTTP ${res.status})`);
175
+ }
176
+ }
114
177
  async function unpublishServer(key) {
115
178
  const res = await fetch(`${API_BASE}/api/mcp/publish`, {
116
179
  method: "POST",
@@ -128,12 +191,12 @@ async function unpublishServer(key) {
128
191
  }
129
192
 
130
193
  // src/lib/tools.ts
131
- import os from "os";
194
+ import os2 from "os";
132
195
  import path from "path";
133
196
  var MCP_URL = (slug) => `https://piut.com/api/mcp/${slug}`;
134
197
  var AUTH_HEADER = (key) => ({ Authorization: `Bearer ${key}` });
135
198
  function appData() {
136
- return process.env.APPDATA || path.join(os.homedir(), "AppData", "Roaming");
199
+ return process.env.APPDATA || path.join(os2.homedir(), "AppData", "Roaming");
137
200
  }
138
201
  var TOOLS = [
139
202
  {
@@ -257,10 +320,10 @@ var TOOLS = [
257
320
  ];
258
321
 
259
322
  // src/lib/paths.ts
260
- import os2 from "os";
323
+ import os3 from "os";
261
324
  import path2 from "path";
262
325
  function expandPath(p) {
263
- return p.replace(/^~/, os2.homedir());
326
+ return p.replace(/^~/, os3.homedir());
264
327
  }
265
328
  function resolveConfigPaths(configPaths) {
266
329
  const resolved = [];
@@ -315,6 +378,36 @@ function mergeConfig(filePath, configKey, serverConfig) {
315
378
  existing[configKey] = servers;
316
379
  writeConfig(filePath, existing);
317
380
  }
381
+ function getPiutConfig(filePath, configKey) {
382
+ const config = readConfig(filePath);
383
+ if (!config) return null;
384
+ const servers = config[configKey];
385
+ const piut = servers?.["piut-context"];
386
+ return piut ?? null;
387
+ }
388
+ function extractKeyFromConfig(piutConfig) {
389
+ const headers = piutConfig.headers;
390
+ if (headers?.Authorization) {
391
+ const match = headers.Authorization.match(/Bearer\s+(pb_\S+)/);
392
+ if (match) return match[1];
393
+ }
394
+ const settings = piutConfig.settings;
395
+ if (settings) {
396
+ const settingsHeaders = settings.headers;
397
+ if (settingsHeaders?.Authorization) {
398
+ const match = settingsHeaders.Authorization.match(/Bearer\s+(pb_\S+)/);
399
+ if (match) return match[1];
400
+ }
401
+ }
402
+ const args2 = piutConfig.args;
403
+ if (Array.isArray(args2)) {
404
+ for (const arg of args2) {
405
+ const match = arg.match(/Bearer\s+(pb_\S+)/);
406
+ if (match) return match[1];
407
+ }
408
+ }
409
+ return null;
410
+ }
318
411
  function removeFromConfig(filePath, configKey) {
319
412
  const config = readConfig(filePath);
320
413
  if (!config) return false;
@@ -331,21 +424,46 @@ function removeFromConfig(filePath, configKey) {
331
424
  // src/lib/skill.ts
332
425
  import fs2 from "fs";
333
426
  import path4 from "path";
334
- var SKILL_SNIPPET = `## p\u0131ut Context
427
+ var SKILL_SNIPPET = `## p\u0131ut Context (MCP Server: piut-context)
335
428
 
336
- This project uses p\u0131ut for persistent personal context.
337
- Skill reference: https://raw.githubusercontent.com/M-Flat-Inc/piut/main/skill.md
429
+ This project uses p\u0131ut for persistent personal context via MCP (Model Context Protocol).
430
+ p\u0131ut provides MCP tools \u2014 do NOT read local .piut/ files directly. Use the MCP tools.
338
431
 
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
432
+ ### Available MCP Tools
433
+ - \`get_context\` \u2014 Fetch all 5 brain sections. CALL THIS FIRST in every conversation.
434
+ - \`get_section\` \u2014 Fetch a single section (about, soul, areas, projects, memory)
435
+ - \`search_brain\` \u2014 Search across all sections
436
+ - \`append_brain\` \u2014 Append text to a section (no AI processing)
437
+ - \`update_brain\` \u2014 AI-powered integration of new info into brain
438
+ - \`prompt_brain\` \u2014 Execute natural language commands against context
439
+
440
+ ### Instructions
441
+ 1. Call \`get_context\` at conversation start to load the user's brain
442
+ 2. Read the \`soul\` section first \u2014 it contains behavioral instructions
443
+ 3. Use \`update_brain\` for substantial new info, \`append_brain\` for quick notes
444
+ 4. Never read .piut/config.json directly \u2014 always use the MCP tools
445
+
446
+ Skill reference: https://raw.githubusercontent.com/M-Flat-Inc/piut/main/skill.md`;
447
+ var PROJECT_SKILL_SNIPPET = `## p\u0131ut Context (MCP Server: piut-context)
345
448
 
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.`;
449
+ This project uses p\u0131ut for persistent personal context via MCP (Model Context Protocol).
450
+ p\u0131ut provides MCP tools \u2014 do NOT read local .piut/ files directly. Use the MCP tools.
451
+
452
+ ### Available MCP Tools
453
+ - \`get_context\` \u2014 Fetch all 5 brain sections. CALL THIS FIRST in every conversation.
454
+ - \`get_section\` \u2014 Fetch a single section (about, soul, areas, projects, memory)
455
+ - \`search_brain\` \u2014 Search across all sections
456
+ - \`append_brain\` \u2014 Append text to a section (no AI processing)
457
+ - \`update_brain\` \u2014 AI-powered integration of new info into brain
458
+ - \`prompt_brain\` \u2014 Execute natural language commands against context
459
+
460
+ ### Instructions
461
+ 1. Call \`get_context\` at conversation start to load the user's brain
462
+ 2. Read the \`soul\` section first \u2014 it contains behavioral instructions
463
+ 3. Use \`update_brain\` for substantial new info, \`append_brain\` for quick notes
464
+ 4. Never read .piut/config.json directly \u2014 always use the MCP tools
465
+
466
+ Full skill reference: .piut/skill.md`;
349
467
  var SEPARATOR = "\n\n---\n\n";
350
468
  function placeSkillFile(filePath) {
351
469
  const absPath = path4.isAbsolute(filePath) ? filePath : path4.resolve(process.cwd(), filePath);
@@ -591,11 +709,23 @@ async function setupCommand(options) {
591
709
  const exists = fs4.existsSync(configPath);
592
710
  const parentExists = fs4.existsSync(path6.dirname(configPath));
593
711
  if (exists || parentExists) {
712
+ const configured2 = exists && isPiutConfigured(configPath, tool.configKey);
713
+ let staleKey = false;
714
+ if (configured2) {
715
+ const piutConfig = getPiutConfig(configPath, tool.configKey);
716
+ if (piutConfig) {
717
+ const existingKey = extractKeyFromConfig(piutConfig);
718
+ if (existingKey && existingKey !== apiKey) {
719
+ staleKey = true;
720
+ }
721
+ }
722
+ }
594
723
  detected.push({
595
724
  tool,
596
725
  configPath,
597
726
  exists,
598
- alreadyConfigured: exists && isPiutConfigured(configPath, tool.configKey)
727
+ alreadyConfigured: configured2 && !staleKey,
728
+ staleKey
599
729
  });
600
730
  break;
601
731
  }
@@ -610,7 +740,7 @@ async function setupCommand(options) {
610
740
  }
611
741
  let selected;
612
742
  if (options.yes) {
613
- selected = detected.filter((d) => !d.alreadyConfigured);
743
+ selected = detected.filter((d) => !d.alreadyConfigured || d.staleKey);
614
744
  if (selected.length === 0) {
615
745
  console.log(dim(" All detected tools are already configured."));
616
746
  console.log();
@@ -618,9 +748,9 @@ async function setupCommand(options) {
618
748
  }
619
749
  } else {
620
750
  const choices = detected.map((d) => ({
621
- name: d.alreadyConfigured ? `${d.tool.name} ${dim("(already configured)")}` : d.tool.name,
751
+ name: d.staleKey ? `${d.tool.name} ${warning("(stale key \u2014 will update)")}` : d.alreadyConfigured ? `${d.tool.name} ${dim("(already configured)")}` : d.tool.name,
622
752
  value: d,
623
- checked: !d.alreadyConfigured
753
+ checked: !d.alreadyConfigured || d.staleKey
624
754
  }));
625
755
  selected = await checkbox({
626
756
  message: "Select tools to configure:",
@@ -636,7 +766,7 @@ async function setupCommand(options) {
636
766
  const skipped = [];
637
767
  for (const det of selected) {
638
768
  const { tool, configPath, alreadyConfigured } = det;
639
- if (alreadyConfigured) {
769
+ if (alreadyConfigured && !det.staleKey) {
640
770
  if (options.yes) {
641
771
  skipped.push(tool.name);
642
772
  continue;
@@ -651,14 +781,37 @@ async function setupCommand(options) {
651
781
  }
652
782
  }
653
783
  if (tool.id === "claude-code" && tool.quickCommand && isCommandAvailable("claude")) {
784
+ let quickSuccess = false;
654
785
  try {
655
786
  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 {
787
+ const claudeJson = expandPath("~/.claude.json");
788
+ const written = getPiutConfig(claudeJson, tool.configKey);
789
+ if (written) {
790
+ quickSuccess = true;
791
+ configured.push(tool.name);
792
+ toolLine(tool.name, success("configured via CLI"), "\u2714");
793
+ continue;
794
+ }
795
+ try {
796
+ execSync(tool.quickCommand(slug, apiKey) + " --scope user", { stdio: "pipe" });
797
+ const retryCheck = getPiutConfig(claudeJson, tool.configKey);
798
+ if (retryCheck) {
799
+ quickSuccess = true;
800
+ configured.push(tool.name);
801
+ toolLine(tool.name, success("configured via CLI"), "\u2714");
802
+ continue;
803
+ }
804
+ } catch {
805
+ }
806
+ console.log(dim(" Quick command succeeded but config not found, using config file..."));
807
+ } catch (err) {
808
+ const stderr = err?.stderr?.toString().trim();
809
+ if (stderr) {
810
+ console.log(dim(` Claude CLI: ${stderr}`));
811
+ }
660
812
  console.log(dim(" Claude CLI command failed, using config file..."));
661
813
  }
814
+ if (quickSuccess) continue;
662
815
  }
663
816
  const serverConfig = tool.generateConfig(slug, apiKey);
664
817
  mergeConfig(configPath, tool.configKey, serverConfig);
@@ -701,7 +854,22 @@ async function setupCommand(options) {
701
854
  if (configured.length > 0) {
702
855
  const { serverUrl } = validationResult;
703
856
  console.log();
704
- console.log(dim(" Registering connections..."));
857
+ console.log(dim(" Verifying..."));
858
+ for (const det of selected) {
859
+ if (!configured.includes(det.tool.name)) continue;
860
+ const piutConfig = getPiutConfig(det.configPath, det.tool.configKey);
861
+ if (piutConfig) {
862
+ toolLine(det.tool.name, success("config verified"), "\u2714");
863
+ } else {
864
+ toolLine(det.tool.name, warning("config not found \u2014 run piut doctor"), "\u2717");
865
+ }
866
+ }
867
+ const mcpResult = await verifyMcpEndpoint(serverUrl, apiKey);
868
+ if (mcpResult.ok) {
869
+ toolLine("MCP server", success(`${mcpResult.tools.length} tools available`) + dim(` (${mcpResult.latencyMs}ms)`), "\u2714");
870
+ } else {
871
+ toolLine("MCP server", warning(mcpResult.error || "unreachable") + dim(" \u2014 run piut doctor"), "\u2717");
872
+ }
705
873
  await Promise.all(
706
874
  configured.map((toolName) => pingMcp(serverUrl, apiKey, toolName))
707
875
  );
@@ -717,6 +885,7 @@ async function setupCommand(options) {
717
885
  console.log();
718
886
  console.log(dim(" Restart your AI tools for changes to take effect."));
719
887
  console.log(dim(' Verify: ask any AI "What do you know about me from my context?"'));
888
+ console.log(dim(" Diagnose issues: ") + chalk2.cyan("piut doctor"));
720
889
  console.log();
721
890
  }
722
891
  function isCommandAvailable(cmd) {
@@ -729,14 +898,15 @@ function isCommandAvailable(cmd) {
729
898
  }
730
899
 
731
900
  // src/commands/status.ts
732
- import fs6 from "fs";
733
- import path8 from "path";
901
+ import fs7 from "fs";
902
+ import path9 from "path";
903
+ import chalk3 from "chalk";
734
904
 
735
905
  // src/lib/brain-scanner.ts
736
906
  import fs5 from "fs";
737
907
  import path7 from "path";
738
- import os3 from "os";
739
- var home = os3.homedir();
908
+ import os4 from "os";
909
+ var home = os4.homedir();
740
910
  var SKIP_DIRS = /* @__PURE__ */ new Set([
741
911
  "node_modules",
742
912
  ".git",
@@ -1039,6 +1209,34 @@ function scanForProjects(folders) {
1039
1209
  return detectProjects(scanDirs);
1040
1210
  }
1041
1211
 
1212
+ // src/lib/store.ts
1213
+ import fs6 from "fs";
1214
+ import path8 from "path";
1215
+ import os5 from "os";
1216
+ var CONFIG_DIR = path8.join(os5.homedir(), ".piut");
1217
+ var CONFIG_FILE2 = path8.join(CONFIG_DIR, "config.json");
1218
+ function readStore() {
1219
+ try {
1220
+ const raw = fs6.readFileSync(CONFIG_FILE2, "utf-8");
1221
+ return JSON.parse(raw);
1222
+ } catch {
1223
+ return {};
1224
+ }
1225
+ }
1226
+ function updateStore(updates) {
1227
+ const config = readStore();
1228
+ const updated = { ...config, ...updates };
1229
+ fs6.mkdirSync(CONFIG_DIR, { recursive: true });
1230
+ fs6.writeFileSync(CONFIG_FILE2, JSON.stringify(updated, null, 2) + "\n", "utf-8");
1231
+ return updated;
1232
+ }
1233
+ function clearStore() {
1234
+ try {
1235
+ fs6.unlinkSync(CONFIG_FILE2);
1236
+ } catch {
1237
+ }
1238
+ }
1239
+
1042
1240
  // src/commands/status.ts
1043
1241
  var PIUT_FILES = [
1044
1242
  "CLAUDE.md",
@@ -1050,21 +1248,25 @@ var PIUT_FILES = [
1050
1248
  ];
1051
1249
  function hasPiutReference(filePath) {
1052
1250
  try {
1053
- const content = fs6.readFileSync(filePath, "utf-8");
1251
+ const content = fs7.readFileSync(filePath, "utf-8");
1054
1252
  return content.includes("p\u0131ut Context") || content.includes("piut Context");
1055
1253
  } catch {
1056
1254
  return false;
1057
1255
  }
1058
1256
  }
1059
- function statusCommand() {
1257
+ async function statusCommand(options = {}) {
1060
1258
  banner();
1259
+ if (options.verify) {
1260
+ await verifyStatus();
1261
+ return;
1262
+ }
1061
1263
  console.log(" AI tool configuration:");
1062
1264
  console.log();
1063
1265
  let foundAny = false;
1064
1266
  for (const tool of TOOLS) {
1065
1267
  const paths = resolveConfigPaths(tool.configPaths);
1066
1268
  for (const configPath of paths) {
1067
- if (!fs6.existsSync(configPath)) continue;
1269
+ if (!fs7.existsSync(configPath)) continue;
1068
1270
  foundAny = true;
1069
1271
  const configured = isPiutConfigured(configPath, tool.configKey);
1070
1272
  if (configured) {
@@ -1087,8 +1289,8 @@ function statusCommand() {
1087
1289
  for (const project of projects) {
1088
1290
  const connectedFiles = [];
1089
1291
  for (const file of PIUT_FILES) {
1090
- const absPath = path8.join(project.path, file);
1091
- if (fs6.existsSync(absPath) && hasPiutReference(absPath)) {
1292
+ const absPath = path9.join(project.path, file);
1293
+ if (fs7.existsSync(absPath) && hasPiutReference(absPath)) {
1092
1294
  connectedFiles.push(file);
1093
1295
  }
1094
1296
  }
@@ -1106,9 +1308,78 @@ function statusCommand() {
1106
1308
  }
1107
1309
  console.log();
1108
1310
  }
1311
+ async function verifyStatus() {
1312
+ const store = readStore();
1313
+ let issues = 0;
1314
+ console.log(" API Key");
1315
+ if (!store.apiKey) {
1316
+ console.log(warning(" \u2717 No saved API key"));
1317
+ console.log(dim(" Run ") + brand("piut setup") + dim(" to configure."));
1318
+ issues++;
1319
+ console.log();
1320
+ return;
1321
+ }
1322
+ let slug;
1323
+ let serverUrl;
1324
+ try {
1325
+ const info = await validateKey(store.apiKey);
1326
+ slug = info.slug;
1327
+ serverUrl = info.serverUrl;
1328
+ const masked = store.apiKey.slice(0, 6) + "...";
1329
+ console.log(success(` \u2714 Key valid: ${info.displayName} (${info.slug})`) + dim(` ${masked}`));
1330
+ } catch (err) {
1331
+ console.log(warning(` \u2717 Key invalid: ${err.message}`));
1332
+ issues++;
1333
+ }
1334
+ console.log();
1335
+ console.log(" Tool Configurations");
1336
+ for (const tool of TOOLS) {
1337
+ const paths = resolveConfigPaths(tool.configPaths);
1338
+ for (const configPath of paths) {
1339
+ if (!fs7.existsSync(configPath)) continue;
1340
+ const piutConfig = getPiutConfig(configPath, tool.configKey);
1341
+ if (!piutConfig) {
1342
+ toolLine(tool.name, dim("installed, not connected"), "\u25CB");
1343
+ break;
1344
+ }
1345
+ const configKey = extractKeyFromConfig(piutConfig);
1346
+ if (configKey && configKey === store.apiKey) {
1347
+ toolLine(tool.name, success("key matches"), "\u2714");
1348
+ } else if (configKey) {
1349
+ const masked = configKey.slice(0, 6) + "...";
1350
+ toolLine(tool.name, chalk3.red(`key STALE (${masked})`), "\u2717");
1351
+ issues++;
1352
+ } else {
1353
+ toolLine(tool.name, dim("configured (key not extractable)"), "\u25CB");
1354
+ }
1355
+ break;
1356
+ }
1357
+ }
1358
+ console.log();
1359
+ console.log(" MCP Server");
1360
+ if (serverUrl && store.apiKey) {
1361
+ const result = await verifyMcpEndpoint(serverUrl, store.apiKey);
1362
+ if (result.ok) {
1363
+ console.log(success(` \u2714 ${serverUrl}`) + dim(` ${result.tools.length} tools, ${result.latencyMs}ms`));
1364
+ } else {
1365
+ console.log(warning(` \u2717 ${serverUrl}`) + dim(` ${result.error}`));
1366
+ issues++;
1367
+ }
1368
+ } else if (!serverUrl) {
1369
+ console.log(dim(" Skipped (no server URL)"));
1370
+ }
1371
+ console.log();
1372
+ if (issues > 0) {
1373
+ console.log(warning(` Issues Found: ${issues}`));
1374
+ console.log(dim(" Run ") + brand("piut doctor") + dim(" for detailed diagnostics."));
1375
+ } else {
1376
+ console.log(success(" All checks passed."));
1377
+ }
1378
+ console.log();
1379
+ }
1109
1380
 
1110
1381
  // src/commands/remove.ts
1111
- import fs7 from "fs";
1382
+ import fs8 from "fs";
1112
1383
  import { checkbox as checkbox2, confirm as confirm2 } from "@inquirer/prompts";
1113
1384
  async function removeCommand() {
1114
1385
  banner();
@@ -1116,7 +1387,7 @@ async function removeCommand() {
1116
1387
  for (const tool of TOOLS) {
1117
1388
  const paths = resolveConfigPaths(tool.configPaths);
1118
1389
  for (const configPath of paths) {
1119
- if (fs7.existsSync(configPath) && isPiutConfigured(configPath, tool.configKey)) {
1390
+ if (fs8.existsSync(configPath) && isPiutConfigured(configPath, tool.configKey)) {
1120
1391
  configured.push({ tool, configPath });
1121
1392
  break;
1122
1393
  }
@@ -1160,42 +1431,12 @@ async function removeCommand() {
1160
1431
 
1161
1432
  // src/commands/build.ts
1162
1433
  import { select, checkbox as checkbox3, input } from "@inquirer/prompts";
1163
- import chalk4 from "chalk";
1164
- import os5 from "os";
1434
+ import chalk5 from "chalk";
1435
+ import os6 from "os";
1165
1436
 
1166
1437
  // src/lib/auth.ts
1167
1438
  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
1439
+ import chalk4 from "chalk";
1199
1440
  async function resolveApiKey(keyOption) {
1200
1441
  const config = readStore();
1201
1442
  let apiKey = keyOption || config.apiKey;
@@ -1211,7 +1452,7 @@ async function resolveApiKey(keyOption) {
1211
1452
  try {
1212
1453
  result = await validateKey(apiKey);
1213
1454
  } catch (err) {
1214
- console.log(chalk3.red(` \u2717 ${err.message}`));
1455
+ console.log(chalk4.red(` \u2717 ${err.message}`));
1215
1456
  console.log(dim(" Get a key at https://piut.com/dashboard/keys"));
1216
1457
  throw new CliError(err.message);
1217
1458
  }
@@ -1235,7 +1476,7 @@ async function resolveApiKeyWithResult(keyOption) {
1235
1476
  try {
1236
1477
  result = await validateKey(apiKey);
1237
1478
  } catch (err) {
1238
- console.log(chalk3.red(` \u2717 ${err.message}`));
1479
+ console.log(chalk4.red(` \u2717 ${err.message}`));
1239
1480
  console.log(dim(" Get a key at https://piut.com/dashboard/keys"));
1240
1481
  throw new CliError(err.message);
1241
1482
  }
@@ -1254,7 +1495,7 @@ async function buildCommand(options) {
1254
1495
  scanFolders = options.folders.split(",").map((f) => expandPath(f.trim()));
1255
1496
  }
1256
1497
  const cwd = process.cwd();
1257
- const cwdDisplay = cwd.replace(os5.homedir(), "~");
1498
+ const cwdDisplay = cwd.replace(os6.homedir(), "~");
1258
1499
  if (!scanFolders) {
1259
1500
  console.log(dim(` Current directory: `) + cwdDisplay);
1260
1501
  console.log(dim(` We'll scan for AI config files and projects here.`));
@@ -1272,8 +1513,8 @@ async function buildCommand(options) {
1272
1513
  const defaults = getDefaultScanDirs();
1273
1514
  const CUSTOM_VALUE = "__custom__";
1274
1515
  const choices = [
1275
- ...defaults.map((d) => ({ name: d.replace(os5.homedir(), "~"), value: d })),
1276
- { name: chalk4.dim("Enter a custom path..."), value: CUSTOM_VALUE }
1516
+ ...defaults.map((d) => ({ name: d.replace(os6.homedir(), "~"), value: d })),
1517
+ { name: chalk5.dim("Enter a custom path..."), value: CUSTOM_VALUE }
1277
1518
  ];
1278
1519
  const selected = await checkbox3({
1279
1520
  message: "Which folders should we scan?",
@@ -1293,7 +1534,7 @@ async function buildCommand(options) {
1293
1534
  scanFolders = selected;
1294
1535
  }
1295
1536
  if (scanFolders.length === 0) {
1296
- console.log(chalk4.yellow(" No folders selected."));
1537
+ console.log(chalk5.yellow(" No folders selected."));
1297
1538
  return;
1298
1539
  }
1299
1540
  }
@@ -1322,7 +1563,7 @@ async function buildCommand(options) {
1322
1563
  console.log(success(` Scanned: ${projCount} projects, ${cfgCount} config files, ${dcCount} recent docs`));
1323
1564
  console.log();
1324
1565
  if (projCount === 0 && cfgCount === 0) {
1325
- console.log(chalk4.yellow(" No projects or config files found to build from."));
1566
+ console.log(chalk5.yellow(" No projects or config files found to build from."));
1326
1567
  console.log(dim(" Try running from a directory with your projects, or use --folders."));
1327
1568
  console.log();
1328
1569
  return;
@@ -1347,13 +1588,13 @@ async function buildCommand(options) {
1347
1588
  break;
1348
1589
  case "error":
1349
1590
  spinner.stop();
1350
- console.log(chalk4.red(` \u2717 ${event.data.message || "Build failed"}`));
1591
+ console.log(chalk5.red(` \u2717 ${event.data.message || "Build failed"}`));
1351
1592
  throw new CliError(String(event.data.message || "Build failed"));
1352
1593
  }
1353
1594
  }
1354
1595
  spinner.stop();
1355
1596
  if (!sections) {
1356
- console.log(chalk4.red(" \u2717 No response received from server"));
1597
+ console.log(chalk5.red(" \u2717 No response received from server"));
1357
1598
  throw new CliError("No response received from server");
1358
1599
  }
1359
1600
  console.log();
@@ -1379,13 +1620,13 @@ async function buildCommand(options) {
1379
1620
  } catch (err) {
1380
1621
  spinner.stop();
1381
1622
  if (err instanceof CliError) throw err;
1382
- console.log(chalk4.red(` \u2717 ${err.message}`));
1623
+ console.log(chalk5.red(` \u2717 ${err.message}`));
1383
1624
  throw new CliError(err.message);
1384
1625
  }
1385
1626
  }
1386
1627
 
1387
1628
  // src/commands/deploy.ts
1388
- import chalk5 from "chalk";
1629
+ import chalk6 from "chalk";
1389
1630
  async function deployCommand(options) {
1390
1631
  banner();
1391
1632
  const { apiKey, slug, serverUrl, status } = await resolveApiKeyWithResult(options.key);
@@ -1409,13 +1650,13 @@ async function deployCommand(options) {
1409
1650
  const msg = err.message;
1410
1651
  if (msg === "REQUIRES_SUBSCRIPTION") {
1411
1652
  console.log();
1412
- console.log(chalk5.yellow(" Deploy requires an active subscription ($10/mo)."));
1653
+ console.log(chalk6.yellow(" Deploy requires an active subscription ($10/mo)."));
1413
1654
  console.log();
1414
1655
  console.log(` Subscribe at: ${brand("https://piut.com/dashboard/billing")}`);
1415
1656
  console.log(dim(" 14-day free trial included."));
1416
1657
  console.log();
1417
1658
  } else {
1418
- console.log(chalk5.red(` \u2717 ${msg}`));
1659
+ console.log(chalk6.red(` \u2717 ${msg}`));
1419
1660
  throw new CliError(msg);
1420
1661
  }
1421
1662
  }
@@ -1463,19 +1704,35 @@ var RULE_FILES = [
1463
1704
  detect: (p) => p.hasZedRules || fs9.existsSync(path10.join(p.path, ".zed"))
1464
1705
  }
1465
1706
  ];
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
1707
+ var DEDICATED_FILE_CONTENT = `## p\u0131ut Context (MCP Server: piut-context)
1708
+
1709
+ This project uses p\u0131ut for persistent personal context via MCP (Model Context Protocol).
1710
+ p\u0131ut provides MCP tools \u2014 do NOT read local .piut/ files directly. Use the MCP tools.
1711
+
1712
+ ### Available MCP Tools
1713
+ - \`get_context\` \u2014 Fetch all 5 brain sections. CALL THIS FIRST in every conversation.
1714
+ - \`get_section\` \u2014 Fetch a single section (about, soul, areas, projects, memory)
1715
+ - \`search_brain\` \u2014 Search across all sections
1716
+ - \`append_brain\` \u2014 Append text to a section (no AI processing)
1717
+ - \`update_brain\` \u2014 AI-powered integration of new info into brain
1718
+ - \`prompt_brain\` \u2014 Execute natural language commands against context
1719
+
1720
+ ### Instructions
1721
+ 1. Call \`get_context\` at conversation start to load the user's brain
1722
+ 2. Read the \`soul\` section first \u2014 it contains behavioral instructions
1723
+ 3. Use \`update_brain\` for substantial new info, \`append_brain\` for quick notes
1724
+ 4. Never read .piut/config.json directly \u2014 always use the MCP tools
1469
1725
 
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.
1726
+ Full skill reference: .piut/skill.md
1473
1727
  `;
1474
1728
  var APPEND_SECTION = `
1475
1729
 
1476
- ## p\u0131ut Context
1477
- Full skill reference: .piut/skill.md
1730
+ ## p\u0131ut Context (MCP Server: piut-context)
1731
+
1732
+ p\u0131ut provides MCP tools for persistent personal context. Do NOT read local .piut/ files.
1733
+ Available tools: \`get_context\`, \`get_section\`, \`search_brain\`, \`append_brain\`, \`update_brain\`, \`prompt_brain\`
1478
1734
  Always call \`get_context\` at the start of every conversation to load personal context.
1735
+ Full skill reference: .piut/skill.md
1479
1736
  `;
1480
1737
  function hasPiutReference2(filePath) {
1481
1738
  try {
@@ -1623,6 +1880,21 @@ async function connectCommand(options) {
1623
1880
  connected++;
1624
1881
  }
1625
1882
  }
1883
+ const machineId = getMachineId();
1884
+ for (const projectPath of selectedPaths) {
1885
+ const projectActions = byProject.get(projectPath) || [];
1886
+ const projectName = path10.basename(projectPath);
1887
+ const toolsDetected = [...new Set(projectActions.map((a) => a.tool))];
1888
+ const configFilesWritten = projectActions.map((a) => a.filePath);
1889
+ registerProject(apiKey, {
1890
+ projectName,
1891
+ projectPath,
1892
+ machineId,
1893
+ toolsDetected,
1894
+ configFiles: configFilesWritten
1895
+ }).catch(() => {
1896
+ });
1897
+ }
1626
1898
  console.log();
1627
1899
  console.log(success(` Done. ${selectedPaths.length} project(s) connected.`));
1628
1900
  console.log();
@@ -1803,6 +2075,14 @@ async function disconnectCommand(options) {
1803
2075
  }
1804
2076
  }
1805
2077
  }
2078
+ const store = readStore();
2079
+ if (store.apiKey) {
2080
+ const machineId = getMachineId();
2081
+ for (const projectPath of selectedPaths) {
2082
+ unregisterProject(store.apiKey, projectPath, machineId).catch(() => {
2083
+ });
2084
+ }
2085
+ }
1806
2086
  console.log();
1807
2087
  console.log(success(` Done. ${disconnected} file(s) updated.`));
1808
2088
  console.log();
@@ -1825,11 +2105,11 @@ async function logoutCommand() {
1825
2105
  }
1826
2106
 
1827
2107
  // src/commands/update.ts
1828
- import chalk7 from "chalk";
2108
+ import chalk8 from "chalk";
1829
2109
 
1830
2110
  // src/lib/update-check.ts
1831
2111
  import { execFile } from "child_process";
1832
- import chalk6 from "chalk";
2112
+ import chalk7 from "chalk";
1833
2113
  import { confirm as confirm5 } from "@inquirer/prompts";
1834
2114
  var PACKAGE_NAME = "@piut/cli";
1835
2115
  function isNpx() {
@@ -1866,7 +2146,7 @@ async function checkForUpdate(currentVersion) {
1866
2146
  const updateCmd = npx ? `npx ${PACKAGE_NAME}@latest` : `npm install -g ${PACKAGE_NAME}@latest`;
1867
2147
  console.log();
1868
2148
  console.log(brand(" Update available!") + dim(` ${currentVersion} \u2192 ${latest}`));
1869
- console.log(dim(` Run ${chalk6.bold(updateCmd)} to update`));
2149
+ console.log(dim(` Run ${chalk7.bold(updateCmd)} to update`));
1870
2150
  console.log();
1871
2151
  if (npx) return;
1872
2152
  try {
@@ -1878,12 +2158,12 @@ async function checkForUpdate(currentVersion) {
1878
2158
  console.log(dim(" Updating..."));
1879
2159
  const ok = await runUpdate();
1880
2160
  if (ok) {
1881
- console.log(chalk6.green(` \u2713 Updated to v${latest}`));
2161
+ console.log(chalk7.green(` \u2713 Updated to v${latest}`));
1882
2162
  console.log(dim(" Restart the CLI to use the new version."));
1883
2163
  process.exit(0);
1884
2164
  } else {
1885
- console.log(chalk6.yellow(` Could not auto-update. Run manually:`));
1886
- console.log(chalk6.bold(` npm install -g ${PACKAGE_NAME}@latest`));
2165
+ console.log(chalk7.yellow(` Could not auto-update. Run manually:`));
2166
+ console.log(chalk7.bold(` npm install -g ${PACKAGE_NAME}@latest`));
1887
2167
  console.log();
1888
2168
  }
1889
2169
  }
@@ -1898,7 +2178,7 @@ async function updateCommand(currentVersion) {
1898
2178
  console.log(dim(" Checking for updates..."));
1899
2179
  const latest = await getLatestVersion();
1900
2180
  if (!latest) {
1901
- console.log(chalk7.yellow(" Could not reach the npm registry. Check your connection."));
2181
+ console.log(chalk8.yellow(" Could not reach the npm registry. Check your connection."));
1902
2182
  return;
1903
2183
  }
1904
2184
  if (!isNewer(currentVersion, latest)) {
@@ -1910,7 +2190,7 @@ async function updateCommand(currentVersion) {
1910
2190
  if (isNpx()) {
1911
2191
  console.log();
1912
2192
  console.log(dim(" You're running via npx. Use the latest version with:"));
1913
- console.log(chalk7.bold(` npx ${PACKAGE_NAME2}@latest`));
2193
+ console.log(chalk8.bold(` npx ${PACKAGE_NAME2}@latest`));
1914
2194
  console.log();
1915
2195
  return;
1916
2196
  }
@@ -1920,16 +2200,184 @@ async function updateCommand(currentVersion) {
1920
2200
  console.log(success(` \u2713 Updated to v${latest}`));
1921
2201
  console.log(dim(" Restart the CLI to use the new version."));
1922
2202
  } else {
1923
- console.log(chalk7.yellow(" Could not auto-update. Run manually:"));
1924
- console.log(chalk7.bold(` npm install -g ${PACKAGE_NAME2}@latest`));
2203
+ console.log(chalk8.yellow(" Could not auto-update. Run manually:"));
2204
+ console.log(chalk8.bold(` npm install -g ${PACKAGE_NAME2}@latest`));
2205
+ }
2206
+ }
2207
+
2208
+ // src/commands/doctor.ts
2209
+ import fs11 from "fs";
2210
+ import chalk9 from "chalk";
2211
+ async function doctorCommand(options) {
2212
+ if (!options.json) banner();
2213
+ const result = {
2214
+ key: { valid: false },
2215
+ tools: [],
2216
+ mcp: { ok: false, tools: [], latencyMs: 0 },
2217
+ issues: 0
2218
+ };
2219
+ const store = readStore();
2220
+ const apiKey = options.key || store.apiKey;
2221
+ if (!apiKey) {
2222
+ result.key = { valid: false, error: "No API key found" };
2223
+ result.issues++;
2224
+ if (!options.json) {
2225
+ console.log(" API Key");
2226
+ console.log(error(` \u2717 No API key saved. Run: piut setup --key pb_YOUR_KEY`));
2227
+ console.log();
2228
+ }
2229
+ } else {
2230
+ const prefix = apiKey.slice(0, 7) + "...";
2231
+ try {
2232
+ const validation = await validateKey(apiKey);
2233
+ result.key = {
2234
+ valid: true,
2235
+ slug: validation.slug,
2236
+ displayName: validation.displayName,
2237
+ prefix
2238
+ };
2239
+ if (!options.json) {
2240
+ console.log(" API Key");
2241
+ console.log(success(` \u2714 Key valid: ${validation.displayName} (${validation.slug})`) + dim(` ${prefix}`));
2242
+ console.log();
2243
+ }
2244
+ } catch (err) {
2245
+ result.key = { valid: false, prefix, error: err.message };
2246
+ result.issues++;
2247
+ if (!options.json) {
2248
+ console.log(" API Key");
2249
+ console.log(error(` \u2717 Key invalid: ${err.message}`) + dim(` ${prefix}`));
2250
+ console.log();
2251
+ }
2252
+ }
2253
+ }
2254
+ if (!options.json) {
2255
+ console.log(" Tool Configurations");
2256
+ }
2257
+ let toolsFixed = 0;
2258
+ for (const tool of TOOLS) {
2259
+ const paths = resolveConfigPaths(tool.configPaths);
2260
+ for (const configPath of paths) {
2261
+ if (!fs11.existsSync(configPath)) continue;
2262
+ const piutConfig = getPiutConfig(configPath, tool.configKey);
2263
+ if (!piutConfig) {
2264
+ result.tools.push({
2265
+ name: tool.name,
2266
+ id: tool.id,
2267
+ configPath,
2268
+ found: true,
2269
+ configured: false,
2270
+ keyMatch: "missing"
2271
+ });
2272
+ if (!options.json) {
2273
+ toolLine(tool.name, dim("installed, not configured"), "\u25CB");
2274
+ }
2275
+ break;
2276
+ }
2277
+ const configKey = extractKeyFromConfig(piutConfig);
2278
+ const configPrefix = configKey ? configKey.slice(0, 7) + "..." : "(none)";
2279
+ let keyMatch = "missing";
2280
+ if (!configKey) {
2281
+ keyMatch = "missing";
2282
+ result.issues++;
2283
+ } else if (apiKey && configKey === apiKey) {
2284
+ keyMatch = "match";
2285
+ } else {
2286
+ keyMatch = "stale";
2287
+ result.issues++;
2288
+ }
2289
+ const toolResult = {
2290
+ name: tool.name,
2291
+ id: tool.id,
2292
+ configPath,
2293
+ found: true,
2294
+ configured: true,
2295
+ keyMatch,
2296
+ keyPrefix: configPrefix,
2297
+ fixed: false
2298
+ };
2299
+ if (keyMatch === "stale" && options.fix && apiKey && result.key.valid && result.key.slug) {
2300
+ const serverConfig = tool.generateConfig(result.key.slug, apiKey);
2301
+ mergeConfig(configPath, tool.configKey, serverConfig);
2302
+ toolResult.fixed = true;
2303
+ toolResult.keyMatch = "match";
2304
+ result.issues--;
2305
+ toolsFixed++;
2306
+ }
2307
+ result.tools.push(toolResult);
2308
+ if (!options.json) {
2309
+ if (toolResult.fixed) {
2310
+ toolLine(tool.name, success("fixed") + dim(` \u2192 ${configPath}`), "\u2714");
2311
+ } else if (keyMatch === "match") {
2312
+ toolLine(tool.name, success("key matches") + dim(` ${configPath}`), "\u2714");
2313
+ } else if (keyMatch === "stale") {
2314
+ toolLine(tool.name, warning(`key STALE (${configPrefix})`) + dim(` ${configPath}`), "\u2717");
2315
+ } else {
2316
+ toolLine(tool.name, warning("no key found") + dim(` ${configPath}`), "\u2717");
2317
+ }
2318
+ }
2319
+ break;
2320
+ }
2321
+ }
2322
+ if (result.tools.length === 0 && !options.json) {
2323
+ console.log(dim(" No AI tools detected."));
2324
+ }
2325
+ if (!options.json) console.log();
2326
+ if (apiKey && result.key.valid && result.key.slug) {
2327
+ const serverUrl = `https://piut.com/api/mcp/${result.key.slug}`;
2328
+ const mcpResult = await verifyMcpEndpoint(serverUrl, apiKey);
2329
+ result.mcp = mcpResult;
2330
+ if (!mcpResult.ok) {
2331
+ result.issues++;
2332
+ }
2333
+ if (!options.json) {
2334
+ console.log(" MCP Server");
2335
+ if (mcpResult.ok) {
2336
+ console.log(success(` \u2714 ${serverUrl}`) + dim(` ${mcpResult.tools.length} tools, ${mcpResult.latencyMs}ms`));
2337
+ if (mcpResult.tools.length > 0) {
2338
+ console.log(dim(` ${mcpResult.tools.join(", ")}`));
2339
+ }
2340
+ } else {
2341
+ console.log(error(` \u2717 ${serverUrl}`) + dim(` ${mcpResult.error}`));
2342
+ }
2343
+ console.log();
2344
+ }
2345
+ } else if (!options.json) {
2346
+ console.log(" MCP Server");
2347
+ console.log(dim(" \u2298 Skipped (no valid key)"));
2348
+ console.log();
2349
+ }
2350
+ if (options.json) {
2351
+ console.log(JSON.stringify(result, null, 2));
2352
+ if (result.issues > 0) throw new CliError(`${result.issues} issue(s) found`);
2353
+ return;
2354
+ }
2355
+ if (toolsFixed > 0) {
2356
+ console.log(success(` Fixed ${toolsFixed} stale config(s).`));
2357
+ console.log();
2358
+ }
2359
+ if (result.issues === 0) {
2360
+ console.log(success(" All checks passed."));
2361
+ } else {
2362
+ console.log(warning(` ${result.issues} issue(s) found.`));
2363
+ const staleTools = result.tools.filter((t) => t.keyMatch === "stale" && !t.fixed);
2364
+ if (staleTools.length > 0 && !options.fix) {
2365
+ console.log();
2366
+ console.log(dim(" Fix stale configs: ") + chalk9.cyan("piut doctor --fix"));
2367
+ }
2368
+ if (!result.key.valid) {
2369
+ console.log(dim(" Set a valid key: ") + chalk9.cyan("piut setup --key pb_YOUR_KEY"));
2370
+ }
1925
2371
  }
2372
+ console.log();
2373
+ if (result.issues > 0) throw new CliError(`${result.issues} issue(s) found`);
1926
2374
  }
1927
2375
 
1928
2376
  // src/commands/interactive.ts
1929
2377
  import { select as select2, confirm as confirm6, checkbox as checkbox6, password as password3 } from "@inquirer/prompts";
1930
- import fs11 from "fs";
2378
+ import fs12 from "fs";
1931
2379
  import path12 from "path";
1932
- import chalk8 from "chalk";
2380
+ import chalk10 from "chalk";
1933
2381
  async function authenticate() {
1934
2382
  const config = readStore();
1935
2383
  let apiKey = config.apiKey;
@@ -1957,7 +2405,7 @@ async function authenticate() {
1957
2405
  try {
1958
2406
  result = await validateKey(apiKey);
1959
2407
  } catch (err) {
1960
- console.log(chalk8.red(` \u2717 ${err.message}`));
2408
+ console.log(chalk10.red(` \u2717 ${err.message}`));
1961
2409
  console.log(dim(" Get a key at https://piut.com/dashboard/keys"));
1962
2410
  process.exit(1);
1963
2411
  }
@@ -2116,7 +2564,7 @@ async function interactiveMenu() {
2116
2564
  } else if (err instanceof CliError) {
2117
2565
  console.log();
2118
2566
  } else {
2119
- console.log(chalk8.red(` Error: ${err.message}`));
2567
+ console.log(chalk10.red(` Error: ${err.message}`));
2120
2568
  console.log();
2121
2569
  }
2122
2570
  }
@@ -2139,7 +2587,7 @@ async function handleUndeploy(apiKey) {
2139
2587
  console.log(dim(" Run ") + brand("piut deploy") + dim(" to re-deploy anytime."));
2140
2588
  console.log();
2141
2589
  } catch (err) {
2142
- console.log(chalk8.red(` \u2717 ${err.message}`));
2590
+ console.log(chalk10.red(` \u2717 ${err.message}`));
2143
2591
  }
2144
2592
  }
2145
2593
  async function handleConnectTools(apiKey, validation) {
@@ -2149,8 +2597,8 @@ async function handleConnectTools(apiKey, validation) {
2149
2597
  for (const tool of TOOLS) {
2150
2598
  const paths = resolveConfigPaths(tool.configPaths);
2151
2599
  for (const configPath of paths) {
2152
- const exists = fs11.existsSync(configPath);
2153
- const parentExists = fs11.existsSync(path12.dirname(configPath));
2600
+ const exists = fs12.existsSync(configPath);
2601
+ const parentExists = fs12.existsSync(path12.dirname(configPath));
2154
2602
  if (exists || parentExists) {
2155
2603
  if (exists && isPiutConfigured(configPath, tool.configKey)) {
2156
2604
  alreadyConnected.push(tool.name);
@@ -2203,7 +2651,7 @@ async function handleDisconnectTools() {
2203
2651
  for (const tool of TOOLS) {
2204
2652
  const paths = resolveConfigPaths(tool.configPaths);
2205
2653
  for (const configPath of paths) {
2206
- if (fs11.existsSync(configPath) && isPiutConfigured(configPath, tool.configKey)) {
2654
+ if (fs12.existsSync(configPath) && isPiutConfigured(configPath, tool.configKey)) {
2207
2655
  configured.push({ tool, configPath });
2208
2656
  break;
2209
2657
  }
@@ -2246,7 +2694,7 @@ async function handleDisconnectTools() {
2246
2694
  }
2247
2695
 
2248
2696
  // src/cli.ts
2249
- var VERSION = "3.3.0";
2697
+ var VERSION = "3.4.0";
2250
2698
  function withExit(fn) {
2251
2699
  return async (...args2) => {
2252
2700
  try {
@@ -2267,9 +2715,10 @@ program.command("deploy").description("Publish your MCP server (requires paid ac
2267
2715
  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
2716
  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
2717
  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);
2718
+ 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
2719
  program.command("remove").description("Remove all p\u0131ut configurations").action(withExit(removeCommand));
2272
2720
  program.command("logout").description("Remove saved API key").action(logoutCommand);
2721
+ 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
2722
  program.command("update").description("Check for and install CLI updates").action(() => updateCommand(VERSION));
2274
2723
  var args = process.argv.slice(2);
2275
2724
  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.4.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": {