@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.
- package/dist/cli.js +560 -111
- 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
|
|
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(
|
|
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
|
|
323
|
+
import os3 from "os";
|
|
261
324
|
import path2 from "path";
|
|
262
325
|
function expandPath(p) {
|
|
263
|
-
return p.replace(/^~/,
|
|
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
|
-
|
|
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
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
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
|
-
|
|
347
|
-
|
|
348
|
-
|
|
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:
|
|
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
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
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("
|
|
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
|
|
733
|
-
import
|
|
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
|
|
739
|
-
var home =
|
|
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 =
|
|
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 (!
|
|
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 =
|
|
1091
|
-
if (
|
|
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
|
|
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 (
|
|
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
|
|
1164
|
-
import
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
1276
|
-
{ name:
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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(
|
|
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(
|
|
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
|
-
|
|
1468
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
2108
|
+
import chalk8 from "chalk";
|
|
1829
2109
|
|
|
1830
2110
|
// src/lib/update-check.ts
|
|
1831
2111
|
import { execFile } from "child_process";
|
|
1832
|
-
import
|
|
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 ${
|
|
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(
|
|
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(
|
|
1886
|
-
console.log(
|
|
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(
|
|
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(
|
|
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(
|
|
1924
|
-
console.log(
|
|
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
|
|
2378
|
+
import fs12 from "fs";
|
|
1931
2379
|
import path12 from "path";
|
|
1932
|
-
import
|
|
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(
|
|
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(
|
|
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(
|
|
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 =
|
|
2153
|
-
const parentExists =
|
|
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 (
|
|
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.
|
|
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")) {
|