@sixb/cli-core 0.1.7 → 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/api-client.d.ts +6 -2
- package/dist/api-client.d.ts.map +1 -1
- package/dist/arguments.d.ts +4 -2
- package/dist/arguments.d.ts.map +1 -1
- package/dist/commands/actions.d.ts +3 -1
- package/dist/commands/actions.d.ts.map +1 -1
- package/dist/commands/index.d.ts +1 -1
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/metadata.d.ts +6 -3
- package/dist/commands/metadata.d.ts.map +1 -1
- package/dist/commands/models.d.ts +79 -0
- package/dist/commands/models.d.ts.map +1 -0
- package/dist/commands/objects.d.ts.map +1 -1
- package/dist/commands/shared.d.ts +2 -0
- package/dist/commands/shared.d.ts.map +1 -1
- package/dist/commands/users.d.ts +3 -0
- package/dist/commands/users.d.ts.map +1 -0
- package/dist/commands/workflows.d.ts.map +1 -1
- package/dist/index.js +561 -63
- package/dist/index.js.map +16 -14
- package/dist/output.d.ts +12 -0
- package/dist/output.d.ts.map +1 -1
- package/dist/policies.d.ts +5 -0
- package/dist/policies.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/api-client.ts +22 -4
- package/src/arguments.ts +18 -6
- package/src/commands/actions.ts +43 -41
- package/src/commands/index.ts +8 -0
- package/src/commands/metadata.ts +80 -8
- package/src/commands/models.ts +508 -0
- package/src/commands/objects.ts +47 -2
- package/src/commands/runs.ts +1 -1
- package/src/commands/shared.ts +11 -7
- package/src/commands/users.ts +67 -0
- package/src/commands/workflows.ts +10 -4
- package/src/output.ts +21 -0
- package/src/policies.ts +2 -0
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."
|
|
@@ -62,16 +80,18 @@ class ApiClient {
|
|
|
62
80
|
const url = this.url(path, query);
|
|
63
81
|
return this.json(url, { method: "GET" });
|
|
64
82
|
}
|
|
65
|
-
async post(path, body) {
|
|
83
|
+
async post(path, body, options = {}) {
|
|
66
84
|
return this.json(this.url(path), {
|
|
67
85
|
method: "POST",
|
|
68
86
|
headers: { "content-type": "application/json" },
|
|
69
|
-
body: JSON.stringify(body)
|
|
87
|
+
body: JSON.stringify(body),
|
|
88
|
+
...options.signal ? { signal: options.signal } : {}
|
|
70
89
|
});
|
|
71
90
|
}
|
|
72
|
-
async upload(path, source, logicalPath) {
|
|
91
|
+
async upload(path, source, logicalPath, mediaType) {
|
|
73
92
|
const form = new FormData;
|
|
74
|
-
|
|
93
|
+
const bytes = new Uint8Array(await readFile(source));
|
|
94
|
+
form.append("file", new Blob([bytes], mediaType ? { type: mediaType } : undefined), basename(source));
|
|
75
95
|
if (logicalPath)
|
|
76
96
|
form.append("logicalPath", logicalPath);
|
|
77
97
|
return this.json(this.url(path), { method: "POST", body: form });
|
|
@@ -117,7 +137,9 @@ class ApiClient {
|
|
|
117
137
|
if (this.authorization)
|
|
118
138
|
headers.set("authorization", this.authorization);
|
|
119
139
|
response = await fetch(url, { ...init, headers });
|
|
120
|
-
} catch {
|
|
140
|
+
} catch (error) {
|
|
141
|
+
if (init.signal?.aborted)
|
|
142
|
+
throw error;
|
|
121
143
|
throw new CliError({
|
|
122
144
|
code: "api_unreachable",
|
|
123
145
|
message: this.unavailableMessage,
|
|
@@ -190,8 +212,9 @@ function parseCommandArgs(args, kinds, command, positionalCount = 0) {
|
|
|
190
212
|
const flag = equals < 0 ? argument : argument.slice(0, equals);
|
|
191
213
|
if (!Object.hasOwn(kinds, flag))
|
|
192
214
|
fail(`Unknown ${command} option '${flag}'.`);
|
|
193
|
-
if (Object.hasOwn(options, flag))
|
|
215
|
+
if (Object.hasOwn(options, flag) && kinds[flag] !== "strings") {
|
|
194
216
|
fail(`${flag} may only be provided once.`);
|
|
217
|
+
}
|
|
195
218
|
if (kinds[flag] === "boolean") {
|
|
196
219
|
if (equals >= 0)
|
|
197
220
|
fail(`${flag} does not accept a value.`);
|
|
@@ -201,7 +224,8 @@ function parseCommandArgs(args, kinds, command, positionalCount = 0) {
|
|
|
201
224
|
if (!value || equals < 0 && value.startsWith("--")) {
|
|
202
225
|
fail(`${flag} requires a value.`);
|
|
203
226
|
}
|
|
204
|
-
options[flag]
|
|
227
|
+
const previous = options[flag];
|
|
228
|
+
options[flag] = kinds[flag] === "strings" ? [...Array.isArray(previous) ? previous : [], value] : value;
|
|
205
229
|
}
|
|
206
230
|
}
|
|
207
231
|
const [minimum, maximum] = typeof positionalCount === "number" ? [positionalCount, positionalCount] : positionalCount;
|
|
@@ -265,8 +289,13 @@ function enumValue(flag, value, allowed) {
|
|
|
265
289
|
function formatAlternatives(values) {
|
|
266
290
|
if (values.length < 2)
|
|
267
291
|
return values[0] ?? "a supported value";
|
|
292
|
+
if (values.length === 2)
|
|
293
|
+
return `${values[0]} or ${values[1]}`;
|
|
268
294
|
return `${values.slice(0, -1).join(", ")}, or ${values.at(-1)}`;
|
|
269
295
|
}
|
|
296
|
+
// src/commands/actions.ts
|
|
297
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
298
|
+
|
|
270
299
|
// src/policies.ts
|
|
271
300
|
var CLI_LIMITS = {
|
|
272
301
|
list: { default: 20, maximum: 1000 },
|
|
@@ -274,6 +303,7 @@ var CLI_LIMITS = {
|
|
|
274
303
|
vectorSearchText: { maximum: 8000 },
|
|
275
304
|
telemetryHistory: { default: 100, maximum: 1000 },
|
|
276
305
|
linkPage: { default: 100, maximum: 1000 },
|
|
306
|
+
users: { default: 20, maximum: 200 },
|
|
277
307
|
inspect: {
|
|
278
308
|
depth: { default: 2, maximum: 3 },
|
|
279
309
|
objects: { default: 20, maximum: 100 },
|
|
@@ -309,6 +339,8 @@ Objects:
|
|
|
309
339
|
sixb objects inspect <type> <id> Inspect an object and its related graph in one command
|
|
310
340
|
sixb objects list [options] Browse materialized objects
|
|
311
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
|
|
312
344
|
sixb objects search <text> Search visible objects by id prefix and full text
|
|
313
345
|
sixb objects search <text> --type <type>
|
|
314
346
|
Semantic search through the type's vector profile
|
|
@@ -332,6 +364,13 @@ Actions and workflows:
|
|
|
332
364
|
Files:
|
|
333
365
|
sixb files upload|download ...
|
|
334
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
|
+
|
|
335
374
|
Run \`sixb <group> --help\` or \`sixb <group> <command> --help\` for exact arguments.
|
|
336
375
|
For query IR, run \`sixb objects query --help\` and \`sixb objects query --example list\`.`;
|
|
337
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
|
|
@@ -339,7 +378,16 @@ var LOCAL_MAIN_HELP = SANDBOX_MAIN_HELP.replace("Sixb agent CLI", "Sixb instance
|
|
|
339
378
|
`, "").replace(`
|
|
340
379
|
When you do not know a type's properties or links, read its file under $SIXB_ONTOLOGY_DIR. Use
|
|
341
380
|
\`sixb ontology get\` for a type that has no file there.
|
|
342
|
-
`, "")
|
|
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
|
+
`);
|
|
343
391
|
function renderInstanceHelp(mode) {
|
|
344
392
|
return mode === "sandbox" ? SANDBOX_MAIN_HELP : LOCAL_MAIN_HELP;
|
|
345
393
|
}
|
|
@@ -356,10 +404,18 @@ var SEARCH_HELP = `Usage:
|
|
|
356
404
|
profiles.
|
|
357
405
|
|
|
358
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"}).`;
|
|
359
414
|
var OBJECTS_HELP = `Usage:
|
|
360
415
|
sixb objects inspect <object-type> <primary-id> [options]
|
|
361
416
|
sixb objects list [options]
|
|
362
417
|
sixb objects get <object-type> <primary-id>...
|
|
418
|
+
sixb objects get-by <object-type> <property> <value> [--json]
|
|
363
419
|
sixb objects search <text> [--type <object-type> [--vector-profile <name>]] [--limit <n>]
|
|
364
420
|
sixb objects query --file <path|-> [--include-total|--no-total]
|
|
365
421
|
sixb objects query --example <name>
|
|
@@ -426,7 +482,7 @@ Query nodes compose through input:
|
|
|
426
482
|
limit {"kind":"limit","input":<query>,"limit":${CLI_LIMITS.list.default}}
|
|
427
483
|
page {"kind":"page","input":<query>,"pageSize":${CLI_LIMITS.list.default},"pageToken":"from nextPageToken"}
|
|
428
484
|
project {"kind":"project","input":<query>,"properties":["name","status"]}
|
|
429
|
-
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"]}
|
|
430
486
|
vector {"kind":"vector","input":<start|filter>,"vector":"text","profile":"name","k":10}
|
|
431
487
|
rerank {"kind":"rerank","input":<vector>,"model":{"provider":"provider","modelId":"model"}}
|
|
432
488
|
|
|
@@ -437,11 +493,16 @@ Predicates use op, not kind:
|
|
|
437
493
|
{"op":"in","propertyId":"status","values":["open","blocked"]}
|
|
438
494
|
{"op":"exists","propertyId":"dueDate","value":true}
|
|
439
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
|
|
440
497
|
|
|
441
498
|
Every field shown is required except includeSubtypes, fields, pageToken, properties, a sort
|
|
442
|
-
direction,
|
|
499
|
+
direction, an expansion's limit, and users. Sort by relevance after text or vector with
|
|
443
500
|
{"kind":"relevance"}. Wrap the outermost node in project to return only the properties you need.
|
|
444
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
|
+
|
|
445
506
|
Traversal and expansion directions are outgoing or incoming. For incoming relationships, the link
|
|
446
507
|
is declared on the child/source type; add sourceObjectTypeId when needed to disambiguate it.
|
|
447
508
|
|
|
@@ -471,11 +532,13 @@ History options:
|
|
|
471
532
|
actions: `Usage:
|
|
472
533
|
sixb actions list [--type <object-type>]
|
|
473
534
|
sixb actions get <action-id>
|
|
474
|
-
sixb actions request <action-id> [--subject-type <type> --subject-id <id>] [--file <path|->] [--run-id <id>]
|
|
535
|
+
sixb actions request <action-id> [--subject-type <type> --subject-id <id>] [--file <path|->] [--run-id <id>]
|
|
475
536
|
|
|
476
537
|
\`actions get\` includes inputSchema, the exact JSON shape accepted by the Action. The JSON file
|
|
477
|
-
contains that parameter object; use - to read standard input.
|
|
478
|
-
run and waits at most 25 seconds
|
|
538
|
+
contains that parameter object; use - to read standard input. \`actions request\` runs the Action
|
|
539
|
+
and prints its finished run, with its status and any error. It waits at most 25 seconds: a run that
|
|
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.`,
|
|
479
542
|
"action-runs": `Usage:
|
|
480
543
|
sixb action-runs list [options]
|
|
481
544
|
sixb action-runs get <run-id>
|
|
@@ -484,7 +547,7 @@ List options:
|
|
|
484
547
|
--action <action-id>
|
|
485
548
|
--type <object-type>
|
|
486
549
|
--id <primary-id>
|
|
487
|
-
--status
|
|
550
|
+
--status succeeded|failed
|
|
488
551
|
--started-after|--started-before <RFC3339>
|
|
489
552
|
--limit <1-${CLI_LIMITS.list.maximum}> Defaults to ${CLI_LIMITS.list.default}
|
|
490
553
|
--offset <n> Non-negative; defaults to 0
|
|
@@ -496,9 +559,48 @@ List options:
|
|
|
496
559
|
workflows: `Usage:
|
|
497
560
|
sixb workflows list
|
|
498
561
|
sixb workflows get <workflow-id>
|
|
499
|
-
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
|
|
500
575
|
|
|
501
|
-
|
|
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.
|
|
599
|
+
|
|
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.`,
|
|
502
604
|
"workflow-runs": `Usage:
|
|
503
605
|
sixb workflow-runs list [options]
|
|
504
606
|
sixb workflow-runs get <run-id>
|
|
@@ -518,9 +620,11 @@ var QUERY_EXAMPLES = {
|
|
|
518
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}}`,
|
|
519
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}}`,
|
|
520
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}}`,
|
|
521
624
|
project: `{"kind":"project","input":{"kind":"limit","input":{"kind":"start","objectTypeId":"Customer"},"limit":${CLI_LIMITS.list.default}},"properties":["name","status"]}`,
|
|
522
625
|
incoming: '{"kind":"traverse","input":{"kind":"refs","refs":[{"objectTypeId":"RepositoryIssue","primaryId":"github:issue:owner/repo#297"}]},"linkId":"issue","direction":"incoming","sourceObjectTypeId":"RepositoryComment"}',
|
|
523
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"]}`,
|
|
524
628
|
sort: `{"kind":"limit","input":{"kind":"sort","input":{"kind":"start","objectTypeId":"Customer"},"fields":[{"kind":"property","propertyId":"name","direction":"asc"}]},"limit":${CLI_LIMITS.list.default}}`,
|
|
525
629
|
page: `{"kind":"page","input":{"kind":"start","objectTypeId":"Customer"},"pageSize":${CLI_LIMITS.list.default}}`,
|
|
526
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}}`
|
|
@@ -558,20 +662,22 @@ function singleFileOption(args, command) {
|
|
|
558
662
|
return options["--file"];
|
|
559
663
|
}
|
|
560
664
|
async function readJson(source) {
|
|
561
|
-
|
|
562
|
-
try {
|
|
563
|
-
text = source === "-" ? await readStdin() : await readFile2(source, "utf8");
|
|
564
|
-
} catch (error) {
|
|
565
|
-
if (isFileError(error, "ENOENT"))
|
|
566
|
-
fail(`JSON file '${source}' does not exist.`);
|
|
567
|
-
throw error;
|
|
568
|
-
}
|
|
665
|
+
const text = await readText(source, "JSON file");
|
|
569
666
|
try {
|
|
570
667
|
return JSON.parse(text);
|
|
571
668
|
} catch {
|
|
572
669
|
fail(source === "-" ? "Standard input is not valid JSON." : `JSON file '${source}' is not valid JSON.`, "invalid_json");
|
|
573
670
|
}
|
|
574
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
|
+
}
|
|
575
681
|
function asRecord2(value) {
|
|
576
682
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
|
|
577
683
|
}
|
|
@@ -593,7 +699,8 @@ async function readStdin() {
|
|
|
593
699
|
}
|
|
594
700
|
|
|
595
701
|
// src/commands/actions.ts
|
|
596
|
-
|
|
702
|
+
var ACTION_REQUEST_WAIT_MS = 25000;
|
|
703
|
+
async function actions(api, args, requestWaitMs = ACTION_REQUEST_WAIT_MS) {
|
|
597
704
|
const [sub, ...rest] = args;
|
|
598
705
|
if (!sub || isHelp(sub) || requestsHelp(rest))
|
|
599
706
|
return writeText(GROUP_HELP.actions);
|
|
@@ -615,15 +722,13 @@ async function actions(api, args) {
|
|
|
615
722
|
"--subject-type": "string",
|
|
616
723
|
"--subject-id": "string",
|
|
617
724
|
"--file": "string",
|
|
618
|
-
"--run-id": "string"
|
|
619
|
-
"--wait": "boolean"
|
|
725
|
+
"--run-id": "string"
|
|
620
726
|
}, "actions request", 1);
|
|
621
727
|
const actionId = positionals[0] ?? "";
|
|
622
728
|
const subjectType = options["--subject-type"];
|
|
623
729
|
const subjectId = options["--subject-id"];
|
|
624
730
|
const paramsSource = options["--file"];
|
|
625
731
|
const runId = options["--run-id"];
|
|
626
|
-
const wait = options["--wait"];
|
|
627
732
|
if (Boolean(subjectType) !== Boolean(subjectId)) {
|
|
628
733
|
fail("--subject-type and --subject-id must be provided together.");
|
|
629
734
|
}
|
|
@@ -631,42 +736,26 @@ async function actions(api, args) {
|
|
|
631
736
|
if (Array.isArray(params) || typeof params !== "object" || params === null) {
|
|
632
737
|
fail("Action params must be a JSON object.");
|
|
633
738
|
}
|
|
634
|
-
const
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
739
|
+
const requestedRunId = runId ?? `act_${randomUUID2()}`;
|
|
740
|
+
const waiting = AbortSignal.timeout(requestWaitMs);
|
|
741
|
+
try {
|
|
742
|
+
return writeRequestResult(await api.post(`/api/actions/${encodeURIComponent(actionId)}`, {
|
|
743
|
+
params,
|
|
744
|
+
...subjectType && subjectId ? { subject: { kind: "object", objectTypeId: subjectType, primaryId: subjectId } } : {},
|
|
745
|
+
runId: requestedRunId
|
|
746
|
+
}, { signal: waiting }));
|
|
747
|
+
} catch (error) {
|
|
748
|
+
if (!waiting.aborted)
|
|
749
|
+
throw error;
|
|
643
750
|
throw new CliError({
|
|
644
|
-
code: "
|
|
645
|
-
message:
|
|
751
|
+
code: "action_wait_timeout",
|
|
752
|
+
message: `Action run '${requestedRunId}' did not finish within ${requestWaitMs / 1000} ` + "seconds. It was not cancelled.",
|
|
753
|
+
hint: `Read it with 'sixb action-runs get ${requestedRunId}': its record exists once it ` + `has ended. A repeated request must pass '--run-id ${requestedRunId}', or the ` + "Action runs twice."
|
|
646
754
|
}, EXIT_API);
|
|
647
755
|
}
|
|
648
|
-
return writeJson(await waitForActionRun(api, requestedRunId));
|
|
649
756
|
}
|
|
650
757
|
fail(`Unknown actions command '${sub}'.`);
|
|
651
758
|
}
|
|
652
|
-
var ACTION_WAIT_TIMEOUT_MS = 25000;
|
|
653
|
-
var ACTION_WAIT_POLL_MS = 250;
|
|
654
|
-
var TERMINAL_ACTION_STATUSES = new Set(["succeeded", "failed", "cancelled"]);
|
|
655
|
-
async function waitForActionRun(api, runId) {
|
|
656
|
-
const startedAt = Date.now();
|
|
657
|
-
while (Date.now() - startedAt < ACTION_WAIT_TIMEOUT_MS) {
|
|
658
|
-
const run = await api.get(`/api/action-runs/${encodeURIComponent(runId)}`);
|
|
659
|
-
const status = asRecord2(run).status;
|
|
660
|
-
if (typeof status === "string" && TERMINAL_ACTION_STATUSES.has(status))
|
|
661
|
-
return run;
|
|
662
|
-
await new Promise((resolve) => setTimeout(resolve, ACTION_WAIT_POLL_MS));
|
|
663
|
-
}
|
|
664
|
-
throw new CliError({
|
|
665
|
-
code: "action_wait_timeout",
|
|
666
|
-
message: `Action run '${runId}' did not finish within ${ACTION_WAIT_TIMEOUT_MS / 1000} seconds.`,
|
|
667
|
-
hint: `Inspect it with 'sixb action-runs get ${runId}'.`
|
|
668
|
-
}, EXIT_API);
|
|
669
|
-
}
|
|
670
759
|
|
|
671
760
|
// src/commands/files.ts
|
|
672
761
|
import { access } from "fs/promises";
|
|
@@ -711,6 +800,340 @@ async function files(api, args) {
|
|
|
711
800
|
fail(`Unknown files command '${sub}'.`);
|
|
712
801
|
}
|
|
713
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
|
+
|
|
714
1137
|
// src/graph.ts
|
|
715
1138
|
async function inspectGraph(api, objectTypeId, primaryId, options) {
|
|
716
1139
|
if (options.depth === 0)
|
|
@@ -976,6 +1399,8 @@ async function objects(api, args) {
|
|
|
976
1399
|
return objectsList(api, rest);
|
|
977
1400
|
case "get":
|
|
978
1401
|
return objectsGet(api, rest);
|
|
1402
|
+
case "get-by":
|
|
1403
|
+
return objectsGetBy(api, rest);
|
|
979
1404
|
case "search":
|
|
980
1405
|
return objectsSearch(api, rest);
|
|
981
1406
|
case "query":
|
|
@@ -1066,6 +1491,31 @@ async function objectsGet(api, args) {
|
|
|
1066
1491
|
includeTotal: false
|
|
1067
1492
|
}));
|
|
1068
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
|
+
}
|
|
1069
1519
|
async function objectsSearch(api, args) {
|
|
1070
1520
|
if (requestsHelp(args))
|
|
1071
1521
|
return writeText(SEARCH_HELP);
|
|
@@ -1263,7 +1713,7 @@ async function project(api, args) {
|
|
|
1263
1713
|
}
|
|
1264
1714
|
|
|
1265
1715
|
// src/commands/runs.ts
|
|
1266
|
-
var ACTION_RUN_STATUSES = ["
|
|
1716
|
+
var ACTION_RUN_STATUSES = ["succeeded", "failed"];
|
|
1267
1717
|
var WORKFLOW_RUN_STATUSES = [
|
|
1268
1718
|
"queued",
|
|
1269
1719
|
"running",
|
|
@@ -1387,7 +1837,45 @@ function normalizeTelemetryQueryInput(input) {
|
|
|
1387
1837
|
return body;
|
|
1388
1838
|
}
|
|
1389
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
|
+
|
|
1390
1877
|
// src/commands/workflows.ts
|
|
1878
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
1391
1879
|
async function workflows(api, args) {
|
|
1392
1880
|
const [sub, ...rest] = args;
|
|
1393
1881
|
if (!sub || isHelp(sub) || requestsHelp(rest))
|
|
@@ -1406,12 +1894,16 @@ async function workflows(api, args) {
|
|
|
1406
1894
|
const {
|
|
1407
1895
|
positionals: [workflowId],
|
|
1408
1896
|
options
|
|
1409
|
-
} = parseCommandArgs(rest, { "--file": "string" }, "workflows start", 1);
|
|
1897
|
+
} = parseCommandArgs(rest, { "--file": "string", "--run-id": "string" }, "workflows start", 1);
|
|
1410
1898
|
const input = options["--file"] ? await readJson(options["--file"]) : {};
|
|
1411
1899
|
if (Array.isArray(input) || typeof input !== "object" || input === null) {
|
|
1412
1900
|
fail("Workflow input must be a JSON object.");
|
|
1413
1901
|
}
|
|
1414
|
-
|
|
1902
|
+
const runId = options["--run-id"] ?? `run_${randomUUID3()}`;
|
|
1903
|
+
return writeRequestResult(await api.post(`/api/workflows/${encodeURIComponent(workflowId ?? "")}/runs`, {
|
|
1904
|
+
input,
|
|
1905
|
+
runId
|
|
1906
|
+
}));
|
|
1415
1907
|
}
|
|
1416
1908
|
fail(`Unknown workflows command '${sub}'.`);
|
|
1417
1909
|
}
|
|
@@ -1426,7 +1918,9 @@ var INSTANCE_COMMANDS = [
|
|
|
1426
1918
|
"action-runs",
|
|
1427
1919
|
"files",
|
|
1428
1920
|
"workflows",
|
|
1429
|
-
"workflow-runs"
|
|
1921
|
+
"workflow-runs",
|
|
1922
|
+
"users",
|
|
1923
|
+
"models"
|
|
1430
1924
|
];
|
|
1431
1925
|
function isInstanceCommand(command) {
|
|
1432
1926
|
return INSTANCE_COMMANDS.includes(command);
|
|
@@ -1451,6 +1945,10 @@ async function dispatch(api, command, args) {
|
|
|
1451
1945
|
return workflows(api, args);
|
|
1452
1946
|
case "workflow-runs":
|
|
1453
1947
|
return runs(api, "workflow", args);
|
|
1948
|
+
case "users":
|
|
1949
|
+
return users(api, args);
|
|
1950
|
+
case "models":
|
|
1951
|
+
return models(api, args);
|
|
1454
1952
|
default:
|
|
1455
1953
|
fail(`Unknown command '${command}'. Run 'sixb --help'.`);
|
|
1456
1954
|
}
|
|
@@ -1490,4 +1988,4 @@ export {
|
|
|
1490
1988
|
writeText
|
|
1491
1989
|
};
|
|
1492
1990
|
|
|
1493
|
-
//# debugId=
|
|
1991
|
+
//# debugId=7F5CD0F40A1B96CA64756E2164756E21
|