@pasko70/pibo 1.4.5 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/dist/apps/chat/agent-profiles.js +4 -1
  2. package/dist/apps/chat/agent-store.js +196 -3
  3. package/dist/apps/chat/chat-settings-routes.js +24 -1
  4. package/dist/apps/chat/data/project-service.js +13 -3
  5. package/dist/apps/chat/telemetry-retention-service.js +69 -0
  6. package/dist/apps/chat/web-app.js +23 -12
  7. package/dist/apps/chat-ui/assets/{dist-oLAGkW6G.js → dist-B9sopUkn.js} +1 -1
  8. package/dist/apps/chat-ui/assets/{dist-BtF63vik.js → dist-BDQhMN_4.js} +1 -1
  9. package/dist/apps/chat-ui/assets/{dist-85Cc5Hut.js → dist-BEStK5um.js} +1 -1
  10. package/dist/apps/chat-ui/assets/{dist-D1Laodgo.js → dist-BiBY_4CK.js} +1 -1
  11. package/dist/apps/chat-ui/assets/{dist-eG89IJAV.js → dist-C2OyzisT.js} +1 -1
  12. package/dist/apps/chat-ui/assets/{dist-DE8W5WKg.js → dist-C9stINOY.js} +1 -1
  13. package/dist/apps/chat-ui/assets/{dist-ecTM1pdv.js → dist-CQKLsKIo.js} +1 -1
  14. package/dist/apps/chat-ui/assets/{dist-B8jspmzT.js → dist-CtSZyFkJ.js} +1 -1
  15. package/dist/apps/chat-ui/assets/{dist-DcME5mJj.js → dist-DPLEwPsG.js} +1 -1
  16. package/dist/apps/chat-ui/assets/{dist-CQXtvBTs.js → dist-DRTq4wrN.js} +1 -1
  17. package/dist/apps/chat-ui/assets/{dist-BGqK-7Ep.js → dist-F2W_jRom.js} +1 -1
  18. package/dist/apps/chat-ui/assets/index-D_60RTKn.css +1 -0
  19. package/dist/apps/chat-ui/assets/index-iaNLOwJ-.js +157 -0
  20. package/dist/apps/chat-ui/index.html +2 -2
  21. package/dist/apps/chat-vscode-web/assets/{index-CRUSv6iR.js → index-lA76A7Pc.js} +4 -4
  22. package/dist/apps/chat-vscode-web/index.html +1 -1
  23. package/dist/apps/cli-ui/inkMarkdown.js +8 -3
  24. package/dist/apps/cli-ui/inkSyntaxHighlighter.js +166 -0
  25. package/dist/apps/vscode-artifacts/latest.vsix +0 -0
  26. package/dist/apps/vscode-artifacts/{pibo-vscode-ext-1.4.5.vsix → pibo-vscode-ext-1.5.0.vsix} +0 -0
  27. package/dist/cli.js +20 -0
  28. package/dist/core/runtime-telemetry.js +11 -3
  29. package/dist/core/session-router.js +7 -1
  30. package/dist/core/telemetry-retention-settings.js +34 -0
  31. package/dist/core/user-settings.js +11 -0
  32. package/dist/data/telemetry.js +11 -0
  33. package/dist/mcp/agent-context.js +10 -6
  34. package/dist/mcp/commands/info.js +19 -7
  35. package/dist/mcp/config.js +98 -65
  36. package/dist/plugins/builtin.js +15 -1
  37. package/dist/session-ui/terminalRows.js +56 -2
  38. package/dist/shared/trace-nodes.js +12 -0
  39. package/dist/skills/cli.js +25 -1
  40. package/dist/tools/guides.js +71 -0
  41. package/dist/tools/index.js +7 -3
  42. package/dist/tools/python-runtime.js +2 -2
  43. package/dist/tools/registry.js +25 -1
  44. package/package.json +93 -93
  45. package/skills/builtin/graphify/SKILL.md +52 -0
  46. package/dist/apps/chat-ui/assets/index-B-qaya1G.css +0 -1
  47. package/dist/apps/chat-ui/assets/index-D4uifikB.js +0 -165
@@ -5,7 +5,7 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <meta name="theme-color" content="#101d22" />
7
7
  <title>Pibo</title>
8
- <script type="module" crossorigin src="/apps/chat-vscode/assets/index-CRUSv6iR.js"></script>
8
+ <script type="module" crossorigin src="/apps/chat-vscode/assets/index-lA76A7Pc.js"></script>
9
9
  <link rel="stylesheet" crossorigin href="/apps/chat-vscode/assets/index-B5QK07zO.css">
10
10
  </head>
11
11
  <body>
@@ -1,4 +1,5 @@
1
1
  import { sanitizeTerminalText, tokenizeJsonTextLine } from "./inkJson.js";
2
+ import { highlightInkCodeLine, normalizeInkCodeLanguage } from "./inkSyntaxHighlighter.js";
2
3
  export function renderInkMarkdownLines(markdown, options = {}) {
3
4
  return renderInkMarkdownTerminalLines(markdown, options).map((line) => line.tokens.map((token) => token.text).join(""));
4
5
  }
@@ -105,14 +106,18 @@ function inlineMarkdownTokens(text, defaults = {}) {
105
106
  return tokens.filter((token) => token.text.length > 0);
106
107
  }
107
108
  function codeFenceTokens(sourceLine, language) {
108
- if (isBashLanguage(language))
109
+ const normalizedLanguage = normalizeInkCodeLanguage(language);
110
+ if (isBashLanguage(normalizedLanguage))
109
111
  return [{ text: " " }, ...tokenizeInkBashCommand(sourceLine)];
110
- if (language === "json" || language === "jsonc")
112
+ if (normalizedLanguage === "json")
111
113
  return [{ text: " " }, ...tokenizeJsonTextLine(sourceLine)];
114
+ const highlighted = highlightInkCodeLine(sourceLine, normalizedLanguage);
115
+ if (highlighted)
116
+ return [{ text: " " }, ...highlighted];
112
117
  return [{ text: ` ${sourceLine}`, tone: "default" }];
113
118
  }
114
119
  function isBashLanguage(language) {
115
- return ["bash", "sh", "shell", "zsh"].includes(language);
120
+ return language === "bash";
116
121
  }
117
122
  function line(tokens) {
118
123
  return { prefix: "none", tokens };
@@ -0,0 +1,166 @@
1
+ import { createRequire } from "node:module";
2
+ const require = createRequire(import.meta.url);
3
+ const languageAliases = new Map([
4
+ ["sh", "bash"],
5
+ ["shell", "bash"],
6
+ ["shellscript", "bash"],
7
+ ["zsh", "bash"],
8
+ ["js", "javascript"],
9
+ ["mjs", "javascript"],
10
+ ["cjs", "javascript"],
11
+ ["ts", "typescript"],
12
+ ["mts", "typescript"],
13
+ ["cts", "typescript"],
14
+ ["md", "markdown"],
15
+ ["yml", "yaml"],
16
+ ["jsonc", "json"],
17
+ ["py", "python"],
18
+ ["rs", "rust"],
19
+ ]);
20
+ const grammarComponents = {
21
+ bash: ["bash"],
22
+ css: ["css"],
23
+ go: ["go"],
24
+ html: ["markup"],
25
+ javascript: ["javascript"],
26
+ json: ["json"],
27
+ jsx: ["markup", "javascript", "jsx"],
28
+ markdown: ["markup", "markdown"],
29
+ python: ["python"],
30
+ rust: ["rust"],
31
+ sql: ["sql"],
32
+ tsx: ["markup", "javascript", "jsx", "typescript", "tsx"],
33
+ typescript: ["javascript", "typescript"],
34
+ yaml: ["yaml"],
35
+ };
36
+ const loadedComponents = new Set();
37
+ let prismInstance;
38
+ export function highlightInkCodeLine(sourceLine, language) {
39
+ if (shouldUsePlainCodeTokens())
40
+ return [{ text: sourceLine, tone: "default" }];
41
+ const normalizedLanguage = normalizeInkCodeLanguage(language);
42
+ const prism = loadPrism();
43
+ if (!loadGrammar(prism, normalizedLanguage))
44
+ return undefined;
45
+ const grammar = prism.languages[normalizedLanguage];
46
+ if (!grammar)
47
+ return undefined;
48
+ try {
49
+ return flattenPrismTokens(prism.tokenize(sourceLine, grammar));
50
+ }
51
+ catch {
52
+ return undefined;
53
+ }
54
+ }
55
+ export function normalizeInkCodeLanguage(language) {
56
+ const normalized = language.trim().toLowerCase();
57
+ return languageAliases.get(normalized) ?? normalized;
58
+ }
59
+ function loadPrism() {
60
+ prismInstance ??= require("prismjs");
61
+ return prismInstance;
62
+ }
63
+ function loadGrammar(prism, language) {
64
+ const components = grammarComponents[language];
65
+ if (!components)
66
+ return false;
67
+ for (const component of components) {
68
+ if (loadedComponents.has(component))
69
+ continue;
70
+ require(`prismjs/components/prism-${component}.js`);
71
+ loadedComponents.add(component);
72
+ }
73
+ return Boolean(prism.languages[language]);
74
+ }
75
+ function flattenPrismTokens(tokens, inheritedTone = "default") {
76
+ const result = [];
77
+ for (const token of tokens) {
78
+ if (typeof token === "string") {
79
+ if (token.length > 0)
80
+ result.push({ text: token, tone: inheritedTone });
81
+ continue;
82
+ }
83
+ const tone = toneForPrismToken(token) ?? inheritedTone;
84
+ appendPrismContent(result, token.content, tone);
85
+ }
86
+ return mergeAdjacentTokens(result);
87
+ }
88
+ function appendPrismContent(result, content, tone) {
89
+ if (typeof content === "string") {
90
+ if (content.length > 0)
91
+ result.push({ text: content, tone });
92
+ return;
93
+ }
94
+ if (Array.isArray(content)) {
95
+ result.push(...flattenPrismTokens(content, tone));
96
+ return;
97
+ }
98
+ const nestedTone = toneForPrismToken(content) ?? tone;
99
+ appendPrismContent(result, content.content, nestedTone);
100
+ }
101
+ function toneForPrismToken(token) {
102
+ const classes = [token.type, ...aliasesForToken(token)];
103
+ for (const className of classes) {
104
+ switch (className) {
105
+ case "comment":
106
+ case "prolog":
107
+ case "doctype":
108
+ case "cdata":
109
+ return "dim";
110
+ case "string":
111
+ case "char":
112
+ case "attr-value":
113
+ case "url":
114
+ return "green";
115
+ case "number":
116
+ case "boolean":
117
+ case "constant":
118
+ return "blue";
119
+ case "keyword":
120
+ case "operator":
121
+ case "punctuation":
122
+ case "important":
123
+ case "atrule":
124
+ return "magenta";
125
+ case "function":
126
+ case "method":
127
+ case "selector":
128
+ case "class-name":
129
+ return "yellow";
130
+ case "tag":
131
+ case "property":
132
+ case "attr-name":
133
+ case "variable":
134
+ case "regex":
135
+ return "cyan";
136
+ case "builtin":
137
+ case "symbol":
138
+ case "deleted":
139
+ return "red";
140
+ default:
141
+ break;
142
+ }
143
+ }
144
+ return undefined;
145
+ }
146
+ function aliasesForToken(token) {
147
+ if (!token.alias)
148
+ return [];
149
+ return Array.isArray(token.alias) ? token.alias : [token.alias];
150
+ }
151
+ function mergeAdjacentTokens(tokens) {
152
+ const merged = [];
153
+ for (const token of tokens) {
154
+ const previous = merged[merged.length - 1];
155
+ if (previous && previous.tone === token.tone && previous.weight === token.weight && previous.italic === token.italic) {
156
+ previous.text += token.text;
157
+ }
158
+ else {
159
+ merged.push({ ...token });
160
+ }
161
+ }
162
+ return merged;
163
+ }
164
+ function shouldUsePlainCodeTokens() {
165
+ return Boolean(process.env.NO_COLOR) || process.env.TERM === "dumb" || process.env.PIBO_ASCII_PROGRESS === "1";
166
+ }
package/dist/cli.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { readFileSync } from "node:fs";
1
2
  import { Command } from "commander";
2
3
  import { PIBO_CONFIG_KEYS, getDefaultPiboConfigPath, deletePiboConfigValue, getDisplayPiboConfigValue, loadPiboConfig, redactPiboConfig, savePiboConfig, setPiboConfigValue, } from "./config/config.js";
3
4
  import { parsePiboThinkingLevel } from "./core/thinking.js";
@@ -20,6 +21,16 @@ function printConfigKeys() {
20
21
  function printRootDiscovery() {
21
22
  console.log(printRootDiscoveryText());
22
23
  }
24
+ function getPiboVersion() {
25
+ const packageJson = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf-8"));
26
+ if (typeof packageJson.version !== "string" || packageJson.version.length === 0) {
27
+ throw new Error("Unable to read Pibo package version");
28
+ }
29
+ return packageJson.version;
30
+ }
31
+ function printPiboVersion() {
32
+ console.log(getPiboVersion());
33
+ }
23
34
  function printConfigDiscovery() {
24
35
  console.log(printConfigDiscoveryText());
25
36
  }
@@ -46,6 +57,10 @@ export async function runPiboCli(argv = process.argv) {
46
57
  printRootDiscovery();
47
58
  return;
48
59
  }
60
+ if (argv[2] === "--version" || argv[2] === "-V") {
61
+ printPiboVersion();
62
+ return;
63
+ }
49
64
  if (argv[2] === "mcp") {
50
65
  const { runMcpCli } = await import("./mcp/index.js");
51
66
  await runMcpCli([argv[0] ?? "node", "pibo mcp", ...argv.slice(3)]);
@@ -348,6 +363,7 @@ export async function runPiboCli(argv = process.argv) {
348
363
  .option("--auth <mode>", "Auth service mode: 'better-auth' (default) or 'local' (loopback-only, no Google OAuth)")
349
364
  .option("--web-host <host>", "Bind the HTTP web host, for example 0.0.0.0 for LAN access")
350
365
  .option("--web-port <port>", "Bind the HTTP web host port", parsePort)
366
+ .option("--gateway-port <port>", "Bind the agent-runtime gateway port", parsePort)
351
367
  .action(async (options) => {
352
368
  const { runWebGatewayServer } = await import("./gateway/web.js");
353
369
  const authMode = options.auth;
@@ -360,6 +376,7 @@ export async function runPiboCli(argv = process.argv) {
360
376
  }
361
377
  await runWebGatewayServer({
362
378
  authMode: authMode,
379
+ port: options.gatewayPort,
363
380
  web: {
364
381
  host: options.webHost,
365
382
  port: options.webPort,
@@ -403,6 +420,9 @@ Commands:
403
420
  gateway Inspect and restart host gateways through safe CLI commands
404
421
  gateway:web Start a web gateway runtime (use --auth=local for loopback-only local auth)
405
422
 
423
+ Options:
424
+ --version Print the Pibo CLI version
425
+
406
426
  Next:
407
427
  pibo <command> --help
408
428
  `;
@@ -5,10 +5,12 @@ export class PiboRuntimeTelemetryRecorder {
5
5
  store;
6
6
  onError;
7
7
  telemetry;
8
- constructor(store, onError) {
8
+ providerEventMode;
9
+ constructor(store, onError, options = {}) {
9
10
  this.store = store;
10
11
  this.onError = onError;
11
12
  this.telemetry = new BestEffortTelemetryService(store, onError);
13
+ this.providerEventMode = options.providerEventMode ?? "aggregate";
12
14
  }
13
15
  recordOutput(event, context = {}) {
14
16
  if (!this.store)
@@ -91,7 +93,7 @@ export class PiboRuntimeTelemetryRecorder {
91
93
  if (summary.upstreamResponseId && summary.upstreamResponseId !== providerRequest.upstreamResponseId) {
92
94
  this.upsertProviderRequestFromExisting(providerRequest, { upstreamResponseId: summary.upstreamResponseId });
93
95
  }
94
- this.telemetry.appendProviderEventSummary({
96
+ const providerEventInput = {
95
97
  providerRequestId: providerRequest.providerRequestId,
96
98
  piboSessionId: turn.piboSessionId,
97
99
  turnId: turn.turnId,
@@ -106,7 +108,13 @@ export class PiboRuntimeTelemetryRecorder {
106
108
  itemId: summary.itemId,
107
109
  toolCallId: summary.toolCallId,
108
110
  safeFields: summary.safeFields,
109
- });
111
+ };
112
+ if (this.providerEventMode === "detailed") {
113
+ this.telemetry.appendProviderEventSummary(providerEventInput);
114
+ }
115
+ else {
116
+ this.telemetry.recordProviderEventSummary(providerEventInput);
117
+ }
110
118
  if (summary.toolCallId && summary.assistantEventType?.startsWith("toolcall_")) {
111
119
  this.recordPiToolCallProgress(turn, providerRequest.providerRequestId, summary, now);
112
120
  }
@@ -110,6 +110,10 @@ function piboRoomIdFromMetadata(metadata) {
110
110
  function telemetryStoreFromSessionStore(store) {
111
111
  return store.getTelemetryStore?.();
112
112
  }
113
+ function providerEventTelemetryModeFromEnv(env = process.env) {
114
+ const value = env.PIBO_TELEMETRY_PROVIDER_EVENTS?.trim().toLowerCase();
115
+ return value === "1" || value === "true" || value === "detailed" ? "detailed" : "aggregate";
116
+ }
113
117
  export class PiboSessionRouter {
114
118
  options;
115
119
  sessions = new Map();
@@ -130,7 +134,9 @@ export class PiboSessionRouter {
130
134
  this.pluginRegistry = options.pluginRegistry ?? createDefaultPiboPluginRegistry();
131
135
  this.sessionStore = options.sessionStore ?? new InMemoryPiboSessionStore();
132
136
  this.telemetryStore = options.telemetryStore ?? telemetryStoreFromSessionStore(this.sessionStore);
133
- this.telemetryRecorder = this.telemetryStore ? new PiboRuntimeTelemetryRecorder(this.telemetryStore) : undefined;
137
+ this.telemetryRecorder = this.telemetryStore
138
+ ? new PiboRuntimeTelemetryRecorder(this.telemetryStore, undefined, { providerEventMode: providerEventTelemetryModeFromEnv() })
139
+ : undefined;
134
140
  const defaultProfileName = selectDefaultPiboProfileName(this.pluginRegistry);
135
141
  this.baseProfile = options.profile ?? createPiboProfileFromRegistryOrDefault(this.pluginRegistry, defaultProfileName);
136
142
  this.reliabilityStore = options.reliabilityStore ?? (options.persistSession === false ? undefined : createDefaultPiboReliabilityStore());
@@ -0,0 +1,34 @@
1
+ export const DEFAULT_TELEMETRY_RETENTION_DAYS = 30;
2
+ export const MIN_TELEMETRY_RETENTION_DAYS = 1;
3
+ export const MAX_TELEMETRY_RETENTION_DAYS = 365;
4
+ export function sanitizeTelemetryRetentionSettings(value) {
5
+ const raw = value && typeof value === "object" && !Array.isArray(value)
6
+ ? value
7
+ : {};
8
+ const lastPrunedAt = sanitizeTelemetryRetentionTimestamp(raw.lastPrunedAt);
9
+ return {
10
+ enabled: typeof raw.enabled === "boolean" ? raw.enabled : true,
11
+ days: sanitizeTelemetryRetentionDays(raw.days) ?? DEFAULT_TELEMETRY_RETENTION_DAYS,
12
+ ...(lastPrunedAt ? { lastPrunedAt } : {}),
13
+ };
14
+ }
15
+ export function sanitizeTelemetryRetentionDays(value) {
16
+ if (typeof value !== "number" || !Number.isFinite(value))
17
+ return undefined;
18
+ const days = Math.trunc(value);
19
+ if (days < MIN_TELEMETRY_RETENTION_DAYS || days > MAX_TELEMETRY_RETENTION_DAYS)
20
+ return undefined;
21
+ return days;
22
+ }
23
+ export function sanitizeTelemetryRetentionTimestamp(value) {
24
+ if (typeof value !== "string")
25
+ return undefined;
26
+ const timestamp = value.trim();
27
+ if (!timestamp)
28
+ return undefined;
29
+ const ms = Date.parse(timestamp);
30
+ return Number.isFinite(ms) ? new Date(ms).toISOString() : undefined;
31
+ }
32
+ export function telemetryRetentionCutoff(days, now = new Date()) {
33
+ return new Date(now.getTime() - days * 24 * 60 * 60 * 1000).toISOString();
34
+ }
@@ -1,6 +1,7 @@
1
1
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
2
  import { dirname } from "node:path";
3
3
  import { piboHomePath } from "./pibo-home.js";
4
+ import { sanitizeTelemetryRetentionSettings } from "./telemetry-retention-settings.js";
4
5
  import { sanitizeTelemetryStaleThresholdSettings } from "./telemetry-staleness.js";
5
6
  export const DEFAULT_USER_TIMEZONE = "UTC";
6
7
  export const DEFAULT_WEB_ANNOTATIONS_TOGGLE_SHORTCUT = "Alt+Shift+A";
@@ -15,6 +16,15 @@ export function updatePiboUserSettings(patch) {
15
16
  writeState(state);
16
17
  return next;
17
18
  }
19
+ export function updateTelemetryRetentionLastPrunedAt(lastPrunedAt) {
20
+ const current = loadPiboUserSettings();
21
+ return updatePiboUserSettings({
22
+ telemetryRetention: {
23
+ ...current.telemetryRetention,
24
+ lastPrunedAt,
25
+ },
26
+ });
27
+ }
18
28
  export function sanitizeShortcutSettings(value) {
19
29
  const raw = value && typeof value === "object" && !Array.isArray(value)
20
30
  ? value
@@ -49,6 +59,7 @@ function sanitizeUserSettings(value) {
49
59
  timezone: sanitizeTimezone(raw.timezone) ?? DEFAULT_USER_TIMEZONE,
50
60
  shortcuts: sanitizeShortcutSettings(raw.shortcuts),
51
61
  telemetryStaleThresholds: sanitizeTelemetryStaleThresholdSettings(raw.telemetryStaleThresholds),
62
+ telemetryRetention: sanitizeTelemetryRetentionSettings(raw.telemetryRetention),
52
63
  };
53
64
  }
54
65
  function readState() {
@@ -168,6 +168,14 @@ export class TelemetryStore {
168
168
  `).run(providerRequestId, input.piboSessionId, input.rootSessionId ?? null, input.roomId ?? null, input.turnId, input.phaseId ?? null, input.provider, input.api, input.model, input.transport ?? existing?.transport ?? "unknown", input.serviceTier ?? null, input.status ?? existing?.status ?? "started", startedAt, input.responseHeadersAt ?? null, input.firstByteAt ?? null, input.lastRawEventAt ?? null, input.lastNormalizedEventAt ?? null, input.completedAt ?? null, input.httpStatus ?? null, input.upstreamResponseId ?? null, input.rawEventCount ?? existing?.rawEventCount ?? 0, input.normalizedEventCount ?? existing?.normalizedEventCount ?? 0, input.parseErrorCount ?? existing?.parseErrorCount ?? 0, input.unknownEventCount ?? existing?.unknownEventCount ?? 0, input.bytesReceived ?? existing?.bytesReceived ?? null, JSON.stringify(input.eventTypeCounts ?? existing?.eventTypeCounts ?? {}), input.eventStreamId ?? null, input.eventId ?? null, input.payloadRef ?? null, input.errorCategory ?? null, input.errorMessage ?? null, input.captureMode ?? existing?.captureMode ?? "metadata_only", input.retentionClass ?? existing?.retentionClass ?? "diagnostic", input.createdAt ?? existing?.createdAt ?? now, now);
169
169
  return this.getProviderRequest(providerRequestId) ?? fail(`Failed to upsert telemetry provider request ${providerRequestId}`);
170
170
  }
171
+ recordProviderEventSummary(input) {
172
+ const now = input.updatedAt ?? new Date().toISOString();
173
+ const receivedAt = input.receivedAt ?? now;
174
+ const byteSize = input.byteSize ?? 0;
175
+ const parseStatus = input.parseStatus ?? "ok";
176
+ const normalizedDelta = input.normalizedEventDelta ?? (input.normalizedType ? 1 : 0);
177
+ this.incrementProviderCounters(input.providerRequestId, input.eventType, receivedAt, byteSize, parseStatus, normalizedDelta);
178
+ }
171
179
  appendProviderEventSummary(input) {
172
180
  const now = input.updatedAt ?? new Date().toISOString();
173
181
  const receivedAt = input.receivedAt ?? now;
@@ -336,6 +344,9 @@ export class BestEffortTelemetryService {
336
344
  upsertProviderRequest(input) {
337
345
  return this.safe(() => this.store?.upsertProviderRequest(input));
338
346
  }
347
+ recordProviderEventSummary(input) {
348
+ this.safe(() => this.store?.recordProviderEventSummary(input));
349
+ }
339
350
  appendProviderEventSummary(input) {
340
351
  return this.safe(() => this.store?.appendProviderEventSummary(input));
341
352
  }
@@ -1,5 +1,5 @@
1
1
  import { readFile, writeFile } from 'node:fs/promises';
2
- import { ensureConfigExists, findConfigPath, isHttpServer, } from './config.js';
2
+ import { ensureConfigExists, isHttpServer, loadConfig, } from './config.js';
3
3
  import { ErrorCode, formatCliError } from './errors.js';
4
4
  export const MCP_SERVER_DESCRIPTION_MAX_LENGTH = 480;
5
5
  export const ENABLED_MCP_SERVERS_CONTEXT_PATH = '.pibo/context/enabled-mcp-servers.md';
@@ -22,11 +22,15 @@ export function normalizeMcpServerDescription(value) {
22
22
  return description;
23
23
  }
24
24
  export async function listMcpServerInfos(configPath) {
25
- const path = findConfigPath(configPath);
26
- if (!path)
27
- return [];
28
- const config = await readMcpConfig(path);
29
- return Object.entries(config.mcpServers).map(([name, server]) => mcpServerInfoFromConfig(name, server));
25
+ try {
26
+ const config = await loadConfig(configPath);
27
+ return Object.entries(config.mcpServers).map(([name, server]) => mcpServerInfoFromConfig(name, server));
28
+ }
29
+ catch (error) {
30
+ if (error.message.includes('CONFIG_NOT_FOUND'))
31
+ return [];
32
+ throw error;
33
+ }
30
34
  }
31
35
  export async function setMcpServerDescription(serverName, descriptionInput, configPath) {
32
36
  const description = normalizeMcpServerDescription(descriptionInput);
@@ -2,7 +2,7 @@
2
2
  * Info command - Show server or tool details
3
3
  */
4
4
  import { getConnection, safeClose } from '../client.js';
5
- import { getServerConfig, loadConfig, } from '../config.js';
5
+ import { formatConfigSourceSummaries, getConfigSourceSummaries, loadConfig, } from '../config.js';
6
6
  import { ErrorCode, formatCliError, serverConnectionError, toolNotFoundError, } from '../errors.js';
7
7
  import { formatServerDetails, formatToolSchema } from '../output.js';
8
8
  /**
@@ -28,12 +28,24 @@ export async function infoCommand(options) {
28
28
  process.exit(ErrorCode.CLIENT_ERROR);
29
29
  }
30
30
  const { server: serverName, tool: toolName } = parseTarget(options.target);
31
- let serverConfig;
32
- try {
33
- serverConfig = getServerConfig(config, serverName);
34
- }
35
- catch (error) {
36
- console.error(error.message);
31
+ const serverConfig = config.mcpServers[serverName];
32
+ if (!serverConfig) {
33
+ const available = Object.keys(config.mcpServers);
34
+ const serverList = available.length > 0 ? available.join(', ') : '(none)';
35
+ const summaries = await getConfigSourceSummaries(options.configPath);
36
+ console.error(formatCliError({
37
+ code: ErrorCode.CLIENT_ERROR,
38
+ type: 'SERVER_NOT_FOUND',
39
+ message: `Server "${serverName}" not found in config`,
40
+ details: [
41
+ `Merged available servers: ${serverList}`,
42
+ 'Config search paths:',
43
+ formatConfigSourceSummaries(summaries),
44
+ ].join('\n'),
45
+ suggestion: available.length > 0
46
+ ? `Use one of: ${available.map((s) => `pibo mcp info ${s}`).join(', ')}`
47
+ : `Add server to mcp_servers.json: { "mcpServers": { "${serverName}": { ... } } }`,
48
+ }));
37
49
  process.exit(ErrorCode.CLIENT_ERROR);
38
50
  }
39
51
  let connection;