@piut/cli 3.2.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 +669 -167
  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)
448
+
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
345
465
 
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.`;
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.
1469
1711
 
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.
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
1725
+
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();
@@ -1824,11 +2104,280 @@ async function logoutCommand() {
1824
2104
  console.log();
1825
2105
  }
1826
2106
 
1827
- // src/commands/interactive.ts
1828
- import { select as select2, confirm as confirm5, checkbox as checkbox6, password as password3 } from "@inquirer/prompts";
2107
+ // src/commands/update.ts
2108
+ import chalk8 from "chalk";
2109
+
2110
+ // src/lib/update-check.ts
2111
+ import { execFile } from "child_process";
2112
+ import chalk7 from "chalk";
2113
+ import { confirm as confirm5 } from "@inquirer/prompts";
2114
+ var PACKAGE_NAME = "@piut/cli";
2115
+ function isNpx() {
2116
+ return process.env.npm_command === "exec" || (process.env._?.includes("npx") ?? false);
2117
+ }
2118
+ async function getLatestVersion() {
2119
+ try {
2120
+ const res = await fetch(`https://registry.npmjs.org/${PACKAGE_NAME}/latest`);
2121
+ if (!res.ok) return null;
2122
+ const data = await res.json();
2123
+ return data.version ?? null;
2124
+ } catch {
2125
+ return null;
2126
+ }
2127
+ }
2128
+ function isNewer(current, latest) {
2129
+ const [cMaj, cMin, cPat] = current.split(".").map(Number);
2130
+ const [lMaj, lMin, lPat] = latest.split(".").map(Number);
2131
+ if (lMaj !== cMaj) return lMaj > cMaj;
2132
+ if (lMin !== cMin) return lMin > cMin;
2133
+ return lPat > cPat;
2134
+ }
2135
+ function runUpdate() {
2136
+ return new Promise((resolve) => {
2137
+ execFile("npm", ["install", "-g", `${PACKAGE_NAME}@latest`], { timeout: 6e4 }, (err) => {
2138
+ resolve(!err);
2139
+ });
2140
+ });
2141
+ }
2142
+ async function checkForUpdate(currentVersion) {
2143
+ const latest = await getLatestVersion();
2144
+ if (!latest || !isNewer(currentVersion, latest)) return;
2145
+ const npx = isNpx();
2146
+ const updateCmd = npx ? `npx ${PACKAGE_NAME}@latest` : `npm install -g ${PACKAGE_NAME}@latest`;
2147
+ console.log();
2148
+ console.log(brand(" Update available!") + dim(` ${currentVersion} \u2192 ${latest}`));
2149
+ console.log(dim(` Run ${chalk7.bold(updateCmd)} to update`));
2150
+ console.log();
2151
+ if (npx) return;
2152
+ try {
2153
+ const shouldUpdate = await confirm5({
2154
+ message: `Update to v${latest} now?`,
2155
+ default: true
2156
+ });
2157
+ if (shouldUpdate) {
2158
+ console.log(dim(" Updating..."));
2159
+ const ok = await runUpdate();
2160
+ if (ok) {
2161
+ console.log(chalk7.green(` \u2713 Updated to v${latest}`));
2162
+ console.log(dim(" Restart the CLI to use the new version."));
2163
+ process.exit(0);
2164
+ } else {
2165
+ console.log(chalk7.yellow(` Could not auto-update. Run manually:`));
2166
+ console.log(chalk7.bold(` npm install -g ${PACKAGE_NAME}@latest`));
2167
+ console.log();
2168
+ }
2169
+ }
2170
+ } catch {
2171
+ }
2172
+ }
2173
+
2174
+ // src/commands/update.ts
2175
+ var PACKAGE_NAME2 = "@piut/cli";
2176
+ async function updateCommand(currentVersion) {
2177
+ console.log(dim(` Current version: ${currentVersion}`));
2178
+ console.log(dim(" Checking for updates..."));
2179
+ const latest = await getLatestVersion();
2180
+ if (!latest) {
2181
+ console.log(chalk8.yellow(" Could not reach the npm registry. Check your connection."));
2182
+ return;
2183
+ }
2184
+ if (!isNewer(currentVersion, latest)) {
2185
+ console.log(success(` \u2713 You're on the latest version (${currentVersion})`));
2186
+ return;
2187
+ }
2188
+ console.log();
2189
+ console.log(brand(" Update available!") + dim(` ${currentVersion} \u2192 ${latest}`));
2190
+ if (isNpx()) {
2191
+ console.log();
2192
+ console.log(dim(" You're running via npx. Use the latest version with:"));
2193
+ console.log(chalk8.bold(` npx ${PACKAGE_NAME2}@latest`));
2194
+ console.log();
2195
+ return;
2196
+ }
2197
+ console.log(dim(" Updating..."));
2198
+ const ok = await runUpdate();
2199
+ if (ok) {
2200
+ console.log(success(` \u2713 Updated to v${latest}`));
2201
+ console.log(dim(" Restart the CLI to use the new version."));
2202
+ } else {
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
1829
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
+ }
2371
+ }
2372
+ console.log();
2373
+ if (result.issues > 0) throw new CliError(`${result.issues} issue(s) found`);
2374
+ }
2375
+
2376
+ // src/commands/interactive.ts
2377
+ import { select as select2, confirm as confirm6, checkbox as checkbox6, password as password3 } from "@inquirer/prompts";
2378
+ import fs12 from "fs";
1830
2379
  import path12 from "path";
1831
- import chalk6 from "chalk";
2380
+ import chalk10 from "chalk";
1832
2381
  async function authenticate() {
1833
2382
  const config = readStore();
1834
2383
  let apiKey = config.apiKey;
@@ -1856,7 +2405,7 @@ async function authenticate() {
1856
2405
  try {
1857
2406
  result = await validateKey(apiKey);
1858
2407
  } catch (err) {
1859
- console.log(chalk6.red(` \u2717 ${err.message}`));
2408
+ console.log(chalk10.red(` \u2717 ${err.message}`));
1860
2409
  console.log(dim(" Get a key at https://piut.com/dashboard/keys"));
1861
2410
  process.exit(1);
1862
2411
  }
@@ -1879,7 +2428,7 @@ async function interactiveMenu() {
1879
2428
  console.log(warning(" You haven\u2019t built a brain yet."));
1880
2429
  console.log(dim(" Your brain is how AI tools learn about you \u2014 your projects, preferences, and context."));
1881
2430
  console.log();
1882
- const wantBuild = await confirm5({
2431
+ const wantBuild = await confirm6({
1883
2432
  message: "Build your brain now?",
1884
2433
  default: true
1885
2434
  });
@@ -1895,7 +2444,7 @@ async function interactiveMenu() {
1895
2444
  console.log(warning(" Your brain is built but not deployed yet."));
1896
2445
  console.log(dim(" Deploy it to make your MCP server live so AI tools can read your brain."));
1897
2446
  console.log();
1898
- const wantDeploy = await confirm5({
2447
+ const wantDeploy = await confirm6({
1899
2448
  message: "Deploy your brain now?",
1900
2449
  default: true
1901
2450
  });
@@ -2015,7 +2564,7 @@ async function interactiveMenu() {
2015
2564
  } else if (err instanceof CliError) {
2016
2565
  console.log();
2017
2566
  } else {
2018
- console.log(chalk6.red(` Error: ${err.message}`));
2567
+ console.log(chalk10.red(` Error: ${err.message}`));
2019
2568
  console.log();
2020
2569
  }
2021
2570
  }
@@ -2026,7 +2575,7 @@ async function interactiveMenu() {
2026
2575
  }
2027
2576
  }
2028
2577
  async function handleUndeploy(apiKey) {
2029
- const confirmed = await confirm5({
2578
+ const confirmed = await confirm6({
2030
2579
  message: "Undeploy your brain? AI tools will lose access to your MCP server.",
2031
2580
  default: false
2032
2581
  });
@@ -2038,7 +2587,7 @@ async function handleUndeploy(apiKey) {
2038
2587
  console.log(dim(" Run ") + brand("piut deploy") + dim(" to re-deploy anytime."));
2039
2588
  console.log();
2040
2589
  } catch (err) {
2041
- console.log(chalk6.red(` \u2717 ${err.message}`));
2590
+ console.log(chalk10.red(` \u2717 ${err.message}`));
2042
2591
  }
2043
2592
  }
2044
2593
  async function handleConnectTools(apiKey, validation) {
@@ -2048,8 +2597,8 @@ async function handleConnectTools(apiKey, validation) {
2048
2597
  for (const tool of TOOLS) {
2049
2598
  const paths = resolveConfigPaths(tool.configPaths);
2050
2599
  for (const configPath of paths) {
2051
- const exists = fs11.existsSync(configPath);
2052
- const parentExists = fs11.existsSync(path12.dirname(configPath));
2600
+ const exists = fs12.existsSync(configPath);
2601
+ const parentExists = fs12.existsSync(path12.dirname(configPath));
2053
2602
  if (exists || parentExists) {
2054
2603
  if (exists && isPiutConfigured(configPath, tool.configKey)) {
2055
2604
  alreadyConnected.push(tool.name);
@@ -2102,7 +2651,7 @@ async function handleDisconnectTools() {
2102
2651
  for (const tool of TOOLS) {
2103
2652
  const paths = resolveConfigPaths(tool.configPaths);
2104
2653
  for (const configPath of paths) {
2105
- if (fs11.existsSync(configPath) && isPiutConfigured(configPath, tool.configKey)) {
2654
+ if (fs12.existsSync(configPath) && isPiutConfigured(configPath, tool.configKey)) {
2106
2655
  configured.push({ tool, configPath });
2107
2656
  break;
2108
2657
  }
@@ -2125,7 +2674,7 @@ async function handleDisconnectTools() {
2125
2674
  console.log(dim(" No tools selected."));
2126
2675
  return;
2127
2676
  }
2128
- const proceed = await confirm5({
2677
+ const proceed = await confirm6({
2129
2678
  message: `Disconnect p\u0131ut from ${selected.length} tool(s)?`,
2130
2679
  default: false
2131
2680
  });
@@ -2144,70 +2693,12 @@ async function handleDisconnectTools() {
2144
2693
  console.log();
2145
2694
  }
2146
2695
 
2147
- // src/lib/update-check.ts
2148
- import { execFile } from "child_process";
2149
- import chalk7 from "chalk";
2150
- import { confirm as confirm6 } from "@inquirer/prompts";
2151
- var PACKAGE_NAME = "@piut/cli";
2152
- async function getLatestVersion() {
2153
- try {
2154
- const res = await fetch(`https://registry.npmjs.org/${PACKAGE_NAME}/latest`);
2155
- if (!res.ok) return null;
2156
- const data = await res.json();
2157
- return data.version ?? null;
2158
- } catch {
2159
- return null;
2160
- }
2161
- }
2162
- function isNewer(current, latest) {
2163
- const [cMaj, cMin, cPat] = current.split(".").map(Number);
2164
- const [lMaj, lMin, lPat] = latest.split(".").map(Number);
2165
- if (lMaj !== cMaj) return lMaj > cMaj;
2166
- if (lMin !== cMin) return lMin > cMin;
2167
- return lPat > cPat;
2168
- }
2169
- function runUpdate() {
2170
- return new Promise((resolve) => {
2171
- execFile("npm", ["install", "-g", `${PACKAGE_NAME}@latest`], { timeout: 6e4 }, (err) => {
2172
- resolve(!err);
2173
- });
2174
- });
2175
- }
2176
- async function checkForUpdate(currentVersion) {
2177
- const latest = await getLatestVersion();
2178
- if (!latest || !isNewer(currentVersion, latest)) return;
2179
- console.log();
2180
- console.log(brand(" Update available!") + dim(` ${currentVersion} \u2192 ${latest}`));
2181
- console.log(dim(` Run ${chalk7.bold(`npm install -g ${PACKAGE_NAME}@latest`)} to update`));
2182
- console.log();
2183
- try {
2184
- const shouldUpdate = await confirm6({
2185
- message: `Update to v${latest} now?`,
2186
- default: true
2187
- });
2188
- if (shouldUpdate) {
2189
- console.log(dim(" Updating..."));
2190
- const ok = await runUpdate();
2191
- if (ok) {
2192
- console.log(chalk7.green(` \u2713 Updated to v${latest}`));
2193
- console.log(dim(" Restart the CLI to use the new version."));
2194
- process.exit(0);
2195
- } else {
2196
- console.log(chalk7.yellow(` Could not auto-update. Run manually:`));
2197
- console.log(chalk7.bold(` npm install -g ${PACKAGE_NAME}@latest`));
2198
- console.log();
2199
- }
2200
- }
2201
- } catch {
2202
- }
2203
- }
2204
-
2205
2696
  // src/cli.ts
2206
- var VERSION = "3.2.0";
2697
+ var VERSION = "3.4.0";
2207
2698
  function withExit(fn) {
2208
- return async (...args) => {
2699
+ return async (...args2) => {
2209
2700
  try {
2210
- await fn(...args);
2701
+ await fn(...args2);
2211
2702
  } catch (err) {
2212
2703
  if (err instanceof CliError) process.exit(1);
2213
2704
  throw err;
@@ -2215,13 +2706,24 @@ function withExit(fn) {
2215
2706
  };
2216
2707
  }
2217
2708
  var program = new Command();
2218
- program.name("piut").description("Build your AI brain instantly. Deploy it as an MCP server. Connect it to every project.").version(VERSION).hook("preAction", () => checkForUpdate(VERSION)).action(interactiveMenu);
2709
+ program.name("piut").description("Build your AI brain instantly. Deploy it as an MCP server. Connect it to every project.").version(VERSION).hook("preAction", (thisCommand, actionCommand) => {
2710
+ if (actionCommand.name() === "update") return;
2711
+ return checkForUpdate(VERSION);
2712
+ }).action(interactiveMenu);
2219
2713
  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));
2220
2714
  program.command("deploy").description("Publish your MCP server (requires paid account)").option("-k, --key <key>", "API key").action(withExit(deployCommand));
2221
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));
2222
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));
2223
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));
2224
- 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));
2225
2719
  program.command("remove").description("Remove all p\u0131ut configurations").action(withExit(removeCommand));
2226
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));
2722
+ program.command("update").description("Check for and install CLI updates").action(() => updateCommand(VERSION));
2723
+ var args = process.argv.slice(2);
2724
+ if (args.includes("--version") || args.includes("-V")) {
2725
+ console.log(VERSION);
2726
+ await checkForUpdate(VERSION);
2727
+ process.exit(0);
2728
+ }
2227
2729
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@piut/cli",
3
- "version": "3.2.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": {