transloadit 4.8.1 → 4.8.2

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 (33) hide show
  1. package/README.md +41 -40
  2. package/dist/cli/generateIntentDocs.d.ts.map +1 -1
  3. package/dist/cli/generateIntentDocs.js +7 -7
  4. package/dist/cli/generateIntentDocs.js.map +1 -1
  5. package/dist/cli/intentCommands.d.ts.map +1 -1
  6. package/dist/cli/intentCommands.js +11 -7
  7. package/dist/cli/intentCommands.js.map +1 -1
  8. package/dist/cli/intentRuntime.d.ts +17 -2
  9. package/dist/cli/intentRuntime.d.ts.map +1 -1
  10. package/dist/cli/intentRuntime.js +163 -33
  11. package/dist/cli/intentRuntime.js.map +1 -1
  12. package/dist/cli/semanticIntents/imageDescribe.d.ts +2 -1
  13. package/dist/cli/semanticIntents/imageDescribe.d.ts.map +1 -1
  14. package/dist/cli/semanticIntents/imageDescribe.js +5 -4
  15. package/dist/cli/semanticIntents/imageDescribe.js.map +1 -1
  16. package/dist/cli/semanticIntents/imageGenerate.d.ts +1 -0
  17. package/dist/cli/semanticIntents/imageGenerate.d.ts.map +1 -1
  18. package/dist/cli/semanticIntents/imageGenerate.js +4 -3
  19. package/dist/cli/semanticIntents/imageGenerate.js.map +1 -1
  20. package/dist/cli/semanticIntents/index.d.ts +1 -0
  21. package/dist/cli/semanticIntents/index.d.ts.map +1 -1
  22. package/dist/cli/semanticIntents/index.js.map +1 -1
  23. package/dist/cli/semanticIntents/markdownPdf.d.ts.map +1 -1
  24. package/dist/cli/semanticIntents/markdownPdf.js +2 -1
  25. package/dist/cli/semanticIntents/markdownPdf.js.map +1 -1
  26. package/package.json +1 -1
  27. package/src/cli/generateIntentDocs.ts +8 -7
  28. package/src/cli/intentCommands.ts +11 -7
  29. package/src/cli/intentRuntime.ts +214 -33
  30. package/src/cli/semanticIntents/imageDescribe.ts +5 -4
  31. package/src/cli/semanticIntents/imageGenerate.ts +4 -3
  32. package/src/cli/semanticIntents/index.ts +1 -0
  33. package/src/cli/semanticIntents/markdownPdf.ts +2 -1
@@ -182,11 +182,12 @@ function getSharedFileInputOutputRows(): DocOptionRow[] {
182
182
  getInputPathsOptionDocumentation(),
183
183
  getInputBase64OptionDocumentation(),
184
184
  {
185
- flags: '--out, -o',
185
+ flags: '--output, -o',
186
186
  type: 'path',
187
- required: 'yes*',
187
+ required: 'no',
188
188
  example: 'output.file',
189
- description: 'Write the result to this path or directory',
189
+ description:
190
+ 'Write the result to this path or directory. If omitted, the CLI infers a local output path.',
190
191
  },
191
192
  getPrintUrlsOptionDocumentation(),
192
193
  ]
@@ -195,11 +196,11 @@ function getSharedFileInputOutputRows(): DocOptionRow[] {
195
196
  function getSharedNoInputOutputRows(): DocOptionRow[] {
196
197
  return [
197
198
  {
198
- flags: '--out, -o',
199
+ flags: '--output, -o',
199
200
  type: 'path',
200
- required: 'yes*',
201
+ required: 'no',
201
202
  example: 'output.file',
202
- description: 'Write the result to this path',
203
+ description: 'Write the result to this path. If omitted, the CLI infers a local output path.',
203
204
  },
204
205
  getPrintUrlsOptionDocumentation(),
205
206
  ]
@@ -349,7 +350,7 @@ export function renderIntentDocsBody({
349
350
  '',
350
351
  renderAtAGlanceTable(definitions),
351
352
  '',
352
- '> At least one of `--out` or `--print-urls` is required on every intent command.',
353
+ '> If you omit `--output`, the CLI writes next to a single local file input when it can, otherwise it falls back to the current working directory. Use `--print-urls` alone when you want URLs without downloading locally.',
353
354
  '',
354
355
  `${heading} Shared flags`,
355
356
  '',
@@ -278,7 +278,7 @@ function inferExamples(
278
278
  }
279
279
 
280
280
  return [
281
- ['Run the command', `transloadit ${spec.paths.join(' ')} --input input.mp4 --out output/`],
281
+ ['Run the command', `transloadit ${spec.paths.join(' ')} --input input.mp4 --output output/`],
282
282
  ]
283
283
  }
284
284
 
@@ -310,7 +310,7 @@ function inferExamples(
310
310
  }
311
311
 
312
312
  const outputMode = spec.intentDefinition.outputMode ?? 'file'
313
- parts.push('--out', inferOutputPath(spec.paths, outputMode, fieldSpecs))
313
+ parts.push('--output', inferOutputPath(spec.paths, outputMode, fieldSpecs))
314
314
 
315
315
  return [['Run the command', parts.join(' ')]]
316
316
  }
@@ -355,11 +355,13 @@ function resolveRobotIntent(definition: RobotIntentDefinition): BuiltIntentComma
355
355
  input.kind === 'none'
356
356
  ? {
357
357
  execution,
358
+ defaultOutputPath: inferOutputPath(paths, outputMode, fieldSpecs),
358
359
  outputDescription: 'Write the result to this path',
359
360
  outputMode,
360
361
  }
361
362
  : {
362
363
  commandLabel,
364
+ defaultOutputPath: inferOutputPath(paths, outputMode, fieldSpecs),
363
365
  execution,
364
366
  inputPolicy: input.inputPolicy,
365
367
  outputDescription:
@@ -388,18 +390,18 @@ function getIntentDetails({
388
390
  robot: string
389
391
  }): string {
390
392
  if (inputMode === 'none') {
391
- return `Runs \`${robot}\` and writes the result to \`--out\`.`
393
+ return `Runs \`${robot}\` and writes the result to \`--output\`.`
392
394
  }
393
395
 
394
396
  if (defaultSingleAssembly) {
395
- return `Runs \`${robot}\` for the provided inputs and writes the result to \`--out\`.`
397
+ return `Runs \`${robot}\` for the provided inputs and writes the result to \`--output\`.`
396
398
  }
397
399
 
398
400
  if (outputMode === 'directory') {
399
- return `Runs \`${robot}\` on each input file and writes the results to \`--out\`.`
401
+ return `Runs \`${robot}\` on each input file and writes the results to \`--output\`.`
400
402
  }
401
403
 
402
- return `Runs \`${robot}\` on each input file and writes the result to \`--out\`.`
404
+ return `Runs \`${robot}\` on each input file and writes the result to \`--output\`.`
403
405
  }
404
406
 
405
407
  function resolveSemanticIntent(definition: SemanticIntentDefinition): BuiltIntentCommandDefinition {
@@ -415,6 +417,7 @@ function resolveSemanticIntent(definition: SemanticIntentDefinition): BuiltInten
415
417
  runnerKind: descriptor.runnerKind,
416
418
  intentDefinition: {
417
419
  commandLabel: paths.join(' '),
420
+ defaultOutputPath: descriptor.defaultOutputPath,
418
421
  execution: descriptor.execution,
419
422
  inputPolicy: descriptor.inputPolicy,
420
423
  outputDescription: descriptor.outputDescription,
@@ -430,12 +433,13 @@ function resolveTemplateIntent(
430
433
  const spec: BuiltIntentCommandDefinition = {
431
434
  className: `${toPascalCase(paths)}Command`,
432
435
  description: `Run ${stripTrailingPunctuation(definition.templateId)}`,
433
- details: `Runs the \`${definition.templateId}\` template and writes the outputs to \`--out\`.`,
436
+ details: `Runs the \`${definition.templateId}\` template and writes the outputs to \`--output\`.`,
434
437
  examples: [],
435
438
  paths,
436
439
  runnerKind: 'standard',
437
440
  intentDefinition: {
438
441
  commandLabel: paths.join(' '),
442
+ defaultOutputPath: inferOutputPath(paths, outputMode, []),
439
443
  execution: {
440
444
  kind: 'template',
441
445
  templateId: definition.templateId,
@@ -1,5 +1,5 @@
1
1
  import { statSync } from 'node:fs'
2
- import { basename } from 'node:path'
2
+ import { basename, dirname, join, parse, resolve } from 'node:path'
3
3
  import { Option } from 'clipanion'
4
4
  import type { z } from 'zod'
5
5
 
@@ -59,6 +59,7 @@ export type IntentFileExecutionDefinition =
59
59
 
60
60
  export interface IntentFileCommandDefinition {
61
61
  commandLabel: string
62
+ defaultOutputPath: string
62
63
  execution: IntentFileExecutionDefinition
63
64
  inputPolicy: IntentInputPolicy
64
65
  outputDescription: string
@@ -66,6 +67,7 @@ export interface IntentFileCommandDefinition {
66
67
  }
67
68
 
68
69
  export interface IntentNoInputCommandDefinition {
70
+ defaultOutputPath: string
69
71
  execution: IntentSingleStepExecutionDefinition
70
72
  outputDescription: string
71
73
  outputMode?: 'directory' | 'file'
@@ -338,6 +340,7 @@ async function executeIntentCommand({
338
340
  client,
339
341
  definition,
340
342
  output,
343
+ outputMode,
341
344
  outputPath,
342
345
  printUrls,
343
346
  rawValues,
@@ -347,6 +350,7 @@ async function executeIntentCommand({
347
350
  createOptions: Omit<AssembliesCreateOptions, 'output' | 'steps' | 'stepsData' | 'template'>
348
351
  definition: IntentFileCommandDefinition | IntentNoInputCommandDefinition
349
352
  output: AuthenticatedCommand['output']
353
+ outputMode?: 'directory' | 'file'
350
354
  outputPath?: string
351
355
  printUrls: boolean
352
356
  rawValues: Record<string, unknown>
@@ -379,7 +383,7 @@ async function executeIntentCommand({
379
383
  const { hasFailures, resultUrls } = await assembliesCommands.create(output, client, {
380
384
  ...createOptions,
381
385
  output: outputPath ?? null,
382
- outputMode: definition.outputMode,
386
+ outputMode,
383
387
  ...executionOptions,
384
388
  })
385
389
  if (printUrls) {
@@ -391,7 +395,8 @@ async function executeIntentCommand({
391
395
  abstract class GeneratedIntentCommandBase extends AuthenticatedCommand {
392
396
  declare static intentDefinition: IntentFileCommandDefinition | IntentNoInputCommandDefinition
393
397
 
394
- outputPath = Option.String('--out,-o', {
398
+ // Intents standardize on --output while the surface is still young enough to change cleanly.
399
+ outputPath = Option.String('--output,-o', {
395
400
  description: this.getOutputDescription(),
396
401
  })
397
402
 
@@ -410,23 +415,58 @@ abstract class GeneratedIntentCommandBase extends AuthenticatedCommand {
410
415
  return this.getIntentDefinition().outputDescription
411
416
  }
412
417
 
413
- protected validateOutputChoice(): number | undefined {
414
- if (this.outputPath == null && !this.printUrls) {
415
- this.output.error('Specify at least one of --out or --print-urls')
416
- return 1
418
+ protected resolveDefaultOutputPath(rawValues: Record<string, unknown>): string | undefined {
419
+ const defaultOutputPath = this.getIntentDefinition().defaultOutputPath
420
+ if (this.getIntentDefinition().outputMode === 'directory') {
421
+ return defaultOutputPath
417
422
  }
418
423
 
419
- return undefined
424
+ const format = rawValues.format
425
+ if (typeof format !== 'string') {
426
+ return defaultOutputPath
427
+ }
428
+
429
+ const trimmedFormat = format.trim().toLowerCase()
430
+ if (!/^[a-z0-9]+(?:[-_][a-z0-9]+)*$/.test(trimmedFormat)) {
431
+ return defaultOutputPath
432
+ }
433
+
434
+ const parsedDefaultOutputPath = parse(defaultOutputPath)
435
+ const outputBasename =
436
+ parsedDefaultOutputPath.name === ''
437
+ ? basename(defaultOutputPath, parsedDefaultOutputPath.ext)
438
+ : parsedDefaultOutputPath.name
439
+
440
+ return join(parsedDefaultOutputPath.dir, `${outputBasename}.${trimmedFormat}`)
441
+ }
442
+
443
+ protected getDefaultOutputPath(rawValues: Record<string, unknown>): string | undefined {
444
+ return this.resolveDefaultOutputPath(rawValues)
445
+ }
446
+
447
+ protected getEffectiveOutputPath(rawValues: Record<string, unknown>): string | undefined {
448
+ if (this.outputPath != null) {
449
+ return this.outputPath
450
+ }
451
+
452
+ if (this.printUrls) {
453
+ return undefined
454
+ }
455
+
456
+ return this.getDefaultOutputPath(rawValues)
457
+ }
458
+
459
+ protected getEffectiveOutputMode(
460
+ _rawValues: Record<string, unknown>,
461
+ _outputPath: string | undefined,
462
+ ): 'directory' | 'file' | undefined {
463
+ return this.getIntentDefinition().outputMode
420
464
  }
421
465
  }
422
466
 
423
467
  export abstract class GeneratedNoInputIntentCommand extends GeneratedIntentCommandBase {
424
468
  protected override async run(): Promise<number | undefined> {
425
- const outputValidationError = this.validateOutputChoice()
426
- if (outputValidationError != null) {
427
- return outputValidationError
428
- }
429
-
469
+ const rawValues = this.getIntentRawValues()
430
470
  return await executeIntentCommand({
431
471
  client: this.client,
432
472
  createOptions: {
@@ -434,9 +474,10 @@ export abstract class GeneratedNoInputIntentCommand extends GeneratedIntentComma
434
474
  },
435
475
  definition: this.getIntentDefinition() as IntentNoInputCommandDefinition,
436
476
  output: this.output,
437
- outputPath: this.outputPath,
477
+ outputMode: this.getEffectiveOutputMode(rawValues, this.getEffectiveOutputPath(rawValues)),
478
+ outputPath: this.getEffectiveOutputPath(rawValues),
438
479
  printUrls: this.printUrls ?? false,
439
- rawValues: this.getIntentRawValues(),
480
+ rawValues,
440
481
  })
441
482
  }
442
483
  }
@@ -481,6 +522,126 @@ abstract class GeneratedFileIntentCommandBase extends GeneratedIntentCommandBase
481
522
  return super.getIntentDefinition() as IntentFileCommandDefinition
482
523
  }
483
524
 
525
+ protected getSingleFilesystemFileInput(): string | null {
526
+ if ((this.inputBase64?.length ?? 0) > 0) {
527
+ return null
528
+ }
529
+
530
+ const localInputs = (this.inputs ?? []).filter((input) => input !== '-' && !isHttpUrl(input))
531
+ if (localInputs.length !== 1) {
532
+ return null
533
+ }
534
+
535
+ const candidate = localInputs[0]
536
+ if (candidate == null) {
537
+ return null
538
+ }
539
+
540
+ try {
541
+ return statSync(candidate).isFile() ? candidate : null
542
+ } catch {
543
+ return null
544
+ }
545
+ }
546
+
547
+ protected hasDirectoryInput(): boolean {
548
+ return (this.inputs ?? []).some((input) => {
549
+ if (input === '-' || isHttpUrl(input)) {
550
+ return false
551
+ }
552
+
553
+ try {
554
+ return statSync(input).isDirectory()
555
+ } catch {
556
+ return false
557
+ }
558
+ })
559
+ }
560
+
561
+ protected prefersDirectoryDefaultOutput(): boolean {
562
+ return this.getIntentDefinition().outputMode === 'directory'
563
+ }
564
+
565
+ protected getSuggestedDirectoryOutputPath(): string {
566
+ if (this.getIntentDefinition().outputMode === 'directory') {
567
+ return this.resolveDefaultOutputPath({}) ?? 'output/'
568
+ }
569
+
570
+ return 'output/'
571
+ }
572
+
573
+ private getSafeSiblingDirectoryOutputPath(inputPath: string): string {
574
+ const parsedInputPath = parse(inputPath)
575
+ const candidateBaseName =
576
+ parsedInputPath.name === '' ? basename(inputPath) : parsedInputPath.name
577
+ const candidateDirectoryPath = join(dirname(inputPath), candidateBaseName)
578
+
579
+ const isSafeDirectoryCandidate = (candidatePath: string): boolean => {
580
+ if (resolve(candidatePath) === resolve(inputPath)) {
581
+ return false
582
+ }
583
+
584
+ try {
585
+ return statSync(candidatePath).isDirectory()
586
+ } catch {
587
+ return true
588
+ }
589
+ }
590
+
591
+ if (isSafeDirectoryCandidate(candidateDirectoryPath)) {
592
+ return candidateDirectoryPath
593
+ }
594
+
595
+ for (let suffixIndex = 0; suffixIndex < 1000; suffixIndex += 1) {
596
+ const suffix = suffixIndex === 0 ? '-output' : `-output-${suffixIndex + 1}`
597
+ const fallbackDirectoryPath = join(dirname(inputPath), `${candidateBaseName}${suffix}`)
598
+ if (isSafeDirectoryCandidate(fallbackDirectoryPath)) {
599
+ return fallbackDirectoryPath
600
+ }
601
+ }
602
+
603
+ throw new Error(`Could not infer a safe output directory path for ${inputPath}`)
604
+ }
605
+
606
+ protected getSiblingOutputPath(inputPath: string, rawValues: Record<string, unknown>): string {
607
+ if (this.getIntentDefinition().outputMode === 'directory') {
608
+ return this.getSafeSiblingDirectoryOutputPath(inputPath)
609
+ }
610
+
611
+ const resolvedDefaultOutputPath = this.resolveDefaultOutputPath(rawValues)
612
+ const extension = parse(
613
+ resolvedDefaultOutputPath ?? this.getIntentDefinition().defaultOutputPath,
614
+ ).ext
615
+ const parsedInputPath = parse(inputPath)
616
+ const candidateOutputPath = join(dirname(inputPath), `${parsedInputPath.name}${extension}`)
617
+ if (resolve(candidateOutputPath) !== resolve(inputPath)) {
618
+ return candidateOutputPath
619
+ }
620
+
621
+ return join(dirname(inputPath), `${parsedInputPath.name}-output${extension}`)
622
+ }
623
+
624
+ protected override getDefaultOutputPath(rawValues: Record<string, unknown>): string | undefined {
625
+ if (this.prefersDirectoryDefaultOutput()) {
626
+ const singleFilesystemFileInput = this.getSingleFilesystemFileInput()
627
+ if (
628
+ this.getIntentDefinition().outputMode === 'directory' &&
629
+ singleFilesystemFileInput != null
630
+ ) {
631
+ return this.getSiblingOutputPath(singleFilesystemFileInput, rawValues)
632
+ }
633
+
634
+ return this.getSuggestedDirectoryOutputPath()
635
+ }
636
+
637
+ const singleFilesystemFileInput = this.getSingleFilesystemFileInput()
638
+ if (singleFilesystemFileInput != null) {
639
+ return this.getSiblingOutputPath(singleFilesystemFileInput, rawValues)
640
+ }
641
+
642
+ return this.resolveDefaultOutputPath(rawValues)
643
+ }
644
+
484
645
  protected async prepareInputs(): Promise<PreparedIntentInputs> {
485
646
  return await prepareIntentInputs({
486
647
  inputValues: this.inputs ?? [],
@@ -513,24 +674,39 @@ abstract class GeneratedFileIntentCommandBase extends GeneratedIntentCommandBase
513
674
  )
514
675
  }
515
676
 
516
- protected resolveOutputMode(): 'directory' | 'file' | undefined {
677
+ protected resolveOutputMode(outputPath: string | undefined): 'directory' | 'file' | undefined {
517
678
  if (this.getIntentDefinition().outputMode != null) {
518
679
  return this.getIntentDefinition().outputMode
519
680
  }
520
681
 
521
- if (this.outputPath == null) {
682
+ if (outputPath == null) {
522
683
  return undefined
523
684
  }
524
685
 
686
+ if (this.outputPath == null && this.prefersDirectoryDefaultOutput()) {
687
+ return 'directory'
688
+ }
689
+
690
+ if (/[\\/]$/.test(outputPath)) {
691
+ return 'directory'
692
+ }
693
+
525
694
  try {
526
- return statSync(this.outputPath).isDirectory() ? 'directory' : 'file'
695
+ return statSync(outputPath).isDirectory() ? 'directory' : 'file'
527
696
  } catch {
528
697
  return 'file'
529
698
  }
530
699
  }
531
700
 
532
701
  protected isDirectoryOutputTarget(): boolean {
533
- return this.resolveOutputMode() === 'directory'
702
+ return this.resolveOutputMode(this.outputPath) === 'directory'
703
+ }
704
+
705
+ protected override getEffectiveOutputMode(
706
+ rawValues: Record<string, unknown>,
707
+ outputPath: string | undefined,
708
+ ): 'directory' | 'file' | undefined {
709
+ return this.resolveOutputMode(outputPath ?? this.getDefaultOutputPath(rawValues))
534
710
  }
535
711
 
536
712
  protected validateInputPresence(rawValues: Record<string, unknown>): number | undefined {
@@ -556,11 +732,6 @@ abstract class GeneratedFileIntentCommandBase extends GeneratedIntentCommandBase
556
732
  }
557
733
 
558
734
  protected validateBeforePreparingInputs(rawValues: Record<string, unknown>): number | undefined {
559
- const outputValidationError = this.validateOutputChoice()
560
- if (outputValidationError != null) {
561
- return outputValidationError
562
- }
563
-
564
735
  const validationError = this.validateInputPresence(rawValues)
565
736
  if (validationError != null) {
566
737
  return validationError
@@ -591,12 +762,14 @@ abstract class GeneratedFileIntentCommandBase extends GeneratedIntentCommandBase
591
762
  }
592
763
  }
593
764
 
765
+ const effectiveOutputPath = this.getEffectiveOutputPath(rawValues)
594
766
  return await executeIntentCommand({
595
767
  client: this.client,
596
768
  createOptions: this.getCreateOptions(effectivePreparedInputs.inputs),
597
769
  definition: this.getIntentDefinition(),
598
770
  output: this.output,
599
- outputPath: this.outputPath,
771
+ outputMode: this.getEffectiveOutputMode(rawValues, effectiveOutputPath),
772
+ outputPath: effectiveOutputPath,
600
773
  printUrls: this.printUrls ?? false,
601
774
  rawValues,
602
775
  })
@@ -669,6 +842,14 @@ export abstract class GeneratedWatchableFileIntentCommand extends GeneratedFileI
669
842
  return false
670
843
  }
671
844
 
845
+ protected override prefersDirectoryDefaultOutput(): boolean {
846
+ return (
847
+ super.prefersDirectoryDefaultOutput() ||
848
+ this.getProvidedInputCount() > 1 ||
849
+ this.hasDirectoryInput()
850
+ )
851
+ }
852
+
672
853
  protected override validatePreparedInputs(
673
854
  preparedInputs: PreparedIntentInputs,
674
855
  ): number | undefined {
@@ -687,6 +868,13 @@ export abstract class GeneratedStandardFileIntentCommand extends GeneratedWatcha
687
868
  return this.singleAssembly
688
869
  }
689
870
 
871
+ protected override prefersDirectoryDefaultOutput(): boolean {
872
+ return (
873
+ super.prefersDirectoryDefaultOutput() ||
874
+ (this.singleAssembly && (this.getProvidedInputCount() > 1 || this.hasDirectoryInput()))
875
+ )
876
+ }
877
+
690
878
  protected override getCreateOptions(
691
879
  inputs: string[],
692
880
  ): Omit<AssembliesCreateOptions, 'output' | 'steps' | 'stepsData' | 'template'> {
@@ -706,14 +894,7 @@ export abstract class GeneratedStandardFileIntentCommand extends GeneratedWatcha
706
894
 
707
895
  if (
708
896
  this.singleAssembly &&
709
- (this.getProvidedInputCount() > 1 ||
710
- this.inputs.some((inputPath) => {
711
- try {
712
- return statSync(inputPath).isDirectory()
713
- } catch {
714
- return false
715
- }
716
- })) &&
897
+ (this.getProvidedInputCount() > 1 || this.hasDirectoryInput()) &&
717
898
  this.outputPath != null &&
718
899
  !this.isDirectoryOutputTarget()
719
900
  ) {
@@ -62,19 +62,19 @@ const imageDescribeExecutionDefinition = {
62
62
  const imageDescribeCommandPresentation = {
63
63
  description: 'Describe images as labels or publishable text fields',
64
64
  details:
65
- 'Generates image labels through `/image/describe`, or structured altText/title/caption/description through `/ai/chat`, then writes the JSON result to `--out`.',
65
+ 'Generates image labels through `/image/describe`, or structured altText/title/caption/description through `/ai/chat`, then writes the JSON result to `--output`.',
66
66
  examples: [
67
67
  [
68
68
  'Describe an image as labels',
69
- 'transloadit image describe --input hero.jpg --out labels.json',
69
+ 'transloadit image describe --input hero.jpg --output labels.json',
70
70
  ],
71
71
  [
72
72
  'Generate WordPress-ready fields',
73
- 'transloadit image describe --input hero.jpg --for wordpress --out fields.json',
73
+ 'transloadit image describe --input hero.jpg --for wordpress --output fields.json',
74
74
  ],
75
75
  [
76
76
  'Request a custom field set',
77
- 'transloadit image describe --input hero.jpg --fields altText,title,caption --out fields.json',
77
+ 'transloadit image describe --input hero.jpg --fields altText,title,caption --output fields.json',
78
78
  ],
79
79
  ] as Array<[string, string]>,
80
80
  } as const satisfies SemanticIntentPresentation
@@ -249,6 +249,7 @@ function createImageDescribeStep(
249
249
 
250
250
  export const imageDescribeSemanticIntentDescriptor = {
251
251
  createStep: createImageDescribeStep,
252
+ defaultOutputPath: 'output.json',
252
253
  execution: imageDescribeExecutionDefinition,
253
254
  inputPolicy: { kind: 'required' },
254
255
  outputDescription: 'Write the JSON result to this path or directory',
@@ -91,15 +91,15 @@ const imageGenerateCommandPresentation = {
91
91
  examples: [
92
92
  [
93
93
  'Generate an image from text',
94
- 'transloadit image generate --prompt "A red bicycle in a studio" --out output.png',
94
+ 'transloadit image generate --prompt "A red bicycle in a studio" --output output.png',
95
95
  ],
96
96
  [
97
97
  'Guide generation with one input image',
98
- 'transloadit image generate --input subject.jpg --prompt "Place subject.jpg on a magazine cover" --out output.png',
98
+ 'transloadit image generate --input subject.jpg --prompt "Place subject.jpg on a magazine cover" --output output.png',
99
99
  ],
100
100
  [
101
101
  'Guide generation with multiple input images',
102
- 'transloadit image generate --input person1.jpg --input person2.jpg --input background.jpg --prompt "Place person1.jpg feeding person2.jpg in front of background.jpg" --out output.png',
102
+ 'transloadit image generate --input person1.jpg --input person2.jpg --input background.jpg --prompt "Place person1.jpg feeding person2.jpg in front of background.jpg" --output output.png',
103
103
  ],
104
104
  ] as Array<[string, string]>,
105
105
  } as const satisfies SemanticIntentPresentation
@@ -234,6 +234,7 @@ function ensureUniqueInputBasenames(preparedInputs: PreparedIntentInputs): Prepa
234
234
 
235
235
  export const imageGenerateSemanticIntentDescriptor = {
236
236
  createStep: createImageGenerateStep,
237
+ defaultOutputPath: 'output.png',
237
238
  execution: {
238
239
  kind: 'dynamic-step',
239
240
  handler: 'image-generate',
@@ -22,6 +22,7 @@ export interface SemanticIntentDescriptor {
22
22
  rawValues: Record<string, unknown>,
23
23
  context: { hasInputs: boolean },
24
24
  ) => Record<string, unknown>
25
+ defaultOutputPath: string
25
26
  execution: IntentDynamicStepExecutionDefinition
26
27
  inputPolicy: IntentInputPolicy
27
28
  outputDescription: string
@@ -66,7 +66,7 @@ function createMarkdownConvertSemanticIntent({
66
66
  examples: [
67
67
  [
68
68
  `Render a Markdown file as a ${formatLabel} file`,
69
- `transloadit markdown ${format} --input README.md --out ${exampleOutput}`,
69
+ `transloadit markdown ${format} --input README.md --output ${exampleOutput}`,
70
70
  ],
71
71
  [
72
72
  'Print a temporary result URL without downloading locally',
@@ -94,6 +94,7 @@ function createMarkdownConvertSemanticIntent({
94
94
  resultStepName: 'convert',
95
95
  fields: markdownOptionDefinitions,
96
96
  },
97
+ defaultOutputPath: exampleOutput,
97
98
  inputPolicy: { kind: 'required' },
98
99
  outputDescription: `Write the rendered ${formatLabel} to this path or directory`,
99
100
  presentation,