@spira-lab/cli 0.0.5 → 0.0.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 (3) hide show
  1. package/README.md +27 -22
  2. package/dist/index.cjs +633 -68
  3. package/package.json +2 -25
package/README.md CHANGED
@@ -1,22 +1,27 @@
1
- # Spira CLI
2
-
3
- Command line client for Spira.
4
-
5
- ## Install
6
-
7
- ```bash
8
- npm install -g @spira-lab/cli
9
- ```
10
-
11
- ## Usage
12
-
13
- ```bash
14
- spira --help
15
- spira auth login
16
- spira ai models list
17
- spira workflows node-catalog --json
18
- spira workflows create --name "Product launch workflow"
19
- spira workflows update <workflowId> --workflow-definition-json '{"nodes":[],"edges":[]}'
20
- ```
21
-
22
- Use `spira --json` when you need machine-readable output.
1
+ # Spira CLI
2
+
3
+ Command line client for Spira.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install -g @spira-lab/cli
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```bash
14
+ spira --help
15
+ spira auth login
16
+ spira ai models list
17
+ spira ai models list --media-type audio
18
+ spira ai jobs create --model-id nano-banana-2 --inputs-json '{"prompt":"A product photo"}' --wait
19
+ spira ai jobs create --model-id elevenlabs-music-generation --inputs-json '{"prompt":"A short ambient cue","duration":"10"}' --wait
20
+ spira workflows node-catalog --json
21
+ spira workflows create --name "Product launch workflow"
22
+ spira workflows update <workflowId> --workflow-definition-json '{"nodes":[],"edges":[]}'
23
+ ```
24
+
25
+ Use `spira --json` when you need machine-readable output.
26
+
27
+ `ai jobs create --wait` prints the job ID and status changes to stderr while it waits. Its final stdout is a compact result containing the job ID, model, status, result type, and result URLs. Use `spira --json ai jobs status <jobId>` when you need the full job and provider execution details.
package/dist/index.cjs CHANGED
@@ -20198,6 +20198,260 @@ var cliAiModelDocs = [
20198
20198
  "first_last_frames_to_video requires exactly 2 referenceImage URLs.",
20199
20199
  "extendFromTaskId requires extendProviderId=socheap-media and resolution=720p."
20200
20200
  ]
20201
+ },
20202
+ {
20203
+ id: "suno-music",
20204
+ name: "Suno Music",
20205
+ mediaType: "audio",
20206
+ description: "Suno music generation with simple prompt and custom song modes.",
20207
+ capabilities: ["text-to-audio"],
20208
+ providers: ["socheap-media"],
20209
+ variants: [
20210
+ {
20211
+ key: "v5_5",
20212
+ display: "v5_5",
20213
+ conditions: {},
20214
+ billingUnit: "per_generation",
20215
+ creditsPerUnit: 6.6
20216
+ }
20217
+ ],
20218
+ fields: [
20219
+ {
20220
+ name: "prompt",
20221
+ type: "string",
20222
+ description: "Song description in simple mode, or lyrics in custom vocal mode."
20223
+ },
20224
+ {
20225
+ name: "mode",
20226
+ type: "string",
20227
+ description: "Suno model version.",
20228
+ values: ["v5_5"],
20229
+ defaultValue: "v5_5"
20230
+ },
20231
+ {
20232
+ name: "customMode",
20233
+ type: "boolean",
20234
+ description: "Enable custom lyrics, style, and title inputs.",
20235
+ defaultValue: false
20236
+ },
20237
+ {
20238
+ name: "instrumental",
20239
+ type: "boolean",
20240
+ description: "Generate an instrumental track. Only used in custom mode.",
20241
+ defaultValue: false
20242
+ },
20243
+ {
20244
+ name: "style",
20245
+ type: "string",
20246
+ description: "Required music style in custom mode."
20247
+ },
20248
+ {
20249
+ name: "title",
20250
+ type: "string",
20251
+ description: "Required song title in custom mode."
20252
+ }
20253
+ ],
20254
+ examples: [
20255
+ {
20256
+ label: "Generate music",
20257
+ inputs: {
20258
+ prompt: "A warm upbeat electronic instrumental for a product launch",
20259
+ mode: "v5_5"
20260
+ }
20261
+ }
20262
+ ],
20263
+ notes: [
20264
+ "Simple mode requires prompt. Custom mode requires style and title; vocal custom mode also requires prompt as lyrics."
20265
+ ]
20266
+ },
20267
+ {
20268
+ id: "elevenlabs-music-generation",
20269
+ name: "Eleven Music V1",
20270
+ mediaType: "audio",
20271
+ description: "ElevenLabs prompt-to-music generation.",
20272
+ capabilities: ["text-to-audio"],
20273
+ providers: ["elevenlabs-audio"],
20274
+ variants: [
20275
+ {
20276
+ key: "default",
20277
+ display: "Per second",
20278
+ conditions: {},
20279
+ billingUnit: "per_second",
20280
+ creditsPerUnit: 0.25
20281
+ }
20282
+ ],
20283
+ fields: [
20284
+ {
20285
+ name: "prompt",
20286
+ type: "string",
20287
+ description: "Description of the music to generate.",
20288
+ required: true
20289
+ },
20290
+ {
20291
+ name: "duration",
20292
+ type: "string",
20293
+ description: "Whole number of seconds from 1 to 300.",
20294
+ defaultValue: "10"
20295
+ }
20296
+ ],
20297
+ examples: [
20298
+ {
20299
+ label: "Generate music",
20300
+ inputs: {
20301
+ prompt: "A calm ambient piano cue with a gentle ending",
20302
+ duration: "10"
20303
+ }
20304
+ }
20305
+ ]
20306
+ },
20307
+ {
20308
+ id: "elevenlabs-sound-effects",
20309
+ name: "Eleven Text To Sound V2",
20310
+ mediaType: "audio",
20311
+ description: "ElevenLabs sound-effect generation from an English prompt.",
20312
+ capabilities: ["text-to-audio"],
20313
+ providers: ["elevenlabs-audio"],
20314
+ variants: [
20315
+ {
20316
+ key: "default",
20317
+ display: "Per generation",
20318
+ conditions: {},
20319
+ billingUnit: "per_generation",
20320
+ creditsPerUnit: 12
20321
+ }
20322
+ ],
20323
+ fields: [
20324
+ {
20325
+ name: "prompt",
20326
+ type: "string",
20327
+ description: "English description of the sound effect.",
20328
+ required: true
20329
+ },
20330
+ {
20331
+ name: "duration",
20332
+ type: "string",
20333
+ description: "Duration in seconds from 0.5 to 30.",
20334
+ defaultValue: "5"
20335
+ }
20336
+ ],
20337
+ examples: [
20338
+ {
20339
+ label: "Generate a sound effect",
20340
+ inputs: {
20341
+ prompt: "A soft camera shutter in a quiet studio",
20342
+ duration: "2"
20343
+ }
20344
+ }
20345
+ ]
20346
+ },
20347
+ {
20348
+ id: "elevenlabs-text-to-speech",
20349
+ name: "ElevenLabs Text to Speech",
20350
+ mediaType: "audio",
20351
+ description: "ElevenLabs text-to-speech using an existing voice ID.",
20352
+ capabilities: ["text-to-speech"],
20353
+ providers: ["elevenlabs-audio"],
20354
+ variants: [
20355
+ {
20356
+ key: "multilingual-v2-v3",
20357
+ display: "Per character",
20358
+ conditions: {},
20359
+ billingUnit: "per_character",
20360
+ creditsPerUnit: 0.01
20361
+ }
20362
+ ],
20363
+ fields: [
20364
+ {
20365
+ name: "text",
20366
+ type: "string",
20367
+ description: "Text to speak.",
20368
+ required: true
20369
+ },
20370
+ {
20371
+ name: "voiceId",
20372
+ type: "string",
20373
+ description: "Existing ElevenLabs voice ID.",
20374
+ required: true
20375
+ },
20376
+ {
20377
+ name: "providerModelId",
20378
+ type: "string",
20379
+ description: "ElevenLabs speech model.",
20380
+ values: ["eleven_multilingual_v2", "eleven_v3"],
20381
+ defaultValue: "eleven_multilingual_v2"
20382
+ }
20383
+ ],
20384
+ examples: [
20385
+ {
20386
+ label: "Generate speech",
20387
+ inputs: {
20388
+ text: "Welcome to Spira.",
20389
+ voiceId: "<voice-id>",
20390
+ providerModelId: "eleven_multilingual_v2"
20391
+ }
20392
+ }
20393
+ ]
20394
+ },
20395
+ {
20396
+ id: "elevenlabs-voice-clone-tts",
20397
+ name: "ElevenLabs Voice TTS",
20398
+ mediaType: "audio",
20399
+ description: "Text-to-speech using either an existing voice ID or one reference audio URL.",
20400
+ capabilities: ["text-to-speech", "audio-to-speech"],
20401
+ providers: ["elevenlabs-audio"],
20402
+ variants: [
20403
+ {
20404
+ key: "voice-clone-tts",
20405
+ display: "Per character",
20406
+ conditions: {},
20407
+ billingUnit: "per_character",
20408
+ creditsPerUnit: 0.01
20409
+ }
20410
+ ],
20411
+ fields: [
20412
+ {
20413
+ name: "text",
20414
+ type: "string",
20415
+ description: "Text to speak.",
20416
+ required: true
20417
+ },
20418
+ {
20419
+ name: "voiceId",
20420
+ type: "string",
20421
+ description: "Existing ElevenLabs voice ID. Do not combine with referenceAudio."
20422
+ },
20423
+ {
20424
+ name: "referenceAudio",
20425
+ type: "string[]",
20426
+ description: "Exactly one audio URL when cloning a voice. Do not combine with voiceId."
20427
+ },
20428
+ {
20429
+ name: "providerModelId",
20430
+ type: "string",
20431
+ description: "ElevenLabs speech model.",
20432
+ values: ["eleven_multilingual_v2", "eleven_v3"],
20433
+ defaultValue: "eleven_multilingual_v2"
20434
+ },
20435
+ {
20436
+ name: "qualityMode",
20437
+ type: "string",
20438
+ description: "Voice delivery mode.",
20439
+ values: ["stable", "expressive"],
20440
+ defaultValue: "stable"
20441
+ }
20442
+ ],
20443
+ examples: [
20444
+ {
20445
+ label: "Generate speech from a reference voice",
20446
+ inputs: {
20447
+ text: "Welcome to Spira.",
20448
+ referenceAudio: ["https://example.com/reference.mp3"],
20449
+ providerModelId: "eleven_multilingual_v2",
20450
+ qualityMode: "stable"
20451
+ }
20452
+ }
20453
+ ],
20454
+ notes: ["Provide either voiceId or exactly one referenceAudio URL, never both."]
20201
20455
  }
20202
20456
  ];
20203
20457
  function listCliAiModelDocs(mediaType) {
@@ -20207,6 +20461,115 @@ function getCliAiModelDoc(modelId) {
20207
20461
  return cliAiModelDocs.find((model) => model.id === modelId);
20208
20462
  }
20209
20463
 
20464
+ // src/ai/wait.ts
20465
+ function wait(durationMs) {
20466
+ return new Promise((resolve) => setTimeout(resolve, durationMs));
20467
+ }
20468
+ function jobStatus(result) {
20469
+ if (!result.data || typeof result.data !== "object") return void 0;
20470
+ const status = result.data.status;
20471
+ return typeof status === "string" ? status : void 0;
20472
+ }
20473
+ function providerError(result) {
20474
+ if (!result.data || typeof result.data !== "object") return void 0;
20475
+ const latestExecution = result.data.latestExecution;
20476
+ if (!latestExecution || typeof latestExecution !== "object") return void 0;
20477
+ const error51 = latestExecution.error;
20478
+ if (typeof error51 === "string") return error51;
20479
+ if (error51 && typeof error51 === "object" && typeof error51.message === "string") {
20480
+ return error51.message;
20481
+ }
20482
+ return void 0;
20483
+ }
20484
+ function completedResult(result) {
20485
+ return cliResultSchema.parse({
20486
+ ...result,
20487
+ resultType: "aiJobStatus",
20488
+ command: "spira ai jobs create",
20489
+ nextActions: []
20490
+ });
20491
+ }
20492
+ function pollingError(result) {
20493
+ return cliResultSchema.parse({
20494
+ ...result,
20495
+ command: "spira ai jobs create"
20496
+ });
20497
+ }
20498
+ function terminalError(result, jobId, status) {
20499
+ const message = providerError(result) ?? `AI job ${jobId} ended with status ${status}.`;
20500
+ return cliResultSchema.parse({
20501
+ ...result,
20502
+ status: "error",
20503
+ resultType: "aiJobStatus",
20504
+ command: "spira ai jobs create",
20505
+ error: {
20506
+ code: status === "FAILED" ? "PROVIDER_FAILED" : "ASYNC_STATE",
20507
+ message,
20508
+ resource: { type: "aiJob", id: jobId },
20509
+ retryable: false,
20510
+ nextActions: [{ label: "Show job status", command: `spira ai jobs status ${jobId}` }]
20511
+ }
20512
+ });
20513
+ }
20514
+ function timeoutResult(jobId, timeoutMs, lastResult) {
20515
+ return cliResultSchema.parse({
20516
+ object: "cli.result",
20517
+ status: "error",
20518
+ resultType: "aiJobStatus",
20519
+ command: "spira ai jobs create",
20520
+ data: lastResult?.data,
20521
+ ids: { ...lastResult?.ids, jobId },
20522
+ error: {
20523
+ code: "TIMEOUT",
20524
+ message: `AI job ${jobId} did not finish within ${timeoutMs}ms.`,
20525
+ resource: { type: "aiJob", id: jobId },
20526
+ retryable: true,
20527
+ nextActions: [{ label: "Show job status", command: `spira ai jobs status ${jobId}` }]
20528
+ }
20529
+ });
20530
+ }
20531
+ function contractError(result, jobId) {
20532
+ return cliResultSchema.parse({
20533
+ ...result,
20534
+ status: "error",
20535
+ resultType: "aiJobStatus",
20536
+ command: "spira ai jobs create",
20537
+ error: {
20538
+ code: "CONTRACT_MISMATCH",
20539
+ message: `AI job ${jobId} status response did not contain a recognized status.`,
20540
+ resource: { type: "aiJob", id: jobId },
20541
+ retryable: false,
20542
+ nextActions: [{ label: "Show job status", command: `spira ai jobs status ${jobId}` }]
20543
+ }
20544
+ });
20545
+ }
20546
+ async function waitForAiJob(input) {
20547
+ const now = input.now ?? Date.now;
20548
+ const sleep = input.sleep ?? wait;
20549
+ const deadline = now() + input.timeoutMs;
20550
+ let lastResult;
20551
+ let lastStatus;
20552
+ while (true) {
20553
+ const result = await input.poll();
20554
+ lastResult = result;
20555
+ if (result.status === "error") {
20556
+ if (!result.error?.retryable) return pollingError(result);
20557
+ } else {
20558
+ const status = jobStatus(result);
20559
+ if (status && status !== lastStatus) {
20560
+ input.onStatus?.(status);
20561
+ lastStatus = status;
20562
+ }
20563
+ if (status === "COMPLETED") return completedResult(result);
20564
+ if (status === "FAILED" || status === "CANCELED") return terminalError(result, input.jobId, status);
20565
+ if (status !== "QUEUED" && status !== "RUNNING") return contractError(result, input.jobId);
20566
+ }
20567
+ const remainingMs = deadline - now();
20568
+ if (remainingMs <= 0) return timeoutResult(input.jobId, input.timeoutMs, lastResult);
20569
+ await sleep(Math.min(input.intervalMs, remainingMs));
20570
+ }
20571
+ }
20572
+
20210
20573
  // src/commands/definitions.ts
20211
20574
  var commonListOptions = [
20212
20575
  {
@@ -20390,7 +20753,10 @@ var commandDefinitions = [
20390
20753
  },
20391
20754
  resultType: "socialSubmissionList",
20392
20755
  examples: ["spira social submissions list --platform tiktok --status draft --limit 10"],
20393
- relatedCommands: ["spira social submissions get <submissionId>", "spira social submissions publish <submissionId> --confirm"]
20756
+ relatedCommands: [
20757
+ "spira social submissions get <submissionId>",
20758
+ "spira social submissions publish <submissionId> --confirm"
20759
+ ]
20394
20760
  },
20395
20761
  {
20396
20762
  path: ["social", "submissions", "get"],
@@ -20436,7 +20802,12 @@ var commandDefinitions = [
20436
20802
  defaultValue: "oauth_account"
20437
20803
  },
20438
20804
  { name: "title", flag: "--title <title>", description: "Draft title.", type: "string" },
20439
- { name: "description", flag: "--description <description>", description: "Draft description/text.", type: "string" },
20805
+ {
20806
+ name: "description",
20807
+ flag: "--description <description>",
20808
+ description: "Draft description/text.",
20809
+ type: "string"
20810
+ },
20440
20811
  { name: "mediaUrls", flag: "--media-url <url>", description: "Media URL. Repeatable.", type: "stringArray" },
20441
20812
  { name: "agentId", flag: "--agent-id <agentId>", description: "Associated agent ID.", type: "string" },
20442
20813
  {
@@ -20463,9 +20834,7 @@ var commandDefinitions = [
20463
20834
  ]
20464
20835
  },
20465
20836
  resultType: "socialSubmissionAction",
20466
- examples: [
20467
- 'spira social submissions create --platform twitter --content-kind text --description "Launch note"'
20468
- ],
20837
+ examples: ['spira social submissions create --platform twitter --content-kind text --description "Launch note"'],
20469
20838
  relatedCommands: [
20470
20839
  "spira social submissions get <submissionId>",
20471
20840
  "spira social submissions publish <submissionId> --confirm"
@@ -20476,9 +20845,20 @@ var commandDefinitions = [
20476
20845
  purpose: "Publish a social submission draft.",
20477
20846
  arguments: [{ name: "submissionId", description: "Submission ID.", required: true }],
20478
20847
  options: [
20479
- { name: "confirm", flag: "--confirm", description: "Required explicit publish confirmation.", type: "boolean", required: true },
20848
+ {
20849
+ name: "confirm",
20850
+ flag: "--confirm",
20851
+ description: "Required explicit publish confirmation.",
20852
+ type: "boolean",
20853
+ required: true
20854
+ },
20480
20855
  { name: "title", flag: "--title <title>", description: "Platform title override.", type: "string" },
20481
- { name: "description", flag: "--description <description>", description: "Platform description override.", type: "string" },
20856
+ {
20857
+ name: "description",
20858
+ flag: "--description <description>",
20859
+ description: "Platform description override.",
20860
+ type: "string"
20861
+ },
20482
20862
  { name: "text", flag: "--text <text>", description: "Text override for X or LinkedIn.", type: "string" },
20483
20863
  { name: "mediaUrls", flag: "--media-url <url>", description: "Media URL. Repeatable.", type: "stringArray" },
20484
20864
  { name: "privacyLevel", flag: "--privacy-level <value>", description: "TikTok privacy level.", type: "string" },
@@ -20519,18 +20899,18 @@ var commandDefinitions = [
20519
20899
  },
20520
20900
  {
20521
20901
  path: ["ai", "models", "list"],
20522
- purpose: "List AI image and video models available from the CLI.",
20902
+ purpose: "List AI image, video, and audio models available from the CLI.",
20523
20903
  options: [
20524
20904
  {
20525
20905
  name: "mediaType",
20526
20906
  flag: "--media-type <type>",
20527
20907
  description: "Filter by model media type.",
20528
20908
  type: "string",
20529
- enumValues: ["image", "video"]
20909
+ enumValues: ["image", "video", "audio"]
20530
20910
  }
20531
20911
  ],
20532
20912
  resultType: "aiModelList",
20533
- examples: ["spira ai models list", "spira ai models list --media-type video"],
20913
+ examples: ["spira ai models list", "spira ai models list --media-type audio"],
20534
20914
  relatedCommands: ["spira ai models get <modelId>", "spira ai jobs create"]
20535
20915
  },
20536
20916
  {
@@ -20543,11 +20923,45 @@ var commandDefinitions = [
20543
20923
  },
20544
20924
  {
20545
20925
  path: ["ai", "jobs", "create"],
20546
- purpose: "Create an AI image or video generation job.",
20926
+ purpose: "Create an AI image, video, or audio generation job.",
20547
20927
  options: [
20548
20928
  { 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 }
20929
+ {
20930
+ name: "providerId",
20931
+ flag: "--provider-id <providerId>",
20932
+ description: "Advanced routing override.",
20933
+ type: "string",
20934
+ hidden: true
20935
+ },
20936
+ {
20937
+ name: "inputsJson",
20938
+ flag: "--inputs-json <json>",
20939
+ description: "Model inputs JSON object.",
20940
+ type: "json",
20941
+ required: true
20942
+ },
20943
+ {
20944
+ name: "wait",
20945
+ flag: "--wait",
20946
+ description: "Wait for the job to finish and print the final result.",
20947
+ type: "boolean"
20948
+ },
20949
+ {
20950
+ name: "waitTimeout",
20951
+ flag: "--wait-timeout <duration>",
20952
+ description: "Maximum wait duration.",
20953
+ type: "duration",
20954
+ defaultValue: 6e5,
20955
+ minimum: 1e3
20956
+ },
20957
+ {
20958
+ name: "waitInterval",
20959
+ flag: "--wait-interval <duration>",
20960
+ description: "Polling interval while waiting.",
20961
+ type: "duration",
20962
+ defaultValue: 3e3,
20963
+ minimum: 1e3
20964
+ }
20551
20965
  ],
20552
20966
  http: {
20553
20967
  method: "POST",
@@ -20560,7 +20974,10 @@ var commandDefinitions = [
20560
20974
  ]
20561
20975
  },
20562
20976
  resultType: "aiJobAction",
20563
- examples: [`spira ai jobs create --model-id nano-banana-2 --inputs-json '{"prompt":"A product photo"}'`],
20977
+ examples: [
20978
+ `spira ai jobs create --model-id nano-banana-2 --inputs-json '{"prompt":"A product photo"}'`,
20979
+ `spira ai jobs create --model-id nano-banana-2 --inputs-json '{"prompt":"A product photo"}' --wait`
20980
+ ],
20564
20981
  relatedCommands: ["spira ai jobs status <jobId>", "spira ai models get <modelId>", "spira credits balance"]
20565
20982
  },
20566
20983
  {
@@ -20617,10 +21034,7 @@ var commandDefinitions = [
20617
21034
  query: [{ name: "surface", source: "option", from: "surface" }]
20618
21035
  },
20619
21036
  resultType: "workflowNodeCatalog",
20620
- examples: [
20621
- "spira workflows node-catalog --json",
20622
- "spira workflows node-catalog --surface admin --json"
20623
- ],
21037
+ examples: ["spira workflows node-catalog --json", "spira workflows node-catalog --surface admin --json"],
20624
21038
  relatedCommands: [
20625
21039
  "spira workflows create --name <name>",
20626
21040
  "spira workflows update <workflowId>",
@@ -20670,7 +21084,11 @@ var commandDefinitions = [
20670
21084
  'spira workflows create --name "Product launch workflow"',
20671
21085
  `spira workflows create --name "Product launch workflow" --workflow-definition-json '{"nodes":[],"edges":[]}'`
20672
21086
  ],
20673
- relatedCommands: ["spira workflows get <workflowId>", "spira workflows update <workflowId>", "spira workflows runs start <workflowId>"]
21087
+ relatedCommands: [
21088
+ "spira workflows get <workflowId>",
21089
+ "spira workflows update <workflowId>",
21090
+ "spira workflows runs start <workflowId>"
21091
+ ]
20674
21092
  },
20675
21093
  {
20676
21094
  path: ["workflows", "get"],
@@ -20808,7 +21226,12 @@ var commandDefinitions = [
20808
21226
  purpose: "Start a workflow run and return its run ID.",
20809
21227
  arguments: [{ name: "workflowId", description: "Workflow ID.", required: true }],
20810
21228
  options: [
20811
- { name: "startOptionsJson", flag: "--start-options-json <json>", description: "Workflow start options JSON.", type: "json" },
21229
+ {
21230
+ name: "startOptionsJson",
21231
+ flag: "--start-options-json <json>",
21232
+ description: "Workflow start options JSON.",
21233
+ type: "json"
21234
+ },
20812
21235
  {
20813
21236
  name: "surface",
20814
21237
  flag: "--surface <surface>",
@@ -20867,7 +21290,9 @@ var commandDefinitions = [
20867
21290
  path: ["schedules", "executions"],
20868
21291
  purpose: "List persisted schedule execution attempts.",
20869
21292
  arguments: [{ name: "scheduleId", description: "Schedule ID.", required: true }],
20870
- options: [{ name: "limit", flag: "--limit <number>", description: "Maximum executions to return.", type: "number" }],
21293
+ options: [
21294
+ { name: "limit", flag: "--limit <number>", description: "Maximum executions to return.", type: "number" }
21295
+ ],
20871
21296
  http: {
20872
21297
  method: "GET",
20873
21298
  path: "/api/schedules/:scheduleId/executions",
@@ -20883,11 +21308,28 @@ var commandDefinitions = [
20883
21308
  path: ["schedules", "calendar"],
20884
21309
  purpose: "List scheduled occurrences between two times.",
20885
21310
  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 },
21311
+ {
21312
+ name: "from",
21313
+ flag: "--from <ms>",
21314
+ description: "Start timestamp in epoch milliseconds.",
21315
+ type: "number",
21316
+ required: true
21317
+ },
21318
+ {
21319
+ name: "to",
21320
+ flag: "--to <ms>",
21321
+ description: "End timestamp in epoch milliseconds.",
21322
+ type: "number",
21323
+ required: true
21324
+ },
20888
21325
  { name: "limit", flag: "--limit <number>", description: "Maximum occurrences to return.", type: "number" },
20889
21326
  { 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" }
21327
+ {
21328
+ name: "scheduleId",
21329
+ flag: "--schedule-id <scheduleId>",
21330
+ description: "Optional schedule id filter.",
21331
+ type: "string"
21332
+ }
20891
21333
  ],
20892
21334
  http: {
20893
21335
  method: "GET",
@@ -20928,7 +21370,12 @@ var commandDefinitions = [
20928
21370
  { name: "lineEnd", flag: "--line-end <number>", description: "End line (inclusive).", type: "number" },
20929
21371
  { name: "page", flag: "--page <number>", description: "Start page (1-based, PDFs).", type: "number" },
20930
21372
  { 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" }
21373
+ {
21374
+ name: "expand",
21375
+ flag: "--expand <number>",
21376
+ description: "Extra lines of context around the range.",
21377
+ type: "number"
21378
+ }
20932
21379
  ],
20933
21380
  http: {
20934
21381
  method: "GET",
@@ -20958,7 +21405,12 @@ var commandDefinitions = [
20958
21405
  options: [
20959
21406
  { name: "scope", flag: "--scope <path>", description: "Restrict to a folder path.", type: "string" },
20960
21407
  { 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" }
21408
+ {
21409
+ name: "debug",
21410
+ flag: "--debug",
21411
+ description: "Include candidate scoring breakdown (JSON mode).",
21412
+ type: "boolean"
21413
+ }
20962
21414
  ],
20963
21415
  http: {
20964
21416
  method: "POST",
@@ -20982,8 +21434,18 @@ var commandDefinitions = [
20982
21434
  options: [
20983
21435
  { name: "scope", flag: "--scope <path>", description: "Restrict to a folder path.", type: "string" },
20984
21436
  { 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" }
21437
+ {
21438
+ name: "rerank",
21439
+ flag: "--rerank",
21440
+ description: "Enable LLM reranking (slower, higher quality).",
21441
+ type: "boolean"
21442
+ },
21443
+ {
21444
+ name: "debug",
21445
+ flag: "--debug",
21446
+ description: "Include candidate scoring breakdown (JSON mode).",
21447
+ type: "boolean"
21448
+ }
20987
21449
  ],
20988
21450
  http: {
20989
21451
  method: "POST",
@@ -21026,6 +21488,13 @@ function validateCommandInput(command, args, options) {
21026
21488
  return validationError(command, option.name, `Expected one of: ${option.enumValues.join(", ")}.`);
21027
21489
  }
21028
21490
  }
21491
+ if (typeof value === "number" && option.minimum !== void 0 && value < option.minimum) {
21492
+ return validationError(
21493
+ command,
21494
+ option.name,
21495
+ `${getOptionDisplayName(option.flag)} must be at least ${option.minimum}.`
21496
+ );
21497
+ }
21029
21498
  }
21030
21499
  return null;
21031
21500
  }
@@ -21579,6 +22048,43 @@ async function logout() {
21579
22048
  });
21580
22049
  }
21581
22050
 
22051
+ // src/render/ai-job-wait.ts
22052
+ function objectRecord(value) {
22053
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
22054
+ }
22055
+ function compactResult(result) {
22056
+ const data = objectRecord(result.data);
22057
+ const execution = objectRecord(data.latestExecution);
22058
+ const status = typeof data.status === "string" ? data.status.toLowerCase() : result.status;
22059
+ const resultUrls = Array.isArray(execution.resultUrls) ? execution.resultUrls.filter((url2) => typeof url2 === "string") : [];
22060
+ return {
22061
+ id: typeof data.id === "string" ? data.id : result.ids.jobId,
22062
+ modelId: typeof data.modelId === "string" ? data.modelId : void 0,
22063
+ status,
22064
+ resultType: typeof execution.resultType === "string" ? execution.resultType : void 0,
22065
+ resultUrls,
22066
+ ...result.error ? {
22067
+ error: {
22068
+ code: result.error.code,
22069
+ message: result.error.message,
22070
+ retryable: result.error.retryable
22071
+ }
22072
+ } : {}
22073
+ };
22074
+ }
22075
+ function renderAiJobWait(result, options) {
22076
+ const compact = compactResult(result);
22077
+ if (options.json) return `${JSON.stringify(compact, null, 2)}
22078
+ `;
22079
+ const lines = [`id: ${compact.id ?? "-"}`, `status: ${compact.status}`];
22080
+ if (compact.modelId) lines.push(`modelId: ${compact.modelId}`);
22081
+ if (compact.resultType) lines.push(`resultType: ${compact.resultType}`);
22082
+ lines.push(...compact.resultUrls);
22083
+ if (compact.error) lines.push(`error: ${compact.error.message}`);
22084
+ return `${lines.join("\n")}
22085
+ `;
22086
+ }
22087
+
21582
22088
  // src/render/human.ts
21583
22089
  function valueToText(value) {
21584
22090
  if (value === null || value === void 0 || value === "") return "-";
@@ -21629,11 +22135,11 @@ function renderKeyValues(data) {
21629
22135
  `;
21630
22136
  return Object.entries(data).filter(([, value]) => typeof value !== "object" || value === null).map(([key, value]) => `${key}: ${valueToText(value)}`).join("\n").concat("\n");
21631
22137
  }
21632
- function objectRecord(data) {
22138
+ function objectRecord2(data) {
21633
22139
  return data && typeof data === "object" ? data : {};
21634
22140
  }
21635
22141
  function formatWhen(value) {
21636
- const record2 = objectRecord(value);
22142
+ const record2 = objectRecord2(value);
21637
22143
  const entries = Object.entries(record2);
21638
22144
  if (!entries.length) return "";
21639
22145
  return entries.map(([key, item]) => `${key}=${valueToText(item)}`).join(", ");
@@ -21649,17 +22155,17 @@ function formatCreditOption(option) {
21649
22155
  function portSummary(ports) {
21650
22156
  if (!Array.isArray(ports) || ports.length === 0) return "-";
21651
22157
  return ports.map((port) => {
21652
- const record2 = objectRecord(port);
22158
+ const record2 = objectRecord2(port);
21653
22159
  const suffix = record2.multiple ? "[]" : "";
21654
22160
  return `${valueToText(record2.id)}:${valueToText(record2.valueType)}${suffix}`;
21655
22161
  }).join(", ");
21656
22162
  }
21657
22163
  function renderWorkflowNodeCatalog(data) {
21658
- const record2 = objectRecord(data);
22164
+ const record2 = objectRecord2(data);
21659
22165
  const nodes = Array.isArray(record2.nodes) ? record2.nodes : [];
21660
22166
  const rows = nodes.map((node) => {
21661
- const nodeRecord = objectRecord(node);
21662
- const ports = objectRecord(nodeRecord.ports);
22167
+ const nodeRecord = objectRecord2(node);
22168
+ const ports = objectRecord2(nodeRecord.ports);
21663
22169
  return {
21664
22170
  type: nodeRecord.type,
21665
22171
  inputs: portSummary(ports.inputs),
@@ -21673,7 +22179,7 @@ function renderAccountList(data) {
21673
22179
  const channels = data && typeof data === "object" ? data.publishChannels : null;
21674
22180
  if (Array.isArray(channels)) {
21675
22181
  const rows = channels.map((channel) => {
21676
- const record2 = objectRecord(channel);
22182
+ const record2 = objectRecord2(channel);
21677
22183
  return {
21678
22184
  platform: record2.platform,
21679
22185
  connected: record2.oauthAccountsCount,
@@ -21686,12 +22192,12 @@ function renderAccountList(data) {
21686
22192
  return renderKeyValues(data);
21687
22193
  }
21688
22194
  function renderAccountConnect(data) {
21689
- const record2 = objectRecord(data);
22195
+ const record2 = objectRecord2(data);
21690
22196
  return `platform: ${valueToText(record2.platform)}
21691
22197
  `;
21692
22198
  }
21693
22199
  function renderCreditBalance(data) {
21694
- const record2 = objectRecord(data);
22200
+ const record2 = objectRecord2(data);
21695
22201
  const lines = [
21696
22202
  `availableCredits: ${formatCredits(record2.availableCredits)}`,
21697
22203
  `permanentCredits: ${formatCredits(record2.availablePermanentCredits)}`,
@@ -21718,7 +22224,7 @@ function renderUser(prefix, value) {
21718
22224
  function renderAuthWhoami(data) {
21719
22225
  if (!data || typeof data !== "object") return renderKeyValues(data);
21720
22226
  const record2 = data;
21721
- const effective = objectRecord(record2.effectiveUser);
22227
+ const effective = objectRecord2(record2.effectiveUser);
21722
22228
  const name = valueToText(effective.name);
21723
22229
  const email3 = valueToText(effective.email);
21724
22230
  const lines = [`user: ${name}${email3 === "-" ? "" : ` <${email3}>`}`];
@@ -21733,8 +22239,8 @@ function renderAuthLogin(data) {
21733
22239
  `;
21734
22240
  }
21735
22241
  function renderSocialSubmissionDetail(data) {
21736
- const raw = objectRecord(data);
21737
- const nested = objectRecord(raw.data);
22242
+ const raw = objectRecord2(data);
22243
+ const nested = objectRecord2(raw.data);
21738
22244
  const record2 = nested.submission_id || nested.postUrl || nested.tweetId ? {
21739
22245
  id: nested.submission_id,
21740
22246
  platform: raw.platform,
@@ -21768,8 +22274,8 @@ function renderSocialSubmissionDetail(data) {
21768
22274
  `;
21769
22275
  }
21770
22276
  function renderAiJobStatus(data) {
21771
- const record2 = objectRecord(data);
21772
- const latestExecution = objectRecord(record2.latestExecution);
22277
+ const record2 = objectRecord2(data);
22278
+ const latestExecution = objectRecord2(record2.latestExecution);
21773
22279
  const resultUrls = Array.isArray(latestExecution.resultUrls) ? latestExecution.resultUrls : [];
21774
22280
  const lines = [
21775
22281
  `id: ${valueToText(record2.id)}`,
@@ -21788,10 +22294,10 @@ function renderAiModelList(data) {
21788
22294
  return renderRows(dataArray(data), ["id", "mediaType", "creditOptions", "name", "capabilities"]);
21789
22295
  }
21790
22296
  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) : [];
22297
+ const record2 = objectRecord2(data);
22298
+ const fields = Array.isArray(record2.fields) ? record2.fields.map(objectRecord2) : [];
22299
+ const creditOptions = Array.isArray(record2.creditOptions) ? record2.creditOptions.map(objectRecord2) : Array.isArray(record2.variants) ? record2.variants.map(objectRecord2) : [];
22300
+ const examples = Array.isArray(record2.examples) ? record2.examples.map(objectRecord2) : [];
21795
22301
  const notes = Array.isArray(record2.notes) ? record2.notes : [];
21796
22302
  const lines = [
21797
22303
  `id: ${valueToText(record2.id)}`,
@@ -21831,8 +22337,8 @@ function renderAiModelDetail(data) {
21831
22337
  `;
21832
22338
  }
21833
22339
  function renderWorkflowDetail(data) {
21834
- const record2 = objectRecord(data);
21835
- const definition = objectRecord(record2.workflowDefinition);
22340
+ const record2 = objectRecord2(data);
22341
+ const definition = objectRecord2(record2.workflowDefinition);
21836
22342
  const nodes = Array.isArray(definition.nodes) ? definition.nodes : [];
21837
22343
  const edges = Array.isArray(definition.edges) ? definition.edges : [];
21838
22344
  return [
@@ -21845,9 +22351,9 @@ function renderWorkflowDetail(data) {
21845
22351
  ].join("\n").concat("\n");
21846
22352
  }
21847
22353
  function renderScheduleDetail(data) {
21848
- const record2 = objectRecord(data);
21849
- const executorConfig = objectRecord(record2.executorConfig);
21850
- const currentPattern = objectRecord(record2.currentPattern);
22354
+ const record2 = objectRecord2(data);
22355
+ const executorConfig = objectRecord2(record2.executorConfig);
22356
+ const currentPattern = objectRecord2(record2.currentPattern);
21851
22357
  return [
21852
22358
  `id: ${valueToText(record2.id)}`,
21853
22359
  `executorType: ${valueToText(record2.executorType)}`,
@@ -21888,9 +22394,9 @@ function formatAnchor(anchor) {
21888
22394
  return parts.join(", ") || "-";
21889
22395
  }
21890
22396
  function renderDataBaseGlob(data) {
21891
- const record2 = objectRecord(data);
22397
+ const record2 = objectRecord2(data);
21892
22398
  const pattern = valueToText(record2.pattern);
21893
- const entries = Array.isArray(record2.entries) ? record2.entries.map(objectRecord) : [];
22399
+ const entries = Array.isArray(record2.entries) ? record2.entries.map(objectRecord2) : [];
21894
22400
  if (entries.length === 0) return `pattern: ${pattern}
21895
22401
  No entries.
21896
22402
  `;
@@ -21910,11 +22416,11 @@ No entries.
21910
22416
  ${renderRows(rows, ["type", "path", "fileType", "size", "status", "updatedAt"])}`;
21911
22417
  }
21912
22418
  function renderDataBaseRead(data) {
21913
- const record2 = objectRecord(data);
22419
+ const record2 = objectRecord2(data);
21914
22420
  const path = valueToText(record2.path);
21915
22421
  const fileType = valueToText(record2.fileType);
21916
22422
  const mimeType = valueToText(record2.mimeType);
21917
- const rangeObj = objectRecord(record2.range);
22423
+ const rangeObj = objectRecord2(record2.range);
21918
22424
  let rangeStr = "-";
21919
22425
  if (Array.isArray(rangeObj.line) && rangeObj.line.length === 2) {
21920
22426
  rangeStr = `lines ${rangeObj.line[0]}-${rangeObj.line[1]}`;
@@ -21946,14 +22452,14 @@ function renderDataBaseRead(data) {
21946
22452
  ${content}${footer}`;
21947
22453
  }
21948
22454
  function renderDataBaseMatches(data) {
21949
- const record2 = objectRecord(data);
22455
+ const record2 = objectRecord2(data);
21950
22456
  const query = valueToText(record2.query);
21951
- const matches = Array.isArray(record2.matches) ? record2.matches.map(objectRecord) : [];
22457
+ const matches = Array.isArray(record2.matches) ? record2.matches.map(objectRecord2) : [];
21952
22458
  if (matches.length === 0) return `query: ${query}
21953
22459
  No matches.
21954
22460
  `;
21955
22461
  const blocks = matches.map((match) => {
21956
- const anchor = formatAnchor(objectRecord(match.anchor));
22462
+ const anchor = formatAnchor(objectRecord2(match.anchor));
21957
22463
  const score = typeof match.score === "number" ? match.score.toFixed(3) : valueToText(match.score);
21958
22464
  const fields = Array.isArray(match.matchedFields) ? match.matchedFields.join(",") : "";
21959
22465
  const header = `${valueToText(match.path)}(${anchor}) [score=${score}${fields ? ` fields=${fields}` : ""}]`;
@@ -21983,7 +22489,7 @@ function renderSuccess(result) {
21983
22489
  case "socialSubmissionList":
21984
22490
  return renderRows(
21985
22491
  dataArray(result.data).map((item) => {
21986
- const record2 = objectRecord(item);
22492
+ const record2 = objectRecord2(item);
21987
22493
  return {
21988
22494
  id: record2.id,
21989
22495
  platform: record2.platform,
@@ -22080,6 +22586,7 @@ function renderJson(result) {
22080
22586
 
22081
22587
  // src/render/index.ts
22082
22588
  function renderResult(result, options) {
22589
+ if (options.compactAiJobWait) return renderAiJobWait(result, options);
22083
22590
  return options.json ? renderJson(result) : renderHuman(result);
22084
22591
  }
22085
22592
 
@@ -22232,6 +22739,24 @@ function parseNumber(value) {
22232
22739
  if (!Number.isFinite(parsed)) throw new InvalidArgumentError("Expected a number.");
22233
22740
  return parsed;
22234
22741
  }
22742
+ function parseDuration(value) {
22743
+ const match = /^(\d+(?:\.\d+)?)(ms|s|m|h)$/.exec(value.trim());
22744
+ if (!match) throw new InvalidArgumentError("Expected a duration such as 500ms, 3s, 10m, or 1h.");
22745
+ const amount = Number(match[1]);
22746
+ const unit = match[2];
22747
+ const multiplier = unit === "ms" ? 1 : unit === "s" ? 1e3 : unit === "m" ? 6e4 : 36e5;
22748
+ const durationMs = amount * multiplier;
22749
+ if (!Number.isSafeInteger(durationMs) || durationMs <= 0) {
22750
+ throw new InvalidArgumentError("Expected a positive duration.");
22751
+ }
22752
+ return durationMs;
22753
+ }
22754
+ function formatDuration(durationMs) {
22755
+ if (durationMs % 36e5 === 0) return `${durationMs / 36e5}h`;
22756
+ if (durationMs % 6e4 === 0) return `${durationMs / 6e4}m`;
22757
+ if (durationMs % 1e3 === 0) return `${durationMs / 1e3}s`;
22758
+ return `${durationMs}ms`;
22759
+ }
22235
22760
  function parseJson(value) {
22236
22761
  try {
22237
22762
  return JSON.parse(value);
@@ -22250,6 +22775,11 @@ function addOption(command, definition) {
22250
22775
  command.addOption(option.argParser(parseNumber).default(definition.defaultValue));
22251
22776
  return;
22252
22777
  }
22778
+ if (definition.type === "duration") {
22779
+ const defaultDescription = typeof definition.defaultValue === "number" ? formatDuration(definition.defaultValue) : void 0;
22780
+ command.addOption(option.argParser(parseDuration).default(definition.defaultValue, defaultDescription));
22781
+ return;
22782
+ }
22253
22783
  if (definition.type === "json") {
22254
22784
  command.addOption(option.argParser(parseJson).default(definition.defaultValue));
22255
22785
  return;
@@ -22306,10 +22836,7 @@ function getOrCreateSubcommand(parent, name) {
22306
22836
  return next;
22307
22837
  }
22308
22838
  function renderCommandReference(definition) {
22309
- const lines = [
22310
- `Command: spira ${definition.path.join(" ")}`,
22311
- `Description: ${definition.purpose}`
22312
- ];
22839
+ const lines = [`Command: spira ${definition.path.join(" ")}`, `Description: ${definition.purpose}`];
22313
22840
  if (definition.arguments?.length) {
22314
22841
  lines.push("Arguments:");
22315
22842
  for (const argument of definition.arguments) {
@@ -22338,8 +22865,8 @@ function renderCommandIndex() {
22338
22865
  `;
22339
22866
  }
22340
22867
  async function packageVersion() {
22341
- if ("0.0.5".length > 0) {
22342
- return "0.0.5";
22868
+ if ("0.0.6".length > 0) {
22869
+ return "0.0.6";
22343
22870
  }
22344
22871
  try {
22345
22872
  const packageUrl = new URL("../package.json", import_meta.url);
@@ -22512,7 +23039,38 @@ ${url2}
22512
23039
  resultType: "aiModelDetail",
22513
23040
  command: "spira ai models get",
22514
23041
  data: publicAiModelDoc(model),
22515
- nextActions: [{ label: "Create job", command: `spira ai jobs create --model-id ${model.id} --inputs-json '<json>'` }]
23042
+ nextActions: [
23043
+ { label: "Create job", command: `spira ai jobs create --model-id ${model.id} --inputs-json '<json>'` }
23044
+ ]
23045
+ });
23046
+ }
23047
+ if (commandKey(definition.path) === "ai jobs create") {
23048
+ const created = await executeHttpCommand({ command: definition, args, options });
23049
+ if (options.wait !== true || created.status === "error") return created;
23050
+ const jobId = created.ids.jobId;
23051
+ if (!jobId) {
23052
+ return cliResultSchema.parse({
23053
+ ...created,
23054
+ status: "error",
23055
+ error: {
23056
+ code: "CONTRACT_MISMATCH",
23057
+ message: "AI job creation response did not contain a jobId.",
23058
+ retryable: false,
23059
+ nextActions: []
23060
+ }
23061
+ });
23062
+ }
23063
+ const statusCommand = findCommandDefinition(["ai", "jobs", "status"]);
23064
+ if (!statusCommand) throw new Error("AI job status command is not registered.");
23065
+ output.error(`AI job ${jobId}: SUBMITTED
23066
+ `);
23067
+ return waitForAiJob({
23068
+ jobId,
23069
+ timeoutMs: options.waitTimeout,
23070
+ intervalMs: options.waitInterval,
23071
+ onStatus: (status) => output.error(`AI job ${jobId}: ${status}
23072
+ `),
23073
+ poll: () => executeHttpCommand({ command: statusCommand, args: { jobId }, options: {} })
22516
23074
  });
22517
23075
  }
22518
23076
  return executeHttpCommand({ command: definition, args, options });
@@ -22531,11 +23089,18 @@ function addLeafCommand(root, definition, output) {
22531
23089
  leaf.action(async (...values) => {
22532
23090
  const command = values[values.length - 1];
22533
23091
  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] ?? ""]));
23092
+ const args = Object.fromEntries(
23093
+ (definition.arguments ?? []).map((argument, index) => [argument.name, positional[index] ?? ""])
23094
+ );
22535
23095
  const options = command.optsWithGlobals();
22536
23096
  const validationResult2 = validateCommandInput(definition, args, options);
22537
23097
  const result = validationResult2 ?? await executeDefinition(definition, args, options, output);
22538
- output.write(renderResult(result, { json: Boolean(options.json) }));
23098
+ output.write(
23099
+ renderResult(result, {
23100
+ json: Boolean(options.json),
23101
+ compactAiJobWait: commandKey(definition.path) === "ai jobs create" && options.wait === true
23102
+ })
23103
+ );
22539
23104
  if (result.status === "error") process.exitCode = 1;
22540
23105
  });
22541
23106
  parent.addCommand(leaf);
package/package.json CHANGED
@@ -1,40 +1,17 @@
1
1
  {
2
2
  "name": "@spira-lab/cli",
3
- "version": "0.0.5",
3
+ "version": "0.0.6",
4
4
  "description": "Command line client for Spira.",
5
5
  "type": "module",
6
- "main": "./dist/index.cjs",
7
6
  "bin": {
8
7
  "spira": "dist/index.cjs"
9
8
  },
9
+ "main": "./dist/index.cjs",
10
10
  "engines": {
11
11
  "node": ">=20.0.0"
12
12
  },
13
13
  "publishConfig": {
14
14
  "access": "public",
15
15
  "registry": "https://registry.npmjs.org/"
16
- },
17
- "files": [
18
- "dist",
19
- "package.json",
20
- "README.md"
21
- ],
22
- "scripts": {
23
- "build": "pnpm --filter @spira-ai/contracts build && node scripts/build.mjs && node scripts/fix-bin-mode.mjs",
24
- "dev": "tsx src/index.ts",
25
- "publish:public": "node scripts/publish.mjs",
26
- "typecheck": "tsgo --noEmit -p tsconfig.json",
27
- "test": "vitest run"
28
- },
29
- "devDependencies": {
30
- "@typescript/native-preview": "catalog:",
31
- "@spira-ai/contracts": "workspace:*",
32
- "@types/node": "catalog:",
33
- "commander": "^14.0.2",
34
- "esbuild": "^0.27.1",
35
- "tsx": "^4.22.4",
36
- "typescript": "catalog:",
37
- "vitest": "catalog:",
38
- "zod": "catalog:"
39
16
  }
40
17
  }