@evantahler/mcpx 0.15.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/.claude/settings.local.json +18 -0
- package/.claude/skills/mcpx.md +165 -0
- package/.claude/worktrees/elastic-jennings/.claude/settings.local.json +18 -0
- package/.claude/worktrees/elastic-jennings/.claude/skills/mcpcli.md +93 -0
- package/.claude/worktrees/elastic-jennings/.github/workflows/auto-release.yml +117 -0
- package/.claude/worktrees/elastic-jennings/.github/workflows/ci.yml +18 -0
- package/.claude/worktrees/elastic-jennings/.prettierignore +4 -0
- package/.claude/worktrees/elastic-jennings/.prettierrc +7 -0
- package/.claude/worktrees/elastic-jennings/CLAUDE.md +19 -0
- package/.claude/worktrees/elastic-jennings/LICENSE +21 -0
- package/.claude/worktrees/elastic-jennings/README.md +487 -0
- package/.claude/worktrees/elastic-jennings/bun.lock +381 -0
- package/.claude/worktrees/elastic-jennings/install.sh +55 -0
- package/.claude/worktrees/elastic-jennings/package.json +56 -0
- package/.claude/worktrees/elastic-jennings/src/cli.ts +39 -0
- package/.claude/worktrees/elastic-jennings/src/client/http.ts +100 -0
- package/.claude/worktrees/elastic-jennings/src/client/manager.ts +266 -0
- package/.claude/worktrees/elastic-jennings/src/client/oauth.ts +299 -0
- package/.claude/worktrees/elastic-jennings/src/client/stdio.ts +12 -0
- package/.claude/worktrees/elastic-jennings/src/commands/add.ts +155 -0
- package/.claude/worktrees/elastic-jennings/src/commands/auth.ts +114 -0
- package/.claude/worktrees/elastic-jennings/src/commands/exec.ts +91 -0
- package/.claude/worktrees/elastic-jennings/src/commands/index.ts +62 -0
- package/.claude/worktrees/elastic-jennings/src/commands/info.ts +38 -0
- package/.claude/worktrees/elastic-jennings/src/commands/list.ts +30 -0
- package/.claude/worktrees/elastic-jennings/src/commands/remove.ts +67 -0
- package/.claude/worktrees/elastic-jennings/src/commands/search.ts +45 -0
- package/.claude/worktrees/elastic-jennings/src/commands/skill.ts +70 -0
- package/.claude/worktrees/elastic-jennings/src/config/env.ts +41 -0
- package/.claude/worktrees/elastic-jennings/src/config/loader.ts +156 -0
- package/.claude/worktrees/elastic-jennings/src/config/schemas.ts +137 -0
- package/.claude/worktrees/elastic-jennings/src/context.ts +53 -0
- package/.claude/worktrees/elastic-jennings/src/output/formatter.ts +316 -0
- package/.claude/worktrees/elastic-jennings/src/output/logger.ts +114 -0
- package/.claude/worktrees/elastic-jennings/src/search/index.ts +69 -0
- package/.claude/worktrees/elastic-jennings/src/search/indexer.ts +92 -0
- package/.claude/worktrees/elastic-jennings/src/search/keyword.ts +86 -0
- package/.claude/worktrees/elastic-jennings/src/search/semantic.ts +75 -0
- package/.claude/worktrees/elastic-jennings/src/search/staleness.ts +8 -0
- package/.claude/worktrees/elastic-jennings/src/validation/schema.ts +77 -0
- package/.claude/worktrees/elastic-jennings/test/cli.test.ts +51 -0
- package/.claude/worktrees/elastic-jennings/test/client/manager.test.ts +249 -0
- package/.claude/worktrees/elastic-jennings/test/client/oauth.test.ts +328 -0
- package/.claude/worktrees/elastic-jennings/test/commands/add-remove.test.ts +253 -0
- package/.claude/worktrees/elastic-jennings/test/commands/exec.test.ts +105 -0
- package/.claude/worktrees/elastic-jennings/test/commands/info.test.ts +48 -0
- package/.claude/worktrees/elastic-jennings/test/commands/list.test.ts +39 -0
- package/.claude/worktrees/elastic-jennings/test/commands/skill.test.ts +98 -0
- package/.claude/worktrees/elastic-jennings/test/config/env.test.ts +61 -0
- package/.claude/worktrees/elastic-jennings/test/config/loader.test.ts +139 -0
- package/.claude/worktrees/elastic-jennings/test/fixtures/.keep +0 -0
- package/.claude/worktrees/elastic-jennings/test/fixtures/auth.json +10 -0
- package/.claude/worktrees/elastic-jennings/test/fixtures/mock-config/.keep +0 -0
- package/.claude/worktrees/elastic-jennings/test/fixtures/mock-config/servers.json +8 -0
- package/.claude/worktrees/elastic-jennings/test/fixtures/mock-server.ts +113 -0
- package/.claude/worktrees/elastic-jennings/test/fixtures/search.json +15 -0
- package/.claude/worktrees/elastic-jennings/test/fixtures/servers.json +18 -0
- package/.claude/worktrees/elastic-jennings/test/integration/stdio-server.test.ts +149 -0
- package/.claude/worktrees/elastic-jennings/test/output/formatter.test.ts +54 -0
- package/.claude/worktrees/elastic-jennings/test/output/logger.test.ts +89 -0
- package/.claude/worktrees/elastic-jennings/test/search/indexer.test.ts +32 -0
- package/.claude/worktrees/elastic-jennings/test/search/keyword.test.ts +80 -0
- package/.claude/worktrees/elastic-jennings/test/search/semantic.test.ts +32 -0
- package/.claude/worktrees/elastic-jennings/test/validation/schema.test.ts +113 -0
- package/.claude/worktrees/elastic-jennings/tsconfig.json +29 -0
- package/.cursor/rules/mcpx.mdc +165 -0
- package/LICENSE +21 -0
- package/README.md +627 -0
- package/package.json +58 -0
- package/src/cli.ts +72 -0
- package/src/client/browser.ts +24 -0
- package/src/client/debug-fetch.ts +81 -0
- package/src/client/elicitation.ts +368 -0
- package/src/client/http.ts +25 -0
- package/src/client/manager.ts +566 -0
- package/src/client/oauth.ts +314 -0
- package/src/client/sse.ts +17 -0
- package/src/client/stdio.ts +12 -0
- package/src/client/trace.ts +184 -0
- package/src/commands/add.ts +179 -0
- package/src/commands/auth.ts +114 -0
- package/src/commands/exec.ts +156 -0
- package/src/commands/index.ts +62 -0
- package/src/commands/info.ts +63 -0
- package/src/commands/list.ts +64 -0
- package/src/commands/ping.ts +69 -0
- package/src/commands/prompt.ts +60 -0
- package/src/commands/remove.ts +67 -0
- package/src/commands/resource.ts +46 -0
- package/src/commands/search.ts +49 -0
- package/src/commands/servers.ts +66 -0
- package/src/commands/skill.ts +112 -0
- package/src/commands/task.ts +82 -0
- package/src/config/env.ts +41 -0
- package/src/config/loader.ts +156 -0
- package/src/config/schemas.ts +152 -0
- package/src/context.ts +62 -0
- package/src/lib/input.ts +36 -0
- package/src/output/formatter.ts +884 -0
- package/src/output/logger.ts +173 -0
- package/src/search/index.ts +69 -0
- package/src/search/indexer.ts +92 -0
- package/src/search/keyword.ts +86 -0
- package/src/search/semantic.ts +75 -0
- package/src/search/staleness.ts +8 -0
- package/src/validation/schema.ts +103 -0
|
@@ -0,0 +1,884 @@
|
|
|
1
|
+
import ansis, { bold, cyan, dim, green, red, yellow } from "ansis";
|
|
2
|
+
import type { Tool, Resource, Prompt } from "../config/schemas.ts";
|
|
3
|
+
import type { ToolWithServer, ResourceWithServer, PromptWithServer } from "../client/manager.ts";
|
|
4
|
+
import type { ValidationError } from "../validation/schema.ts";
|
|
5
|
+
import type { SearchResult } from "../search/index.ts";
|
|
6
|
+
|
|
7
|
+
export interface FormatOptions {
|
|
8
|
+
json?: boolean;
|
|
9
|
+
withDescriptions?: boolean;
|
|
10
|
+
verbose?: boolean;
|
|
11
|
+
showSecrets?: boolean;
|
|
12
|
+
logLevel?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface UnifiedItem {
|
|
16
|
+
server: string;
|
|
17
|
+
type: "tool" | "resource" | "prompt";
|
|
18
|
+
name: string;
|
|
19
|
+
description?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Check if stdout is a TTY (interactive terminal) */
|
|
23
|
+
export function isInteractive(options: FormatOptions): boolean {
|
|
24
|
+
if (options.json) return false;
|
|
25
|
+
return process.stdout.isTTY ?? false;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Get terminal width, or undefined if not a TTY. Subtracts 1 for safety margin. */
|
|
29
|
+
function getTerminalWidth(): number | undefined {
|
|
30
|
+
if (process.stdout.isTTY) return Math.max(process.stdout.columns - 1, 40);
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Measure visible length of a string (excluding ANSI escape codes) */
|
|
35
|
+
function visibleLength(s: string): number {
|
|
36
|
+
return ansis.strip(s).length;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Word-wrap text to a max width, hard-breaking words that exceed it */
|
|
40
|
+
function wrapLines(text: string, maxWidth: number): string[] {
|
|
41
|
+
const words = text.split(/\s+/).filter((w) => w.length > 0);
|
|
42
|
+
if (words.length === 0) return [""];
|
|
43
|
+
|
|
44
|
+
const lines: string[] = [];
|
|
45
|
+
let current = "";
|
|
46
|
+
|
|
47
|
+
for (const word of words) {
|
|
48
|
+
if (word.length > maxWidth) {
|
|
49
|
+
if (current) {
|
|
50
|
+
lines.push(current);
|
|
51
|
+
current = "";
|
|
52
|
+
}
|
|
53
|
+
for (let j = 0; j < word.length; j += maxWidth) {
|
|
54
|
+
lines.push(word.slice(j, j + maxWidth));
|
|
55
|
+
}
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (current.length === 0) {
|
|
59
|
+
current = word;
|
|
60
|
+
} else if (current.length + 1 + word.length <= maxWidth) {
|
|
61
|
+
current += " " + word;
|
|
62
|
+
} else {
|
|
63
|
+
lines.push(current);
|
|
64
|
+
current = word;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (current) lines.push(current);
|
|
68
|
+
return lines;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Word-wrap a description string to fit within the available terminal width.
|
|
73
|
+
* Returns dim()-wrapped text with continuation lines indented to prefixWidth.
|
|
74
|
+
* @param text - raw description text (before dim())
|
|
75
|
+
* @param prefixWidth - visible character width of everything before the description
|
|
76
|
+
* @param termWidth - terminal width in columns
|
|
77
|
+
*/
|
|
78
|
+
export function wrapDescription(text: string, prefixWidth: number, termWidth: number): string {
|
|
79
|
+
const available = termWidth - prefixWidth;
|
|
80
|
+
|
|
81
|
+
// If prefix is so wide there's barely room, wrap onto the next line with a small indent
|
|
82
|
+
if (available < 20) {
|
|
83
|
+
const fallbackIndent = Math.min(prefixWidth, 4);
|
|
84
|
+
const fallbackAvail = termWidth - fallbackIndent;
|
|
85
|
+
if (fallbackAvail < 20) {
|
|
86
|
+
return dim(text.length > termWidth ? text.slice(0, termWidth - 3) + "..." : text);
|
|
87
|
+
}
|
|
88
|
+
const wrapped = wrapLines(text, fallbackAvail);
|
|
89
|
+
const indent = " ".repeat(fallbackIndent);
|
|
90
|
+
return wrapped.map((l) => `\n${indent}${dim(l)}`).join("");
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const wrapped = wrapLines(text, available);
|
|
94
|
+
const indent = " ".repeat(prefixWidth);
|
|
95
|
+
return wrapped.map((l, i) => (i === 0 ? dim(l) : `\n${indent}${dim(l)}`)).join("");
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export interface ServerOverview {
|
|
99
|
+
serverName: string;
|
|
100
|
+
version?: { name: string; version: string };
|
|
101
|
+
capabilities?: Record<string, unknown>;
|
|
102
|
+
instructions?: string;
|
|
103
|
+
tools: Tool[];
|
|
104
|
+
resourceCount: number;
|
|
105
|
+
promptCount: number;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const KNOWN_CAPABILITIES = ["tools", "resources", "prompts", "logging", "completions", "tasks"];
|
|
109
|
+
|
|
110
|
+
/** Format a full server overview (version, capabilities, tools, counts) */
|
|
111
|
+
export function formatServerOverview(overview: ServerOverview, options: FormatOptions): string {
|
|
112
|
+
if (!isInteractive(options)) {
|
|
113
|
+
return JSON.stringify(
|
|
114
|
+
{
|
|
115
|
+
server: overview.serverName,
|
|
116
|
+
version: overview.version ?? null,
|
|
117
|
+
capabilities: overview.capabilities ?? null,
|
|
118
|
+
instructions: overview.instructions ?? null,
|
|
119
|
+
tools: overview.tools.map((t) => ({ name: t.name, description: t.description ?? "" })),
|
|
120
|
+
resourceCount: overview.resourceCount,
|
|
121
|
+
promptCount: overview.promptCount,
|
|
122
|
+
},
|
|
123
|
+
null,
|
|
124
|
+
2,
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const lines: string[] = [];
|
|
129
|
+
|
|
130
|
+
// Header: server name + version
|
|
131
|
+
const header = cyan.bold(overview.serverName);
|
|
132
|
+
if (overview.version) {
|
|
133
|
+
lines.push(
|
|
134
|
+
`${header} ${dim(`v${overview.version.version}`)} ${dim(`(${overview.version.name})`)}`,
|
|
135
|
+
);
|
|
136
|
+
} else {
|
|
137
|
+
lines.push(header);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Capabilities
|
|
141
|
+
if (overview.capabilities) {
|
|
142
|
+
lines.push("");
|
|
143
|
+
lines.push(bold("Capabilities:"));
|
|
144
|
+
const caps = overview.capabilities;
|
|
145
|
+
const present = KNOWN_CAPABILITIES.filter((k) => k in caps);
|
|
146
|
+
const absent = KNOWN_CAPABILITIES.filter((k) => !(k in caps));
|
|
147
|
+
const capLines: string[] = [];
|
|
148
|
+
for (const k of present) capLines.push(` ${green("✓")} ${k}`);
|
|
149
|
+
for (const k of absent) capLines.push(` ${dim("✗")} ${dim(k)}`);
|
|
150
|
+
lines.push(...capLines);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Instructions
|
|
154
|
+
if (overview.instructions) {
|
|
155
|
+
lines.push("");
|
|
156
|
+
lines.push(bold("Instructions:"));
|
|
157
|
+
lines.push(` ${dim(overview.instructions)}`);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Tools
|
|
161
|
+
lines.push("");
|
|
162
|
+
if (overview.tools.length === 0) {
|
|
163
|
+
lines.push(bold("Tools:") + " " + dim("none"));
|
|
164
|
+
} else {
|
|
165
|
+
lines.push(bold(`Tools (${overview.tools.length}):`));
|
|
166
|
+
const maxName = Math.max(...overview.tools.map((t) => t.name.length));
|
|
167
|
+
const termWidth = getTerminalWidth();
|
|
168
|
+
for (let i = 0; i < overview.tools.length; i++) {
|
|
169
|
+
const t = overview.tools[i];
|
|
170
|
+
if (i > 0) lines.push("");
|
|
171
|
+
const name = ` ${bold(t.name.padEnd(maxName))}`;
|
|
172
|
+
if (t.description) {
|
|
173
|
+
const pw = visibleLength(name) + 2;
|
|
174
|
+
const desc =
|
|
175
|
+
termWidth != null ? wrapDescription(t.description, pw, termWidth) : dim(t.description);
|
|
176
|
+
lines.push(`${name} ${desc}`);
|
|
177
|
+
} else {
|
|
178
|
+
lines.push(name);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Resource/prompt counts
|
|
184
|
+
const counts: string[] = [];
|
|
185
|
+
counts.push(`Resources: ${overview.resourceCount}`);
|
|
186
|
+
counts.push(`Prompts: ${overview.promptCount}`);
|
|
187
|
+
lines.push("");
|
|
188
|
+
lines.push(dim(counts.join(" | ")));
|
|
189
|
+
|
|
190
|
+
return lines.join("\n");
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** Format a list of tools with server names */
|
|
194
|
+
export function formatToolList(tools: ToolWithServer[], options: FormatOptions): string {
|
|
195
|
+
if (!isInteractive(options)) {
|
|
196
|
+
if (options.withDescriptions) {
|
|
197
|
+
return JSON.stringify(
|
|
198
|
+
tools.map((t) => ({
|
|
199
|
+
server: t.server,
|
|
200
|
+
tool: t.tool.name,
|
|
201
|
+
description: t.tool.description ?? "",
|
|
202
|
+
})),
|
|
203
|
+
null,
|
|
204
|
+
2,
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
return JSON.stringify(
|
|
208
|
+
tools.map((t) => ({ server: t.server, tool: t.tool.name })),
|
|
209
|
+
null,
|
|
210
|
+
2,
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (tools.length === 0) {
|
|
215
|
+
return dim("No tools found");
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Calculate column widths
|
|
219
|
+
const maxServer = Math.max(...tools.map((t) => t.server.length));
|
|
220
|
+
const maxTool = Math.max(...tools.map((t) => t.tool.name.length));
|
|
221
|
+
const termWidth = getTerminalWidth();
|
|
222
|
+
|
|
223
|
+
return tools
|
|
224
|
+
.map((t) => {
|
|
225
|
+
const server = cyan(t.server.padEnd(maxServer));
|
|
226
|
+
const tool = bold(t.tool.name.padEnd(maxTool));
|
|
227
|
+
if (options.withDescriptions && t.tool.description) {
|
|
228
|
+
const prefix = `${server} ${tool}`;
|
|
229
|
+
const pw = visibleLength(prefix) + 2;
|
|
230
|
+
const desc =
|
|
231
|
+
termWidth != null
|
|
232
|
+
? wrapDescription(t.tool.description, pw, termWidth)
|
|
233
|
+
: dim(t.tool.description);
|
|
234
|
+
return `${prefix} ${desc}`;
|
|
235
|
+
}
|
|
236
|
+
return `${server} ${tool}`;
|
|
237
|
+
})
|
|
238
|
+
.join("\n");
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/** Format tools for a single server */
|
|
242
|
+
export function formatServerTools(
|
|
243
|
+
serverName: string,
|
|
244
|
+
tools: Tool[],
|
|
245
|
+
options: FormatOptions,
|
|
246
|
+
): string {
|
|
247
|
+
if (!isInteractive(options)) {
|
|
248
|
+
return JSON.stringify(
|
|
249
|
+
{
|
|
250
|
+
server: serverName,
|
|
251
|
+
tools: tools.map((t) => ({ name: t.name, description: t.description ?? "" })),
|
|
252
|
+
},
|
|
253
|
+
null,
|
|
254
|
+
2,
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (tools.length === 0) {
|
|
259
|
+
return dim(`No tools found for ${serverName}`);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const header = cyan.bold(serverName);
|
|
263
|
+
const maxName = Math.max(...tools.map((t) => t.name.length));
|
|
264
|
+
const termWidth = getTerminalWidth();
|
|
265
|
+
|
|
266
|
+
const lines = tools.map((t) => {
|
|
267
|
+
const name = ` ${bold(t.name.padEnd(maxName))}`;
|
|
268
|
+
if (t.description) {
|
|
269
|
+
const pw = visibleLength(name) + 2;
|
|
270
|
+
const desc =
|
|
271
|
+
termWidth != null ? wrapDescription(t.description, pw, termWidth) : dim(t.description);
|
|
272
|
+
return `${name} ${desc}`;
|
|
273
|
+
}
|
|
274
|
+
return name;
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
return [header, ...lines].join("\n");
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/** Format a tool schema */
|
|
281
|
+
export function formatToolSchema(serverName: string, tool: Tool, options: FormatOptions): string {
|
|
282
|
+
if (!isInteractive(options)) {
|
|
283
|
+
return JSON.stringify(
|
|
284
|
+
{
|
|
285
|
+
server: serverName,
|
|
286
|
+
tool: tool.name,
|
|
287
|
+
description: tool.description ?? "",
|
|
288
|
+
inputSchema: tool.inputSchema,
|
|
289
|
+
},
|
|
290
|
+
null,
|
|
291
|
+
2,
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const lines: string[] = [];
|
|
296
|
+
lines.push(`${cyan(serverName)}/${bold(tool.name)}`);
|
|
297
|
+
|
|
298
|
+
if (tool.description) {
|
|
299
|
+
lines.push(dim(tool.description));
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
lines.push("");
|
|
303
|
+
lines.push(bold("Input Schema:"));
|
|
304
|
+
lines.push(formatSchema(tool.inputSchema, 2));
|
|
305
|
+
|
|
306
|
+
return lines.join("\n");
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/** Format a JSON schema as a readable parameter list */
|
|
310
|
+
function formatSchema(schema: Tool["inputSchema"], indent: number): string {
|
|
311
|
+
const pad = " ".repeat(indent);
|
|
312
|
+
const properties = schema.properties ?? {};
|
|
313
|
+
const required = new Set(schema.required ?? []);
|
|
314
|
+
|
|
315
|
+
if (Object.keys(properties).length === 0) {
|
|
316
|
+
return `${pad}${dim("(no parameters)")}`;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
return Object.entries(properties)
|
|
320
|
+
.map(([name, prop]) => {
|
|
321
|
+
const p = prop as Record<string, unknown>;
|
|
322
|
+
const type = (p.type as string) ?? "any";
|
|
323
|
+
const req = required.has(name) ? red("*") : "";
|
|
324
|
+
const desc = p.description ? ` ${dim(String(p.description))}` : "";
|
|
325
|
+
return `${pad}${green(name)}${req} ${dim(`(${type})`)}${desc}`;
|
|
326
|
+
})
|
|
327
|
+
.join("\n");
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/** Format detailed tool help with example payload */
|
|
331
|
+
export function formatToolHelp(serverName: string, tool: Tool, options: FormatOptions): string {
|
|
332
|
+
if (!isInteractive(options)) {
|
|
333
|
+
return JSON.stringify(
|
|
334
|
+
{
|
|
335
|
+
server: serverName,
|
|
336
|
+
tool: tool.name,
|
|
337
|
+
description: tool.description ?? "",
|
|
338
|
+
inputSchema: tool.inputSchema,
|
|
339
|
+
example: generateExample(tool.inputSchema),
|
|
340
|
+
},
|
|
341
|
+
null,
|
|
342
|
+
2,
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const lines: string[] = [];
|
|
347
|
+
lines.push(`${cyan(serverName)}/${bold(tool.name)}`);
|
|
348
|
+
|
|
349
|
+
if (tool.description) {
|
|
350
|
+
lines.push(dim(tool.description));
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
lines.push("");
|
|
354
|
+
lines.push(bold("Parameters:"));
|
|
355
|
+
lines.push(formatSchema(tool.inputSchema, 2));
|
|
356
|
+
|
|
357
|
+
const example = generateExample(tool.inputSchema);
|
|
358
|
+
lines.push("");
|
|
359
|
+
lines.push(bold("Example:"));
|
|
360
|
+
lines.push(dim(` mcpx call ${serverName} ${tool.name} '${JSON.stringify(example)}'`));
|
|
361
|
+
|
|
362
|
+
return lines.join("\n");
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/** Generate an example payload from a JSON schema */
|
|
366
|
+
function generateExample(schema: Tool["inputSchema"]): Record<string, unknown> {
|
|
367
|
+
const properties = schema.properties ?? {};
|
|
368
|
+
const required = new Set(schema.required ?? []);
|
|
369
|
+
const example: Record<string, unknown> = {};
|
|
370
|
+
|
|
371
|
+
for (const [name, prop] of Object.entries(properties)) {
|
|
372
|
+
const p = prop as Record<string, unknown>;
|
|
373
|
+
// Include required fields and first few optional fields
|
|
374
|
+
if (required.has(name) || Object.keys(example).length < 3) {
|
|
375
|
+
example[name] = exampleValue(name, p);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
return example;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function exampleValue(name: string, prop: Record<string, unknown>): unknown {
|
|
383
|
+
// Use enum first choice if available
|
|
384
|
+
if (Array.isArray(prop.enum) && prop.enum.length > 0) {
|
|
385
|
+
return prop.enum[0];
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Use default if provided
|
|
389
|
+
if (prop.default !== undefined) {
|
|
390
|
+
return prop.default;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
const type = prop.type as string | undefined;
|
|
394
|
+
switch (type) {
|
|
395
|
+
case "string":
|
|
396
|
+
return `<${name}>`;
|
|
397
|
+
case "number":
|
|
398
|
+
case "integer":
|
|
399
|
+
return 0;
|
|
400
|
+
case "boolean":
|
|
401
|
+
return true;
|
|
402
|
+
case "array":
|
|
403
|
+
return [];
|
|
404
|
+
case "object":
|
|
405
|
+
return {};
|
|
406
|
+
default:
|
|
407
|
+
return `<${name}>`;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/** Format a tool call result */
|
|
412
|
+
export function formatCallResult(result: unknown, _options: FormatOptions): string {
|
|
413
|
+
return JSON.stringify(parseNestedJson(result), null, 2);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/** Recursively parse JSON strings inside MCP content blocks */
|
|
417
|
+
function parseNestedJson(value: unknown): unknown {
|
|
418
|
+
if (typeof value === "string") {
|
|
419
|
+
try {
|
|
420
|
+
return parseNestedJson(JSON.parse(value));
|
|
421
|
+
} catch {
|
|
422
|
+
return value;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
if (Array.isArray(value)) {
|
|
426
|
+
return value.map(parseNestedJson);
|
|
427
|
+
}
|
|
428
|
+
if (typeof value === "object" && value !== null) {
|
|
429
|
+
return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, parseNestedJson(v)]));
|
|
430
|
+
}
|
|
431
|
+
return value;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/** Format validation errors for tool input */
|
|
435
|
+
export function formatValidationErrors(
|
|
436
|
+
serverName: string,
|
|
437
|
+
toolName: string,
|
|
438
|
+
errors: ValidationError[],
|
|
439
|
+
options: FormatOptions,
|
|
440
|
+
): string {
|
|
441
|
+
if (!isInteractive(options)) {
|
|
442
|
+
return JSON.stringify({
|
|
443
|
+
error: "validation",
|
|
444
|
+
server: serverName,
|
|
445
|
+
tool: toolName,
|
|
446
|
+
details: errors,
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
const header = `${red("error:")} invalid arguments for ${cyan(serverName)}/${bold(toolName)}`;
|
|
451
|
+
const details = errors.map((e) => ` ${yellow(e.path)}: ${e.message}`).join("\n");
|
|
452
|
+
return `${header}\n${details}`;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/** Format search results */
|
|
456
|
+
export function formatSearchResults(results: SearchResult[], options: FormatOptions): string {
|
|
457
|
+
if (!isInteractive(options)) {
|
|
458
|
+
return JSON.stringify(results, null, 2);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
if (results.length === 0) {
|
|
462
|
+
return dim("No matching tools found");
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
const termWidth = getTerminalWidth();
|
|
466
|
+
const descIndent = 2;
|
|
467
|
+
|
|
468
|
+
return results
|
|
469
|
+
.map((r) => {
|
|
470
|
+
const header = `${cyan(r.server)} ${bold(r.tool)} ${yellow(r.score.toFixed(2))}`;
|
|
471
|
+
|
|
472
|
+
// Join all description lines into a single string for wrapping
|
|
473
|
+
const fullDesc = r.description
|
|
474
|
+
.split("\n")
|
|
475
|
+
.map((l) => l.trim())
|
|
476
|
+
.filter((l) => l.length > 0)
|
|
477
|
+
.join(" ");
|
|
478
|
+
|
|
479
|
+
const indent = " ".repeat(descIndent);
|
|
480
|
+
const desc =
|
|
481
|
+
termWidth != null ? wrapDescription(fullDesc, descIndent, termWidth) : dim(fullDesc);
|
|
482
|
+
|
|
483
|
+
return `${header}\n${indent}${desc}`;
|
|
484
|
+
})
|
|
485
|
+
.join("\n\n");
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/** Format a list of resources with server names */
|
|
489
|
+
export function formatResourceList(
|
|
490
|
+
resources: ResourceWithServer[],
|
|
491
|
+
options: FormatOptions,
|
|
492
|
+
): string {
|
|
493
|
+
if (!isInteractive(options)) {
|
|
494
|
+
return JSON.stringify(
|
|
495
|
+
resources.map((r) => ({
|
|
496
|
+
server: r.server,
|
|
497
|
+
uri: r.resource.uri,
|
|
498
|
+
name: r.resource.name,
|
|
499
|
+
...(options.withDescriptions ? { description: r.resource.description ?? "" } : {}),
|
|
500
|
+
})),
|
|
501
|
+
null,
|
|
502
|
+
2,
|
|
503
|
+
);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
if (resources.length === 0) {
|
|
507
|
+
return dim("No resources found");
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
const maxServer = Math.max(...resources.map((r) => r.server.length));
|
|
511
|
+
const maxUri = Math.max(...resources.map((r) => r.resource.uri.length));
|
|
512
|
+
const termWidth = getTerminalWidth();
|
|
513
|
+
|
|
514
|
+
return resources
|
|
515
|
+
.map((r) => {
|
|
516
|
+
const server = cyan(r.server.padEnd(maxServer));
|
|
517
|
+
const uri = bold(r.resource.uri.padEnd(maxUri));
|
|
518
|
+
if (options.withDescriptions && r.resource.description) {
|
|
519
|
+
const prefix = `${server} ${uri}`;
|
|
520
|
+
const pw = visibleLength(prefix) + 2;
|
|
521
|
+
const desc =
|
|
522
|
+
termWidth != null
|
|
523
|
+
? wrapDescription(r.resource.description, pw, termWidth)
|
|
524
|
+
: dim(r.resource.description);
|
|
525
|
+
return `${prefix} ${desc}`;
|
|
526
|
+
}
|
|
527
|
+
return `${server} ${uri}`;
|
|
528
|
+
})
|
|
529
|
+
.join("\n");
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/** Format resources for a single server */
|
|
533
|
+
export function formatServerResources(
|
|
534
|
+
serverName: string,
|
|
535
|
+
resources: Resource[],
|
|
536
|
+
options: FormatOptions,
|
|
537
|
+
): string {
|
|
538
|
+
if (!isInteractive(options)) {
|
|
539
|
+
return JSON.stringify(
|
|
540
|
+
{
|
|
541
|
+
server: serverName,
|
|
542
|
+
resources: resources.map((r) => ({
|
|
543
|
+
uri: r.uri,
|
|
544
|
+
name: r.name,
|
|
545
|
+
description: r.description ?? "",
|
|
546
|
+
mimeType: r.mimeType ?? "",
|
|
547
|
+
})),
|
|
548
|
+
},
|
|
549
|
+
null,
|
|
550
|
+
2,
|
|
551
|
+
);
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
if (resources.length === 0) {
|
|
555
|
+
return dim(`No resources found for ${serverName}`);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
const header = cyan.bold(serverName);
|
|
559
|
+
const maxUri = Math.max(...resources.map((r) => r.uri.length));
|
|
560
|
+
const termWidth = getTerminalWidth();
|
|
561
|
+
|
|
562
|
+
const lines = resources.map((r) => {
|
|
563
|
+
const uri = ` ${bold(r.uri.padEnd(maxUri))}`;
|
|
564
|
+
if (r.description) {
|
|
565
|
+
const pw = visibleLength(uri) + 2;
|
|
566
|
+
const desc =
|
|
567
|
+
termWidth != null ? wrapDescription(r.description, pw, termWidth) : dim(r.description);
|
|
568
|
+
return `${uri} ${desc}`;
|
|
569
|
+
}
|
|
570
|
+
return uri;
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
return [header, ...lines].join("\n");
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
/** Format resource contents */
|
|
577
|
+
export function formatResourceContents(
|
|
578
|
+
serverName: string,
|
|
579
|
+
uri: string,
|
|
580
|
+
result: unknown,
|
|
581
|
+
options: FormatOptions,
|
|
582
|
+
): string {
|
|
583
|
+
if (!isInteractive(options)) {
|
|
584
|
+
return JSON.stringify(
|
|
585
|
+
{ server: serverName, uri, contents: (result as { contents: unknown })?.contents ?? result },
|
|
586
|
+
null,
|
|
587
|
+
2,
|
|
588
|
+
);
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
const contents =
|
|
592
|
+
(result as { contents?: Array<{ text?: string; blob?: string; mimeType?: string }> })
|
|
593
|
+
?.contents ?? [];
|
|
594
|
+
const lines: string[] = [];
|
|
595
|
+
lines.push(`${cyan(serverName)}/${bold(uri)}`);
|
|
596
|
+
lines.push("");
|
|
597
|
+
|
|
598
|
+
if (contents.length === 0) {
|
|
599
|
+
lines.push(dim("(empty)"));
|
|
600
|
+
} else {
|
|
601
|
+
for (const item of contents) {
|
|
602
|
+
if (item.text !== undefined) {
|
|
603
|
+
lines.push(item.text);
|
|
604
|
+
} else if (item.blob !== undefined) {
|
|
605
|
+
lines.push(dim(`<binary blob, ${item.blob.length} bytes base64>`));
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
return lines.join("\n");
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
/** Format a list of prompts with server names */
|
|
614
|
+
export function formatPromptList(prompts: PromptWithServer[], options: FormatOptions): string {
|
|
615
|
+
if (!isInteractive(options)) {
|
|
616
|
+
return JSON.stringify(
|
|
617
|
+
prompts.map((p) => ({
|
|
618
|
+
server: p.server,
|
|
619
|
+
name: p.prompt.name,
|
|
620
|
+
...(options.withDescriptions ? { description: p.prompt.description ?? "" } : {}),
|
|
621
|
+
})),
|
|
622
|
+
null,
|
|
623
|
+
2,
|
|
624
|
+
);
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
if (prompts.length === 0) {
|
|
628
|
+
return dim("No prompts found");
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
const maxServer = Math.max(...prompts.map((p) => p.server.length));
|
|
632
|
+
const maxName = Math.max(...prompts.map((p) => p.prompt.name.length));
|
|
633
|
+
const termWidth = getTerminalWidth();
|
|
634
|
+
|
|
635
|
+
return prompts
|
|
636
|
+
.map((p) => {
|
|
637
|
+
const server = cyan(p.server.padEnd(maxServer));
|
|
638
|
+
const name = bold(p.prompt.name.padEnd(maxName));
|
|
639
|
+
if (options.withDescriptions && p.prompt.description) {
|
|
640
|
+
const prefix = `${server} ${name}`;
|
|
641
|
+
const pw = visibleLength(prefix) + 2;
|
|
642
|
+
const desc =
|
|
643
|
+
termWidth != null
|
|
644
|
+
? wrapDescription(p.prompt.description, pw, termWidth)
|
|
645
|
+
: dim(p.prompt.description);
|
|
646
|
+
return `${prefix} ${desc}`;
|
|
647
|
+
}
|
|
648
|
+
return `${server} ${name}`;
|
|
649
|
+
})
|
|
650
|
+
.join("\n");
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
/** Format prompts for a single server */
|
|
654
|
+
export function formatServerPrompts(
|
|
655
|
+
serverName: string,
|
|
656
|
+
prompts: Prompt[],
|
|
657
|
+
options: FormatOptions,
|
|
658
|
+
): string {
|
|
659
|
+
if (!isInteractive(options)) {
|
|
660
|
+
return JSON.stringify(
|
|
661
|
+
{
|
|
662
|
+
server: serverName,
|
|
663
|
+
prompts: prompts.map((p) => ({
|
|
664
|
+
name: p.name,
|
|
665
|
+
description: p.description ?? "",
|
|
666
|
+
arguments: p.arguments ?? [],
|
|
667
|
+
})),
|
|
668
|
+
},
|
|
669
|
+
null,
|
|
670
|
+
2,
|
|
671
|
+
);
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
if (prompts.length === 0) {
|
|
675
|
+
return dim(`No prompts found for ${serverName}`);
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
const header = cyan.bold(serverName);
|
|
679
|
+
const maxName = Math.max(...prompts.map((p) => p.name.length));
|
|
680
|
+
|
|
681
|
+
const termWidth = getTerminalWidth();
|
|
682
|
+
|
|
683
|
+
const lines = prompts.map((p) => {
|
|
684
|
+
const name = ` ${bold(p.name.padEnd(maxName))}`;
|
|
685
|
+
const args =
|
|
686
|
+
p.arguments && p.arguments.length > 0
|
|
687
|
+
? ` ${dim(`(${p.arguments.map((a) => (a.required ? a.name : `[${a.name}]`)).join(", ")})`)}`
|
|
688
|
+
: "";
|
|
689
|
+
if (p.description) {
|
|
690
|
+
const prefix = `${name}${args}`;
|
|
691
|
+
const pw = visibleLength(prefix) + 2;
|
|
692
|
+
const desc =
|
|
693
|
+
termWidth != null ? wrapDescription(p.description, pw, termWidth) : dim(p.description);
|
|
694
|
+
return `${prefix} ${desc}`;
|
|
695
|
+
}
|
|
696
|
+
return `${name}${args}`;
|
|
697
|
+
});
|
|
698
|
+
|
|
699
|
+
return [header, ...lines].join("\n");
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
/** Format prompt messages */
|
|
703
|
+
export function formatPromptMessages(
|
|
704
|
+
serverName: string,
|
|
705
|
+
name: string,
|
|
706
|
+
result: unknown,
|
|
707
|
+
options: FormatOptions,
|
|
708
|
+
): string {
|
|
709
|
+
if (!isInteractive(options)) {
|
|
710
|
+
return JSON.stringify({ server: serverName, prompt: name, ...(result as object) }, null, 2);
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
const r = result as {
|
|
714
|
+
description?: string;
|
|
715
|
+
messages?: Array<{ role: string; content: { type: string; text?: string } }>;
|
|
716
|
+
};
|
|
717
|
+
const lines: string[] = [];
|
|
718
|
+
lines.push(`${cyan(serverName)}/${bold(name)}`);
|
|
719
|
+
|
|
720
|
+
if (r.description) {
|
|
721
|
+
lines.push(dim(r.description));
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
lines.push("");
|
|
725
|
+
|
|
726
|
+
for (const msg of r.messages ?? []) {
|
|
727
|
+
lines.push(`${bold(msg.role)}:`);
|
|
728
|
+
if (msg.content.text !== undefined) {
|
|
729
|
+
lines.push(` ${msg.content.text}`);
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
return lines.join("\n");
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
/** Format a unified list of tools, resources, and prompts across servers */
|
|
737
|
+
export function formatUnifiedList(items: UnifiedItem[], options: FormatOptions): string {
|
|
738
|
+
if (!isInteractive(options)) {
|
|
739
|
+
return JSON.stringify(
|
|
740
|
+
items.map((i) => ({
|
|
741
|
+
server: i.server,
|
|
742
|
+
type: i.type,
|
|
743
|
+
name: i.name,
|
|
744
|
+
...(options.withDescriptions ? { description: i.description ?? "" } : {}),
|
|
745
|
+
})),
|
|
746
|
+
null,
|
|
747
|
+
2,
|
|
748
|
+
);
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
if (items.length === 0) {
|
|
752
|
+
return dim("No tools, resources, or prompts found");
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
const maxServer = Math.max(...items.map((i) => i.server.length));
|
|
756
|
+
const maxType = 8; // "resource" is the longest at 8 chars
|
|
757
|
+
const maxName = Math.max(...items.map((i) => i.name.length));
|
|
758
|
+
|
|
759
|
+
const typeLabel = (t: UnifiedItem["type"]) => {
|
|
760
|
+
const padded = t.padEnd(maxType);
|
|
761
|
+
if (t === "tool") return green(padded);
|
|
762
|
+
if (t === "resource") return cyan(padded);
|
|
763
|
+
return yellow(padded);
|
|
764
|
+
};
|
|
765
|
+
|
|
766
|
+
const termWidth = getTerminalWidth();
|
|
767
|
+
|
|
768
|
+
return items
|
|
769
|
+
.map((i) => {
|
|
770
|
+
const server = cyan(i.server.padEnd(maxServer));
|
|
771
|
+
const type = typeLabel(i.type);
|
|
772
|
+
const name = bold(i.name.padEnd(maxName));
|
|
773
|
+
if (options.withDescriptions && i.description) {
|
|
774
|
+
const prefix = `${server} ${type} ${name}`;
|
|
775
|
+
const pw = visibleLength(prefix) + 2;
|
|
776
|
+
const desc =
|
|
777
|
+
termWidth != null ? wrapDescription(i.description, pw, termWidth) : dim(i.description);
|
|
778
|
+
return `${prefix} ${desc}`;
|
|
779
|
+
}
|
|
780
|
+
return `${server} ${type} ${name}`;
|
|
781
|
+
})
|
|
782
|
+
.join("\n");
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
/** Format a single task status */
|
|
786
|
+
export function formatTaskStatus(
|
|
787
|
+
task: { taskId: string; status: string; [key: string]: unknown },
|
|
788
|
+
options: FormatOptions,
|
|
789
|
+
): string {
|
|
790
|
+
if (!isInteractive(options)) {
|
|
791
|
+
return JSON.stringify(task, null, 2);
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
const statusColor = (s: string) => {
|
|
795
|
+
switch (s) {
|
|
796
|
+
case "completed":
|
|
797
|
+
return green(s);
|
|
798
|
+
case "working":
|
|
799
|
+
return yellow(s);
|
|
800
|
+
case "failed":
|
|
801
|
+
case "cancelled":
|
|
802
|
+
return red(s);
|
|
803
|
+
case "input_required":
|
|
804
|
+
return yellow(s);
|
|
805
|
+
default:
|
|
806
|
+
return s;
|
|
807
|
+
}
|
|
808
|
+
};
|
|
809
|
+
|
|
810
|
+
const lines: string[] = [];
|
|
811
|
+
lines.push(`${bold("Task:")} ${cyan(task.taskId)}`);
|
|
812
|
+
lines.push(`${bold("Status:")} ${statusColor(task.status)}`);
|
|
813
|
+
if (task.statusMessage) lines.push(`${bold("Message:")} ${dim(String(task.statusMessage))}`);
|
|
814
|
+
if (task.createdAt) lines.push(`${bold("Created:")} ${dim(String(task.createdAt))}`);
|
|
815
|
+
if (task.lastUpdatedAt) lines.push(`${bold("Updated:")} ${dim(String(task.lastUpdatedAt))}`);
|
|
816
|
+
if (task.ttl != null) lines.push(`${bold("TTL:")} ${dim(String(task.ttl) + "ms")}`);
|
|
817
|
+
if (task.pollInterval != null)
|
|
818
|
+
lines.push(`${bold("Poll interval:")} ${dim(String(task.pollInterval) + "ms")}`);
|
|
819
|
+
return lines.join("\n");
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
/** Format a list of tasks */
|
|
823
|
+
export function formatTasksList(
|
|
824
|
+
tasks: Array<{ taskId: string; status: string; [key: string]: unknown }>,
|
|
825
|
+
nextCursor: string | undefined,
|
|
826
|
+
options: FormatOptions,
|
|
827
|
+
): string {
|
|
828
|
+
if (!isInteractive(options)) {
|
|
829
|
+
return JSON.stringify({ tasks, ...(nextCursor ? { nextCursor } : {}) }, null, 2);
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
if (tasks.length === 0) {
|
|
833
|
+
return dim("No tasks found");
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
const statusColor = (s: string) => {
|
|
837
|
+
switch (s) {
|
|
838
|
+
case "completed":
|
|
839
|
+
return green(s.padEnd(14));
|
|
840
|
+
case "working":
|
|
841
|
+
return yellow(s.padEnd(14));
|
|
842
|
+
case "failed":
|
|
843
|
+
case "cancelled":
|
|
844
|
+
return red(s.padEnd(14));
|
|
845
|
+
default:
|
|
846
|
+
return s.padEnd(14);
|
|
847
|
+
}
|
|
848
|
+
};
|
|
849
|
+
|
|
850
|
+
const maxId = Math.max(...tasks.map((t) => t.taskId.length));
|
|
851
|
+
|
|
852
|
+
const lines = tasks.map((t) => {
|
|
853
|
+
const id = cyan(t.taskId.padEnd(maxId));
|
|
854
|
+
const status = statusColor(t.status);
|
|
855
|
+
const updated = t.lastUpdatedAt ? dim(String(t.lastUpdatedAt)) : "";
|
|
856
|
+
return `${id} ${status} ${updated}`;
|
|
857
|
+
});
|
|
858
|
+
|
|
859
|
+
if (nextCursor) {
|
|
860
|
+
lines.push("");
|
|
861
|
+
lines.push(dim(`Next cursor: ${nextCursor}`));
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
return lines.join("\n");
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
/** Format task creation output (for --no-wait) */
|
|
868
|
+
export function formatTaskCreated(
|
|
869
|
+
task: { taskId: string; status: string; [key: string]: unknown },
|
|
870
|
+
options: FormatOptions,
|
|
871
|
+
): string {
|
|
872
|
+
if (!isInteractive(options)) {
|
|
873
|
+
return JSON.stringify({ task }, null, 2);
|
|
874
|
+
}
|
|
875
|
+
return `${green("Task created:")} ${cyan(task.taskId)} ${dim(`(status: ${task.status})`)}`;
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
/** Format an error message */
|
|
879
|
+
export function formatError(message: string, options: FormatOptions): string {
|
|
880
|
+
if (!isInteractive(options)) {
|
|
881
|
+
return JSON.stringify({ error: message });
|
|
882
|
+
}
|
|
883
|
+
return `${red("error:")} ${message}`;
|
|
884
|
+
}
|