@spira-lab/cli 0.0.5 → 0.0.7

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 (3) hide show
  1. package/README.md +27 -22
  2. package/dist/index.cjs +724 -142
  3. package/package.json +2 -25
package/dist/index.cjs CHANGED
@@ -18048,6 +18048,7 @@ var cliResultTypeSchema = external_exports.enum([
18048
18048
  "aiJobAction",
18049
18049
  "aiModelList",
18050
18050
  "aiModelDetail",
18051
+ "aiModelProviderList",
18051
18052
  "workflowList",
18052
18053
  "workflowNodeCatalog",
18053
18054
  "workflowDetail",
@@ -18083,16 +18084,13 @@ var cliResultSchema = external_exports.object({
18083
18084
  // src/ai/model-docs.ts
18084
18085
  var cliAiModelDocs = [
18085
18086
  {
18086
- "id": "higgsfield-ai/soul/standard",
18087
+ "id": "ai-influencer-image",
18087
18088
  "name": "AI Influencer",
18088
18089
  "mediaType": "image",
18089
18090
  "description": "AI Influencer image generation model.",
18090
18091
  "capabilities": [
18091
18092
  "text-to-image"
18092
18093
  ],
18093
- "providers": [
18094
- "image-higgsfield"
18095
- ],
18096
18094
  "variants": [
18097
18095
  {
18098
18096
  "key": "default",
@@ -18155,9 +18153,6 @@ var cliAiModelDocs = [
18155
18153
  "text-to-image",
18156
18154
  "image-to-image"
18157
18155
  ],
18158
- "providers": [
18159
- "socheap-media"
18160
- ],
18161
18156
  "variants": [
18162
18157
  {
18163
18158
  "key": "1k",
@@ -18248,9 +18243,6 @@ var cliAiModelDocs = [
18248
18243
  "text-to-image",
18249
18244
  "image-to-image"
18250
18245
  ],
18251
- "providers": [
18252
- "socheap-media"
18253
- ],
18254
18246
  "variants": [
18255
18247
  {
18256
18248
  "key": "standard",
@@ -18331,9 +18323,6 @@ var cliAiModelDocs = [
18331
18323
  "text-to-image",
18332
18324
  "image-to-image"
18333
18325
  ],
18334
- "providers": [
18335
- "socheap-media"
18336
- ],
18337
18326
  "variants": [
18338
18327
  {
18339
18328
  "key": "1k",
@@ -18430,9 +18419,6 @@ var cliAiModelDocs = [
18430
18419
  "text-to-image",
18431
18420
  "image-to-image"
18432
18421
  ],
18433
- "providers": [
18434
- "image-kie"
18435
- ],
18436
18422
  "variants": [
18437
18423
  {
18438
18424
  "key": "default",
@@ -18505,9 +18491,6 @@ var cliAiModelDocs = [
18505
18491
  "text-to-image",
18506
18492
  "image-to-image"
18507
18493
  ],
18508
- "providers": [
18509
- "socheap-media"
18510
- ],
18511
18494
  "variants": [
18512
18495
  {
18513
18496
  "key": "1k",
@@ -18598,9 +18581,6 @@ var cliAiModelDocs = [
18598
18581
  "text-to-video",
18599
18582
  "image-to-video"
18600
18583
  ],
18601
- "providers": [
18602
- "socheap-media"
18603
- ],
18604
18584
  "variants": [
18605
18585
  {
18606
18586
  "key": "4s-720p",
@@ -18860,9 +18840,6 @@ var cliAiModelDocs = [
18860
18840
  "text-to-video",
18861
18841
  "image-to-video"
18862
18842
  ],
18863
- "providers": [
18864
- "socheap-media"
18865
- ],
18866
18843
  "variants": [
18867
18844
  {
18868
18845
  "key": "480p",
@@ -18974,9 +18951,6 @@ var cliAiModelDocs = [
18974
18951
  "text-to-video",
18975
18952
  "image-to-video"
18976
18953
  ],
18977
- "providers": [
18978
- "hailuo-comfly"
18979
- ],
18980
18954
  "variants": [
18981
18955
  {
18982
18956
  "key": "default",
@@ -19030,8 +19004,8 @@ var cliAiModelDocs = [
19030
19004
  }
19031
19005
  ],
19032
19006
  "notes": [
19033
- "referenceVideo and generationType are not supported by the Hailuo Comfly provider.",
19034
- "duration=10 with resolution=1080P is rejected by the provider validator."
19007
+ "referenceVideo and generationType are not supported for this model.",
19008
+ "duration=10 with resolution=1080P is not supported."
19035
19009
  ]
19036
19010
  },
19037
19011
  {
@@ -19043,9 +19017,6 @@ var cliAiModelDocs = [
19043
19017
  "text-to-video",
19044
19018
  "image-to-video"
19045
19019
  ],
19046
- "providers": [
19047
- "socheap-media"
19048
- ],
19049
19020
  "variants": [
19050
19021
  {
19051
19022
  "key": "720p",
@@ -19169,9 +19140,6 @@ var cliAiModelDocs = [
19169
19140
  "capabilities": [
19170
19141
  "image-to-video"
19171
19142
  ],
19172
- "providers": [
19173
- "fal-ai"
19174
- ],
19175
19143
  "variants": [
19176
19144
  {
19177
19145
  "key": "default",
@@ -19286,9 +19254,6 @@ var cliAiModelDocs = [
19286
19254
  "text-to-video",
19287
19255
  "image-to-video"
19288
19256
  ],
19289
- "providers": [
19290
- "socheap-media"
19291
- ],
19292
19257
  "variants": [
19293
19258
  {
19294
19259
  "key": "standard-no-sound",
@@ -19431,7 +19396,7 @@ var cliAiModelDocs = [
19431
19396
  ],
19432
19397
  "notes": [
19433
19398
  "generation type is derived from referenceImage count: 0 text, 1 reference, 2 first/last frames.",
19434
- "referenceVideo, negativePrompt, and elements are not supported on the SoCheap Kling 3.0 path.",
19399
+ "referenceVideo, negativePrompt, and elements are not supported for Kling 3.0.",
19435
19400
  "Use mode=4K for 4K generation; do not pass resolution."
19436
19401
  ]
19437
19402
  },
@@ -19443,9 +19408,6 @@ var cliAiModelDocs = [
19443
19408
  "capabilities": [
19444
19409
  "image-to-video"
19445
19410
  ],
19446
- "providers": [
19447
- "socheap-media"
19448
- ],
19449
19411
  "variants": [
19450
19412
  {
19451
19413
  "key": "standard",
@@ -19566,9 +19528,6 @@ var cliAiModelDocs = [
19566
19528
  "text-to-video",
19567
19529
  "image-to-video"
19568
19530
  ],
19569
- "providers": [
19570
- "socheap-media"
19571
- ],
19572
19531
  "variants": [
19573
19532
  {
19574
19533
  "key": "standard-480p-no-sound",
@@ -19730,9 +19689,6 @@ var cliAiModelDocs = [
19730
19689
  "text-to-video",
19731
19690
  "image-to-video"
19732
19691
  ],
19733
- "providers": [
19734
- "socheap-media"
19735
- ],
19736
19692
  "variants": [
19737
19693
  {
19738
19694
  "key": "standard-480p",
@@ -20016,9 +19972,6 @@ var cliAiModelDocs = [
20016
19972
  "text-to-video",
20017
19973
  "image-to-video"
20018
19974
  ],
20019
- "providers": [
20020
- "socheap-media"
20021
- ],
20022
19975
  "variants": [
20023
19976
  {
20024
19977
  "key": "fast-720p",
@@ -20133,8 +20086,7 @@ var cliAiModelDocs = [
20133
20086
  "description": "Output aspect ratio.",
20134
20087
  "values": [
20135
20088
  "16:9",
20136
- "9:16",
20137
- "1:1"
20089
+ "9:16"
20138
20090
  ],
20139
20091
  "defaultValue": "16:9"
20140
20092
  },
@@ -20170,14 +20122,9 @@ var cliAiModelDocs = [
20170
20122
  "description": "Whether the generated video should be extendable when supported."
20171
20123
  },
20172
20124
  {
20173
- "name": "extendFromTaskId",
20125
+ "name": "extendFromJobId",
20174
20126
  "type": "string",
20175
- "description": "Provider task id of an existing Veo generation to extend."
20176
- },
20177
- {
20178
- "name": "extendProviderId",
20179
- "type": "string",
20180
- "description": "Original provider id for extension lineage. Must be socheap-media for this CLI path."
20127
+ "description": "Spira job ID of an extendable Veo generation owned by the current user."
20181
20128
  }
20182
20129
  ],
20183
20130
  "examples": [
@@ -20193,11 +20140,261 @@ var cliAiModelDocs = [
20193
20140
  }
20194
20141
  ],
20195
20142
  "notes": [
20143
+ "Veo 3.1 supports 16:9 and 9:16 output aspect ratios.",
20196
20144
  "referenceVideo and referenceAudio are not supported.",
20197
20145
  "image_to_video is not a supported generationType; use reference_to_video with one or more referenceImage URLs.",
20198
20146
  "first_last_frames_to_video requires exactly 2 referenceImage URLs.",
20199
- "extendFromTaskId requires extendProviderId=socheap-media and resolution=720p."
20147
+ "extendFromJobId requires resolution=720p."
20200
20148
  ]
20149
+ },
20150
+ {
20151
+ id: "suno-music",
20152
+ name: "Suno Music",
20153
+ mediaType: "audio",
20154
+ description: "Suno music generation with simple prompt and custom song modes.",
20155
+ capabilities: ["text-to-audio"],
20156
+ variants: [
20157
+ {
20158
+ key: "v5_5",
20159
+ display: "v5_5",
20160
+ conditions: {},
20161
+ billingUnit: "per_generation",
20162
+ creditsPerUnit: 6.6
20163
+ }
20164
+ ],
20165
+ fields: [
20166
+ {
20167
+ name: "prompt",
20168
+ type: "string",
20169
+ description: "Song description in simple mode, or lyrics in custom vocal mode."
20170
+ },
20171
+ {
20172
+ name: "mode",
20173
+ type: "string",
20174
+ description: "Suno model version.",
20175
+ values: ["v5_5"],
20176
+ defaultValue: "v5_5"
20177
+ },
20178
+ {
20179
+ name: "customMode",
20180
+ type: "boolean",
20181
+ description: "Enable custom lyrics, style, and title inputs.",
20182
+ defaultValue: false
20183
+ },
20184
+ {
20185
+ name: "instrumental",
20186
+ type: "boolean",
20187
+ description: "Generate an instrumental track. Only used in custom mode.",
20188
+ defaultValue: false
20189
+ },
20190
+ {
20191
+ name: "style",
20192
+ type: "string",
20193
+ description: "Required music style in custom mode."
20194
+ },
20195
+ {
20196
+ name: "title",
20197
+ type: "string",
20198
+ description: "Required song title in custom mode."
20199
+ }
20200
+ ],
20201
+ examples: [
20202
+ {
20203
+ label: "Generate music",
20204
+ inputs: {
20205
+ prompt: "A warm upbeat electronic instrumental for a product launch",
20206
+ mode: "v5_5"
20207
+ }
20208
+ }
20209
+ ],
20210
+ notes: [
20211
+ "Simple mode requires prompt. Custom mode requires style and title; vocal custom mode also requires prompt as lyrics."
20212
+ ]
20213
+ },
20214
+ {
20215
+ id: "elevenlabs-music-generation",
20216
+ name: "Eleven Music V1",
20217
+ mediaType: "audio",
20218
+ description: "ElevenLabs prompt-to-music generation.",
20219
+ capabilities: ["text-to-audio"],
20220
+ variants: [
20221
+ {
20222
+ key: "default",
20223
+ display: "Per second",
20224
+ conditions: {},
20225
+ billingUnit: "per_second",
20226
+ creditsPerUnit: 0.25
20227
+ }
20228
+ ],
20229
+ fields: [
20230
+ {
20231
+ name: "prompt",
20232
+ type: "string",
20233
+ description: "Description of the music to generate.",
20234
+ required: true
20235
+ },
20236
+ {
20237
+ name: "duration",
20238
+ type: "string",
20239
+ description: "Whole number of seconds from 1 to 300.",
20240
+ defaultValue: "10"
20241
+ }
20242
+ ],
20243
+ examples: [
20244
+ {
20245
+ label: "Generate music",
20246
+ inputs: {
20247
+ prompt: "A calm ambient piano cue with a gentle ending",
20248
+ duration: "10"
20249
+ }
20250
+ }
20251
+ ]
20252
+ },
20253
+ {
20254
+ id: "elevenlabs-sound-effects",
20255
+ name: "Eleven Text To Sound V2",
20256
+ mediaType: "audio",
20257
+ description: "ElevenLabs sound-effect generation from an English prompt.",
20258
+ capabilities: ["text-to-audio"],
20259
+ variants: [
20260
+ {
20261
+ key: "default",
20262
+ display: "Per generation",
20263
+ conditions: {},
20264
+ billingUnit: "per_generation",
20265
+ creditsPerUnit: 12
20266
+ }
20267
+ ],
20268
+ fields: [
20269
+ {
20270
+ name: "prompt",
20271
+ type: "string",
20272
+ description: "English description of the sound effect.",
20273
+ required: true
20274
+ },
20275
+ {
20276
+ name: "duration",
20277
+ type: "string",
20278
+ description: "Duration in seconds from 0.5 to 30.",
20279
+ defaultValue: "5"
20280
+ }
20281
+ ],
20282
+ examples: [
20283
+ {
20284
+ label: "Generate a sound effect",
20285
+ inputs: {
20286
+ prompt: "A soft camera shutter in a quiet studio",
20287
+ duration: "2"
20288
+ }
20289
+ }
20290
+ ]
20291
+ },
20292
+ {
20293
+ id: "elevenlabs-text-to-speech",
20294
+ name: "ElevenLabs Text to Speech",
20295
+ mediaType: "audio",
20296
+ description: "ElevenLabs text-to-speech using an existing voice ID.",
20297
+ capabilities: ["text-to-speech"],
20298
+ variants: [
20299
+ {
20300
+ key: "multilingual-v2-v3",
20301
+ display: "Per character",
20302
+ conditions: {},
20303
+ billingUnit: "per_character",
20304
+ creditsPerUnit: 0.01
20305
+ }
20306
+ ],
20307
+ fields: [
20308
+ {
20309
+ name: "text",
20310
+ type: "string",
20311
+ description: "Text to speak.",
20312
+ required: true
20313
+ },
20314
+ {
20315
+ name: "voiceId",
20316
+ type: "string",
20317
+ description: "Existing ElevenLabs voice ID.",
20318
+ required: true
20319
+ },
20320
+ {
20321
+ name: "providerModelId",
20322
+ type: "string",
20323
+ description: "ElevenLabs speech model.",
20324
+ values: ["eleven_multilingual_v2", "eleven_v3"],
20325
+ defaultValue: "eleven_multilingual_v2"
20326
+ }
20327
+ ],
20328
+ examples: [
20329
+ {
20330
+ label: "Generate speech",
20331
+ inputs: {
20332
+ text: "Welcome to Spira.",
20333
+ voiceId: "<voice-id>",
20334
+ providerModelId: "eleven_multilingual_v2"
20335
+ }
20336
+ }
20337
+ ]
20338
+ },
20339
+ {
20340
+ id: "elevenlabs-voice-clone-tts",
20341
+ name: "ElevenLabs Voice TTS",
20342
+ mediaType: "audio",
20343
+ description: "Text-to-speech using either an existing voice ID or one reference audio URL.",
20344
+ capabilities: ["text-to-speech", "audio-to-speech"],
20345
+ variants: [
20346
+ {
20347
+ key: "voice-clone-tts",
20348
+ display: "Per character",
20349
+ conditions: {},
20350
+ billingUnit: "per_character",
20351
+ creditsPerUnit: 0.01
20352
+ }
20353
+ ],
20354
+ fields: [
20355
+ {
20356
+ name: "text",
20357
+ type: "string",
20358
+ description: "Text to speak.",
20359
+ required: true
20360
+ },
20361
+ {
20362
+ name: "voiceId",
20363
+ type: "string",
20364
+ description: "Existing ElevenLabs voice ID. Do not combine with referenceAudio."
20365
+ },
20366
+ {
20367
+ name: "referenceAudio",
20368
+ type: "string[]",
20369
+ description: "Exactly one audio URL when cloning a voice. Do not combine with voiceId."
20370
+ },
20371
+ {
20372
+ name: "providerModelId",
20373
+ type: "string",
20374
+ description: "ElevenLabs speech model.",
20375
+ values: ["eleven_multilingual_v2", "eleven_v3"],
20376
+ defaultValue: "eleven_multilingual_v2"
20377
+ },
20378
+ {
20379
+ name: "qualityMode",
20380
+ type: "string",
20381
+ description: "Voice delivery mode.",
20382
+ values: ["stable", "expressive"],
20383
+ defaultValue: "stable"
20384
+ }
20385
+ ],
20386
+ examples: [
20387
+ {
20388
+ label: "Generate speech from a reference voice",
20389
+ inputs: {
20390
+ text: "Welcome to Spira.",
20391
+ referenceAudio: ["https://example.com/reference.mp3"],
20392
+ providerModelId: "eleven_multilingual_v2",
20393
+ qualityMode: "stable"
20394
+ }
20395
+ }
20396
+ ],
20397
+ notes: ["Provide either voiceId or exactly one referenceAudio URL, never both."]
20201
20398
  }
20202
20399
  ];
20203
20400
  function listCliAiModelDocs(mediaType) {
@@ -20207,6 +20404,120 @@ function getCliAiModelDoc(modelId) {
20207
20404
  return cliAiModelDocs.find((model) => model.id === modelId);
20208
20405
  }
20209
20406
 
20407
+ // src/ai/wait.ts
20408
+ var DEFAULT_WAIT_COMMAND = "spira ai jobs create";
20409
+ function wait(durationMs) {
20410
+ return new Promise((resolve) => setTimeout(resolve, durationMs));
20411
+ }
20412
+ function jobStatus(result) {
20413
+ if (!result.data || typeof result.data !== "object") return void 0;
20414
+ const status = result.data.status;
20415
+ return typeof status === "string" ? status : void 0;
20416
+ }
20417
+ function providerError(result) {
20418
+ if (!result.data || typeof result.data !== "object") return void 0;
20419
+ const latestExecution = result.data.latestExecution;
20420
+ if (!latestExecution || typeof latestExecution !== "object") return void 0;
20421
+ const error51 = latestExecution.error;
20422
+ if (typeof error51 === "string") return error51;
20423
+ if (error51 && typeof error51 === "object" && typeof error51.message === "string") {
20424
+ return error51.message;
20425
+ }
20426
+ return void 0;
20427
+ }
20428
+ function completedResult(result, command) {
20429
+ return cliResultSchema.parse({
20430
+ ...result,
20431
+ resultType: "aiJobStatus",
20432
+ command,
20433
+ nextActions: []
20434
+ });
20435
+ }
20436
+ function pollingError(result, command) {
20437
+ return cliResultSchema.parse({
20438
+ ...result,
20439
+ command
20440
+ });
20441
+ }
20442
+ function terminalError(result, jobId, status, command) {
20443
+ const message = providerError(result) ?? `AI job ${jobId} ended with status ${status}.`;
20444
+ return cliResultSchema.parse({
20445
+ ...result,
20446
+ status: "error",
20447
+ resultType: "aiJobStatus",
20448
+ command,
20449
+ error: {
20450
+ code: status === "FAILED" ? "PROVIDER_FAILED" : "ASYNC_STATE",
20451
+ message,
20452
+ resource: { type: "aiJob", id: jobId },
20453
+ retryable: false,
20454
+ nextActions: [{ label: "Show job status", command: `spira ai jobs status ${jobId}` }]
20455
+ }
20456
+ });
20457
+ }
20458
+ function timeoutResult(jobId, timeoutMs, command, lastResult) {
20459
+ return cliResultSchema.parse({
20460
+ object: "cli.result",
20461
+ status: "error",
20462
+ resultType: "aiJobStatus",
20463
+ command,
20464
+ data: lastResult?.data,
20465
+ ids: { ...lastResult?.ids, jobId },
20466
+ error: {
20467
+ code: "TIMEOUT",
20468
+ message: `AI job ${jobId} did not finish within ${timeoutMs}ms.`,
20469
+ resource: { type: "aiJob", id: jobId },
20470
+ retryable: true,
20471
+ nextActions: [
20472
+ { label: "Keep waiting", command: `spira ai jobs wait ${jobId}` },
20473
+ { label: "Show job status", command: `spira ai jobs status ${jobId}` }
20474
+ ]
20475
+ }
20476
+ });
20477
+ }
20478
+ function contractError(result, jobId, command) {
20479
+ return cliResultSchema.parse({
20480
+ ...result,
20481
+ status: "error",
20482
+ resultType: "aiJobStatus",
20483
+ command,
20484
+ error: {
20485
+ code: "CONTRACT_MISMATCH",
20486
+ message: `AI job ${jobId} status response did not contain a recognized status.`,
20487
+ resource: { type: "aiJob", id: jobId },
20488
+ retryable: false,
20489
+ nextActions: [{ label: "Show job status", command: `spira ai jobs status ${jobId}` }]
20490
+ }
20491
+ });
20492
+ }
20493
+ async function waitForAiJob(input) {
20494
+ const now = input.now ?? Date.now;
20495
+ const sleep = input.sleep ?? wait;
20496
+ const command = input.command ?? DEFAULT_WAIT_COMMAND;
20497
+ const deadline = now() + input.timeoutMs;
20498
+ let lastResult;
20499
+ let lastStatus;
20500
+ while (true) {
20501
+ const result = await input.poll();
20502
+ lastResult = result;
20503
+ if (result.status === "error") {
20504
+ if (!result.error?.retryable) return pollingError(result, command);
20505
+ } else {
20506
+ const status = jobStatus(result);
20507
+ if (status && status !== lastStatus) {
20508
+ input.onStatus?.(status);
20509
+ lastStatus = status;
20510
+ }
20511
+ if (status === "COMPLETED") return completedResult(result, command);
20512
+ if (status === "FAILED" || status === "CANCELED") return terminalError(result, input.jobId, status, command);
20513
+ if (status !== "QUEUED" && status !== "RUNNING") return contractError(result, input.jobId, command);
20514
+ }
20515
+ const remainingMs = deadline - now();
20516
+ if (remainingMs <= 0) return timeoutResult(input.jobId, input.timeoutMs, command, lastResult);
20517
+ await sleep(Math.min(input.intervalMs, remainingMs));
20518
+ }
20519
+ }
20520
+
20210
20521
  // src/commands/definitions.ts
20211
20522
  var commonListOptions = [
20212
20523
  {
@@ -20390,7 +20701,10 @@ var commandDefinitions = [
20390
20701
  },
20391
20702
  resultType: "socialSubmissionList",
20392
20703
  examples: ["spira social submissions list --platform tiktok --status draft --limit 10"],
20393
- relatedCommands: ["spira social submissions get <submissionId>", "spira social submissions publish <submissionId> --confirm"]
20704
+ relatedCommands: [
20705
+ "spira social submissions get <submissionId>",
20706
+ "spira social submissions publish <submissionId> --confirm"
20707
+ ]
20394
20708
  },
20395
20709
  {
20396
20710
  path: ["social", "submissions", "get"],
@@ -20436,7 +20750,12 @@ var commandDefinitions = [
20436
20750
  defaultValue: "oauth_account"
20437
20751
  },
20438
20752
  { name: "title", flag: "--title <title>", description: "Draft title.", type: "string" },
20439
- { name: "description", flag: "--description <description>", description: "Draft description/text.", type: "string" },
20753
+ {
20754
+ name: "description",
20755
+ flag: "--description <description>",
20756
+ description: "Draft description/text.",
20757
+ type: "string"
20758
+ },
20440
20759
  { name: "mediaUrls", flag: "--media-url <url>", description: "Media URL. Repeatable.", type: "stringArray" },
20441
20760
  { name: "agentId", flag: "--agent-id <agentId>", description: "Associated agent ID.", type: "string" },
20442
20761
  {
@@ -20463,9 +20782,7 @@ var commandDefinitions = [
20463
20782
  ]
20464
20783
  },
20465
20784
  resultType: "socialSubmissionAction",
20466
- examples: [
20467
- 'spira social submissions create --platform twitter --content-kind text --description "Launch note"'
20468
- ],
20785
+ examples: ['spira social submissions create --platform twitter --content-kind text --description "Launch note"'],
20469
20786
  relatedCommands: [
20470
20787
  "spira social submissions get <submissionId>",
20471
20788
  "spira social submissions publish <submissionId> --confirm"
@@ -20476,9 +20793,20 @@ var commandDefinitions = [
20476
20793
  purpose: "Publish a social submission draft.",
20477
20794
  arguments: [{ name: "submissionId", description: "Submission ID.", required: true }],
20478
20795
  options: [
20479
- { name: "confirm", flag: "--confirm", description: "Required explicit publish confirmation.", type: "boolean", required: true },
20796
+ {
20797
+ name: "confirm",
20798
+ flag: "--confirm",
20799
+ description: "Required explicit publish confirmation.",
20800
+ type: "boolean",
20801
+ required: true
20802
+ },
20480
20803
  { name: "title", flag: "--title <title>", description: "Platform title override.", type: "string" },
20481
- { name: "description", flag: "--description <description>", description: "Platform description override.", type: "string" },
20804
+ {
20805
+ name: "description",
20806
+ flag: "--description <description>",
20807
+ description: "Platform description override.",
20808
+ type: "string"
20809
+ },
20482
20810
  { name: "text", flag: "--text <text>", description: "Text override for X or LinkedIn.", type: "string" },
20483
20811
  { name: "mediaUrls", flag: "--media-url <url>", description: "Media URL. Repeatable.", type: "stringArray" },
20484
20812
  { name: "privacyLevel", flag: "--privacy-level <value>", description: "TikTok privacy level.", type: "string" },
@@ -20515,22 +20843,48 @@ var commandDefinitions = [
20515
20843
  },
20516
20844
  resultType: "aiJobStatus",
20517
20845
  examples: ["spira ai jobs status job_123"],
20518
- relatedCommands: []
20846
+ relatedCommands: ["spira ai jobs wait <jobId>"]
20847
+ },
20848
+ {
20849
+ path: ["ai", "jobs", "wait"],
20850
+ purpose: "Block until an AI job finishes, then print the final result.",
20851
+ arguments: [{ name: "jobId", description: "AI job ID.", required: true }],
20852
+ options: [
20853
+ {
20854
+ name: "timeout",
20855
+ flag: "--timeout <duration>",
20856
+ description: "Maximum wait duration.",
20857
+ type: "duration",
20858
+ defaultValue: 6e5,
20859
+ minimum: 1e3
20860
+ },
20861
+ {
20862
+ name: "interval",
20863
+ flag: "--interval <duration>",
20864
+ description: "Polling interval while waiting.",
20865
+ type: "duration",
20866
+ defaultValue: 3e3,
20867
+ minimum: 1e3
20868
+ }
20869
+ ],
20870
+ resultType: "aiJobStatus",
20871
+ examples: ["spira ai jobs wait job_123", "spira ai jobs wait job_123 --timeout 20m --interval 5s"],
20872
+ relatedCommands: ["spira ai jobs status <jobId>", "spira ai jobs create"]
20519
20873
  },
20520
20874
  {
20521
20875
  path: ["ai", "models", "list"],
20522
- purpose: "List AI image and video models available from the CLI.",
20876
+ purpose: "List AI image, video, and audio models available from the CLI.",
20523
20877
  options: [
20524
20878
  {
20525
20879
  name: "mediaType",
20526
20880
  flag: "--media-type <type>",
20527
20881
  description: "Filter by model media type.",
20528
20882
  type: "string",
20529
- enumValues: ["image", "video"]
20883
+ enumValues: ["image", "video", "audio"]
20530
20884
  }
20531
20885
  ],
20532
20886
  resultType: "aiModelList",
20533
- examples: ["spira ai models list", "spira ai models list --media-type video"],
20887
+ examples: ["spira ai models list", "spira ai models list --media-type audio"],
20534
20888
  relatedCommands: ["spira ai models get <modelId>", "spira ai jobs create"]
20535
20889
  },
20536
20890
  {
@@ -20541,13 +20895,61 @@ var commandDefinitions = [
20541
20895
  examples: ["spira ai models get nano-banana-2", "spira ai models get seedance-2.0"],
20542
20896
  relatedCommands: ["spira ai jobs create"]
20543
20897
  },
20898
+ {
20899
+ path: ["ai", "models", "providers"],
20900
+ purpose: "List provider routing IDs for AI models; requires admin.access.",
20901
+ permissions: ["admin.access"],
20902
+ arguments: [{ name: "modelId", description: "Optional AI model ID.", required: false }],
20903
+ http: {
20904
+ method: "GET",
20905
+ path: "/api/cli/v1/ai/models/providers",
20906
+ authRequired: true,
20907
+ query: [{ name: "modelId", source: "argument", from: "modelId" }]
20908
+ },
20909
+ resultType: "aiModelProviderList",
20910
+ examples: ["spira ai models providers", "spira ai models providers veo3.1 --json"],
20911
+ relatedCommands: ["spira ai models list", "spira ai jobs create"]
20912
+ },
20544
20913
  {
20545
20914
  path: ["ai", "jobs", "create"],
20546
- purpose: "Create an AI image or video generation job.",
20915
+ purpose: "Create an AI image, video, or audio generation job.",
20547
20916
  options: [
20548
20917
  { name: "modelId", flag: "--model-id <modelId>", description: "AI model ID.", type: "string", required: true },
20549
- { name: "providerId", flag: "--provider-id <providerId>", description: "Advanced routing override.", type: "string", hidden: true },
20550
- { name: "inputsJson", flag: "--inputs-json <json>", description: "Model inputs JSON object.", type: "json", required: true }
20918
+ {
20919
+ name: "providerId",
20920
+ flag: "--provider-id <providerId>",
20921
+ description: "Explicit provider routing override; requires admin.access.",
20922
+ type: "string"
20923
+ },
20924
+ {
20925
+ name: "inputsJson",
20926
+ flag: "--inputs-json <json>",
20927
+ description: "Model inputs JSON object.",
20928
+ type: "json",
20929
+ required: true
20930
+ },
20931
+ {
20932
+ name: "wait",
20933
+ flag: "--wait",
20934
+ description: "Wait for the job to finish and print the final result.",
20935
+ type: "boolean"
20936
+ },
20937
+ {
20938
+ name: "waitTimeout",
20939
+ flag: "--wait-timeout <duration>",
20940
+ description: "Maximum wait duration.",
20941
+ type: "duration",
20942
+ defaultValue: 6e5,
20943
+ minimum: 1e3
20944
+ },
20945
+ {
20946
+ name: "waitInterval",
20947
+ flag: "--wait-interval <duration>",
20948
+ description: "Polling interval while waiting.",
20949
+ type: "duration",
20950
+ defaultValue: 3e3,
20951
+ minimum: 1e3
20952
+ }
20551
20953
  ],
20552
20954
  http: {
20553
20955
  method: "POST",
@@ -20560,8 +20962,16 @@ var commandDefinitions = [
20560
20962
  ]
20561
20963
  },
20562
20964
  resultType: "aiJobAction",
20563
- examples: [`spira ai jobs create --model-id nano-banana-2 --inputs-json '{"prompt":"A product photo"}'`],
20564
- relatedCommands: ["spira ai jobs status <jobId>", "spira ai models get <modelId>", "spira credits balance"]
20965
+ examples: [
20966
+ `spira ai jobs create --model-id nano-banana-2 --inputs-json '{"prompt":"A product photo"}'`,
20967
+ `spira ai jobs create --model-id nano-banana-2 --inputs-json '{"prompt":"A product photo"}' --wait`
20968
+ ],
20969
+ relatedCommands: [
20970
+ "spira ai jobs wait <jobId>",
20971
+ "spira ai jobs status <jobId>",
20972
+ "spira ai models get <modelId>",
20973
+ "spira credits balance"
20974
+ ]
20565
20975
  },
20566
20976
  {
20567
20977
  path: ["workflows", "list"],
@@ -20617,10 +21027,7 @@ var commandDefinitions = [
20617
21027
  query: [{ name: "surface", source: "option", from: "surface" }]
20618
21028
  },
20619
21029
  resultType: "workflowNodeCatalog",
20620
- examples: [
20621
- "spira workflows node-catalog --json",
20622
- "spira workflows node-catalog --surface admin --json"
20623
- ],
21030
+ examples: ["spira workflows node-catalog --json", "spira workflows node-catalog --surface admin --json"],
20624
21031
  relatedCommands: [
20625
21032
  "spira workflows create --name <name>",
20626
21033
  "spira workflows update <workflowId>",
@@ -20670,7 +21077,11 @@ var commandDefinitions = [
20670
21077
  'spira workflows create --name "Product launch workflow"',
20671
21078
  `spira workflows create --name "Product launch workflow" --workflow-definition-json '{"nodes":[],"edges":[]}'`
20672
21079
  ],
20673
- relatedCommands: ["spira workflows get <workflowId>", "spira workflows update <workflowId>", "spira workflows runs start <workflowId>"]
21080
+ relatedCommands: [
21081
+ "spira workflows get <workflowId>",
21082
+ "spira workflows update <workflowId>",
21083
+ "spira workflows runs start <workflowId>"
21084
+ ]
20674
21085
  },
20675
21086
  {
20676
21087
  path: ["workflows", "get"],
@@ -20808,7 +21219,12 @@ var commandDefinitions = [
20808
21219
  purpose: "Start a workflow run and return its run ID.",
20809
21220
  arguments: [{ name: "workflowId", description: "Workflow ID.", required: true }],
20810
21221
  options: [
20811
- { name: "startOptionsJson", flag: "--start-options-json <json>", description: "Workflow start options JSON.", type: "json" },
21222
+ {
21223
+ name: "startOptionsJson",
21224
+ flag: "--start-options-json <json>",
21225
+ description: "Workflow start options JSON.",
21226
+ type: "json"
21227
+ },
20812
21228
  {
20813
21229
  name: "surface",
20814
21230
  flag: "--surface <surface>",
@@ -20867,7 +21283,9 @@ var commandDefinitions = [
20867
21283
  path: ["schedules", "executions"],
20868
21284
  purpose: "List persisted schedule execution attempts.",
20869
21285
  arguments: [{ name: "scheduleId", description: "Schedule ID.", required: true }],
20870
- options: [{ name: "limit", flag: "--limit <number>", description: "Maximum executions to return.", type: "number" }],
21286
+ options: [
21287
+ { name: "limit", flag: "--limit <number>", description: "Maximum executions to return.", type: "number" }
21288
+ ],
20871
21289
  http: {
20872
21290
  method: "GET",
20873
21291
  path: "/api/schedules/:scheduleId/executions",
@@ -20883,11 +21301,28 @@ var commandDefinitions = [
20883
21301
  path: ["schedules", "calendar"],
20884
21302
  purpose: "List scheduled occurrences between two times.",
20885
21303
  options: [
20886
- { name: "from", flag: "--from <ms>", description: "Start timestamp in epoch milliseconds.", type: "number", required: true },
20887
- { name: "to", flag: "--to <ms>", description: "End timestamp in epoch milliseconds.", type: "number", required: true },
21304
+ {
21305
+ name: "from",
21306
+ flag: "--from <ms>",
21307
+ description: "Start timestamp in epoch milliseconds.",
21308
+ type: "number",
21309
+ required: true
21310
+ },
21311
+ {
21312
+ name: "to",
21313
+ flag: "--to <ms>",
21314
+ description: "End timestamp in epoch milliseconds.",
21315
+ type: "number",
21316
+ required: true
21317
+ },
20888
21318
  { name: "limit", flag: "--limit <number>", description: "Maximum occurrences to return.", type: "number" },
20889
21319
  { name: "offset", flag: "--offset <number>", description: "Zero-based occurrence offset.", type: "number" },
20890
- { name: "scheduleId", flag: "--schedule-id <scheduleId>", description: "Optional schedule id filter.", type: "string" }
21320
+ {
21321
+ name: "scheduleId",
21322
+ flag: "--schedule-id <scheduleId>",
21323
+ description: "Optional schedule id filter.",
21324
+ type: "string"
21325
+ }
20891
21326
  ],
20892
21327
  http: {
20893
21328
  method: "GET",
@@ -20928,7 +21363,12 @@ var commandDefinitions = [
20928
21363
  { name: "lineEnd", flag: "--line-end <number>", description: "End line (inclusive).", type: "number" },
20929
21364
  { name: "page", flag: "--page <number>", description: "Start page (1-based, PDFs).", type: "number" },
20930
21365
  { name: "pageEnd", flag: "--page-end <number>", description: "End page (inclusive).", type: "number" },
20931
- { name: "expand", flag: "--expand <number>", description: "Extra lines of context around the range.", type: "number" }
21366
+ {
21367
+ name: "expand",
21368
+ flag: "--expand <number>",
21369
+ description: "Extra lines of context around the range.",
21370
+ type: "number"
21371
+ }
20932
21372
  ],
20933
21373
  http: {
20934
21374
  method: "GET",
@@ -20958,7 +21398,12 @@ var commandDefinitions = [
20958
21398
  options: [
20959
21399
  { name: "scope", flag: "--scope <path>", description: "Restrict to a folder path.", type: "string" },
20960
21400
  { name: "limit", flag: "--limit <number>", description: "Max matches (default 20, max 100).", type: "number" },
20961
- { name: "debug", flag: "--debug", description: "Include candidate scoring breakdown (JSON mode).", type: "boolean" }
21401
+ {
21402
+ name: "debug",
21403
+ flag: "--debug",
21404
+ description: "Include candidate scoring breakdown (JSON mode).",
21405
+ type: "boolean"
21406
+ }
20962
21407
  ],
20963
21408
  http: {
20964
21409
  method: "POST",
@@ -20982,8 +21427,18 @@ var commandDefinitions = [
20982
21427
  options: [
20983
21428
  { name: "scope", flag: "--scope <path>", description: "Restrict to a folder path.", type: "string" },
20984
21429
  { name: "limit", flag: "--limit <number>", description: "Max matches (default 20, max 100).", type: "number" },
20985
- { name: "rerank", flag: "--rerank", description: "Enable LLM reranking (slower, higher quality).", type: "boolean" },
20986
- { name: "debug", flag: "--debug", description: "Include candidate scoring breakdown (JSON mode).", type: "boolean" }
21430
+ {
21431
+ name: "rerank",
21432
+ flag: "--rerank",
21433
+ description: "Enable LLM reranking (slower, higher quality).",
21434
+ type: "boolean"
21435
+ },
21436
+ {
21437
+ name: "debug",
21438
+ flag: "--debug",
21439
+ description: "Include candidate scoring breakdown (JSON mode).",
21440
+ type: "boolean"
21441
+ }
20987
21442
  ],
20988
21443
  http: {
20989
21444
  method: "POST",
@@ -21026,6 +21481,13 @@ function validateCommandInput(command, args, options) {
21026
21481
  return validationError(command, option.name, `Expected one of: ${option.enumValues.join(", ")}.`);
21027
21482
  }
21028
21483
  }
21484
+ if (typeof value === "number" && option.minimum !== void 0 && value < option.minimum) {
21485
+ return validationError(
21486
+ command,
21487
+ option.name,
21488
+ `${getOptionDisplayName(option.flag)} must be at least ${option.minimum}.`
21489
+ );
21490
+ }
21029
21491
  }
21030
21492
  return null;
21031
21493
  }
@@ -21579,6 +22041,43 @@ async function logout() {
21579
22041
  });
21580
22042
  }
21581
22043
 
22044
+ // src/render/ai-job-wait.ts
22045
+ function objectRecord(value) {
22046
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
22047
+ }
22048
+ function compactResult(result) {
22049
+ const data = objectRecord(result.data);
22050
+ const execution = objectRecord(data.latestExecution);
22051
+ const status = typeof data.status === "string" ? data.status.toLowerCase() : result.status;
22052
+ const resultUrls = Array.isArray(execution.resultUrls) ? execution.resultUrls.filter((url2) => typeof url2 === "string") : [];
22053
+ return {
22054
+ id: typeof data.id === "string" ? data.id : result.ids.jobId,
22055
+ modelId: typeof data.modelId === "string" ? data.modelId : void 0,
22056
+ status,
22057
+ resultType: typeof execution.resultType === "string" ? execution.resultType : void 0,
22058
+ resultUrls,
22059
+ ...result.error ? {
22060
+ error: {
22061
+ code: result.error.code,
22062
+ message: result.error.message,
22063
+ retryable: result.error.retryable
22064
+ }
22065
+ } : {}
22066
+ };
22067
+ }
22068
+ function renderAiJobWait(result, options) {
22069
+ const compact = compactResult(result);
22070
+ if (options.json) return `${JSON.stringify(compact, null, 2)}
22071
+ `;
22072
+ const lines = [`id: ${compact.id ?? "-"}`, `status: ${compact.status}`];
22073
+ if (compact.modelId) lines.push(`modelId: ${compact.modelId}`);
22074
+ if (compact.resultType) lines.push(`resultType: ${compact.resultType}`);
22075
+ lines.push(...compact.resultUrls);
22076
+ if (compact.error) lines.push(`error: ${compact.error.message}`);
22077
+ return `${lines.join("\n")}
22078
+ `;
22079
+ }
22080
+
21582
22081
  // src/render/human.ts
21583
22082
  function valueToText(value) {
21584
22083
  if (value === null || value === void 0 || value === "") return "-";
@@ -21604,20 +22103,18 @@ function dataArray(data) {
21604
22103
  }
21605
22104
  function renderRows(rows, columns) {
21606
22105
  if (rows.length === 0) return "No results.\n";
21607
- const records = rows.map((row) => row && typeof row === "object" ? row : { value: row });
22106
+ const records = rows.map(
22107
+ (row) => row && typeof row === "object" ? row : { value: row }
22108
+ );
21608
22109
  const widths = columns.map(
21609
- (column) => Math.min(
21610
- 42,
21611
- Math.max(
21612
- column.length,
21613
- ...records.map((record2) => valueToText(record2[column]).slice(0, 42).length)
21614
- )
21615
- )
22110
+ (column) => Math.min(42, Math.max(column.length, ...records.map((record2) => valueToText(record2[column]).slice(0, 42).length)))
21616
22111
  );
21617
22112
  const header = columns.map((column, index) => column.padEnd(widths[index])).join(" ");
21618
22113
  const divider = widths.map((width) => "-".repeat(width)).join(" ");
21619
22114
  const body = records.map(
21620
- (record2) => columns.map((column, index) => valueToText(record2[column]).replace(/\s+/g, " ").slice(0, widths[index]).padEnd(widths[index])).join(" ")
22115
+ (record2) => columns.map(
22116
+ (column, index) => valueToText(record2[column]).replace(/\s+/g, " ").slice(0, widths[index]).padEnd(widths[index])
22117
+ ).join(" ")
21621
22118
  ).join("\n");
21622
22119
  return `${header}
21623
22120
  ${divider}
@@ -21629,11 +22126,11 @@ function renderKeyValues(data) {
21629
22126
  `;
21630
22127
  return Object.entries(data).filter(([, value]) => typeof value !== "object" || value === null).map(([key, value]) => `${key}: ${valueToText(value)}`).join("\n").concat("\n");
21631
22128
  }
21632
- function objectRecord(data) {
22129
+ function objectRecord2(data) {
21633
22130
  return data && typeof data === "object" ? data : {};
21634
22131
  }
21635
22132
  function formatWhen(value) {
21636
- const record2 = objectRecord(value);
22133
+ const record2 = objectRecord2(value);
21637
22134
  const entries = Object.entries(record2);
21638
22135
  if (!entries.length) return "";
21639
22136
  return entries.map(([key, item]) => `${key}=${valueToText(item)}`).join(", ");
@@ -21649,17 +22146,17 @@ function formatCreditOption(option) {
21649
22146
  function portSummary(ports) {
21650
22147
  if (!Array.isArray(ports) || ports.length === 0) return "-";
21651
22148
  return ports.map((port) => {
21652
- const record2 = objectRecord(port);
22149
+ const record2 = objectRecord2(port);
21653
22150
  const suffix = record2.multiple ? "[]" : "";
21654
22151
  return `${valueToText(record2.id)}:${valueToText(record2.valueType)}${suffix}`;
21655
22152
  }).join(", ");
21656
22153
  }
21657
22154
  function renderWorkflowNodeCatalog(data) {
21658
- const record2 = objectRecord(data);
22155
+ const record2 = objectRecord2(data);
21659
22156
  const nodes = Array.isArray(record2.nodes) ? record2.nodes : [];
21660
22157
  const rows = nodes.map((node) => {
21661
- const nodeRecord = objectRecord(node);
21662
- const ports = objectRecord(nodeRecord.ports);
22158
+ const nodeRecord = objectRecord2(node);
22159
+ const ports = objectRecord2(nodeRecord.ports);
21663
22160
  return {
21664
22161
  type: nodeRecord.type,
21665
22162
  inputs: portSummary(ports.inputs),
@@ -21673,7 +22170,7 @@ function renderAccountList(data) {
21673
22170
  const channels = data && typeof data === "object" ? data.publishChannels : null;
21674
22171
  if (Array.isArray(channels)) {
21675
22172
  const rows = channels.map((channel) => {
21676
- const record2 = objectRecord(channel);
22173
+ const record2 = objectRecord2(channel);
21677
22174
  return {
21678
22175
  platform: record2.platform,
21679
22176
  connected: record2.oauthAccountsCount,
@@ -21686,12 +22183,12 @@ function renderAccountList(data) {
21686
22183
  return renderKeyValues(data);
21687
22184
  }
21688
22185
  function renderAccountConnect(data) {
21689
- const record2 = objectRecord(data);
22186
+ const record2 = objectRecord2(data);
21690
22187
  return `platform: ${valueToText(record2.platform)}
21691
22188
  `;
21692
22189
  }
21693
22190
  function renderCreditBalance(data) {
21694
- const record2 = objectRecord(data);
22191
+ const record2 = objectRecord2(data);
21695
22192
  const lines = [
21696
22193
  `availableCredits: ${formatCredits(record2.availableCredits)}`,
21697
22194
  `permanentCredits: ${formatCredits(record2.availablePermanentCredits)}`,
@@ -21718,7 +22215,7 @@ function renderUser(prefix, value) {
21718
22215
  function renderAuthWhoami(data) {
21719
22216
  if (!data || typeof data !== "object") return renderKeyValues(data);
21720
22217
  const record2 = data;
21721
- const effective = objectRecord(record2.effectiveUser);
22218
+ const effective = objectRecord2(record2.effectiveUser);
21722
22219
  const name = valueToText(effective.name);
21723
22220
  const email3 = valueToText(effective.email);
21724
22221
  const lines = [`user: ${name}${email3 === "-" ? "" : ` <${email3}>`}`];
@@ -21733,8 +22230,8 @@ function renderAuthLogin(data) {
21733
22230
  `;
21734
22231
  }
21735
22232
  function renderSocialSubmissionDetail(data) {
21736
- const raw = objectRecord(data);
21737
- const nested = objectRecord(raw.data);
22233
+ const raw = objectRecord2(data);
22234
+ const nested = objectRecord2(raw.data);
21738
22235
  const record2 = nested.submission_id || nested.postUrl || nested.tweetId ? {
21739
22236
  id: nested.submission_id,
21740
22237
  platform: raw.platform,
@@ -21768,8 +22265,8 @@ function renderSocialSubmissionDetail(data) {
21768
22265
  `;
21769
22266
  }
21770
22267
  function renderAiJobStatus(data) {
21771
- const record2 = objectRecord(data);
21772
- const latestExecution = objectRecord(record2.latestExecution);
22268
+ const record2 = objectRecord2(data);
22269
+ const latestExecution = objectRecord2(record2.latestExecution);
21773
22270
  const resultUrls = Array.isArray(latestExecution.resultUrls) ? latestExecution.resultUrls : [];
21774
22271
  const lines = [
21775
22272
  `id: ${valueToText(record2.id)}`,
@@ -21787,11 +22284,14 @@ function renderAiJobStatus(data) {
21787
22284
  function renderAiModelList(data) {
21788
22285
  return renderRows(dataArray(data), ["id", "mediaType", "creditOptions", "name", "capabilities"]);
21789
22286
  }
22287
+ function renderAiModelProviderList(data) {
22288
+ return renderRows(dataArray(data), ["modelId", "name", "providers", "hidden"]);
22289
+ }
21790
22290
  function renderAiModelDetail(data) {
21791
- const record2 = objectRecord(data);
21792
- const fields = Array.isArray(record2.fields) ? record2.fields.map(objectRecord) : [];
21793
- const creditOptions = Array.isArray(record2.creditOptions) ? record2.creditOptions.map(objectRecord) : Array.isArray(record2.variants) ? record2.variants.map(objectRecord) : [];
21794
- const examples = Array.isArray(record2.examples) ? record2.examples.map(objectRecord) : [];
22291
+ const record2 = objectRecord2(data);
22292
+ const fields = Array.isArray(record2.fields) ? record2.fields.map(objectRecord2) : [];
22293
+ const creditOptions = Array.isArray(record2.creditOptions) ? record2.creditOptions.map(objectRecord2) : Array.isArray(record2.variants) ? record2.variants.map(objectRecord2) : [];
22294
+ const examples = Array.isArray(record2.examples) ? record2.examples.map(objectRecord2) : [];
21795
22295
  const notes = Array.isArray(record2.notes) ? record2.notes : [];
21796
22296
  const lines = [
21797
22297
  `id: ${valueToText(record2.id)}`,
@@ -21831,8 +22331,8 @@ function renderAiModelDetail(data) {
21831
22331
  `;
21832
22332
  }
21833
22333
  function renderWorkflowDetail(data) {
21834
- const record2 = objectRecord(data);
21835
- const definition = objectRecord(record2.workflowDefinition);
22334
+ const record2 = objectRecord2(data);
22335
+ const definition = objectRecord2(record2.workflowDefinition);
21836
22336
  const nodes = Array.isArray(definition.nodes) ? definition.nodes : [];
21837
22337
  const edges = Array.isArray(definition.edges) ? definition.edges : [];
21838
22338
  return [
@@ -21845,9 +22345,9 @@ function renderWorkflowDetail(data) {
21845
22345
  ].join("\n").concat("\n");
21846
22346
  }
21847
22347
  function renderScheduleDetail(data) {
21848
- const record2 = objectRecord(data);
21849
- const executorConfig = objectRecord(record2.executorConfig);
21850
- const currentPattern = objectRecord(record2.currentPattern);
22348
+ const record2 = objectRecord2(data);
22349
+ const executorConfig = objectRecord2(record2.executorConfig);
22350
+ const currentPattern = objectRecord2(record2.currentPattern);
21851
22351
  return [
21852
22352
  `id: ${valueToText(record2.id)}`,
21853
22353
  `executorType: ${valueToText(record2.executorType)}`,
@@ -21888,9 +22388,9 @@ function formatAnchor(anchor) {
21888
22388
  return parts.join(", ") || "-";
21889
22389
  }
21890
22390
  function renderDataBaseGlob(data) {
21891
- const record2 = objectRecord(data);
22391
+ const record2 = objectRecord2(data);
21892
22392
  const pattern = valueToText(record2.pattern);
21893
- const entries = Array.isArray(record2.entries) ? record2.entries.map(objectRecord) : [];
22393
+ const entries = Array.isArray(record2.entries) ? record2.entries.map(objectRecord2) : [];
21894
22394
  if (entries.length === 0) return `pattern: ${pattern}
21895
22395
  No entries.
21896
22396
  `;
@@ -21910,11 +22410,11 @@ No entries.
21910
22410
  ${renderRows(rows, ["type", "path", "fileType", "size", "status", "updatedAt"])}`;
21911
22411
  }
21912
22412
  function renderDataBaseRead(data) {
21913
- const record2 = objectRecord(data);
22413
+ const record2 = objectRecord2(data);
21914
22414
  const path = valueToText(record2.path);
21915
22415
  const fileType = valueToText(record2.fileType);
21916
22416
  const mimeType = valueToText(record2.mimeType);
21917
- const rangeObj = objectRecord(record2.range);
22417
+ const rangeObj = objectRecord2(record2.range);
21918
22418
  let rangeStr = "-";
21919
22419
  if (Array.isArray(rangeObj.line) && rangeObj.line.length === 2) {
21920
22420
  rangeStr = `lines ${rangeObj.line[0]}-${rangeObj.line[1]}`;
@@ -21946,14 +22446,14 @@ function renderDataBaseRead(data) {
21946
22446
  ${content}${footer}`;
21947
22447
  }
21948
22448
  function renderDataBaseMatches(data) {
21949
- const record2 = objectRecord(data);
22449
+ const record2 = objectRecord2(data);
21950
22450
  const query = valueToText(record2.query);
21951
- const matches = Array.isArray(record2.matches) ? record2.matches.map(objectRecord) : [];
22451
+ const matches = Array.isArray(record2.matches) ? record2.matches.map(objectRecord2) : [];
21952
22452
  if (matches.length === 0) return `query: ${query}
21953
22453
  No matches.
21954
22454
  `;
21955
22455
  const blocks = matches.map((match) => {
21956
- const anchor = formatAnchor(objectRecord(match.anchor));
22456
+ const anchor = formatAnchor(objectRecord2(match.anchor));
21957
22457
  const score = typeof match.score === "number" ? match.score.toFixed(3) : valueToText(match.score);
21958
22458
  const fields = Array.isArray(match.matchedFields) ? match.matchedFields.join(",") : "";
21959
22459
  const header = `${valueToText(match.path)}(${anchor}) [score=${score}${fields ? ` fields=${fields}` : ""}]`;
@@ -21983,7 +22483,7 @@ function renderSuccess(result) {
21983
22483
  case "socialSubmissionList":
21984
22484
  return renderRows(
21985
22485
  dataArray(result.data).map((item) => {
21986
- const record2 = objectRecord(item);
22486
+ const record2 = objectRecord2(item);
21987
22487
  return {
21988
22488
  id: record2.id,
21989
22489
  platform: record2.platform,
@@ -22006,6 +22506,8 @@ function renderSuccess(result) {
22006
22506
  return renderAiModelList(result.data);
22007
22507
  case "aiModelDetail":
22008
22508
  return renderAiModelDetail(result.data);
22509
+ case "aiModelProviderList":
22510
+ return renderAiModelProviderList(result.data);
22009
22511
  case "workflowList":
22010
22512
  return renderRows(dataArray(result.data), ["id", "name", "creatorName", "nodeTypes", "updatedAt"]);
22011
22513
  case "workflowNodeCatalog":
@@ -22021,7 +22523,14 @@ function renderSuccess(result) {
22021
22523
  case "scheduleOccurrenceList":
22022
22524
  return renderRows(dataArray(result.data), ["id", "scheduleId", "scheduledAt", "status", "executorType"]);
22023
22525
  case "scheduleExecutionList":
22024
- return renderRows(dataArray(result.data), ["id", "scheduleId", "status", "originalScheduledAt", "startedAt", "finishedAt"]);
22526
+ return renderRows(dataArray(result.data), [
22527
+ "id",
22528
+ "scheduleId",
22529
+ "status",
22530
+ "originalScheduledAt",
22531
+ "startedAt",
22532
+ "finishedAt"
22533
+ ]);
22025
22534
  case "dataBaseGlob":
22026
22535
  return renderDataBaseGlob(result.data);
22027
22536
  case "dataBaseRead":
@@ -22080,6 +22589,7 @@ function renderJson(result) {
22080
22589
 
22081
22590
  // src/render/index.ts
22082
22591
  function renderResult(result, options) {
22592
+ if (options.compactAiJobWait) return renderAiJobWait(result, options);
22083
22593
  return options.json ? renderJson(result) : renderHuman(result);
22084
22594
  }
22085
22595
 
@@ -22232,6 +22742,24 @@ function parseNumber(value) {
22232
22742
  if (!Number.isFinite(parsed)) throw new InvalidArgumentError("Expected a number.");
22233
22743
  return parsed;
22234
22744
  }
22745
+ function parseDuration(value) {
22746
+ const match = /^(\d+(?:\.\d+)?)(ms|s|m|h)$/.exec(value.trim());
22747
+ if (!match) throw new InvalidArgumentError("Expected a duration such as 500ms, 3s, 10m, or 1h.");
22748
+ const amount = Number(match[1]);
22749
+ const unit = match[2];
22750
+ const multiplier = unit === "ms" ? 1 : unit === "s" ? 1e3 : unit === "m" ? 6e4 : 36e5;
22751
+ const durationMs = amount * multiplier;
22752
+ if (!Number.isSafeInteger(durationMs) || durationMs <= 0) {
22753
+ throw new InvalidArgumentError("Expected a positive duration.");
22754
+ }
22755
+ return durationMs;
22756
+ }
22757
+ function formatDuration(durationMs) {
22758
+ if (durationMs % 36e5 === 0) return `${durationMs / 36e5}h`;
22759
+ if (durationMs % 6e4 === 0) return `${durationMs / 6e4}m`;
22760
+ if (durationMs % 1e3 === 0) return `${durationMs / 1e3}s`;
22761
+ return `${durationMs}ms`;
22762
+ }
22235
22763
  function parseJson(value) {
22236
22764
  try {
22237
22765
  return JSON.parse(value);
@@ -22250,6 +22778,11 @@ function addOption(command, definition) {
22250
22778
  command.addOption(option.argParser(parseNumber).default(definition.defaultValue));
22251
22779
  return;
22252
22780
  }
22781
+ if (definition.type === "duration") {
22782
+ const defaultDescription = typeof definition.defaultValue === "number" ? formatDuration(definition.defaultValue) : void 0;
22783
+ command.addOption(option.argParser(parseDuration).default(definition.defaultValue, defaultDescription));
22784
+ return;
22785
+ }
22253
22786
  if (definition.type === "json") {
22254
22787
  command.addOption(option.argParser(parseJson).default(definition.defaultValue));
22255
22788
  return;
@@ -22306,10 +22839,7 @@ function getOrCreateSubcommand(parent, name) {
22306
22839
  return next;
22307
22840
  }
22308
22841
  function renderCommandReference(definition) {
22309
- const lines = [
22310
- `Command: spira ${definition.path.join(" ")}`,
22311
- `Description: ${definition.purpose}`
22312
- ];
22842
+ const lines = [`Command: spira ${definition.path.join(" ")}`, `Description: ${definition.purpose}`];
22313
22843
  if (definition.arguments?.length) {
22314
22844
  lines.push("Arguments:");
22315
22845
  for (const argument of definition.arguments) {
@@ -22338,8 +22868,8 @@ function renderCommandIndex() {
22338
22868
  `;
22339
22869
  }
22340
22870
  async function packageVersion() {
22341
- if ("0.0.5".length > 0) {
22342
- return "0.0.5";
22871
+ if ("0.0.7".length > 0) {
22872
+ return "0.0.7";
22343
22873
  }
22344
22874
  try {
22345
22875
  const packageUrl = new URL("../package.json", import_meta.url);
@@ -22512,7 +23042,52 @@ ${url2}
22512
23042
  resultType: "aiModelDetail",
22513
23043
  command: "spira ai models get",
22514
23044
  data: publicAiModelDoc(model),
22515
- nextActions: [{ label: "Create job", command: `spira ai jobs create --model-id ${model.id} --inputs-json '<json>'` }]
23045
+ nextActions: [
23046
+ { label: "Create job", command: `spira ai jobs create --model-id ${model.id} --inputs-json '<json>'` }
23047
+ ]
23048
+ });
23049
+ }
23050
+ if (commandKey(definition.path) === "ai jobs create") {
23051
+ const created = await executeHttpCommand({ command: definition, args, options });
23052
+ if (options.wait !== true || created.status === "error") return created;
23053
+ const jobId = created.ids.jobId;
23054
+ if (!jobId) {
23055
+ return cliResultSchema.parse({
23056
+ ...created,
23057
+ status: "error",
23058
+ error: {
23059
+ code: "CONTRACT_MISMATCH",
23060
+ message: "AI job creation response did not contain a jobId.",
23061
+ retryable: false,
23062
+ nextActions: []
23063
+ }
23064
+ });
23065
+ }
23066
+ const statusCommand = findCommandDefinition(["ai", "jobs", "status"]);
23067
+ if (!statusCommand) throw new Error("AI job status command is not registered.");
23068
+ output.error(`AI job ${jobId}: SUBMITTED
23069
+ `);
23070
+ return waitForAiJob({
23071
+ jobId,
23072
+ timeoutMs: options.waitTimeout,
23073
+ intervalMs: options.waitInterval,
23074
+ onStatus: (status) => output.error(`AI job ${jobId}: ${status}
23075
+ `),
23076
+ poll: () => executeHttpCommand({ command: statusCommand, args: { jobId }, options: {} })
23077
+ });
23078
+ }
23079
+ if (commandKey(definition.path) === "ai jobs wait") {
23080
+ const statusCommand = findCommandDefinition(["ai", "jobs", "status"]);
23081
+ if (!statusCommand) throw new Error("AI job status command is not registered.");
23082
+ const jobId = args.jobId;
23083
+ return waitForAiJob({
23084
+ jobId,
23085
+ command: "spira ai jobs wait",
23086
+ timeoutMs: options.timeout,
23087
+ intervalMs: options.interval,
23088
+ onStatus: (status) => output.error(`AI job ${jobId}: ${status}
23089
+ `),
23090
+ poll: () => executeHttpCommand({ command: statusCommand, args: { jobId }, options: {} })
22516
23091
  });
22517
23092
  }
22518
23093
  return executeHttpCommand({ command: definition, args, options });
@@ -22531,11 +23106,18 @@ function addLeafCommand(root, definition, output) {
22531
23106
  leaf.action(async (...values) => {
22532
23107
  const command = values[values.length - 1];
22533
23108
  const positional = values.slice(0, -1).map((value) => value === void 0 ? "" : String(value));
22534
- const args = Object.fromEntries((definition.arguments ?? []).map((argument, index) => [argument.name, positional[index] ?? ""]));
23109
+ const args = Object.fromEntries(
23110
+ (definition.arguments ?? []).map((argument, index) => [argument.name, positional[index] ?? ""])
23111
+ );
22535
23112
  const options = command.optsWithGlobals();
22536
23113
  const validationResult2 = validateCommandInput(definition, args, options);
22537
23114
  const result = validationResult2 ?? await executeDefinition(definition, args, options, output);
22538
- output.write(renderResult(result, { json: Boolean(options.json) }));
23115
+ output.write(
23116
+ renderResult(result, {
23117
+ json: Boolean(options.json),
23118
+ compactAiJobWait: commandKey(definition.path) === "ai jobs create" && options.wait === true || commandKey(definition.path) === "ai jobs wait"
23119
+ })
23120
+ );
22539
23121
  if (result.status === "error") process.exitCode = 1;
22540
23122
  });
22541
23123
  parent.addCommand(leaf);