@llmindset/hf-mcp 0.2.39 → 0.2.41

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 (41) hide show
  1. package/dist/docs-search/docs-semantic-search.test.js +20 -44
  2. package/dist/docs-search/docs-semantic-search.test.js.map +1 -1
  3. package/dist/hf-api-call.d.ts.map +1 -1
  4. package/dist/hf-api-call.js +21 -7
  5. package/dist/hf-api-call.js.map +1 -1
  6. package/dist/jobs/commands/inspect.js +1 -1
  7. package/dist/jobs/commands/inspect.js.map +1 -1
  8. package/dist/jobs/commands/logs.js +3 -3
  9. package/dist/jobs/commands/logs.js.map +1 -1
  10. package/dist/jobs/commands/ps.js +1 -1
  11. package/dist/jobs/commands/ps.js.map +1 -1
  12. package/dist/jobs/commands/run.js +6 -6
  13. package/dist/jobs/commands/run.js.map +1 -1
  14. package/dist/jobs/commands/scheduled.js +5 -5
  15. package/dist/jobs/commands/scheduled.js.map +1 -1
  16. package/dist/jobs/jobs-tool.d.ts +4 -4
  17. package/dist/jobs/jobs-tool.d.ts.map +1 -1
  18. package/dist/jobs/jobs-tool.js +202 -137
  19. package/dist/jobs/jobs-tool.js.map +1 -1
  20. package/dist/jobs/schema-help.d.ts +1 -2
  21. package/dist/jobs/schema-help.d.ts.map +1 -1
  22. package/dist/jobs/sse-handler.d.ts +3 -0
  23. package/dist/jobs/sse-handler.d.ts.map +1 -1
  24. package/dist/jobs/sse-handler.js +4 -1
  25. package/dist/jobs/sse-handler.js.map +1 -1
  26. package/dist/jobs/types.d.ts +4 -4
  27. package/dist/jobs/types.d.ts.map +1 -1
  28. package/dist/jobs/types.js +8 -4
  29. package/dist/jobs/types.js.map +1 -1
  30. package/package.json +1 -1
  31. package/src/docs-search/docs-semantic-search.test.ts +22 -44
  32. package/src/hf-api-call.ts +30 -8
  33. package/src/jobs/commands/inspect.ts +1 -1
  34. package/src/jobs/commands/logs.ts +3 -3
  35. package/src/jobs/commands/ps.ts +1 -1
  36. package/src/jobs/commands/run.ts +6 -6
  37. package/src/jobs/commands/scheduled.ts +5 -5
  38. package/src/jobs/jobs-tool.ts +231 -149
  39. package/src/jobs/schema-help.ts +1 -1
  40. package/src/jobs/sse-handler.ts +16 -1
  41. package/src/jobs/types.ts +8 -4
@@ -14,9 +14,10 @@ import {
14
14
  scheduledSuspendCommand,
15
15
  scheduledResumeCommand,
16
16
  } from './commands/scheduled.js';
17
- import { formatCommandHelp } from './schema-help.js';
17
+ import { formatCommandHelp, extractFieldDetails, type AnyZodType } from './schema-help.js';
18
18
  import type { ToolResult } from '../types/tool-result.js';
19
19
  import { CPU_FLAVORS, GPU_FLAVORS, SPECIALIZED_FLAVORS } from './types.js';
20
+ import { DEFAULT_LOG_WAIT_SECONDS } from './sse-handler.js';
20
21
  import type {
21
22
  RunArgs,
22
23
  UvArgs,
@@ -48,10 +49,90 @@ import {
48
49
  scheduledJobArgsSchema,
49
50
  } from './types.js';
50
51
 
52
+ const OPERATION_NAMES = [
53
+ 'run',
54
+ 'uv',
55
+ 'ps',
56
+ 'logs',
57
+ 'inspect',
58
+ 'cancel',
59
+ 'scheduled run',
60
+ 'scheduled uv',
61
+ 'scheduled ps',
62
+ 'scheduled inspect',
63
+ 'scheduled delete',
64
+ 'scheduled suspend',
65
+ 'scheduled resume',
66
+ ] as const;
67
+
68
+ type OperationName = (typeof OPERATION_NAMES)[number];
69
+
70
+ const OPERATION_EXAMPLES: Partial<Record<OperationName, string>> = {
71
+ run: `{
72
+ "operation": "run",
73
+ "args": {
74
+ "image": "python:3.12",
75
+ "command": ["python", "-c", "print('Hello from HF Jobs!')"],
76
+ "flavor": "cpu-basic"
77
+ }
78
+ }`,
79
+ uv: `{
80
+ "operation": "uv",
81
+ "args": {
82
+ "script": "import random\\nprint(42 + random.randint(1, 5))"
83
+ }
84
+ }`,
85
+ ps: `{"operation": "ps"}`,
86
+ logs: `{
87
+ "operation": "logs",
88
+ "args": {"job_id": "your-job-id"}
89
+ }`,
90
+ inspect: `{
91
+ "operation": "inspect",
92
+ "args": {"job_id": "your-job-id"}
93
+ }`,
94
+ cancel: `{
95
+ "operation": "cancel",
96
+ "args": {"job_id": "your-job-id"}
97
+ }`,
98
+ 'scheduled run': `{
99
+ "operation": "scheduled run",
100
+ "args": {
101
+ "schedule": "@hourly",
102
+ "image": "python:3.12",
103
+ "command": ["python", "backup.py"]
104
+ }
105
+ }`,
106
+ 'scheduled uv': `{
107
+ "operation": "scheduled uv",
108
+ "args": {
109
+ "schedule": "0 9 * * 1-5",
110
+ "script": "import datetime\\nprint('daily check', datetime.datetime.utcnow())"
111
+ }
112
+ }`,
113
+ 'scheduled ps': `{"operation": "scheduled ps"}`,
114
+ 'scheduled inspect': `{
115
+ "operation": "scheduled inspect",
116
+ "args": {"scheduled_job_id": "your-scheduled-job-id"}
117
+ }`,
118
+ 'scheduled delete': `{
119
+ "operation": "scheduled delete",
120
+ "args": {"scheduled_job_id": "your-scheduled-job-id"}
121
+ }`,
122
+ 'scheduled suspend': `{
123
+ "operation": "scheduled suspend",
124
+ "args": {"scheduled_job_id": "your-scheduled-job-id"}
125
+ }`,
126
+ 'scheduled resume': `{
127
+ "operation": "scheduled resume",
128
+ "args": {"scheduled_job_id": "your-scheduled-job-id"}
129
+ }`,
130
+ };
131
+
51
132
  /**
52
- * Map of command names to their validation schemas
133
+ * Map of operation names to their validation schemas
53
134
  */
54
- const COMMAND_SCHEMAS = {
135
+ const OPERATION_SCHEMAS: Record<OperationName, z.ZodSchema> = {
55
136
  run: runArgsSchema,
56
137
  uv: uvArgsSchema,
57
138
  ps: psArgsSchema,
@@ -65,9 +146,10 @@ const COMMAND_SCHEMAS = {
65
146
  'scheduled delete': scheduledJobArgsSchema,
66
147
  'scheduled suspend': scheduledJobArgsSchema,
67
148
  'scheduled resume': scheduledJobArgsSchema,
68
- } as const;
149
+ };
69
150
 
70
151
  const HELP_FLAG = 'help';
152
+ const operationRequiresArgsCache = new Map<OperationName, boolean>();
71
153
 
72
154
  const CPU_FLAVOR_LIST = CPU_FLAVORS.join(', ');
73
155
  const GPU_FLAVOR_LIST = GPU_FLAVORS.join(', ');
@@ -98,19 +180,83 @@ function removeHelpFlag(args: Record<string, unknown> | undefined): Record<strin
98
180
  return rest;
99
181
  }
100
182
 
183
+ function isOperationName(value: string): value is OperationName {
184
+ return (OPERATION_NAMES as readonly string[]).includes(value);
185
+ }
186
+
187
+ function formatExampleSnippet(operation: OperationName): string | undefined {
188
+ const example = OPERATION_EXAMPLES[operation];
189
+ if (!example) {
190
+ return undefined;
191
+ }
192
+
193
+ return `Call this tool with:\n\`\`\`json\n${example}\n\`\`\``;
194
+ }
195
+
196
+ function renderExampleSection(title: string, operation: OperationName): string {
197
+ const snippet = formatExampleSnippet(operation);
198
+ if (!snippet) {
199
+ return '';
200
+ }
201
+
202
+ return `### ${title}
203
+ ${snippet}
204
+ `;
205
+ }
206
+
207
+ function operationRequiresArgs(operation: OperationName): boolean {
208
+ const cached = operationRequiresArgsCache.get(operation);
209
+ if (cached !== undefined) {
210
+ return cached;
211
+ }
212
+
213
+ const schema = OPERATION_SCHEMAS[operation];
214
+ if (!schema) {
215
+ operationRequiresArgsCache.set(operation, false);
216
+ return false;
217
+ }
218
+
219
+ const fields = extractFieldDetails(schema as AnyZodType);
220
+ const requiresArgs = fields.some((field) => !field.isOptional);
221
+ operationRequiresArgsCache.set(operation, requiresArgs);
222
+ return requiresArgs;
223
+ }
224
+
225
+ function formatCommandHelpWithExample(operation: OperationName, schema: z.ZodSchema): string {
226
+ let help = formatCommandHelp(operation, schema);
227
+ const exampleSnippet = formatExampleSnippet(operation);
228
+
229
+ if (exampleSnippet) {
230
+ help += `\n\n### Example\n${exampleSnippet}`;
231
+ }
232
+
233
+ return help;
234
+ }
235
+
236
+ function extractTopLevelArgs(params: { [key: string]: unknown }): Record<string, unknown> {
237
+ const legacyArgs: Record<string, unknown> = {};
238
+ for (const [key, value] of Object.entries(params)) {
239
+ if (key === 'operation' || key === 'args') {
240
+ continue;
241
+ }
242
+ legacyArgs[key] = value;
243
+ }
244
+ return legacyArgs;
245
+ }
246
+
101
247
  /**
102
- * Validate command arguments against a Zod schema
248
+ * Validate operation arguments against a Zod schema
103
249
  * Returns a ToolResult with detailed error message if validation fails
104
250
  */
105
- function validateArgs(
106
- schema: z.ZodSchema,
251
+ function validateArgs<T extends z.ZodTypeAny>(
252
+ schema: T,
107
253
  args: unknown,
108
- commandName: string
109
- ): { success: true } | { success: false; errorResult: ToolResult } {
254
+ operationName: string
255
+ ): { success: true; data: z.infer<T> } | { success: false; errorResult: ToolResult } {
110
256
  const result = schema.safeParse(args);
111
257
 
112
258
  if (result.success) {
113
- return { success: true };
259
+ return { success: true, data: result.data as z.infer<T> };
114
260
  }
115
261
 
116
262
  // Format Zod errors into a helpful message
@@ -127,7 +273,7 @@ function validateArgs(
127
273
  }
128
274
  }
129
275
 
130
- let errorMessage = `Error: Invalid parameters for '${commandName}'\n\n`;
276
+ let errorMessage = `Error: Invalid parameters for '${operationName}'\n\n`;
131
277
 
132
278
  if (missingFields.length > 0) {
133
279
  errorMessage += `Missing required parameters:\n${missingFields.join('\n')}\n\n`;
@@ -137,7 +283,7 @@ function validateArgs(
137
283
  errorMessage += `Invalid parameters:\n${invalidFields.join('\n')}\n\n`;
138
284
  }
139
285
 
140
- errorMessage += `Call hf_jobs("${commandName}", {"help": true}) to see valid arguments.`;
286
+ errorMessage += `Call this tool with {"operation": "${operationName}", "args": {"help": true}} to see valid arguments.`;
141
287
 
142
288
  return {
143
289
  success: false,
@@ -178,97 +324,7 @@ Manage compute jobs on Hugging Face infrastructure.
178
324
 
179
325
  ## Examples
180
326
 
181
- ### Run a simple job
182
- \`\`\`
183
- hf_jobs("run", {
184
- "image": "python:3.12",
185
- "command": ["python", "-c", "print('Hello from HF Jobs!')"],
186
- "flavor": "cpu-basic"
187
- })
188
- \`\`\`
189
-
190
- ### Use a Hugging Face Space as the image
191
- \`\`\`
192
- hf_jobs("run", {
193
- "image": "hf.co/spaces/username/spacename",
194
- "command": ["python", "app.py"],
195
- "flavor": "cpu-basic"
196
- })
197
- \`\`\`
198
-
199
- ### Run multiline Python scripts
200
- \`\`\`
201
- hf_jobs("run", {
202
- "image": "python:3.12",
203
- "command": ["python", "-c", "import sys\\nprint('Line 1')\\nprint('Line 2')"],
204
- "flavor": "cpu-basic"
205
- })
206
- \`\`\`
207
-
208
- ### Run a Python Script from a URL with UV
209
- \`\`\`
210
- hf_jobs("uv", {
211
- "script": "https://huggingface.co/datasets/uv-scripts/classification/blob/main/classify-dataset.py",
212
- "with_deps": ["pandas"],
213
- "script_args": ["--input", "data.csv"],
214
- "flavor": "cpu-basic"
215
- })
216
- \`\`\`
217
-
218
- ### Run an inline Python script with UV
219
- \`\`\`
220
- hf_jobs("uv", {
221
- "script": "import math\\nprint('area:', math.pi * 4**2)"
222
- })
223
- \`\`\`
224
-
225
-
226
- ### Run bash/shell commands
227
- \`\`\`
228
- hf_jobs("run", {
229
- "image": "ubuntu:22.04",
230
- "command": ["/bin/sh", "-lc", "apt-get update && apt-get install -y curl"],
231
- "flavor": "cpu-basic"
232
- })
233
- \`\`\`
234
-
235
- ### List running jobs
236
- \`\`\`
237
- hf_jobs("ps")
238
- \`\`\`
239
-
240
- ### Get job logs
241
- \`\`\`
242
- hf_jobs("logs", {"job_id": "your-job-id"})
243
- \`\`\`
244
-
245
- ### Run with GPU
246
- \`\`\`
247
- hf_jobs("run", {
248
- "image": "pytorch/pytorch:2.6.0-cuda12.4-cudnn9-devel",
249
- "command": ["python", "train.py"],
250
- "flavor": "a10g-small"
251
- })
252
- \`\`\`
253
-
254
- ### Schedule a job
255
- \`\`\`
256
- hf_jobs("scheduled run", {
257
- "schedule": "@hourly",
258
- "image": "python:3.12",
259
- "command": ["python", "backup.py"]
260
- })
261
- \`\`\`
262
-
263
- ### Schedule a UV script
264
- \`\`\`
265
- hf_jobs("scheduled uv", {
266
- "schedule": "0 9 * * 1-5",
267
- "script": "https://huggingface.co/datasets/uv-scripts/classification/blob/main/classify-dataset.py",
268
- "with_deps": ["pandas"],
269
- "script_args": ["--input", "data.csv"]
270
- })
271
- \`\`\`
327
+ ${renderExampleSection('Run a simple job', 'run')}${renderExampleSection('Run a Python script with UV', 'uv')}
272
328
 
273
329
  ## Hardware Flavors
274
330
 
@@ -291,17 +347,18 @@ ${HARDWARE_FLAVORS_SECTION}
291
347
  - UV inline scripts are automatically base64-decoded inside the container; just send the raw script text
292
348
 
293
349
  ### Show command-specific help
294
- \`\`\`
295
- hf_jobs("<command>", {"help": true})
350
+ Call this tool with:
351
+ \`\`\`json
352
+ {"operation": "<operation>", "args": {"help": true}}
296
353
  \`\`\`
297
354
 
298
355
  ## Tips
299
356
 
300
357
  - The uv-scripts organisation contains examples for common tasks. dataset_search {'author':'uv-scripts'}
301
- - Jobs default to detached mode, returning after 10 seconds..
358
+ - Jobs default to non-detached mode (tail logs for up to ${DEFAULT_LOG_WAIT_SECONDS}s or until completion). Set \`detach: true\` to return immediately.
302
359
  - Prefer array commands to avoid shell parsing surprises
303
360
  - To access private Hub assets, include \`secrets: { "HF_TOKEN": "$HF_TOKEN" }\` (or \`${'${HF_TOKEN}'}\`) to inject your auth token.
304
- - Logs are time-limited (10s max) - check job page for full logs
361
+ - When not detached, logs are time-limited (${DEFAULT_LOG_WAIT_SECONDS}s max or until job completes) - check job page for full logs
305
362
  `;
306
363
 
307
364
  /**
@@ -310,20 +367,15 @@ hf_jobs("<command>", {"help": true})
310
367
  export const HF_JOBS_TOOL_CONFIG = {
311
368
  name: 'hf_jobs',
312
369
  description:
313
- 'Manage HuggingFace compute jobs. Run commands in Docker containers, ' +
314
- 'execute Python scripts with UV, schedule and monitor jobs, status and logs. ' +
315
- 'Call hf_jobs with no command for full usage instructions and examples. ' +
316
- 'Supports CPU and GPU hardware.',
370
+ 'Manage Hugging Face CPU/GPU compute jobs. Run commands in Docker containers, ' +
371
+ 'execute Python scripts with UV. List, schedule and monitor jobs/logs. ' +
372
+ 'Call this tool with no operation for full usage instructions and examples. ',
317
373
  schema: z.object({
318
- command: z
319
- .string()
374
+ operation: z
375
+ .enum(OPERATION_NAMES)
320
376
  .optional()
321
- .describe(
322
- 'Command to execute: "run", "uv", "ps", "logs", "inspect", "cancel", ' +
323
- '"scheduled run", "scheduled uv", "scheduled ps", "scheduled inspect", ' +
324
- '"scheduled delete", "scheduled suspend", "scheduled resume"'
325
- ),
326
- args: z.record(z.any()).optional().describe('Command-specific arguments as a JSON object'),
377
+ .describe(`Operation to execute. Valid values: ${OPERATION_NAMES.map((cmd) => `"${cmd}"`).join(', ')}`),
378
+ args: z.record(z.any()).optional().describe('Operation-specific arguments as a JSON object'),
327
379
  }),
328
380
  annotations: {
329
381
  title: 'Hugging Face Jobs', // omit destructive hint.
@@ -347,9 +399,9 @@ export class HfJobsTool {
347
399
  }
348
400
 
349
401
  /**
350
- * Execute a jobs command
402
+ * Execute a jobs operation
351
403
  */
352
- async execute(params: { command?: string; args?: Record<string, unknown> }): Promise<ToolResult> {
404
+ async execute(params: { operation?: string; args?: Record<string, unknown> }): Promise<ToolResult> {
353
405
  // If not authenticated, show upgrade message
354
406
  if (!this.isAuthenticated) {
355
407
  return {
@@ -360,8 +412,10 @@ export class HfJobsTool {
360
412
  };
361
413
  }
362
414
 
363
- // If no command provided, return usage instructions
364
- if (!params.command) {
415
+ const requestedOperation = params.operation;
416
+
417
+ // If no operation provided, return usage instructions
418
+ if (!requestedOperation) {
365
419
  return {
366
420
  formatted: USAGE_INSTRUCTIONS,
367
421
  totalResults: 1,
@@ -369,101 +423,129 @@ export class HfJobsTool {
369
423
  };
370
424
  }
371
425
 
372
- const command = params.command.toLowerCase();
373
- const rawArgs = params.args || {};
374
- const schema = COMMAND_SCHEMAS[command as keyof typeof COMMAND_SCHEMAS];
426
+ const normalizedOperation = requestedOperation.toLowerCase();
427
+ if (!isOperationName(normalizedOperation)) {
428
+ return {
429
+ formatted: `Unknown operation: "${requestedOperation}"
430
+ Available operations:
431
+ - run, uv, ps, logs, inspect, cancel
432
+ - scheduled run, scheduled uv, scheduled ps, scheduled inspect, scheduled delete, scheduled suspend, scheduled resume
433
+
434
+ Call this tool with no operation for full usage instructions.`,
435
+ totalResults: 0,
436
+ resultsShared: 0,
437
+ };
438
+ }
439
+
440
+ const operation = normalizedOperation;
441
+ const legacyArgs = extractTopLevelArgs(params as Record<string, unknown>);
442
+ const rawArgs = params.args ? params.args : Object.keys(legacyArgs).length > 0 ? legacyArgs : {};
443
+ const schema = OPERATION_SCHEMAS[operation];
444
+ const noArgsProvided = !params.args || Object.keys(params.args).length === 0;
445
+
446
+ if (schema && noArgsProvided && operationRequiresArgs(operation)) {
447
+ const helpText = formatCommandHelpWithExample(operation, schema);
448
+ return {
449
+ formatted: `No arguments provided for "${operation}".\n\n${helpText}`,
450
+ totalResults: 1,
451
+ resultsShared: 1,
452
+ };
453
+ }
375
454
  const helpRequested = isHelpRequested(rawArgs);
376
455
 
377
456
  if (helpRequested) {
378
457
  if (!schema) {
379
458
  return {
380
- formatted: `No help available for '${params.command}'.`,
459
+ formatted: `No help available for '${requestedOperation}'.`,
381
460
  totalResults: 0,
382
461
  resultsShared: 0,
383
462
  };
384
463
  }
385
464
 
465
+ const helpText = formatCommandHelpWithExample(operation, schema);
386
466
  return {
387
- formatted: formatCommandHelp(command, schema),
467
+ formatted: helpText,
388
468
  totalResults: 1,
389
469
  resultsShared: 1,
390
470
  };
391
471
  }
392
472
 
393
- const args = removeHelpFlag(rawArgs);
473
+ const cleanedArgs = removeHelpFlag(rawArgs);
474
+ let parsedArgs: Record<string, unknown> = cleanedArgs;
394
475
 
395
- // Validate command arguments if schema exists
476
+ // Validate operation arguments if schema exists
396
477
  if (schema) {
397
- const validation = validateArgs(schema, args, command);
478
+ const validation = validateArgs(schema, cleanedArgs, operation);
398
479
  if (!validation.success) {
399
480
  return validation.errorResult;
400
481
  }
482
+ parsedArgs = validation.data as Record<string, unknown>;
401
483
  }
402
484
 
403
485
  try {
404
486
  let result: string;
405
487
 
406
- switch (command) {
488
+ switch (operation) {
407
489
  case 'run':
408
- result = await runCommand(args as RunArgs, this.client, this.hfToken);
490
+ result = await runCommand(parsedArgs as RunArgs, this.client, this.hfToken);
409
491
  break;
410
492
 
411
493
  case 'uv':
412
- result = await uvCommand(args as UvArgs, this.client, this.hfToken);
494
+ result = await uvCommand(parsedArgs as UvArgs, this.client, this.hfToken);
413
495
  break;
414
496
 
415
497
  case 'ps':
416
- result = await psCommand(args as PsArgs, this.client);
498
+ result = await psCommand(parsedArgs as PsArgs, this.client);
417
499
  break;
418
500
 
419
501
  case 'logs':
420
- result = await logsCommand(args as LogsArgs, this.client, this.hfToken);
502
+ result = await logsCommand(parsedArgs as LogsArgs, this.client, this.hfToken);
421
503
  break;
422
504
 
423
505
  case 'inspect':
424
- result = await inspectCommand(args as InspectArgs, this.client);
506
+ result = await inspectCommand(parsedArgs as InspectArgs, this.client);
425
507
  break;
426
508
 
427
509
  case 'cancel':
428
- result = await cancelCommand(args as CancelArgs, this.client);
510
+ result = await cancelCommand(parsedArgs as CancelArgs, this.client);
429
511
  break;
430
512
 
431
513
  case 'scheduled run':
432
- result = await scheduledRunCommand(args as ScheduledRunArgs, this.client, this.hfToken);
514
+ result = await scheduledRunCommand(parsedArgs as ScheduledRunArgs, this.client, this.hfToken);
433
515
  break;
434
516
 
435
517
  case 'scheduled uv':
436
- result = await scheduledUvCommand(args as ScheduledUvArgs, this.client, this.hfToken);
518
+ result = await scheduledUvCommand(parsedArgs as ScheduledUvArgs, this.client, this.hfToken);
437
519
  break;
438
520
 
439
521
  case 'scheduled ps':
440
- result = await scheduledPsCommand(args as ScheduledPsArgs, this.client);
522
+ result = await scheduledPsCommand(parsedArgs as ScheduledPsArgs, this.client);
441
523
  break;
442
524
 
443
525
  case 'scheduled inspect':
444
- result = await scheduledInspectCommand(args as ScheduledJobArgs, this.client);
526
+ result = await scheduledInspectCommand(parsedArgs as ScheduledJobArgs, this.client);
445
527
  break;
446
528
 
447
529
  case 'scheduled delete':
448
- result = await scheduledDeleteCommand(args as ScheduledJobArgs, this.client);
530
+ result = await scheduledDeleteCommand(parsedArgs as ScheduledJobArgs, this.client);
449
531
  break;
450
532
 
451
533
  case 'scheduled suspend':
452
- result = await scheduledSuspendCommand(args as ScheduledJobArgs, this.client);
534
+ result = await scheduledSuspendCommand(parsedArgs as ScheduledJobArgs, this.client);
453
535
  break;
454
536
 
455
537
  case 'scheduled resume':
456
- result = await scheduledResumeCommand(args as ScheduledJobArgs, this.client);
538
+ result = await scheduledResumeCommand(parsedArgs as ScheduledJobArgs, this.client);
457
539
  break;
458
540
 
459
541
  default:
460
542
  return {
461
- formatted: `Unknown command: "${params.command}"
462
- Available commands:
543
+ formatted: `Unknown operation: "${requestedOperation ?? 'unknown'}"
544
+ Available operations:
463
545
  - run, uv, ps, logs, inspect, cancel
464
546
  - scheduled run, scheduled uv, scheduled ps, scheduled inspect, scheduled delete, scheduled suspend, scheduled resume
465
547
 
466
- Call hf_jobs() with no arguments for full usage instructions.`,
548
+ Call this tool with no operation for full usage instructions.`,
467
549
  totalResults: 0,
468
550
  resultsShared: 0,
469
551
  };
@@ -493,7 +575,7 @@ Call hf_jobs() with no arguments for full usage instructions.`,
493
575
  }
494
576
 
495
577
  return {
496
- formatted: `Error executing ${params.command}: ${errorMessage}`,
578
+ formatted: `Error executing ${requestedOperation ?? 'operation'}: ${errorMessage}`,
497
579
  totalResults: 0,
498
580
  resultsShared: 0,
499
581
  isError: true,
@@ -5,7 +5,7 @@ import { z } from 'zod';
5
5
  * Provides introspection and documentation generation for command arguments
6
6
  */
7
7
 
8
- type AnyZodType = z.ZodType<unknown, z.ZodTypeDef, unknown>;
8
+ export type AnyZodType = z.ZodType<unknown, z.ZodTypeDef, unknown>;
9
9
 
10
10
  export interface FieldDetails {
11
11
  key: string;
@@ -1,5 +1,20 @@
1
1
  import type { LogEvent } from './types.js';
2
2
 
3
+ /**
4
+ * Default duration to wait for logs when not detached (in milliseconds)
5
+ */
6
+ export const DEFAULT_LOG_WAIT_MS = 10000; // 10 seconds
7
+
8
+ /**
9
+ * Default duration to wait for logs when not detached (in seconds, for display)
10
+ */
11
+ export const DEFAULT_LOG_WAIT_SECONDS = DEFAULT_LOG_WAIT_MS / 1000;
12
+
13
+ /**
14
+ * Default maximum number of log lines to return
15
+ */
16
+ export const DEFAULT_MAX_LOG_LINES = 20;
17
+
3
18
  /**
4
19
  * Options for fetching logs via SSE
5
20
  */
@@ -33,7 +48,7 @@ export interface SseLogResult {
33
48
  * @returns Log result with collected lines and status
34
49
  */
35
50
  export async function fetchJobLogs(url: string, options: SseLogOptions = {}): Promise<SseLogResult> {
36
- const { maxDuration = 10000, maxLines = 20, token } = options;
51
+ const { maxDuration = DEFAULT_LOG_WAIT_MS, maxLines = DEFAULT_MAX_LOG_LINES, token } = options;
37
52
 
38
53
  const logLines: string[] = [];
39
54
  let finished = false;
package/src/jobs/types.ts CHANGED
@@ -133,7 +133,11 @@ const commonArgsSchema = z.object({
133
133
 
134
134
  // Run command args
135
135
  export const runArgsSchema = commonArgsSchema.extend({
136
- image: z.string().describe('Docker image or HF Space URL (e.g., "python:3.12" or "hf.co/spaces/user/space")'),
136
+ image: z
137
+ .string()
138
+ .describe('Docker image or HF Space URL (e.g., "python:3.12" or "hf.co/spaces/user/space")')
139
+ .optional()
140
+ .default('python:3.12'), // NOTE -- this is a deviation from the hf jobs command (which has no default)
137
141
  command: z
138
142
  .union([z.string(), z.array(z.string())])
139
143
  .describe(
@@ -155,8 +159,8 @@ export const runArgsSchema = commonArgsSchema.extend({
155
159
  detach: z
156
160
  .boolean()
157
161
  .optional()
158
- .default(true)
159
- .describe('Run in background and return after 10 seconds (default: true)'),
162
+ .default(false)
163
+ .describe('If true, return immediately with job ID. If false (default), tail logs for up to 10 seconds.'),
160
164
  });
161
165
 
162
166
  // UV command args
@@ -175,7 +179,7 @@ export const uvArgsSchema = commonArgsSchema.extend({
175
179
  .optional()
176
180
  .describe('Secrets as key-value pairs. Use HF_TOKEN=$HF_TOKEN to include your token'),
177
181
  timeout: z.string().optional().default('30m').describe('Max duration'),
178
- detach: z.boolean().optional().default(true).describe('Run in background and return after 10 seconds'),
182
+ detach: z.boolean().optional().default(false).describe('If true, return immediately with job ID. If false (default), tail logs for up to 10 seconds.'),
179
183
  });
180
184
 
181
185
  // PS command args