@sixb/cli-core 0.1.8 → 0.1.9

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.
package/dist/index.js CHANGED
@@ -11,6 +11,7 @@ import { pipeline } from "stream/promises";
11
11
  var INSTANCE_CLI_VERSION = "1";
12
12
  var EXIT_USAGE = 2;
13
13
  var EXIT_API = 3;
14
+ var EXIT_AWAITING_APPROVAL = 4;
14
15
 
15
16
  class CliError extends Error {
16
17
  body;
@@ -29,12 +30,29 @@ function writeJson(value) {
29
30
  process.stdout.write(`${JSON.stringify(value)}
30
31
  `);
31
32
  }
33
+
34
+ class CliExit extends Error {
35
+ exitCode;
36
+ constructor(exitCode) {
37
+ super(`The Sixb CLI exited with code ${exitCode}.`);
38
+ this.exitCode = exitCode;
39
+ this.name = "CliExit";
40
+ }
41
+ }
42
+ function writeRequestResult(value) {
43
+ writeJson(value);
44
+ const status = typeof value === "object" && value !== null ? Reflect.get(value, "status") : null;
45
+ if (status === "awaiting_approval")
46
+ throw new CliExit(EXIT_AWAITING_APPROVAL);
47
+ }
32
48
  function writeText(value) {
33
49
  process.stdout.write(value.endsWith(`
34
50
  `) ? value : `${value}
35
51
  `);
36
52
  }
37
53
  function reportError(error) {
54
+ if (error instanceof CliExit)
55
+ return error.exitCode;
38
56
  const cliError = error instanceof CliError ? error : new CliError({
39
57
  code: "internal_error",
40
58
  message: error instanceof Error ? error.message : "The Sixb CLI failed unexpectedly."
@@ -70,9 +88,10 @@ class ApiClient {
70
88
  ...options.signal ? { signal: options.signal } : {}
71
89
  });
72
90
  }
73
- async upload(path, source, logicalPath) {
91
+ async upload(path, source, logicalPath, mediaType) {
74
92
  const form = new FormData;
75
- form.append("file", new Blob([new Uint8Array(await readFile(source))]), basename(source));
93
+ const bytes = new Uint8Array(await readFile(source));
94
+ form.append("file", new Blob([bytes], mediaType ? { type: mediaType } : undefined), basename(source));
76
95
  if (logicalPath)
77
96
  form.append("logicalPath", logicalPath);
78
97
  return this.json(this.url(path), { method: "POST", body: form });
@@ -193,8 +212,9 @@ function parseCommandArgs(args, kinds, command, positionalCount = 0) {
193
212
  const flag = equals < 0 ? argument : argument.slice(0, equals);
194
213
  if (!Object.hasOwn(kinds, flag))
195
214
  fail(`Unknown ${command} option '${flag}'.`);
196
- if (Object.hasOwn(options, flag))
215
+ if (Object.hasOwn(options, flag) && kinds[flag] !== "strings") {
197
216
  fail(`${flag} may only be provided once.`);
217
+ }
198
218
  if (kinds[flag] === "boolean") {
199
219
  if (equals >= 0)
200
220
  fail(`${flag} does not accept a value.`);
@@ -204,7 +224,8 @@ function parseCommandArgs(args, kinds, command, positionalCount = 0) {
204
224
  if (!value || equals < 0 && value.startsWith("--")) {
205
225
  fail(`${flag} requires a value.`);
206
226
  }
207
- options[flag] = value;
227
+ const previous = options[flag];
228
+ options[flag] = kinds[flag] === "strings" ? [...Array.isArray(previous) ? previous : [], value] : value;
208
229
  }
209
230
  }
210
231
  const [minimum, maximum] = typeof positionalCount === "number" ? [positionalCount, positionalCount] : positionalCount;
@@ -282,6 +303,7 @@ var CLI_LIMITS = {
282
303
  vectorSearchText: { maximum: 8000 },
283
304
  telemetryHistory: { default: 100, maximum: 1000 },
284
305
  linkPage: { default: 100, maximum: 1000 },
306
+ users: { default: 20, maximum: 200 },
285
307
  inspect: {
286
308
  depth: { default: 2, maximum: 3 },
287
309
  objects: { default: 20, maximum: 100 },
@@ -317,6 +339,8 @@ Objects:
317
339
  sixb objects inspect <type> <id> Inspect an object and its related graph in one command
318
340
  sixb objects list [options] Browse materialized objects
319
341
  sixb objects get <type> <id>... Exact lookup through opaque object references
342
+ sixb objects get-by <type> <property> <value>
343
+ The object holding a value of a unique property
320
344
  sixb objects search <text> Search visible objects by id prefix and full text
321
345
  sixb objects search <text> --type <type>
322
346
  Semantic search through the type's vector profile
@@ -340,6 +364,13 @@ Actions and workflows:
340
364
  Files:
341
365
  sixb files upload|download ...
342
366
 
367
+ Members:
368
+ sixb users search <text> Find members by name or email
369
+ sixb users list|get ...
370
+
371
+ A user parameter or property is {"type":"user","id":"<user-id>"}. Find the id with
372
+ \`sixb users search <name>\`.
373
+
343
374
  Run \`sixb <group> --help\` or \`sixb <group> <command> --help\` for exact arguments.
344
375
  For query IR, run \`sixb objects query --help\` and \`sixb objects query --example list\`.`;
345
376
  var LOCAL_MAIN_HELP = SANDBOX_MAIN_HELP.replace("Sixb agent CLI", "Sixb instance CLI").replace("The CLI uses the run-scoped SIXB_API_BASE_URL. Do not configure authentication or another origin.", "The CLI uses the selected local profile and its API credentials.").replace(` sixb doctor Check the sandbox and API gateway
@@ -347,7 +378,16 @@ var LOCAL_MAIN_HELP = SANDBOX_MAIN_HELP.replace("Sixb agent CLI", "Sixb instance
347
378
  `, "").replace(`
348
379
  When you do not know a type's properties or links, read its file under $SIXB_ONTOLOGY_DIR. Use
349
380
  \`sixb ontology get\` for a type that has no file there.
350
- `, "");
381
+ `, "").replace(`Files:
382
+ sixb files upload|download ...
383
+ `, `Files:
384
+ sixb files upload|download ...
385
+
386
+ Models:
387
+ sixb models list [--kind <kind>] Configured models of every kind, with prices
388
+ sixb models get <model> One model's options and rate card
389
+ sixb models text|image|speech|transcribe ... Call a model; see sixb models --help
390
+ `);
351
391
  function renderInstanceHelp(mode) {
352
392
  return mode === "sandbox" ? SANDBOX_MAIN_HELP : LOCAL_MAIN_HELP;
353
393
  }
@@ -364,10 +404,18 @@ var SEARCH_HELP = `Usage:
364
404
  profiles.
365
405
 
366
406
  ${SEARCH_DETAILS}`;
407
+ var GET_BY_HELP = `Usage:
408
+ sixb objects get-by <object-type> <property> <value> [--json]
409
+
410
+ Prints the object of exactly <object-type> holding <value> for a property marked unique in its
411
+ ontology doc, or null when no visible object holds it. Values match exactly, case included.
412
+ The value is a string; with --json it is parsed as JSON, for an integer (42) or a user
413
+ reference ({"type":"user","id":"usr_1"}).`;
367
414
  var OBJECTS_HELP = `Usage:
368
415
  sixb objects inspect <object-type> <primary-id> [options]
369
416
  sixb objects list [options]
370
417
  sixb objects get <object-type> <primary-id>...
418
+ sixb objects get-by <object-type> <property> <value> [--json]
371
419
  sixb objects search <text> [--type <object-type> [--vector-profile <name>]] [--limit <n>]
372
420
  sixb objects query --file <path|-> [--include-total|--no-total]
373
421
  sixb objects query --example <name>
@@ -434,7 +482,7 @@ Query nodes compose through input:
434
482
  limit {"kind":"limit","input":<query>,"limit":${CLI_LIMITS.list.default}}
435
483
  page {"kind":"page","input":<query>,"pageSize":${CLI_LIMITS.list.default},"pageToken":"from nextPageToken"}
436
484
  project {"kind":"project","input":<query>,"properties":["name","status"]}
437
- expand {"kind":"expand","input":<query>,"expansions":[{"linkId":"link","direction":"outgoing","limit":5}]}
485
+ expand {"kind":"expand","input":<query>,"expansions":[{"linkId":"link","direction":"outgoing","limit":5}],"users":["assignee"]}
438
486
  vector {"kind":"vector","input":<start|filter>,"vector":"text","profile":"name","k":10}
439
487
  rerank {"kind":"rerank","input":<vector>,"model":{"provider":"provider","modelId":"model"}}
440
488
 
@@ -445,11 +493,16 @@ Predicates use op, not kind:
445
493
  {"op":"in","propertyId":"status","values":["open","blocked"]}
446
494
  {"op":"exists","propertyId":"dueDate","value":true}
447
495
  {"op":"contains","propertyId":"tags","value":"urgent"}
496
+ {"op":"eq","propertyId":"assignee","value":{"type":"currentUser"}} the caller, on a user reference: neq, in, contains too
448
497
 
449
498
  Every field shown is required except includeSubtypes, fields, pageToken, properties, a sort
450
- direction, and an expansion's limit. Sort by relevance after text or vector with
499
+ direction, an expansion's limit, and users. Sort by relevance after text or vector with
451
500
  {"kind":"relevance"}. Wrap the outermost node in project to return only the properties you need.
452
501
 
502
+ An expand's users, or an expansion's, lists user-reference properties: each row then carries the
503
+ members' profiles under users (id, email, displayName, avatarUrl, status). Without access to the
504
+ member directory, users is empty.
505
+
453
506
  Traversal and expansion directions are outgoing or incoming. For incoming relationships, the link
454
507
  is declared on the child/source type; add sourceObjectTypeId when needed to disambiguate it.
455
508
 
@@ -484,7 +537,8 @@ History options:
484
537
  \`actions get\` includes inputSchema, the exact JSON shape accepted by the Action. The JSON file
485
538
  contains that parameter object; use - to read standard input. \`actions request\` runs the Action
486
539
  and prints its finished run, with its status and any error. It waits at most 25 seconds: a run that
487
- takes longer keeps running, and the error names the run to read with \`action-runs get\`.`,
540
+ takes longer keeps running, and the error names the run to read with \`action-runs get\`. A request
541
+ that waits for the user's approval prints \`"status": "awaiting_approval"\` and exits with code 4.`,
488
542
  "action-runs": `Usage:
489
543
  sixb action-runs list [options]
490
544
  sixb action-runs get <run-id>
@@ -505,9 +559,48 @@ List options:
505
559
  workflows: `Usage:
506
560
  sixb workflows list
507
561
  sixb workflows get <workflow-id>
508
- sixb workflows start <workflow-id> [--file <path|->]
562
+ sixb workflows start <workflow-id> [--file <path|->] [--run-id <id>]
563
+
564
+ The JSON file contains the workflow input object. Use - to read standard input. A start that waits
565
+ for the user's approval prints \`"status": "awaiting_approval"\` and exits with code 4.`,
566
+ users: `Usage:
567
+ sixb users list [options]
568
+ sixb users search <text> [options]
569
+ sixb users get <user-id>
570
+
571
+ Options:
572
+ --status active|suspended|all Defaults to active
573
+ --limit <1-${CLI_LIMITS.users.maximum}> Defaults to ${CLI_LIMITS.users.default}
574
+ --offset <n> Non-negative; defaults to 0
575
+
576
+ \`users search\` matches part of a display name or email, ignoring case. Each member is
577
+ {"id","email","displayName","avatarUrl","status"}; a user parameter takes
578
+ {"type":"user","id":"<id>"}. Reading members requires can.view(project.users).`,
579
+ models: `Usage:
580
+ sixb models list [--kind language|image|speech|transcription]
581
+ sixb models get <model> [--kind <kind>]
582
+ sixb models text "<prompt>"|--file <path|-> [--model <tier|model>] [--instructions <text>]
583
+ [--max-output-tokens <n>] [--reasoning <level>]
584
+ sixb models image "<prompt>" --output <path> [--model <model>] [--n <n>]
585
+ [--size <size>] [--aspect-ratio <ratio>] [--quality <q>] [--style <s>]
586
+ [--seed <n>] [--image <file>]...
587
+ sixb models speech "<text>" --output <path> [--model <model>] [--voice <voice>]
588
+ [--format mp3|wav|ogg|aac|flac] [--instructions <text>]
589
+ [--speed <n>] [--language <code>]
590
+ sixb models transcribe <file> [--model <model>]
591
+ sixb models calls files <call-id> [--output <path>]
592
+
593
+ <model> is a model id, or provider:model-id when two providers share one; without --model, the
594
+ kind's default answers. \`list\` shows each model's kind, default, description and price; \`get\`
595
+ adds the sizes, voices and other options a request can use. Prices are in USD.
596
+
597
+ \`text\` also takes a tier (light, standard or heavy) for --model, and reads a long prompt from
598
+ --file: a path, or - for standard input.
509
599
 
510
- The JSON file contains the workflow input object. Use - to read standard input.`,
600
+ --output is a file, numbered when a call returns several, or a directory ending in /. The
601
+ extension follows the file's type, and the result lists the paths written. --image uploads a
602
+ reference image; repeat it for several. \`calls files\` lists a past call's files, or downloads
603
+ them again.`,
511
604
  "workflow-runs": `Usage:
512
605
  sixb workflow-runs list [options]
513
606
  sixb workflow-runs get <run-id>
@@ -527,9 +620,11 @@ var QUERY_EXAMPLES = {
527
620
  not: `{"kind":"limit","input":{"kind":"filter","input":{"kind":"start","objectTypeId":"Customer"},"predicate":{"op":"not","item":{"op":"eq","propertyId":"status","value":"churned"}}},"limit":${CLI_LIMITS.list.default}}`,
528
621
  in: `{"kind":"limit","input":{"kind":"filter","input":{"kind":"start","objectTypeId":"Customer"},"predicate":{"op":"in","propertyId":"status","values":["active","trial"]}},"limit":${CLI_LIMITS.list.default}}`,
529
622
  exists: `{"kind":"limit","input":{"kind":"filter","input":{"kind":"start","objectTypeId":"Customer"},"predicate":{"op":"exists","propertyId":"accountManager","value":false}},"limit":${CLI_LIMITS.list.default}}`,
623
+ mine: `{"kind":"limit","input":{"kind":"filter","input":{"kind":"start","objectTypeId":"Task"},"predicate":{"op":"eq","propertyId":"assignee","value":{"type":"currentUser"}}},"limit":${CLI_LIMITS.list.default}}`,
530
624
  project: `{"kind":"project","input":{"kind":"limit","input":{"kind":"start","objectTypeId":"Customer"},"limit":${CLI_LIMITS.list.default}},"properties":["name","status"]}`,
531
625
  incoming: '{"kind":"traverse","input":{"kind":"refs","refs":[{"objectTypeId":"RepositoryIssue","primaryId":"github:issue:owner/repo#297"}]},"linkId":"issue","direction":"incoming","sourceObjectTypeId":"RepositoryComment"}',
532
626
  expand: `{"kind":"expand","input":{"kind":"refs","refs":[{"objectTypeId":"RepositoryIssue","primaryId":"github:issue:owner/repo#297"}]},"expansions":[{"linkId":"issue","direction":"incoming","sourceObjectTypeId":"RepositoryComment","limit":${CLI_LIMITS.list.default}}]}`,
627
+ users: `{"kind":"expand","input":{"kind":"limit","input":{"kind":"start","objectTypeId":"Task"},"limit":${CLI_LIMITS.list.default}},"expansions":[],"users":["assignee"]}`,
533
628
  sort: `{"kind":"limit","input":{"kind":"sort","input":{"kind":"start","objectTypeId":"Customer"},"fields":[{"kind":"property","propertyId":"name","direction":"asc"}]},"limit":${CLI_LIMITS.list.default}}`,
534
629
  page: `{"kind":"page","input":{"kind":"start","objectTypeId":"Customer"},"pageSize":${CLI_LIMITS.list.default}}`,
535
630
  vector: `{"kind":"vector","input":{"kind":"filter","input":{"kind":"start","objectTypeId":"Product"},"predicate":{"op":"eq","propertyId":"status","value":"active"}},"vector":"waterproof trail shoes","profile":"content","k":${CLI_LIMITS.search.default}}`
@@ -567,20 +662,22 @@ function singleFileOption(args, command) {
567
662
  return options["--file"];
568
663
  }
569
664
  async function readJson(source) {
570
- let text;
571
- try {
572
- text = source === "-" ? await readStdin() : await readFile2(source, "utf8");
573
- } catch (error) {
574
- if (isFileError(error, "ENOENT"))
575
- fail(`JSON file '${source}' does not exist.`);
576
- throw error;
577
- }
665
+ const text = await readText(source, "JSON file");
578
666
  try {
579
667
  return JSON.parse(text);
580
668
  } catch {
581
669
  fail(source === "-" ? "Standard input is not valid JSON." : `JSON file '${source}' is not valid JSON.`, "invalid_json");
582
670
  }
583
671
  }
672
+ async function readText(source, noun) {
673
+ try {
674
+ return source === "-" ? await readStdin() : await readFile2(source, "utf8");
675
+ } catch (error) {
676
+ if (isFileError(error, "ENOENT"))
677
+ fail(`${noun} '${source}' does not exist.`);
678
+ throw error;
679
+ }
680
+ }
584
681
  function asRecord2(value) {
585
682
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
586
683
  }
@@ -642,7 +739,7 @@ async function actions(api, args, requestWaitMs = ACTION_REQUEST_WAIT_MS) {
642
739
  const requestedRunId = runId ?? `act_${randomUUID2()}`;
643
740
  const waiting = AbortSignal.timeout(requestWaitMs);
644
741
  try {
645
- return writeJson(await api.post(`/api/actions/${encodeURIComponent(actionId)}`, {
742
+ return writeRequestResult(await api.post(`/api/actions/${encodeURIComponent(actionId)}`, {
646
743
  params,
647
744
  ...subjectType && subjectId ? { subject: { kind: "object", objectTypeId: subjectType, primaryId: subjectId } } : {},
648
745
  runId: requestedRunId
@@ -703,6 +800,340 @@ async function files(api, args) {
703
800
  fail(`Unknown files command '${sub}'.`);
704
801
  }
705
802
 
803
+ // src/commands/models.ts
804
+ import { access as access2, mkdir, stat } from "fs/promises";
805
+ import { dirname as dirname2, extname, join as join2, parse } from "path";
806
+ var MODEL_KINDS = ["language", "image", "speech", "transcription"];
807
+ async function models(api, args) {
808
+ const [sub, ...rest] = args;
809
+ if (!sub || isHelp(sub) || requestsHelp(rest))
810
+ return writeText(GROUP_HELP.models);
811
+ if (sub === "list") {
812
+ const { options } = parseCommandArgs(rest, { "--kind": "string" }, "models list", 0);
813
+ const kind = options["--kind"] === undefined ? undefined : modelKind(options["--kind"]);
814
+ const entries = catalogEntries(await api.get("/api/models"));
815
+ return writeJson(entries.filter((entry) => kind === undefined || entry.kind === kind).map(({ kind, entry }) => ({
816
+ kind,
817
+ model: entry.modelId,
818
+ provider: entry.provider,
819
+ ...entry.tier ? { tier: entry.tier } : {},
820
+ ...entry.isDefault ? { default: true } : {},
821
+ ...entry.description ? { description: entry.description } : {},
822
+ price: describePrice(entry.rateCard)
823
+ })));
824
+ }
825
+ if (sub === "get") {
826
+ const { positionals, options } = parseCommandArgs(rest, { "--kind": "string" }, "models get", 1);
827
+ const kind = options["--kind"] === undefined ? undefined : modelKind(options["--kind"]);
828
+ const entries = catalogEntries(await api.get("/api/models"));
829
+ const { kind: found, entry } = findModel(entries, positionals[0] ?? "", kind);
830
+ const { provider, modelId, isDefault, rateCard, ...details } = entry;
831
+ return writeJson({
832
+ kind: found,
833
+ model: modelId,
834
+ provider,
835
+ default: isDefault,
836
+ ...details,
837
+ price: describePrice(rateCard),
838
+ ...rateCard ? { rateCard } : {}
839
+ });
840
+ }
841
+ if (sub === "text")
842
+ return generateText(api, rest);
843
+ if (sub === "image")
844
+ return generateImages(api, rest);
845
+ if (sub === "speech")
846
+ return generateSpeech(api, rest);
847
+ if (sub === "transcribe")
848
+ return transcribe(api, rest);
849
+ if (sub === "calls")
850
+ return calls(api, rest);
851
+ fail(`Unknown models command '${sub}'.`);
852
+ }
853
+ var SPEECH_FORMATS = {
854
+ mp3: "audio/mpeg",
855
+ wav: "audio/wav",
856
+ ogg: "audio/ogg",
857
+ aac: "audio/aac",
858
+ flac: "audio/flac"
859
+ };
860
+ var SPEECH_FORMAT_NAMES = Object.keys(SPEECH_FORMATS);
861
+ var MEDIA_TYPES = {
862
+ ".png": "image/png",
863
+ ".jpg": "image/jpeg",
864
+ ".jpeg": "image/jpeg",
865
+ ".webp": "image/webp",
866
+ ".gif": "image/gif",
867
+ ".mp3": "audio/mpeg",
868
+ ".wav": "audio/wav",
869
+ ".ogg": "audio/ogg",
870
+ ".aac": "audio/aac",
871
+ ".flac": "audio/flac",
872
+ ".opus": "audio/opus",
873
+ ".m4a": "audio/mp4",
874
+ ".webm": "audio/webm"
875
+ };
876
+ var EXTENSIONS = Object.fromEntries(Object.entries(MEDIA_TYPES).filter(([extension]) => extension !== ".jpeg").map(([extension, mediaType]) => [mediaType, extension]));
877
+ async function generateText(api, args) {
878
+ const { positionals, options } = parseCommandArgs(args, {
879
+ "--model": "string",
880
+ "--instructions": "string",
881
+ "--max-output-tokens": "string",
882
+ "--reasoning": "string",
883
+ "--file": "string"
884
+ }, "models text", [0, 1]);
885
+ const prompt = await readPrompt(positionals[0], options["--file"]);
886
+ const maxOutputTokens = options["--max-output-tokens"];
887
+ const result = await api.post("/api/models/language/generations", {
888
+ ...await languageModelRef(api, options["--model"]),
889
+ prompt,
890
+ ...options["--instructions"] === undefined ? {} : { instructions: options["--instructions"] },
891
+ ...maxOutputTokens === undefined ? {} : { maxOutputTokens: integerInRange("--max-output-tokens", maxOutputTokens, 1, 1e6) },
892
+ ...options["--reasoning"] === undefined ? {} : { reasoning: options["--reasoning"] }
893
+ });
894
+ writeJson({
895
+ callId: result.callId,
896
+ model: result.model.modelId,
897
+ text: result.text,
898
+ finishReason: result.finishReason,
899
+ cost: describeCost(result.cost)
900
+ });
901
+ }
902
+ async function generateImages(api, args) {
903
+ const { positionals, options } = parseCommandArgs(args, {
904
+ "--output": "string",
905
+ "--model": "string",
906
+ "--n": "string",
907
+ "--size": "string",
908
+ "--aspect-ratio": "string",
909
+ "--quality": "string",
910
+ "--style": "string",
911
+ "--seed": "string",
912
+ "--image": "strings"
913
+ }, "models image", 1);
914
+ const output = requireOutput(options["--output"], "models image");
915
+ const seed = options["--seed"];
916
+ if (seed !== undefined && !/^-?[0-9]+$/.test(seed))
917
+ fail("--seed must be an integer.");
918
+ const model = await modelRef(api, options["--model"], "image");
919
+ const images = [];
920
+ for (const path of options["--image"] ?? [])
921
+ images.push(await uploadInput(api, path, "image"));
922
+ const result = await api.post("/api/models/image/generations", {
923
+ ...model,
924
+ prompt: positionals[0],
925
+ ...options["--n"] === undefined ? {} : { n: integerInRange("--n", options["--n"], 1, 100) },
926
+ ...optional("size", options["--size"]),
927
+ ...optional("aspectRatio", options["--aspect-ratio"]),
928
+ ...optional("quality", options["--quality"]),
929
+ ...optional("style", options["--style"]),
930
+ ...seed === undefined ? {} : { seed: Number(seed) },
931
+ ...images.length > 0 ? { images } : {}
932
+ });
933
+ writeJson(await savedResult(api, result, output));
934
+ }
935
+ async function generateSpeech(api, args) {
936
+ const { positionals, options } = parseCommandArgs(args, {
937
+ "--output": "string",
938
+ "--model": "string",
939
+ "--voice": "string",
940
+ "--format": "string",
941
+ "--instructions": "string",
942
+ "--speed": "string",
943
+ "--language": "string"
944
+ }, "models speech", 1);
945
+ const output = requireOutput(options["--output"], "models speech");
946
+ const format = options["--format"];
947
+ const speed = options["--speed"];
948
+ if (speed !== undefined && !(Number(speed) > 0))
949
+ fail("--speed must be a positive number.");
950
+ const result = await api.post("/api/models/audio/speech", {
951
+ ...await modelRef(api, options["--model"], "speech"),
952
+ text: positionals[0],
953
+ ...optional("voice", options["--voice"]),
954
+ ...format === undefined ? {} : { mediaType: SPEECH_FORMATS[enumValue("--format", format, SPEECH_FORMAT_NAMES)] },
955
+ ...optional("instructions", options["--instructions"]),
956
+ ...speed === undefined ? {} : { speed: Number(speed) },
957
+ ...optional("language", options["--language"])
958
+ });
959
+ writeJson(await savedResult(api, result, output));
960
+ }
961
+ async function transcribe(api, args) {
962
+ const { positionals, options } = parseCommandArgs(args, { "--model": "string" }, "models transcribe", 1);
963
+ const model = await modelRef(api, options["--model"], "transcription");
964
+ const audio = await uploadInput(api, positionals[0] ?? "", "audio");
965
+ const result = await api.post("/api/models/audio/transcriptions", {
966
+ ...model,
967
+ audio
968
+ });
969
+ writeJson({
970
+ callId: result.callId,
971
+ model: result.model.modelId,
972
+ text: result.text,
973
+ cost: describeCost(result.cost)
974
+ });
975
+ }
976
+ async function calls(api, args) {
977
+ const [sub, ...rest] = args;
978
+ if (sub !== "files")
979
+ fail(`Unknown models calls command '${sub ?? ""}'. Use 'models calls files'.`);
980
+ const { positionals, options } = parseCommandArgs(rest, { "--output": "string" }, "models calls files", 1);
981
+ const callId = positionals[0] ?? "";
982
+ const call = await api.get(`/api/ai/model-calls/${encodeURIComponent(callId)}/files`);
983
+ const output = options["--output"];
984
+ if (output === undefined)
985
+ return writeJson(call);
986
+ writeJson({ callId: call.callId, files: await saveFiles(api, call.callId, call.files, output) });
987
+ }
988
+ async function modelRef(api, reference, kind) {
989
+ if (reference === undefined)
990
+ return {};
991
+ const { entry } = findModel(catalogEntries(await api.get("/api/models")), reference, kind);
992
+ return { model: { provider: entry.provider, modelId: entry.modelId } };
993
+ }
994
+ var LANGUAGE_TIERS = ["light", "standard", "heavy"];
995
+ async function languageModelRef(api, reference) {
996
+ const tier = LANGUAGE_TIERS.find((candidate) => candidate === reference);
997
+ if (tier !== undefined)
998
+ return { model: tier };
999
+ return modelRef(api, reference, "language");
1000
+ }
1001
+ async function readPrompt(argument, source) {
1002
+ if (argument !== undefined && source !== undefined) {
1003
+ fail("models text takes its prompt as an argument or from --file, not both.");
1004
+ }
1005
+ const prompt = source === undefined ? argument : await readText(source, "Prompt file");
1006
+ if (prompt === undefined) {
1007
+ fail('models text requires a prompt: "<prompt>", or --file <path|-> to read it.');
1008
+ }
1009
+ if (!prompt.trim())
1010
+ fail("The prompt is empty.");
1011
+ return prompt;
1012
+ }
1013
+ function requireOutput(output, command) {
1014
+ if (!output)
1015
+ fail(`${command} requires --output <path>: a file, or a directory ending in /.`);
1016
+ return output;
1017
+ }
1018
+ function optional(field, value) {
1019
+ return value === undefined ? {} : { [field]: value };
1020
+ }
1021
+ async function uploadInput(api, path, kind) {
1022
+ try {
1023
+ await access2(path);
1024
+ } catch {
1025
+ fail(`Input file '${path}' does not exist.`);
1026
+ }
1027
+ const mediaType = MEDIA_TYPES[extname(path).toLowerCase()];
1028
+ if (!mediaType?.startsWith(`${kind}/`)) {
1029
+ const accepted = Object.entries(MEDIA_TYPES).filter(([, type]) => type.startsWith(`${kind}/`)).map(([extension]) => extension);
1030
+ fail(`'${path}' is not an ${kind} file. Use ${accepted.join(", ")}.`);
1031
+ }
1032
+ return api.upload("/api/files", path, undefined, mediaType);
1033
+ }
1034
+ async function savedResult(api, result, output) {
1035
+ return {
1036
+ callId: result.callId,
1037
+ model: result.model.modelId,
1038
+ files: await saveFiles(api, result.callId, result.files ?? [], output),
1039
+ ...result.warnings?.length ? { warnings: result.warnings } : {},
1040
+ cost: describeCost(result.cost)
1041
+ };
1042
+ }
1043
+ async function saveFiles(api, callId, files, output) {
1044
+ const directory = output.endsWith("/") || (await stat(output).catch(() => null))?.isDirectory();
1045
+ const paths = files.map((file, index) => {
1046
+ const number = files.length > 1 ? `-${index + 1}` : "";
1047
+ const extension = (file.mediaType && EXTENSIONS[file.mediaType]) ?? "";
1048
+ if (directory)
1049
+ return join2(output, `${callId}${number}${extension || ".bin"}`);
1050
+ const { dir, name, ext } = parse(output);
1051
+ return join2(dir, `${name}${number}${extension || ext}`);
1052
+ });
1053
+ for (const [index, path] of paths.entries()) {
1054
+ await mkdir(dirname2(path), { recursive: true });
1055
+ await api.download(`/api/ai/model-calls/${encodeURIComponent(callId)}/files/content`, path, {
1056
+ path: `/files/${index}`
1057
+ });
1058
+ }
1059
+ return paths;
1060
+ }
1061
+ function describeCost(cost) {
1062
+ if (!cost.money)
1063
+ return "unknown";
1064
+ const nanos = BigInt(cost.money.amountNanos);
1065
+ const whole = nanos / 1000000000n;
1066
+ const fraction = (nanos % 1000000000n).toString().padStart(9, "0").replace(/0+$/, "");
1067
+ return `$${whole}${fraction ? `.${fraction}` : ""}`;
1068
+ }
1069
+ function modelKind(value) {
1070
+ const kind = MODEL_KINDS.find((candidate) => candidate === value);
1071
+ if (!kind)
1072
+ fail(`Unknown model kind '${value}'. Use one of: ${MODEL_KINDS.join(", ")}.`);
1073
+ return kind;
1074
+ }
1075
+ function catalogEntries(catalog) {
1076
+ return [
1077
+ ...catalog.language.map((entry) => ({ kind: "language", entry })),
1078
+ ...catalog.image.map((entry) => ({ kind: "image", entry })),
1079
+ ...catalog.audio.speech.map((entry) => ({ kind: "speech", entry })),
1080
+ ...catalog.audio.transcription.map((entry) => ({ kind: "transcription", entry }))
1081
+ ];
1082
+ }
1083
+ function findModel(entries, reference, kind) {
1084
+ const ofKind = entries.filter((candidate) => kind === undefined || candidate.kind === kind);
1085
+ let matches = ofKind.filter((candidate) => candidate.entry.modelId === reference);
1086
+ const separator = reference.indexOf(":");
1087
+ if (matches.length === 0 && separator > 0) {
1088
+ const provider = reference.slice(0, separator);
1089
+ const modelId = reference.slice(separator + 1);
1090
+ matches = ofKind.filter((candidate) => candidate.entry.provider === provider && candidate.entry.modelId === modelId);
1091
+ }
1092
+ const [match] = matches;
1093
+ if (!match) {
1094
+ fail(`No ${kind === undefined ? "" : `${kind} `}model '${reference}' is configured. Run 'sixb models list'.`);
1095
+ }
1096
+ if (matches.length > 1) {
1097
+ const kinds = new Set(matches.map((candidate) => candidate.kind));
1098
+ fail(kinds.size > 1 ? `'${reference}' is configured as ${[...kinds].join(" and ")}. Add --kind <kind>.` : `'${reference}' is configured by ${matches.map((candidate) => candidate.entry.provider).join(" and ")}. Use provider:${reference}.`);
1099
+ }
1100
+ return match;
1101
+ }
1102
+ function describePrice(card) {
1103
+ if (!card)
1104
+ return "price unknown";
1105
+ const parts = [
1106
+ variantPrice(card.images, "per image", "at"),
1107
+ variantPrice(card.characters, "per 1M characters", "for"),
1108
+ variantPrice(card.audioInputSeconds, "per second of audio", "for"),
1109
+ card.input !== undefined && card.output !== undefined ? `${dollars(tokenPrice(card.input))} input \xB7 ${dollars(tokenPrice(card.output))} output per 1M tokens` : undefined,
1110
+ card.audioOutput === undefined ? undefined : `${dollars(tokenPrice(card.audioOutput))} per 1M audio tokens`
1111
+ ].filter((part) => part !== undefined);
1112
+ return parts.length === 0 ? "price unknown" : parts.join(" \xB7 ");
1113
+ }
1114
+ function variantPrice(price, unit, joiner) {
1115
+ if (price === undefined)
1116
+ return;
1117
+ if (typeof price === "string")
1118
+ return `${dollars(price)} ${unit}`;
1119
+ const only = price.default;
1120
+ if (only !== undefined && price.variants.every((variant) => variant.price === only)) {
1121
+ return `${dollars(only)} ${unit}`;
1122
+ }
1123
+ const variants = price.variants.map(({ price: amount, ...dimensions }) => {
1124
+ const label = Object.values(dimensions).join(" ");
1125
+ return `${dollars(amount)} ${joiner} ${label}`;
1126
+ });
1127
+ const otherwise = price.default === undefined ? [] : [`otherwise ${dollars(price.default)}`];
1128
+ return `${unit}: ${[...variants, ...otherwise].join(", ")}`;
1129
+ }
1130
+ function tokenPrice(price) {
1131
+ return typeof price === "string" ? price : price.default;
1132
+ }
1133
+ function dollars(amount) {
1134
+ return `$${amount}`;
1135
+ }
1136
+
706
1137
  // src/graph.ts
707
1138
  async function inspectGraph(api, objectTypeId, primaryId, options) {
708
1139
  if (options.depth === 0)
@@ -968,6 +1399,8 @@ async function objects(api, args) {
968
1399
  return objectsList(api, rest);
969
1400
  case "get":
970
1401
  return objectsGet(api, rest);
1402
+ case "get-by":
1403
+ return objectsGetBy(api, rest);
971
1404
  case "search":
972
1405
  return objectsSearch(api, rest);
973
1406
  case "query":
@@ -1058,6 +1491,31 @@ async function objectsGet(api, args) {
1058
1491
  includeTotal: false
1059
1492
  }));
1060
1493
  }
1494
+ async function objectsGetBy(api, args) {
1495
+ if (requestsHelp(args))
1496
+ return writeText(GET_BY_HELP);
1497
+ const { positionals, options } = parseCommandArgs(args, { "--json": "boolean" }, "objects get-by", 3);
1498
+ const [objectTypeId = "", propertyId = "", text = ""] = positionals;
1499
+ const value = options["--json"] ? parseJsonValue(text) : text;
1500
+ const path = `/api/objects/${encodeURIComponent(objectTypeId)}/by/${encodeURIComponent(propertyId)}`;
1501
+ try {
1502
+ writeJson(await api.post(path, { value }));
1503
+ } catch (error) {
1504
+ if (!isObjectNotFound(error))
1505
+ throw error;
1506
+ writeJson(null);
1507
+ }
1508
+ }
1509
+ function parseJsonValue(text) {
1510
+ try {
1511
+ return JSON.parse(text);
1512
+ } catch {
1513
+ fail(`--json needs the value as JSON, such as 42 or '{"type":"user","id":"usr_1"}'.`);
1514
+ }
1515
+ }
1516
+ function isObjectNotFound(error) {
1517
+ return error instanceof CliError && error.body.status === 404 && error.body.code === "object.not_found";
1518
+ }
1061
1519
  async function objectsSearch(api, args) {
1062
1520
  if (requestsHelp(args))
1063
1521
  return writeText(SEARCH_HELP);
@@ -1379,7 +1837,45 @@ function normalizeTelemetryQueryInput(input) {
1379
1837
  return body;
1380
1838
  }
1381
1839
 
1840
+ // src/commands/users.ts
1841
+ var USER_STATUSES = ["active", "suspended", "all"];
1842
+ var LIST_FLAGS = {
1843
+ "--status": "string",
1844
+ "--limit": "string",
1845
+ "--offset": "string"
1846
+ };
1847
+ async function users(api, args) {
1848
+ const [sub, ...rest] = args;
1849
+ if (!sub || isHelp(sub) || requestsHelp(rest))
1850
+ return writeText(GROUP_HELP.users);
1851
+ if (sub === "get") {
1852
+ const { positionals } = parseCommandArgs(rest, {}, "users get", 1);
1853
+ return writeJson(await api.get(`/api/users/${encodeURIComponent(positionals[0] ?? "")}`));
1854
+ }
1855
+ if (sub === "list") {
1856
+ const { options } = parseCommandArgs(rest, LIST_FLAGS, "users list");
1857
+ return writeJson(await api.get("/api/users", listQuery(options)));
1858
+ }
1859
+ if (sub === "search") {
1860
+ const { positionals, options } = parseCommandArgs(rest, LIST_FLAGS, "users search", 1);
1861
+ const search = positionals[0]?.trim();
1862
+ if (!search)
1863
+ fail("users search requires the text to find in a name or email.");
1864
+ return writeJson(await api.get("/api/users", { ...listQuery(options), search }));
1865
+ }
1866
+ fail(`Unknown users command '${sub}'.`);
1867
+ }
1868
+ function listQuery(options) {
1869
+ const limit = integerInRange("--limit", options["--limit"] ?? String(CLI_LIMITS.users.default), 1, CLI_LIMITS.users.maximum);
1870
+ return {
1871
+ status: enumValue("--status", options["--status"] ?? "active", USER_STATUSES),
1872
+ limit: String(limit),
1873
+ ...options["--offset"] === undefined ? {} : { offset: String(nonNegativeInteger("--offset", options["--offset"])) }
1874
+ };
1875
+ }
1876
+
1382
1877
  // src/commands/workflows.ts
1878
+ import { randomUUID as randomUUID3 } from "crypto";
1383
1879
  async function workflows(api, args) {
1384
1880
  const [sub, ...rest] = args;
1385
1881
  if (!sub || isHelp(sub) || requestsHelp(rest))
@@ -1398,12 +1894,16 @@ async function workflows(api, args) {
1398
1894
  const {
1399
1895
  positionals: [workflowId],
1400
1896
  options
1401
- } = parseCommandArgs(rest, { "--file": "string" }, "workflows start", 1);
1897
+ } = parseCommandArgs(rest, { "--file": "string", "--run-id": "string" }, "workflows start", 1);
1402
1898
  const input = options["--file"] ? await readJson(options["--file"]) : {};
1403
1899
  if (Array.isArray(input) || typeof input !== "object" || input === null) {
1404
1900
  fail("Workflow input must be a JSON object.");
1405
1901
  }
1406
- return writeJson(await api.post(`/api/workflows/${encodeURIComponent(workflowId ?? "")}/runs`, { input }));
1902
+ const runId = options["--run-id"] ?? `run_${randomUUID3()}`;
1903
+ return writeRequestResult(await api.post(`/api/workflows/${encodeURIComponent(workflowId ?? "")}/runs`, {
1904
+ input,
1905
+ runId
1906
+ }));
1407
1907
  }
1408
1908
  fail(`Unknown workflows command '${sub}'.`);
1409
1909
  }
@@ -1418,7 +1918,9 @@ var INSTANCE_COMMANDS = [
1418
1918
  "action-runs",
1419
1919
  "files",
1420
1920
  "workflows",
1421
- "workflow-runs"
1921
+ "workflow-runs",
1922
+ "users",
1923
+ "models"
1422
1924
  ];
1423
1925
  function isInstanceCommand(command) {
1424
1926
  return INSTANCE_COMMANDS.includes(command);
@@ -1443,6 +1945,10 @@ async function dispatch(api, command, args) {
1443
1945
  return workflows(api, args);
1444
1946
  case "workflow-runs":
1445
1947
  return runs(api, "workflow", args);
1948
+ case "users":
1949
+ return users(api, args);
1950
+ case "models":
1951
+ return models(api, args);
1446
1952
  default:
1447
1953
  fail(`Unknown command '${command}'. Run 'sixb --help'.`);
1448
1954
  }
@@ -1482,4 +1988,4 @@ export {
1482
1988
  writeText
1483
1989
  };
1484
1990
 
1485
- //# debugId=D8A3DF8338E3251F64756E2164756E21
1991
+ //# debugId=7F5CD0F40A1B96CA64756E2164756E21