@llmindset/hf-mcp 0.2.32 → 0.2.34
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/docs-search/docs-semantic-search.d.ts +2 -2
- package/dist/docs-search/docs-semantic-search.d.ts.map +1 -1
- package/dist/docs-search/docs-semantic-search.js +56 -21
- package/dist/docs-search/docs-semantic-search.js.map +1 -1
- package/dist/hf-api-call.d.ts.map +1 -1
- package/dist/hf-api-call.js +4 -0
- package/dist/hf-api-call.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/jobs/api-client.d.ts +19 -0
- package/dist/jobs/api-client.d.ts.map +1 -0
- package/dist/jobs/api-client.js +104 -0
- package/dist/jobs/api-client.js.map +1 -0
- package/dist/jobs/commands/inspect.d.ts +5 -0
- package/dist/jobs/commands/inspect.d.ts.map +1 -0
- package/dist/jobs/commands/inspect.js +21 -0
- package/dist/jobs/commands/inspect.js.map +1 -0
- package/dist/jobs/commands/logs.d.ts +4 -0
- package/dist/jobs/commands/logs.d.ts.map +1 -0
- package/dist/jobs/commands/logs.js +24 -0
- package/dist/jobs/commands/logs.js.map +1 -0
- package/dist/jobs/commands/ps.d.ts +4 -0
- package/dist/jobs/commands/ps.d.ts.map +1 -0
- package/dist/jobs/commands/ps.js +23 -0
- package/dist/jobs/commands/ps.js.map +1 -0
- package/dist/jobs/commands/run.d.ts +5 -0
- package/dist/jobs/commands/run.d.ts.map +1 -0
- package/dist/jobs/commands/run.js +89 -0
- package/dist/jobs/commands/run.js.map +1 -0
- package/dist/jobs/commands/scheduled.d.ts +10 -0
- package/dist/jobs/commands/scheduled.d.ts.map +1 -0
- package/dist/jobs/commands/scheduled.js +111 -0
- package/dist/jobs/commands/scheduled.js.map +1 -0
- package/dist/jobs/commands/utils.d.ts +19 -0
- package/dist/jobs/commands/utils.d.ts.map +1 -0
- package/dist/jobs/commands/utils.js +99 -0
- package/dist/jobs/commands/utils.js.map +1 -0
- package/dist/jobs/formatters.d.ts +6 -0
- package/dist/jobs/formatters.d.ts.map +1 -0
- package/dist/jobs/formatters.js +98 -0
- package/dist/jobs/formatters.js.map +1 -0
- package/dist/jobs/sse-handler.d.ts +12 -0
- package/dist/jobs/sse-handler.d.ts.map +1 -0
- package/dist/jobs/sse-handler.js +80 -0
- package/dist/jobs/sse-handler.js.map +1 -0
- package/dist/jobs/tool.d.ts +35 -0
- package/dist/jobs/tool.d.ts.map +1 -0
- package/dist/jobs/tool.js +333 -0
- package/dist/jobs/tool.js.map +1 -0
- package/dist/jobs/types.d.ts +295 -0
- package/dist/jobs/types.d.ts.map +1 -0
- package/dist/jobs/types.js +95 -0
- package/dist/jobs/types.js.map +1 -0
- package/dist/tool-ids.d.ts +3 -2
- package/dist/tool-ids.d.ts.map +1 -1
- package/dist/tool-ids.js +10 -2
- package/dist/tool-ids.js.map +1 -1
- package/dist/types/tool-result.d.ts +1 -0
- package/dist/types/tool-result.d.ts.map +1 -1
- package/dist/use-space.d.ts +0 -1
- package/dist/use-space.d.ts.map +1 -1
- package/dist/use-space.js +0 -1
- package/dist/use-space.js.map +1 -1
- package/package.json +4 -2
- package/src/docs-search/docs-semantic-search.ts +71 -20
- package/src/hf-api-call.ts +6 -0
- package/src/index.ts +1 -0
- package/src/jobs/api-client.ts +195 -0
- package/src/jobs/commands/inspect.ts +38 -0
- package/src/jobs/commands/logs.ts +36 -0
- package/src/jobs/commands/ps.ts +40 -0
- package/src/jobs/commands/run.ts +134 -0
- package/src/jobs/commands/scheduled.ts +189 -0
- package/src/jobs/commands/utils.ts +158 -0
- package/src/jobs/formatters.ts +149 -0
- package/src/jobs/sse-handler.ts +144 -0
- package/src/jobs/tool.ts +435 -0
- package/src/jobs/types.ts +237 -0
- package/src/tool-ids.ts +11 -1
- package/src/types/tool-result.ts +6 -0
- package/src/use-space.ts +0 -1
- package/test/jobs/command-translation.spec.ts +277 -0
- package/test/jobs/formatters.spec.ts +267 -0
- package/test/jobs/uv-command.spec.ts +81 -0
- package/dist/types/mcp-ui-server-shim.d.ts +0 -37
- package/dist/types/mcp-ui-server-shim.d.ts.map +0 -1
- package/dist/types/mcp-ui-server-shim.js +0 -2
- package/dist/types/mcp-ui-server-shim.js.map +0 -1
- package/src/types/mcp-ui-server-shim.ts +0 -35
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import type { JobInfo, ScheduledJobInfo } from './types.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Truncate a string to a maximum length with ellipsis
|
|
5
|
+
*/
|
|
6
|
+
function truncate(str: string, maxLength: number): string {
|
|
7
|
+
if (str.length <= maxLength) {
|
|
8
|
+
return str;
|
|
9
|
+
}
|
|
10
|
+
return str.substring(0, maxLength - 3) + '...';
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Format a date string to a readable format
|
|
15
|
+
*/
|
|
16
|
+
function formatDate(dateStr: string | undefined): string {
|
|
17
|
+
if (!dateStr) {
|
|
18
|
+
return 'N/A';
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
const date = new Date(dateStr);
|
|
22
|
+
return date.toISOString().replace('T', ' ').substring(0, 19);
|
|
23
|
+
} catch {
|
|
24
|
+
return dateStr;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Format command array as a single string
|
|
30
|
+
*/
|
|
31
|
+
function formatCommand(command?: string[]): string {
|
|
32
|
+
if (!command || command.length === 0) {
|
|
33
|
+
return 'N/A';
|
|
34
|
+
}
|
|
35
|
+
return command.join(' ');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Get image/space identifier from job
|
|
40
|
+
*/
|
|
41
|
+
function getImageOrSpace(job: JobInfo | { dockerImage?: string; spaceId?: string }): string {
|
|
42
|
+
if (job.spaceId) {
|
|
43
|
+
return job.spaceId;
|
|
44
|
+
}
|
|
45
|
+
if (job.dockerImage) {
|
|
46
|
+
return job.dockerImage;
|
|
47
|
+
}
|
|
48
|
+
return 'N/A';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Format jobs as a markdown table
|
|
53
|
+
*/
|
|
54
|
+
export function formatJobsTable(jobs: JobInfo[]): string {
|
|
55
|
+
if (jobs.length === 0) {
|
|
56
|
+
return 'No jobs found.';
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Calculate dynamic ID column width - never truncate IDs!
|
|
60
|
+
const longestIdLength = Math.max(...jobs.map((job) => job.id.length));
|
|
61
|
+
const idColumnWidth = Math.max(longestIdLength, 'JOB ID'.length);
|
|
62
|
+
|
|
63
|
+
// Define column widths
|
|
64
|
+
const colWidths = {
|
|
65
|
+
id: idColumnWidth,
|
|
66
|
+
image: 20,
|
|
67
|
+
command: 30,
|
|
68
|
+
created: 19,
|
|
69
|
+
status: 12,
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// Build header
|
|
73
|
+
const header = `| ${'JOB ID'.padEnd(colWidths.id)} | ${'IMAGE/SPACE'.padEnd(colWidths.image)} | ${'COMMAND'.padEnd(colWidths.command)} | ${'CREATED'.padEnd(colWidths.created)} | ${'STATUS'.padEnd(colWidths.status)} |`;
|
|
74
|
+
const separator = `|${'-'.repeat(colWidths.id + 2)}|${'-'.repeat(colWidths.image + 2)}|${'-'.repeat(colWidths.command + 2)}|${'-'.repeat(colWidths.created + 2)}|${'-'.repeat(colWidths.status + 2)}|`;
|
|
75
|
+
|
|
76
|
+
// Build rows
|
|
77
|
+
const rows = jobs.map((job) => {
|
|
78
|
+
const id = job.id; // Never truncate IDs!
|
|
79
|
+
const image = truncate(getImageOrSpace(job), colWidths.image);
|
|
80
|
+
const command = truncate(formatCommand(job.command), colWidths.command);
|
|
81
|
+
const created = truncate(formatDate(job.createdAt), colWidths.created);
|
|
82
|
+
const status = truncate(job.status.stage, colWidths.status);
|
|
83
|
+
|
|
84
|
+
return `| ${id.padEnd(colWidths.id)} | ${image.padEnd(colWidths.image)} | ${command.padEnd(colWidths.command)} | ${created.padEnd(colWidths.created)} | ${status.padEnd(colWidths.status)} |`;
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
return [header, separator, ...rows].join('\n');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Format scheduled jobs as a markdown table
|
|
92
|
+
*/
|
|
93
|
+
export function formatScheduledJobsTable(jobs: ScheduledJobInfo[]): string {
|
|
94
|
+
if (jobs.length === 0) {
|
|
95
|
+
return 'No scheduled jobs found.';
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Calculate dynamic ID column width - never truncate IDs!
|
|
99
|
+
const longestIdLength = Math.max(...jobs.map((job) => job.id.length));
|
|
100
|
+
const idColumnWidth = Math.max(longestIdLength, 'ID'.length);
|
|
101
|
+
|
|
102
|
+
// Define column widths
|
|
103
|
+
const colWidths = {
|
|
104
|
+
id: idColumnWidth,
|
|
105
|
+
schedule: 12,
|
|
106
|
+
image: 18,
|
|
107
|
+
command: 25,
|
|
108
|
+
lastRun: 19,
|
|
109
|
+
nextRun: 19,
|
|
110
|
+
suspend: 9,
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
// Build header
|
|
114
|
+
const header = `| ${'ID'.padEnd(colWidths.id)} | ${'SCHEDULE'.padEnd(colWidths.schedule)} | ${'IMAGE/SPACE'.padEnd(colWidths.image)} | ${'COMMAND'.padEnd(colWidths.command)} | ${'LAST RUN'.padEnd(colWidths.lastRun)} | ${'NEXT RUN'.padEnd(colWidths.nextRun)} | ${'SUSPENDED'.padEnd(colWidths.suspend)} |`;
|
|
115
|
+
const separator = `|${'-'.repeat(colWidths.id + 2)}|${'-'.repeat(colWidths.schedule + 2)}|${'-'.repeat(colWidths.image + 2)}|${'-'.repeat(colWidths.command + 2)}|${'-'.repeat(colWidths.lastRun + 2)}|${'-'.repeat(colWidths.nextRun + 2)}|${'-'.repeat(colWidths.suspend + 2)}|`;
|
|
116
|
+
|
|
117
|
+
// Build rows
|
|
118
|
+
const rows = jobs.map((job) => {
|
|
119
|
+
const id = job.id; // Never truncate IDs!
|
|
120
|
+
const schedule = truncate(job.schedule, colWidths.schedule);
|
|
121
|
+
const image = truncate(getImageOrSpace(job.jobSpec), colWidths.image);
|
|
122
|
+
const command = truncate(formatCommand(job.jobSpec.command), colWidths.command);
|
|
123
|
+
const lastRun = truncate(formatDate(job.lastRun), colWidths.lastRun);
|
|
124
|
+
const nextRun = truncate(formatDate(job.nextRun), colWidths.nextRun);
|
|
125
|
+
const suspend = job.suspend ? 'Yes' : 'No';
|
|
126
|
+
|
|
127
|
+
return `| ${id.padEnd(colWidths.id)} | ${schedule.padEnd(colWidths.schedule)} | ${image.padEnd(colWidths.image)} | ${command.padEnd(colWidths.command)} | ${lastRun.padEnd(colWidths.lastRun)} | ${nextRun.padEnd(colWidths.nextRun)} | ${suspend.padEnd(colWidths.suspend)} |`;
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
return [header, separator, ...rows].join('\n');
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Format job details as JSON in a markdown code block
|
|
135
|
+
*/
|
|
136
|
+
export function formatJobDetails(jobs: JobInfo | JobInfo[]): string {
|
|
137
|
+
const jobArray = Array.isArray(jobs) ? jobs : [jobs];
|
|
138
|
+
const json = JSON.stringify(jobArray, null, 2);
|
|
139
|
+
return `\`\`\`json\n${json}\n\`\`\``;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Format scheduled job details as JSON in a markdown code block
|
|
144
|
+
*/
|
|
145
|
+
export function formatScheduledJobDetails(jobs: ScheduledJobInfo | ScheduledJobInfo[]): string {
|
|
146
|
+
const jobArray = Array.isArray(jobs) ? jobs : [jobs];
|
|
147
|
+
const json = JSON.stringify(jobArray, null, 2);
|
|
148
|
+
return `\`\`\`json\n${json}\n\`\`\``;
|
|
149
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import type { LogEvent } from './types.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Options for fetching logs via SSE
|
|
5
|
+
*/
|
|
6
|
+
export interface SseLogOptions {
|
|
7
|
+
/** Maximum time to collect logs in milliseconds (default: 10000 = 10s) */
|
|
8
|
+
maxDuration?: number;
|
|
9
|
+
/** Maximum number of lines to return (default: 20) */
|
|
10
|
+
maxLines?: number;
|
|
11
|
+
/** HF API token for authentication */
|
|
12
|
+
token?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Result from fetching logs
|
|
17
|
+
*/
|
|
18
|
+
export interface SseLogResult {
|
|
19
|
+
/** Log lines collected */
|
|
20
|
+
logs: string[];
|
|
21
|
+
/** Whether the job finished during collection */
|
|
22
|
+
finished: boolean;
|
|
23
|
+
/** Whether collection was truncated due to timeout */
|
|
24
|
+
truncated: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Fetch logs from a job via Server-Sent Events (SSE)
|
|
29
|
+
* Collects logs for a maximum duration and returns the last N lines
|
|
30
|
+
*
|
|
31
|
+
* @param url - The SSE endpoint URL for job logs
|
|
32
|
+
* @param options - Options for log collection
|
|
33
|
+
* @returns Log result with collected lines and status
|
|
34
|
+
*/
|
|
35
|
+
export async function fetchJobLogs(url: string, options: SseLogOptions = {}): Promise<SseLogResult> {
|
|
36
|
+
const { maxDuration = 10000, maxLines = 20, token } = options;
|
|
37
|
+
|
|
38
|
+
const logLines: string[] = [];
|
|
39
|
+
let finished = false;
|
|
40
|
+
let truncated = false;
|
|
41
|
+
|
|
42
|
+
// Create abort controller for timeout
|
|
43
|
+
const controller = new AbortController();
|
|
44
|
+
const timeoutId = setTimeout(() => {
|
|
45
|
+
controller.abort();
|
|
46
|
+
truncated = true;
|
|
47
|
+
}, maxDuration);
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
const headers: Record<string, string> = {
|
|
51
|
+
Accept: 'text/event-stream',
|
|
52
|
+
};
|
|
53
|
+
if (token) {
|
|
54
|
+
headers['Authorization'] = `Bearer ${token}`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const response = await fetch(url, {
|
|
58
|
+
headers,
|
|
59
|
+
signal: controller.signal,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
if (!response.ok) {
|
|
63
|
+
throw new Error(`Failed to fetch logs: ${response.status} ${response.statusText}`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (!response.body) {
|
|
67
|
+
throw new Error('Response body is null');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Process the SSE stream
|
|
71
|
+
const reader = response.body.getReader();
|
|
72
|
+
const decoder = new TextDecoder();
|
|
73
|
+
let buffer = '';
|
|
74
|
+
|
|
75
|
+
while (true) {
|
|
76
|
+
const { done, value } = await reader.read();
|
|
77
|
+
|
|
78
|
+
if (done) {
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Decode chunk and add to buffer
|
|
83
|
+
buffer += decoder.decode(value, { stream: true });
|
|
84
|
+
|
|
85
|
+
// Process complete lines
|
|
86
|
+
const lines = buffer.split('\n');
|
|
87
|
+
buffer = lines.pop() || ''; // Keep incomplete line in buffer
|
|
88
|
+
|
|
89
|
+
for (const line of lines) {
|
|
90
|
+
// SSE format: "data: {json}"
|
|
91
|
+
if (line.startsWith('data: ')) {
|
|
92
|
+
try {
|
|
93
|
+
const jsonStr = line.substring(6); // Remove "data: " prefix
|
|
94
|
+
const event = JSON.parse(jsonStr) as LogEvent;
|
|
95
|
+
|
|
96
|
+
// Filter out system messages
|
|
97
|
+
if (event.data.startsWith('===== Job started')) {
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Check for job finished message
|
|
102
|
+
if (event.data.startsWith('===== Job finished')) {
|
|
103
|
+
finished = true;
|
|
104
|
+
// Extract status from message if present
|
|
105
|
+
// e.g., "===== Job finished: status=COMPLETED ====="
|
|
106
|
+
logLines.push(event.data);
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Add log line
|
|
111
|
+
logLines.push(event.data);
|
|
112
|
+
} catch {
|
|
113
|
+
// Ignore malformed JSON
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Break if job finished
|
|
120
|
+
if (finished) {
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Close the reader
|
|
126
|
+
await reader.cancel();
|
|
127
|
+
} catch (error) {
|
|
128
|
+
// If aborted due to timeout, that's expected
|
|
129
|
+
if ((error as Error).name !== 'AbortError') {
|
|
130
|
+
throw error;
|
|
131
|
+
}
|
|
132
|
+
} finally {
|
|
133
|
+
clearTimeout(timeoutId);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Return last N lines
|
|
137
|
+
const lastLines = logLines.slice(-maxLines);
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
logs: lastLines,
|
|
141
|
+
finished,
|
|
142
|
+
truncated: truncated && !finished,
|
|
143
|
+
};
|
|
144
|
+
}
|