transloadit 4.7.3 → 4.7.6

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 (152) hide show
  1. package/README.md +897 -5
  2. package/dist/Transloadit.d.ts +13 -3
  3. package/dist/Transloadit.d.ts.map +1 -1
  4. package/dist/Transloadit.js +22 -2
  5. package/dist/Transloadit.js.map +1 -1
  6. package/dist/alphalib/types/assembliesGet.d.ts +5 -0
  7. package/dist/alphalib/types/assembliesGet.d.ts.map +1 -1
  8. package/dist/alphalib/types/assemblyReplay.d.ts +5 -0
  9. package/dist/alphalib/types/assemblyReplay.d.ts.map +1 -1
  10. package/dist/alphalib/types/assemblyReplayNotification.d.ts +5 -0
  11. package/dist/alphalib/types/assemblyReplayNotification.d.ts.map +1 -1
  12. package/dist/alphalib/types/assemblyStatus.d.ts +25 -25
  13. package/dist/alphalib/types/assemblyStatus.d.ts.map +1 -1
  14. package/dist/alphalib/types/assemblyStatus.js +4 -1
  15. package/dist/alphalib/types/assemblyStatus.js.map +1 -1
  16. package/dist/alphalib/types/bill.d.ts +5 -0
  17. package/dist/alphalib/types/bill.d.ts.map +1 -1
  18. package/dist/alphalib/types/builtinTemplates.d.ts +83 -0
  19. package/dist/alphalib/types/builtinTemplates.d.ts.map +1 -0
  20. package/dist/alphalib/types/builtinTemplates.js +19 -0
  21. package/dist/alphalib/types/builtinTemplates.js.map +1 -0
  22. package/dist/alphalib/types/robots/ai-chat.d.ts.map +1 -1
  23. package/dist/alphalib/types/robots/ai-chat.js +1 -0
  24. package/dist/alphalib/types/robots/ai-chat.js.map +1 -1
  25. package/dist/alphalib/types/skillFrontmatter.d.ts +29 -0
  26. package/dist/alphalib/types/skillFrontmatter.d.ts.map +1 -0
  27. package/dist/alphalib/types/skillFrontmatter.js +19 -0
  28. package/dist/alphalib/types/skillFrontmatter.js.map +1 -0
  29. package/dist/alphalib/types/template.d.ts +36 -0
  30. package/dist/alphalib/types/template.d.ts.map +1 -1
  31. package/dist/alphalib/types/template.js +10 -0
  32. package/dist/alphalib/types/template.js.map +1 -1
  33. package/dist/alphalib/types/templateCredential.d.ts +10 -0
  34. package/dist/alphalib/types/templateCredential.d.ts.map +1 -1
  35. package/dist/bearerToken.d.ts +31 -0
  36. package/dist/bearerToken.d.ts.map +1 -0
  37. package/dist/bearerToken.js +158 -0
  38. package/dist/bearerToken.js.map +1 -0
  39. package/dist/cli/commands/assemblies.d.ts +8 -2
  40. package/dist/cli/commands/assemblies.d.ts.map +1 -1
  41. package/dist/cli/commands/assemblies.js +566 -411
  42. package/dist/cli/commands/assemblies.js.map +1 -1
  43. package/dist/cli/commands/auth.d.ts +1 -4
  44. package/dist/cli/commands/auth.d.ts.map +1 -1
  45. package/dist/cli/commands/auth.js +7 -123
  46. package/dist/cli/commands/auth.js.map +1 -1
  47. package/dist/cli/commands/index.d.ts.map +1 -1
  48. package/dist/cli/commands/index.js +5 -0
  49. package/dist/cli/commands/index.js.map +1 -1
  50. package/dist/cli/commands/templates.d.ts.map +1 -1
  51. package/dist/cli/commands/templates.js +4 -14
  52. package/dist/cli/commands/templates.js.map +1 -1
  53. package/dist/cli/fileProcessingOptions.d.ts +35 -0
  54. package/dist/cli/fileProcessingOptions.d.ts.map +1 -0
  55. package/dist/cli/fileProcessingOptions.js +182 -0
  56. package/dist/cli/fileProcessingOptions.js.map +1 -0
  57. package/dist/cli/generateIntentDocs.d.ts +2 -0
  58. package/dist/cli/generateIntentDocs.d.ts.map +1 -0
  59. package/dist/cli/generateIntentDocs.js +321 -0
  60. package/dist/cli/generateIntentDocs.js.map +1 -0
  61. package/dist/cli/intentCommandSpecs.d.ts +36 -0
  62. package/dist/cli/intentCommandSpecs.d.ts.map +1 -0
  63. package/dist/cli/intentCommandSpecs.js +181 -0
  64. package/dist/cli/intentCommandSpecs.js.map +1 -0
  65. package/dist/cli/intentCommands.d.ts +13 -0
  66. package/dist/cli/intentCommands.d.ts.map +1 -0
  67. package/dist/cli/intentCommands.js +368 -0
  68. package/dist/cli/intentCommands.js.map +1 -0
  69. package/dist/cli/intentFields.d.ts +25 -0
  70. package/dist/cli/intentFields.d.ts.map +1 -0
  71. package/dist/cli/intentFields.js +298 -0
  72. package/dist/cli/intentFields.js.map +1 -0
  73. package/dist/cli/intentInputPolicy.d.ts +10 -0
  74. package/dist/cli/intentInputPolicy.d.ts.map +1 -0
  75. package/dist/cli/intentInputPolicy.js +2 -0
  76. package/dist/cli/intentInputPolicy.js.map +1 -0
  77. package/dist/cli/intentRuntime.d.ts +114 -0
  78. package/dist/cli/intentRuntime.d.ts.map +1 -0
  79. package/dist/cli/intentRuntime.js +464 -0
  80. package/dist/cli/intentRuntime.js.map +1 -0
  81. package/dist/cli/resultFiles.d.ts +19 -0
  82. package/dist/cli/resultFiles.d.ts.map +1 -0
  83. package/dist/cli/resultFiles.js +66 -0
  84. package/dist/cli/resultFiles.js.map +1 -0
  85. package/dist/cli/resultUrls.d.ts +19 -0
  86. package/dist/cli/resultUrls.d.ts.map +1 -0
  87. package/dist/cli/resultUrls.js +36 -0
  88. package/dist/cli/resultUrls.js.map +1 -0
  89. package/dist/cli/semanticIntents/imageDescribe.d.ts +43 -0
  90. package/dist/cli/semanticIntents/imageDescribe.d.ts.map +1 -0
  91. package/dist/cli/semanticIntents/imageDescribe.js +188 -0
  92. package/dist/cli/semanticIntents/imageDescribe.js.map +1 -0
  93. package/dist/cli/semanticIntents/index.d.ts +18 -0
  94. package/dist/cli/semanticIntents/index.d.ts.map +1 -0
  95. package/dist/cli/semanticIntents/index.js +18 -0
  96. package/dist/cli/semanticIntents/index.js.map +1 -0
  97. package/dist/cli/semanticIntents/markdownPdf.d.ts +4 -0
  98. package/dist/cli/semanticIntents/markdownPdf.d.ts.map +1 -0
  99. package/dist/cli/semanticIntents/markdownPdf.js +93 -0
  100. package/dist/cli/semanticIntents/markdownPdf.js.map +1 -0
  101. package/dist/cli/semanticIntents/parsing.d.ts +11 -0
  102. package/dist/cli/semanticIntents/parsing.d.ts.map +1 -0
  103. package/dist/cli/semanticIntents/parsing.js +29 -0
  104. package/dist/cli/semanticIntents/parsing.js.map +1 -0
  105. package/dist/cli/stepsInput.d.ts +4 -0
  106. package/dist/cli/stepsInput.d.ts.map +1 -0
  107. package/dist/cli/stepsInput.js +23 -0
  108. package/dist/cli/stepsInput.js.map +1 -0
  109. package/dist/cli.d.ts +1 -1
  110. package/dist/cli.d.ts.map +1 -1
  111. package/dist/cli.js +5 -4
  112. package/dist/cli.js.map +1 -1
  113. package/dist/ensureUniqueCounter.d.ts +8 -0
  114. package/dist/ensureUniqueCounter.d.ts.map +1 -0
  115. package/dist/ensureUniqueCounter.js +48 -0
  116. package/dist/ensureUniqueCounter.js.map +1 -0
  117. package/dist/inputFiles.d.ts +9 -0
  118. package/dist/inputFiles.d.ts.map +1 -1
  119. package/dist/inputFiles.js +177 -26
  120. package/dist/inputFiles.js.map +1 -1
  121. package/dist/robots.js +1 -1
  122. package/dist/robots.js.map +1 -1
  123. package/package.json +9 -7
  124. package/src/Transloadit.ts +35 -3
  125. package/src/alphalib/types/assemblyStatus.ts +4 -1
  126. package/src/alphalib/types/builtinTemplates.ts +24 -0
  127. package/src/alphalib/types/robots/ai-chat.ts +1 -0
  128. package/src/alphalib/types/skillFrontmatter.ts +24 -0
  129. package/src/alphalib/types/template.ts +14 -0
  130. package/src/bearerToken.ts +208 -0
  131. package/src/cli/commands/assemblies.ts +825 -505
  132. package/src/cli/commands/auth.ts +9 -151
  133. package/src/cli/commands/index.ts +6 -3
  134. package/src/cli/commands/templates.ts +6 -17
  135. package/src/cli/fileProcessingOptions.ts +294 -0
  136. package/src/cli/generateIntentDocs.ts +419 -0
  137. package/src/cli/intentCommandSpecs.ts +282 -0
  138. package/src/cli/intentCommands.ts +525 -0
  139. package/src/cli/intentFields.ts +403 -0
  140. package/src/cli/intentInputPolicy.ts +11 -0
  141. package/src/cli/intentRuntime.ts +734 -0
  142. package/src/cli/resultFiles.ts +105 -0
  143. package/src/cli/resultUrls.ts +72 -0
  144. package/src/cli/semanticIntents/imageDescribe.ts +254 -0
  145. package/src/cli/semanticIntents/index.ts +48 -0
  146. package/src/cli/semanticIntents/markdownPdf.ts +120 -0
  147. package/src/cli/semanticIntents/parsing.ts +56 -0
  148. package/src/cli/stepsInput.ts +32 -0
  149. package/src/cli.ts +5 -4
  150. package/src/ensureUniqueCounter.ts +75 -0
  151. package/src/inputFiles.ts +277 -26
  152. package/src/robots.ts +1 -1
@@ -0,0 +1,734 @@
1
+ import { statSync } from 'node:fs'
2
+ import { basename } from 'node:path'
3
+ import { Option } from 'clipanion'
4
+ import type { z } from 'zod'
5
+
6
+ import { prepareInputFiles } from '../inputFiles.ts'
7
+ import type { AssembliesCreateOptions } from './commands/assemblies.ts'
8
+ import * as assembliesCommands from './commands/assemblies.ts'
9
+ import { AuthenticatedCommand } from './commands/BaseCommand.ts'
10
+ import type { SharedCliOptionDocumentation } from './fileProcessingOptions.ts'
11
+ import {
12
+ concurrencyOption,
13
+ countProvidedInputs,
14
+ deleteAfterProcessingOption,
15
+ inputPathsOption,
16
+ printUrlsOption,
17
+ recursiveOption,
18
+ reprocessStaleOption,
19
+ singleAssemblyOption,
20
+ validateSharedFileProcessingOptions,
21
+ watchOption,
22
+ } from './fileProcessingOptions.ts'
23
+ import type { IntentFieldSpec } from './intentFields.ts'
24
+ import { coerceIntentFieldValue } from './intentFields.ts'
25
+ import type { IntentInputPolicy } from './intentInputPolicy.ts'
26
+ import { printResultUrls } from './resultUrls.ts'
27
+ import { getSemanticIntentDescriptor } from './semanticIntents/index.ts'
28
+
29
+ export interface PreparedIntentInputs {
30
+ cleanup: Array<() => Promise<void>>
31
+ hasTransientInputs: boolean
32
+ inputs: string[]
33
+ }
34
+
35
+ export interface IntentSingleStepExecutionDefinition {
36
+ fields: readonly IntentOptionDefinition[]
37
+ fixedValues: Record<string, unknown>
38
+ kind: 'single-step'
39
+ resultStepName: string
40
+ schema: z.AnyZodObject
41
+ }
42
+
43
+ export interface IntentDynamicStepExecutionDefinition {
44
+ fields: readonly IntentOptionDefinition[]
45
+ handler: string
46
+ kind: 'dynamic-step'
47
+ resultStepName: string
48
+ }
49
+
50
+ export interface IntentTemplateExecutionDefinition {
51
+ kind: 'template'
52
+ templateId: string
53
+ }
54
+
55
+ export type IntentFileExecutionDefinition =
56
+ | IntentDynamicStepExecutionDefinition
57
+ | IntentSingleStepExecutionDefinition
58
+ | IntentTemplateExecutionDefinition
59
+
60
+ export interface IntentFileCommandDefinition {
61
+ commandLabel: string
62
+ execution: IntentFileExecutionDefinition
63
+ inputPolicy: IntentInputPolicy
64
+ outputDescription: string
65
+ outputMode?: 'directory' | 'file'
66
+ }
67
+
68
+ export interface IntentNoInputCommandDefinition {
69
+ execution: IntentSingleStepExecutionDefinition
70
+ outputDescription: string
71
+ outputMode?: 'directory' | 'file'
72
+ }
73
+
74
+ export type IntentRunnerKind = 'bundled' | 'no-input' | 'standard' | 'watchable'
75
+
76
+ export interface IntentCommandDefinition {
77
+ className: string
78
+ description: string
79
+ details: string
80
+ examples: Array<[string, string]>
81
+ intentDefinition: IntentFileCommandDefinition | IntentNoInputCommandDefinition
82
+ paths: string[]
83
+ runnerKind: IntentRunnerKind
84
+ }
85
+
86
+ export interface IntentOptionDefinition extends IntentFieldSpec {
87
+ description?: string
88
+ exampleValue?: unknown
89
+ optionFlags: string
90
+ propertyName: string
91
+ required?: boolean
92
+ }
93
+
94
+ const inputBase64OptionDocumentation = {
95
+ flags: '--input-base64',
96
+ type: 'base64 | data URL',
97
+ required: 'no',
98
+ example: 'data:text/plain;base64,SGVsbG8=',
99
+ description: 'Provide base64-encoded input content directly',
100
+ } as const satisfies SharedCliOptionDocumentation
101
+
102
+ export function getInputBase64OptionDocumentation(): SharedCliOptionDocumentation {
103
+ return inputBase64OptionDocumentation
104
+ }
105
+
106
+ function inputBase64Option(): string[] {
107
+ return Option.Array(inputBase64OptionDocumentation.flags, {
108
+ description: inputBase64OptionDocumentation.description,
109
+ }) as unknown as string[]
110
+ }
111
+
112
+ function isHttpUrl(value: string): boolean {
113
+ try {
114
+ const url = new URL(value)
115
+ return url.protocol === 'http:' || url.protocol === 'https:'
116
+ } catch {
117
+ return false
118
+ }
119
+ }
120
+
121
+ function parseBase64DataUrl(
122
+ value: string,
123
+ ): { mediaType: string | null; payload: string; trimmed: string } | null {
124
+ const trimmed = value.trim()
125
+ const marker = ';base64,'
126
+ const markerIndex = trimmed.indexOf(marker)
127
+ if (!trimmed.startsWith('data:') || markerIndex === -1) {
128
+ return null
129
+ }
130
+
131
+ return {
132
+ trimmed,
133
+ mediaType: trimmed.slice('data:'.length, markerIndex).split(';')[0]?.toLowerCase() ?? null,
134
+ payload: trimmed.slice(markerIndex + marker.length),
135
+ }
136
+ }
137
+
138
+ function normalizeBase64Value(value: string): string {
139
+ const parsed = parseBase64DataUrl(value)
140
+ return parsed?.payload ?? value.trim()
141
+ }
142
+
143
+ function inferFilenameFromBase64Value(value: string, index: number): string {
144
+ const parsed = parseBase64DataUrl(value)
145
+ if (parsed == null) {
146
+ return `input-base64-${index}.bin`
147
+ }
148
+
149
+ const extensionByMediaType = {
150
+ 'text/plain': 'txt',
151
+ 'text/markdown': 'md',
152
+ 'application/pdf': 'pdf',
153
+ 'image/png': 'png',
154
+ 'image/jpeg': 'jpg',
155
+ 'image/webp': 'webp',
156
+ 'application/json': 'json',
157
+ } as const satisfies Record<string, string>
158
+ const extension =
159
+ (extensionByMediaType as Record<string, string>)[parsed.mediaType ?? ''] ?? 'bin'
160
+
161
+ return `input-base64-${index}.${extension}`
162
+ }
163
+
164
+ export async function prepareIntentInputs({
165
+ inputBase64Values,
166
+ inputValues,
167
+ }: {
168
+ inputBase64Values: string[]
169
+ inputValues: string[]
170
+ }): Promise<PreparedIntentInputs> {
171
+ const preparedOrder: string[] = []
172
+ const syntheticInputs: Array<
173
+ | {
174
+ base64: string
175
+ field: string
176
+ filename: string
177
+ kind: 'base64'
178
+ }
179
+ | {
180
+ field: string
181
+ kind: 'url'
182
+ url: string
183
+ }
184
+ > = []
185
+
186
+ for (const value of inputValues) {
187
+ if (!isHttpUrl(value)) {
188
+ preparedOrder.push(value)
189
+ continue
190
+ }
191
+
192
+ const field = `input_url_${syntheticInputs.length + 1}`
193
+ syntheticInputs.push({
194
+ kind: 'url',
195
+ field,
196
+ url: value,
197
+ })
198
+ preparedOrder.push(field)
199
+ }
200
+
201
+ for (const [index, value] of inputBase64Values.entries()) {
202
+ const field = `input_base64_${index + 1}`
203
+ const filename = inferFilenameFromBase64Value(value, index + 1)
204
+ syntheticInputs.push({
205
+ kind: 'base64',
206
+ field,
207
+ filename,
208
+ base64: normalizeBase64Value(value),
209
+ })
210
+ preparedOrder.push(field)
211
+ }
212
+
213
+ if (syntheticInputs.length === 0) {
214
+ return {
215
+ cleanup: [],
216
+ hasTransientInputs: false,
217
+ inputs: preparedOrder,
218
+ }
219
+ }
220
+
221
+ const prepared = await prepareInputFiles({
222
+ inputFiles: syntheticInputs.map((input) => {
223
+ if (input.kind === 'url') {
224
+ return {
225
+ kind: 'url' as const,
226
+ field: input.field,
227
+ url: input.url,
228
+ filename: basename(new URL(input.url).pathname) || undefined,
229
+ }
230
+ }
231
+
232
+ return {
233
+ kind: 'base64' as const,
234
+ field: input.field,
235
+ base64: input.base64,
236
+ filename: input.filename,
237
+ }
238
+ }),
239
+ base64Strategy: 'tempfile',
240
+ allowPrivateUrls: false,
241
+ urlStrategy: 'download',
242
+ })
243
+
244
+ const inputs = preparedOrder.map((value) => prepared.files[value] ?? value)
245
+
246
+ return {
247
+ cleanup: prepared.cleanup,
248
+ hasTransientInputs: true,
249
+ inputs,
250
+ }
251
+ }
252
+
253
+ function parseIntentStep<TSchema extends z.AnyZodObject>({
254
+ fields,
255
+ fixedValues,
256
+ rawValues,
257
+ schema,
258
+ }: {
259
+ fields: readonly IntentFieldSpec[]
260
+ fixedValues: Record<string, unknown>
261
+ rawValues: Record<string, unknown>
262
+ schema: TSchema
263
+ }): z.input<TSchema> {
264
+ const input: Record<string, unknown> = { ...fixedValues }
265
+
266
+ for (const fieldSpec of fields) {
267
+ const rawValue = rawValues[fieldSpec.name]
268
+ if (rawValue == null) continue
269
+ const fieldSchema = schema.shape[fieldSpec.name]
270
+ input[fieldSpec.name] = coerceIntentFieldValue(fieldSpec.kind, rawValue, fieldSchema)
271
+ }
272
+
273
+ const parsed = schema.parse(input) as Record<string, unknown>
274
+ const normalizedInput: Record<string, unknown> = { ...fixedValues }
275
+
276
+ for (const fieldSpec of fields) {
277
+ const rawValue = rawValues[fieldSpec.name]
278
+ if (rawValue == null) continue
279
+ normalizedInput[fieldSpec.name] = parsed[fieldSpec.name]
280
+ }
281
+
282
+ return normalizedInput as z.input<TSchema>
283
+ }
284
+
285
+ function resolveSingleStepFixedValues(
286
+ execution: IntentSingleStepExecutionDefinition,
287
+ inputPolicy: IntentInputPolicy,
288
+ hasInputs: boolean,
289
+ ): Record<string, unknown> {
290
+ if (!hasInputs) {
291
+ return execution.fixedValues
292
+ }
293
+
294
+ if (inputPolicy.kind !== 'optional' || inputPolicy.attachUseWhenInputsProvided !== true) {
295
+ return execution.fixedValues
296
+ }
297
+
298
+ return {
299
+ ...execution.fixedValues,
300
+ use: ':original',
301
+ }
302
+ }
303
+
304
+ function createSingleStep(
305
+ execution: IntentSingleStepExecutionDefinition,
306
+ inputPolicy: IntentInputPolicy,
307
+ rawValues: Record<string, unknown>,
308
+ hasInputs: boolean,
309
+ ): z.input<typeof execution.schema> {
310
+ return parseIntentStep({
311
+ schema: execution.schema,
312
+ fixedValues: resolveSingleStepFixedValues(execution, inputPolicy, hasInputs),
313
+ fields: execution.fields,
314
+ rawValues,
315
+ })
316
+ }
317
+
318
+ function createDynamicIntentStep(
319
+ execution: IntentDynamicStepExecutionDefinition,
320
+ rawValues: Record<string, unknown>,
321
+ ): Record<string, unknown> {
322
+ return getSemanticIntentDescriptor(execution.handler).createStep(rawValues)
323
+ }
324
+
325
+ function requiresLocalInput(
326
+ inputPolicy: IntentInputPolicy,
327
+ rawValues: Record<string, unknown>,
328
+ ): boolean {
329
+ if (inputPolicy.kind === 'required') {
330
+ return true
331
+ }
332
+
333
+ return rawValues[inputPolicy.field] == null
334
+ }
335
+
336
+ async function executeIntentCommand({
337
+ client,
338
+ definition,
339
+ output,
340
+ outputPath,
341
+ printUrls,
342
+ rawValues,
343
+ createOptions,
344
+ }: {
345
+ client: AuthenticatedCommand['client']
346
+ createOptions: Omit<AssembliesCreateOptions, 'output' | 'steps' | 'stepsData' | 'template'>
347
+ definition: IntentFileCommandDefinition | IntentNoInputCommandDefinition
348
+ output: AuthenticatedCommand['output']
349
+ outputPath?: string
350
+ printUrls: boolean
351
+ rawValues: Record<string, unknown>
352
+ }): Promise<number | undefined> {
353
+ const inputPolicy: IntentInputPolicy =
354
+ 'inputPolicy' in definition ? definition.inputPolicy : { kind: 'required' }
355
+ const executionOptions =
356
+ definition.execution.kind === 'template'
357
+ ? {
358
+ template: definition.execution.templateId,
359
+ }
360
+ : {
361
+ stepsData: {
362
+ [definition.execution.resultStepName]:
363
+ definition.execution.kind === 'single-step'
364
+ ? createSingleStep(
365
+ definition.execution,
366
+ inputPolicy,
367
+ rawValues,
368
+ createOptions.inputs.length > 0,
369
+ )
370
+ : createDynamicIntentStep(definition.execution, rawValues),
371
+ } as AssembliesCreateOptions['stepsData'],
372
+ }
373
+
374
+ const { hasFailures, resultUrls } = await assembliesCommands.create(output, client, {
375
+ ...createOptions,
376
+ output: outputPath ?? null,
377
+ outputMode: definition.outputMode,
378
+ ...executionOptions,
379
+ })
380
+ if (printUrls) {
381
+ printResultUrls(output, resultUrls)
382
+ }
383
+ return hasFailures ? 1 : undefined
384
+ }
385
+
386
+ abstract class GeneratedIntentCommandBase extends AuthenticatedCommand {
387
+ declare static intentDefinition: IntentFileCommandDefinition | IntentNoInputCommandDefinition
388
+
389
+ outputPath = Option.String('--out,-o', {
390
+ description: this.getOutputDescription(),
391
+ })
392
+
393
+ printUrls = printUrlsOption()
394
+
395
+ protected getIntentDefinition(): IntentFileCommandDefinition | IntentNoInputCommandDefinition {
396
+ const commandClass = this.constructor as unknown as typeof GeneratedIntentCommandBase
397
+ return commandClass.intentDefinition
398
+ }
399
+
400
+ protected getIntentRawValues(): Record<string, unknown> {
401
+ return readIntentRawValues(this, getIntentOptionDefinitions(this.getIntentDefinition()))
402
+ }
403
+
404
+ private getOutputDescription(): string {
405
+ return this.getIntentDefinition().outputDescription
406
+ }
407
+
408
+ protected validateOutputChoice(): number | undefined {
409
+ if (this.outputPath == null && !this.printUrls) {
410
+ this.output.error('Specify at least one of --out or --print-urls')
411
+ return 1
412
+ }
413
+
414
+ return undefined
415
+ }
416
+ }
417
+
418
+ export abstract class GeneratedNoInputIntentCommand extends GeneratedIntentCommandBase {
419
+ protected override async run(): Promise<number | undefined> {
420
+ const outputValidationError = this.validateOutputChoice()
421
+ if (outputValidationError != null) {
422
+ return outputValidationError
423
+ }
424
+
425
+ return await executeIntentCommand({
426
+ client: this.client,
427
+ createOptions: {
428
+ inputs: [],
429
+ },
430
+ definition: this.getIntentDefinition() as IntentNoInputCommandDefinition,
431
+ output: this.output,
432
+ outputPath: this.outputPath,
433
+ printUrls: this.printUrls ?? false,
434
+ rawValues: this.getIntentRawValues(),
435
+ })
436
+ }
437
+ }
438
+
439
+ export function getIntentOptionDefinitions(
440
+ definition: IntentFileCommandDefinition | IntentNoInputCommandDefinition,
441
+ ): readonly IntentOptionDefinition[] {
442
+ if (definition.execution.kind !== 'single-step' && definition.execution.kind !== 'dynamic-step') {
443
+ return []
444
+ }
445
+
446
+ return definition.execution.fields
447
+ }
448
+
449
+ function readIntentRawValues(
450
+ command: object,
451
+ fieldDefinitions: readonly IntentOptionDefinition[],
452
+ ): Record<string, unknown> {
453
+ const rawValues: Record<string, unknown> = {}
454
+
455
+ for (const fieldDefinition of fieldDefinitions) {
456
+ rawValues[fieldDefinition.name] = (command as Record<string, unknown>)[
457
+ fieldDefinition.propertyName
458
+ ]
459
+ }
460
+
461
+ return rawValues
462
+ }
463
+
464
+ abstract class GeneratedFileIntentCommandBase extends GeneratedIntentCommandBase {
465
+ inputs = inputPathsOption('Provide an input path, directory, URL, or - for stdin')
466
+
467
+ inputBase64 = inputBase64Option()
468
+
469
+ recursive = recursiveOption()
470
+
471
+ deleteAfterProcessing = deleteAfterProcessingOption()
472
+
473
+ reprocessStale = reprocessStaleOption()
474
+
475
+ protected override getIntentDefinition(): IntentFileCommandDefinition {
476
+ return super.getIntentDefinition() as IntentFileCommandDefinition
477
+ }
478
+
479
+ protected async prepareInputs(): Promise<PreparedIntentInputs> {
480
+ return await prepareIntentInputs({
481
+ inputValues: this.inputs ?? [],
482
+ inputBase64Values: this.inputBase64 ?? [],
483
+ })
484
+ }
485
+
486
+ protected getCreateOptions(
487
+ inputs: string[],
488
+ ): Omit<AssembliesCreateOptions, 'output' | 'steps' | 'stepsData' | 'template'> {
489
+ return {
490
+ del: this.deleteAfterProcessing,
491
+ inputs,
492
+ reprocessStale: this.reprocessStale,
493
+ recursive: this.recursive,
494
+ }
495
+ }
496
+
497
+ protected getProvidedInputCount(): number {
498
+ return countProvidedInputs({
499
+ inputs: this.inputs,
500
+ inputBase64: this.inputBase64,
501
+ })
502
+ }
503
+
504
+ protected hasTransientInputSources(): boolean {
505
+ return (
506
+ (this.inputs?.some((input) => isHttpUrl(input)) ?? false) ||
507
+ (this.inputBase64?.length ?? 0) > 0
508
+ )
509
+ }
510
+
511
+ protected resolveOutputMode(): 'directory' | 'file' | undefined {
512
+ if (this.getIntentDefinition().outputMode != null) {
513
+ return this.getIntentDefinition().outputMode
514
+ }
515
+
516
+ if (this.outputPath == null) {
517
+ return undefined
518
+ }
519
+
520
+ try {
521
+ return statSync(this.outputPath).isDirectory() ? 'directory' : 'file'
522
+ } catch {
523
+ return 'file'
524
+ }
525
+ }
526
+
527
+ protected isDirectoryOutputTarget(): boolean {
528
+ return this.resolveOutputMode() === 'directory'
529
+ }
530
+
531
+ protected validateInputPresence(rawValues: Record<string, unknown>): number | undefined {
532
+ const intentDefinition = this.getIntentDefinition()
533
+ const inputCount = this.getProvidedInputCount()
534
+ if (inputCount !== 0) {
535
+ return undefined
536
+ }
537
+
538
+ if (!requiresLocalInput(intentDefinition.inputPolicy, rawValues)) {
539
+ return undefined
540
+ }
541
+
542
+ if (intentDefinition.inputPolicy.kind === 'required') {
543
+ this.output.error(`${intentDefinition.commandLabel} requires --input or --input-base64`)
544
+ return 1
545
+ }
546
+
547
+ this.output.error(
548
+ `${intentDefinition.commandLabel} requires --input or --${intentDefinition.inputPolicy.field.replaceAll('_', '-')}`,
549
+ )
550
+ return 1
551
+ }
552
+
553
+ protected validateBeforePreparingInputs(rawValues: Record<string, unknown>): number | undefined {
554
+ const outputValidationError = this.validateOutputChoice()
555
+ if (outputValidationError != null) {
556
+ return outputValidationError
557
+ }
558
+
559
+ const validationError = this.validateInputPresence(rawValues)
560
+ if (validationError != null) {
561
+ return validationError
562
+ }
563
+
564
+ const execution = this.getIntentDefinition().execution
565
+ if (execution.kind === 'dynamic-step') {
566
+ createDynamicIntentStep(execution, rawValues)
567
+ }
568
+
569
+ return undefined
570
+ }
571
+
572
+ protected validatePreparedInputs(_preparedInputs: PreparedIntentInputs): number | undefined {
573
+ return undefined
574
+ }
575
+
576
+ protected async executePreparedInputs(
577
+ rawValues: Record<string, unknown>,
578
+ preparedInputs: PreparedIntentInputs,
579
+ ): Promise<number | undefined> {
580
+ let effectivePreparedInputs = preparedInputs
581
+ const execution = this.getIntentDefinition().execution
582
+ if (execution.kind === 'dynamic-step') {
583
+ const descriptor = getSemanticIntentDescriptor(execution.handler)
584
+ if (descriptor.prepareInputs != null) {
585
+ effectivePreparedInputs = await descriptor.prepareInputs(preparedInputs, rawValues)
586
+ }
587
+ }
588
+
589
+ return await executeIntentCommand({
590
+ client: this.client,
591
+ createOptions: this.getCreateOptions(effectivePreparedInputs.inputs),
592
+ definition: this.getIntentDefinition(),
593
+ output: this.output,
594
+ outputPath: this.outputPath,
595
+ printUrls: this.printUrls ?? false,
596
+ rawValues,
597
+ })
598
+ }
599
+
600
+ protected override async run(): Promise<number | undefined> {
601
+ const rawValues = this.getIntentRawValues()
602
+ const validationError = this.validateBeforePreparingInputs(rawValues)
603
+ if (validationError != null) {
604
+ return validationError
605
+ }
606
+
607
+ const preparedInputs = await this.prepareInputs()
608
+ try {
609
+ const preparedInputError = this.validatePreparedInputs(preparedInputs)
610
+ if (preparedInputError != null) {
611
+ return preparedInputError
612
+ }
613
+
614
+ return await this.executePreparedInputs(rawValues, preparedInputs)
615
+ } finally {
616
+ await Promise.all(preparedInputs.cleanup.map((cleanup) => cleanup()))
617
+ }
618
+ }
619
+ }
620
+
621
+ export abstract class GeneratedWatchableFileIntentCommand extends GeneratedFileIntentCommandBase {
622
+ watch = watchOption()
623
+
624
+ concurrency = concurrencyOption()
625
+
626
+ protected override getCreateOptions(
627
+ inputs: string[],
628
+ ): Omit<AssembliesCreateOptions, 'output' | 'steps' | 'stepsData' | 'template'> {
629
+ return {
630
+ ...super.getCreateOptions(inputs),
631
+ concurrency: this.concurrency,
632
+ watch: this.watch,
633
+ }
634
+ }
635
+
636
+ protected override validateBeforePreparingInputs(
637
+ rawValues: Record<string, unknown>,
638
+ ): number | undefined {
639
+ const validationError = super.validateBeforePreparingInputs(rawValues)
640
+ if (validationError != null) {
641
+ return validationError
642
+ }
643
+
644
+ const sharedValidationError = validateSharedFileProcessingOptions({
645
+ explicitInputCount: this.getProvidedInputCount(),
646
+ singleAssembly: this.getSingleAssemblyEnabled(),
647
+ watch: this.watch,
648
+ watchRequiresInputsMessage: `${this.getIntentDefinition().commandLabel} --watch requires --input or --input-base64`,
649
+ })
650
+ if (sharedValidationError != null) {
651
+ this.output.error(sharedValidationError)
652
+ return 1
653
+ }
654
+
655
+ if (this.watch && this.hasTransientInputSources()) {
656
+ this.output.error('--watch is only supported for filesystem inputs')
657
+ return 1
658
+ }
659
+
660
+ return undefined
661
+ }
662
+
663
+ protected getSingleAssemblyEnabled(): boolean {
664
+ return false
665
+ }
666
+
667
+ protected override validatePreparedInputs(
668
+ preparedInputs: PreparedIntentInputs,
669
+ ): number | undefined {
670
+ if (this.watch && preparedInputs.hasTransientInputs) {
671
+ this.output.error('--watch is only supported for filesystem inputs')
672
+ return 1
673
+ }
674
+ return undefined
675
+ }
676
+ }
677
+
678
+ export abstract class GeneratedStandardFileIntentCommand extends GeneratedWatchableFileIntentCommand {
679
+ singleAssembly = singleAssemblyOption()
680
+
681
+ protected override getSingleAssemblyEnabled(): boolean {
682
+ return this.singleAssembly
683
+ }
684
+
685
+ protected override getCreateOptions(
686
+ inputs: string[],
687
+ ): Omit<AssembliesCreateOptions, 'output' | 'steps' | 'stepsData' | 'template'> {
688
+ return {
689
+ ...super.getCreateOptions(inputs),
690
+ singleAssembly: this.singleAssembly,
691
+ }
692
+ }
693
+
694
+ protected override validateBeforePreparingInputs(
695
+ rawValues: Record<string, unknown>,
696
+ ): number | undefined {
697
+ const validationError = super.validateBeforePreparingInputs(rawValues)
698
+ if (validationError != null) {
699
+ return validationError
700
+ }
701
+
702
+ if (
703
+ this.singleAssembly &&
704
+ (this.getProvidedInputCount() > 1 ||
705
+ this.inputs.some((inputPath) => {
706
+ try {
707
+ return statSync(inputPath).isDirectory()
708
+ } catch {
709
+ return false
710
+ }
711
+ })) &&
712
+ this.outputPath != null &&
713
+ !this.isDirectoryOutputTarget()
714
+ ) {
715
+ this.output.error(
716
+ 'Output must be a directory when using --single-assembly with multiple inputs',
717
+ )
718
+ return 1
719
+ }
720
+
721
+ return undefined
722
+ }
723
+ }
724
+
725
+ export abstract class GeneratedBundledFileIntentCommand extends GeneratedFileIntentCommandBase {
726
+ protected override getCreateOptions(
727
+ inputs: string[],
728
+ ): Omit<AssembliesCreateOptions, 'output' | 'steps' | 'stepsData' | 'template'> {
729
+ return {
730
+ ...super.getCreateOptions(inputs),
731
+ singleAssembly: true,
732
+ }
733
+ }
734
+ }