@llmindset/hf-mcp 0.2.33 → 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/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/package.json +3 -1
- 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/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
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { formatJobsTable, formatScheduledJobsTable, formatJobDetails } from '../../src/jobs/formatters.js';
|
|
3
|
+
import type { JobInfo, ScheduledJobInfo, JobSpec } from '../../src/jobs/types.js';
|
|
4
|
+
|
|
5
|
+
describe('Jobs Formatters', () => {
|
|
6
|
+
describe('formatJobsTable', () => {
|
|
7
|
+
it('should return message for empty job list', () => {
|
|
8
|
+
const result = formatJobsTable([]);
|
|
9
|
+
expect(result).toBe('No jobs found.');
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('should format a single job as markdown table', () => {
|
|
13
|
+
const jobs: JobInfo[] = [
|
|
14
|
+
{
|
|
15
|
+
id: 'job123',
|
|
16
|
+
createdAt: '2025-01-20T10:00:00Z',
|
|
17
|
+
dockerImage: 'python:3.12',
|
|
18
|
+
command: ['python', 'script.py'],
|
|
19
|
+
flavor: 'cpu-basic',
|
|
20
|
+
status: { stage: 'RUNNING' },
|
|
21
|
+
owner: { id: 'u123', name: 'testuser', type: 'user' },
|
|
22
|
+
environment: {},
|
|
23
|
+
},
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
const result = formatJobsTable(jobs);
|
|
27
|
+
|
|
28
|
+
// Should be a markdown table
|
|
29
|
+
expect(result).toContain('| JOB ID');
|
|
30
|
+
expect(result).toContain('| IMAGE/SPACE');
|
|
31
|
+
expect(result).toContain('| COMMAND');
|
|
32
|
+
expect(result).toContain('| CREATED');
|
|
33
|
+
expect(result).toContain('| STATUS');
|
|
34
|
+
|
|
35
|
+
// Should contain separator line
|
|
36
|
+
expect(result).toContain('|---');
|
|
37
|
+
|
|
38
|
+
// Should contain job data
|
|
39
|
+
expect(result).toContain('job123');
|
|
40
|
+
expect(result).toContain('python:3.12');
|
|
41
|
+
expect(result).toContain('python script.py');
|
|
42
|
+
expect(result).toContain('RUNNING');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('should format multiple jobs', () => {
|
|
46
|
+
const jobs: JobInfo[] = [
|
|
47
|
+
{
|
|
48
|
+
id: 'job123',
|
|
49
|
+
createdAt: '2025-01-20T10:00:00Z',
|
|
50
|
+
dockerImage: 'python:3.12',
|
|
51
|
+
command: ['python', 'script.py'],
|
|
52
|
+
flavor: 'cpu-basic',
|
|
53
|
+
status: { stage: 'RUNNING' },
|
|
54
|
+
owner: { id: 'u123', name: 'testuser', type: 'user' },
|
|
55
|
+
environment: {},
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
id: 'job456',
|
|
59
|
+
createdAt: '2025-01-20T11:00:00Z',
|
|
60
|
+
dockerImage: 'ubuntu',
|
|
61
|
+
command: ['bash', 'test.sh'],
|
|
62
|
+
flavor: 'a10g-small',
|
|
63
|
+
status: { stage: 'COMPLETED' },
|
|
64
|
+
owner: { id: 'u123', name: 'testuser', type: 'user' },
|
|
65
|
+
environment: {},
|
|
66
|
+
},
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
const result = formatJobsTable(jobs);
|
|
70
|
+
|
|
71
|
+
// Should contain both jobs
|
|
72
|
+
expect(result).toContain('job123');
|
|
73
|
+
expect(result).toContain('job456');
|
|
74
|
+
expect(result).toContain('python:3.12');
|
|
75
|
+
expect(result).toContain('ubuntu');
|
|
76
|
+
expect(result).toContain('RUNNING');
|
|
77
|
+
expect(result).toContain('COMPLETED');
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('should handle Space IDs instead of Docker images', () => {
|
|
81
|
+
const jobs: JobInfo[] = [
|
|
82
|
+
{
|
|
83
|
+
id: 'job789',
|
|
84
|
+
createdAt: '2025-01-20T12:00:00Z',
|
|
85
|
+
spaceId: 'user/myspace',
|
|
86
|
+
command: ['python', 'app.py'],
|
|
87
|
+
flavor: 'cpu-basic',
|
|
88
|
+
status: { stage: 'RUNNING' },
|
|
89
|
+
owner: { id: 'u123', name: 'testuser', type: 'user' },
|
|
90
|
+
environment: {},
|
|
91
|
+
},
|
|
92
|
+
];
|
|
93
|
+
|
|
94
|
+
const result = formatJobsTable(jobs);
|
|
95
|
+
expect(result).toContain('user/myspace');
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('should truncate long values with ellipsis', () => {
|
|
99
|
+
const jobs: JobInfo[] = [
|
|
100
|
+
{
|
|
101
|
+
id: 'a'.repeat(50),
|
|
102
|
+
createdAt: '2025-01-20T10:00:00Z',
|
|
103
|
+
dockerImage: 'very-long-image-name-that-exceeds-column-width',
|
|
104
|
+
command: ['python', '-c', 'print(' + 'x'.repeat(100) + ')'],
|
|
105
|
+
flavor: 'cpu-basic',
|
|
106
|
+
status: { stage: 'RUNNING' },
|
|
107
|
+
owner: { id: 'u123', name: 'testuser', type: 'user' },
|
|
108
|
+
environment: {},
|
|
109
|
+
},
|
|
110
|
+
];
|
|
111
|
+
|
|
112
|
+
const result = formatJobsTable(jobs);
|
|
113
|
+
expect(result).toContain('...');
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
describe('formatScheduledJobsTable', () => {
|
|
118
|
+
it('should return message for empty scheduled job list', () => {
|
|
119
|
+
const result = formatScheduledJobsTable([]);
|
|
120
|
+
expect(result).toBe('No scheduled jobs found.');
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('should format a scheduled job as markdown table', () => {
|
|
124
|
+
const jobSpec: JobSpec = {
|
|
125
|
+
dockerImage: 'python:3.12',
|
|
126
|
+
command: ['python', 'backup.py'],
|
|
127
|
+
flavor: 'cpu-basic',
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const jobs: ScheduledJobInfo[] = [
|
|
131
|
+
{
|
|
132
|
+
id: 'sched123',
|
|
133
|
+
schedule: '@hourly',
|
|
134
|
+
suspend: false,
|
|
135
|
+
jobSpec,
|
|
136
|
+
lastRun: '2025-01-20T10:00:00Z',
|
|
137
|
+
nextRun: '2025-01-20T11:00:00Z',
|
|
138
|
+
owner: { id: 'u123', name: 'testuser', type: 'user' },
|
|
139
|
+
createdAt: '2025-01-20T09:00:00Z',
|
|
140
|
+
},
|
|
141
|
+
];
|
|
142
|
+
|
|
143
|
+
const result = formatScheduledJobsTable(jobs);
|
|
144
|
+
|
|
145
|
+
// Should be a markdown table
|
|
146
|
+
expect(result).toContain('| ID');
|
|
147
|
+
expect(result).toContain('| SCHEDULE');
|
|
148
|
+
expect(result).toContain('| IMAGE/SPACE');
|
|
149
|
+
expect(result).toContain('| COMMAND');
|
|
150
|
+
expect(result).toContain('| LAST RUN');
|
|
151
|
+
expect(result).toContain('| NEXT RUN');
|
|
152
|
+
expect(result).toContain('| SUSPENDED');
|
|
153
|
+
|
|
154
|
+
// Should contain job data
|
|
155
|
+
expect(result).toContain('sched123');
|
|
156
|
+
expect(result).toContain('@hourly');
|
|
157
|
+
expect(result).toContain('python:3.12');
|
|
158
|
+
expect(result).toContain('No'); // Not suspended
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('should show Yes for suspended jobs', () => {
|
|
162
|
+
const jobSpec: JobSpec = {
|
|
163
|
+
dockerImage: 'ubuntu',
|
|
164
|
+
command: ['bash', 'cleanup.sh'],
|
|
165
|
+
flavor: 'cpu-basic',
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const jobs: ScheduledJobInfo[] = [
|
|
169
|
+
{
|
|
170
|
+
id: 'sched456',
|
|
171
|
+
schedule: '@daily',
|
|
172
|
+
suspend: true,
|
|
173
|
+
jobSpec,
|
|
174
|
+
owner: { id: 'u123', name: 'testuser', type: 'user' },
|
|
175
|
+
createdAt: '2025-01-20T09:00:00Z',
|
|
176
|
+
},
|
|
177
|
+
];
|
|
178
|
+
|
|
179
|
+
const result = formatScheduledJobsTable(jobs);
|
|
180
|
+
expect(result).toContain('Yes'); // Suspended
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
describe('formatJobDetails', () => {
|
|
185
|
+
it('should format a single job as JSON in code block', () => {
|
|
186
|
+
const job: JobInfo = {
|
|
187
|
+
id: 'job123',
|
|
188
|
+
createdAt: '2025-01-20T10:00:00Z',
|
|
189
|
+
dockerImage: 'python:3.12',
|
|
190
|
+
command: ['python', 'script.py'],
|
|
191
|
+
flavor: 'cpu-basic',
|
|
192
|
+
status: { stage: 'RUNNING', message: null },
|
|
193
|
+
owner: { id: 'u123', name: 'testuser', type: 'user' },
|
|
194
|
+
environment: {},
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
const result = formatJobDetails(job);
|
|
198
|
+
|
|
199
|
+
// Should be wrapped in code block
|
|
200
|
+
expect(result).toMatch(/^```json\n/);
|
|
201
|
+
expect(result).toMatch(/\n```$/);
|
|
202
|
+
|
|
203
|
+
// Should contain job data as JSON
|
|
204
|
+
expect(result).toContain('"id": "job123"');
|
|
205
|
+
expect(result).toContain('"dockerImage": "python:3.12"');
|
|
206
|
+
expect(result).toContain('"stage": "RUNNING"');
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it('should format multiple jobs as JSON array', () => {
|
|
210
|
+
const jobs: JobInfo[] = [
|
|
211
|
+
{
|
|
212
|
+
id: 'job123',
|
|
213
|
+
createdAt: '2025-01-20T10:00:00Z',
|
|
214
|
+
dockerImage: 'python:3.12',
|
|
215
|
+
command: ['python', 'script.py'],
|
|
216
|
+
flavor: 'cpu-basic',
|
|
217
|
+
status: { stage: 'RUNNING' },
|
|
218
|
+
owner: { id: 'u123', name: 'testuser', type: 'user' },
|
|
219
|
+
environment: {},
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
id: 'job456',
|
|
223
|
+
createdAt: '2025-01-20T11:00:00Z',
|
|
224
|
+
dockerImage: 'ubuntu',
|
|
225
|
+
command: ['bash', 'test.sh'],
|
|
226
|
+
flavor: 'cpu-basic',
|
|
227
|
+
status: { stage: 'COMPLETED' },
|
|
228
|
+
owner: { id: 'u123', name: 'testuser', type: 'user' },
|
|
229
|
+
environment: {},
|
|
230
|
+
},
|
|
231
|
+
];
|
|
232
|
+
|
|
233
|
+
const result = formatJobDetails(jobs);
|
|
234
|
+
|
|
235
|
+
// Should be wrapped in code block
|
|
236
|
+
expect(result).toMatch(/^```json\n/);
|
|
237
|
+
expect(result).toMatch(/\n```$/);
|
|
238
|
+
|
|
239
|
+
// Should be an array
|
|
240
|
+
expect(result).toContain('[');
|
|
241
|
+
expect(result).toContain(']');
|
|
242
|
+
|
|
243
|
+
// Should contain both jobs
|
|
244
|
+
expect(result).toContain('"id": "job123"');
|
|
245
|
+
expect(result).toContain('"id": "job456"');
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it('should format JSON with proper indentation', () => {
|
|
249
|
+
const job: JobInfo = {
|
|
250
|
+
id: 'job123',
|
|
251
|
+
createdAt: '2025-01-20T10:00:00Z',
|
|
252
|
+
dockerImage: 'python:3.12',
|
|
253
|
+
command: ['python', 'script.py'],
|
|
254
|
+
flavor: 'cpu-basic',
|
|
255
|
+
status: { stage: 'RUNNING' },
|
|
256
|
+
owner: { id: 'u123', name: 'testuser', type: 'user' },
|
|
257
|
+
environment: {},
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
const result = formatJobDetails(job);
|
|
261
|
+
|
|
262
|
+
// Should have proper indentation (2 spaces)
|
|
263
|
+
expect(result).toContain(' "id"');
|
|
264
|
+
expect(result).toContain(' "createdAt"');
|
|
265
|
+
});
|
|
266
|
+
});
|
|
267
|
+
});
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { uvCommand } from '../../src/jobs/commands/run.js';
|
|
3
|
+
import type { JobsApiClient } from '../../src/jobs/api-client.js';
|
|
4
|
+
import type { JobInfo, JobSpec } from '../../src/jobs/types.js';
|
|
5
|
+
|
|
6
|
+
function setupMockClient() {
|
|
7
|
+
let capturedSpec: JobSpec | undefined;
|
|
8
|
+
|
|
9
|
+
const runJob = vi.fn(async (spec: JobSpec) => {
|
|
10
|
+
capturedSpec = spec;
|
|
11
|
+
const job: JobInfo = {
|
|
12
|
+
id: 'job-123',
|
|
13
|
+
createdAt: new Date().toISOString(),
|
|
14
|
+
command: spec.command,
|
|
15
|
+
arguments: spec.arguments,
|
|
16
|
+
environment: spec.environment ?? {},
|
|
17
|
+
secrets: {},
|
|
18
|
+
flavor: spec.flavor,
|
|
19
|
+
status: { stage: 'RUNNING' },
|
|
20
|
+
owner: { id: 'owner-1', name: 'tester', type: 'user' },
|
|
21
|
+
};
|
|
22
|
+
return job;
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const getLogsUrl = vi.fn(() => 'https://example.test/logs');
|
|
26
|
+
|
|
27
|
+
const client = {
|
|
28
|
+
runJob,
|
|
29
|
+
getLogsUrl,
|
|
30
|
+
} as unknown as JobsApiClient;
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
client,
|
|
34
|
+
runJob,
|
|
35
|
+
getLogsUrl,
|
|
36
|
+
get lastSpec() {
|
|
37
|
+
return capturedSpec;
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
describe('uvCommand', () => {
|
|
43
|
+
it('wraps inline scripts in a shell pipeline executed via /bin/sh', async () => {
|
|
44
|
+
const harness = setupMockClient();
|
|
45
|
+
const script = 'print("hello")\nprint("world")';
|
|
46
|
+
|
|
47
|
+
await uvCommand({ script, detach: true }, harness.client);
|
|
48
|
+
|
|
49
|
+
expect(harness.runJob).toHaveBeenCalledTimes(1);
|
|
50
|
+
expect(harness.lastSpec).toBeDefined();
|
|
51
|
+
expect(harness.lastSpec?.command).toEqual([
|
|
52
|
+
'/bin/sh',
|
|
53
|
+
'-lc',
|
|
54
|
+
expect.stringContaining('uv run'),
|
|
55
|
+
]);
|
|
56
|
+
|
|
57
|
+
const encoded = Buffer.from(script).toString('base64');
|
|
58
|
+
expect(harness.lastSpec?.command?.[2]).toContain(`echo "${encoded}" | base64 -d | uv run`);
|
|
59
|
+
expect(harness.lastSpec?.command?.[2]).toContain(' -');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('includes dependency and python flags when provided', async () => {
|
|
63
|
+
const harness = setupMockClient();
|
|
64
|
+
const script = 'print("deps")\nprint("python")';
|
|
65
|
+
|
|
66
|
+
await uvCommand(
|
|
67
|
+
{
|
|
68
|
+
script,
|
|
69
|
+
with_deps: ['numpy', 'pandas'],
|
|
70
|
+
python: '3.12',
|
|
71
|
+
detach: true,
|
|
72
|
+
},
|
|
73
|
+
harness.client
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
const shellCommand = harness.lastSpec?.command?.[2];
|
|
77
|
+
expect(shellCommand).toContain('--with numpy');
|
|
78
|
+
expect(shellCommand).toContain('--with pandas');
|
|
79
|
+
expect(shellCommand).toContain('-p 3.12');
|
|
80
|
+
});
|
|
81
|
+
});
|