@sixb/cli-core 0.1.8 → 0.1.10

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 (71) hide show
  1. package/README.md +21 -16
  2. package/dist/api-client.d.ts +3 -1
  3. package/dist/api-client.d.ts.map +1 -1
  4. package/dist/arguments.d.ts +4 -2
  5. package/dist/arguments.d.ts.map +1 -1
  6. package/dist/bin/sixb +26 -0
  7. package/dist/commands/data.d.ts +5 -0
  8. package/dist/commands/data.d.ts.map +1 -0
  9. package/dist/commands/files.d.ts.map +1 -1
  10. package/dist/commands/index.d.ts +1 -1
  11. package/dist/commands/index.d.ts.map +1 -1
  12. package/dist/commands/memory.d.ts +3 -0
  13. package/dist/commands/memory.d.ts.map +1 -0
  14. package/dist/commands/metadata.d.ts +14 -5
  15. package/dist/commands/metadata.d.ts.map +1 -1
  16. package/dist/commands/models.d.ts +79 -0
  17. package/dist/commands/models.d.ts.map +1 -0
  18. package/dist/commands/objects.d.ts.map +1 -1
  19. package/dist/commands/runs.d.ts +8 -1
  20. package/dist/commands/runs.d.ts.map +1 -1
  21. package/dist/commands/shared.d.ts +4 -0
  22. package/dist/commands/shared.d.ts.map +1 -1
  23. package/dist/commands/system.d.ts +21 -0
  24. package/dist/commands/system.d.ts.map +1 -0
  25. package/dist/commands/users.d.ts +3 -0
  26. package/dist/commands/users.d.ts.map +1 -0
  27. package/dist/commands/workflows.d.ts.map +1 -1
  28. package/dist/generated/sixb.mjs +2547 -0
  29. package/dist/index.d.ts +8 -3
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +800 -62
  32. package/dist/index.js.map +24 -18
  33. package/dist/main.d.ts +3 -0
  34. package/dist/main.d.ts.map +1 -0
  35. package/dist/memory.d.ts +21 -0
  36. package/dist/memory.d.ts.map +1 -0
  37. package/dist/output.d.ts +12 -0
  38. package/dist/output.d.ts.map +1 -1
  39. package/dist/policies.d.ts +14 -0
  40. package/dist/policies.d.ts.map +1 -1
  41. package/dist/run.d.ts +1 -0
  42. package/dist/run.d.ts.map +1 -1
  43. package/dist/sandbox-cli.d.ts +8 -0
  44. package/dist/sandbox-cli.d.ts.map +1 -0
  45. package/package.json +15 -1
  46. package/src/api-client.ts +83 -4
  47. package/src/arguments.ts +17 -6
  48. package/src/bin/sixb +26 -0
  49. package/src/commands/actions.ts +5 -5
  50. package/src/commands/data.ts +90 -0
  51. package/src/commands/files.ts +27 -3
  52. package/src/commands/index.ts +24 -0
  53. package/src/commands/memory.ts +358 -0
  54. package/src/commands/metadata.ts +137 -26
  55. package/src/commands/models.ts +508 -0
  56. package/src/commands/objects.ts +47 -2
  57. package/src/commands/ontology.ts +2 -2
  58. package/src/commands/runs.ts +22 -16
  59. package/src/commands/shared.ts +17 -7
  60. package/src/commands/system.ts +67 -0
  61. package/src/commands/telemetry.ts +2 -2
  62. package/src/commands/users.ts +87 -0
  63. package/src/commands/workflows.ts +12 -6
  64. package/src/generated/sixb.mjs +2547 -0
  65. package/src/index.ts +12 -3
  66. package/src/main.ts +39 -0
  67. package/src/memory.ts +30 -0
  68. package/src/output.ts +21 -0
  69. package/src/policies.ts +5 -0
  70. package/src/run.ts +3 -2
  71. package/src/sandbox-cli.ts +18 -0
package/dist/index.js CHANGED
@@ -3,6 +3,7 @@
3
3
  import { randomUUID } from "crypto";
4
4
  import { createWriteStream } from "fs";
5
5
  import { readFile, rename, rm } from "fs/promises";
6
+ import { request as httpRequest } from "http";
6
7
  import { basename, dirname, join } from "path";
7
8
  import { Readable } from "stream";
8
9
  import { pipeline } from "stream/promises";
@@ -11,6 +12,7 @@ import { pipeline } from "stream/promises";
11
12
  var INSTANCE_CLI_VERSION = "1";
12
13
  var EXIT_USAGE = 2;
13
14
  var EXIT_API = 3;
15
+ var EXIT_AWAITING_APPROVAL = 4;
14
16
 
15
17
  class CliError extends Error {
16
18
  body;
@@ -29,12 +31,29 @@ function writeJson(value) {
29
31
  process.stdout.write(`${JSON.stringify(value)}
30
32
  `);
31
33
  }
34
+
35
+ class CliExit extends Error {
36
+ exitCode;
37
+ constructor(exitCode) {
38
+ super(`The Sixb CLI exited with code ${exitCode}.`);
39
+ this.exitCode = exitCode;
40
+ this.name = "CliExit";
41
+ }
42
+ }
43
+ function writeRequestResult(value) {
44
+ writeJson(value);
45
+ const status = typeof value === "object" && value !== null ? Reflect.get(value, "status") : null;
46
+ if (status === "awaiting_approval")
47
+ throw new CliExit(EXIT_AWAITING_APPROVAL);
48
+ }
32
49
  function writeText(value) {
33
50
  process.stdout.write(value.endsWith(`
34
51
  `) ? value : `${value}
35
52
  `);
36
53
  }
37
54
  function reportError(error) {
55
+ if (error instanceof CliExit)
56
+ return error.exitCode;
38
57
  const cliError = error instanceof CliError ? error : new CliError({
39
58
  code: "internal_error",
40
59
  message: error instanceof Error ? error.message : "The Sixb CLI failed unexpectedly."
@@ -45,14 +64,19 @@ function reportError(error) {
45
64
  }
46
65
 
47
66
  // src/api-client.ts
67
+ var UNIX_BASE_URL_PREFIX = "unix:";
68
+
48
69
  class ApiClient {
49
70
  baseUrl;
71
+ socketPath;
50
72
  authorization;
51
73
  missingBaseUrlMessage;
52
74
  unavailableMessage;
53
75
  unavailableHint;
54
76
  constructor(options) {
55
- this.baseUrl = options.baseUrl.replace(/\/+$/, "");
77
+ const baseUrl = options.baseUrl.replace(/\/+$/, "");
78
+ this.socketPath = baseUrl.startsWith(UNIX_BASE_URL_PREFIX) ? baseUrl.slice(UNIX_BASE_URL_PREFIX.length) : undefined;
79
+ this.baseUrl = this.socketPath === undefined ? baseUrl : "http://localhost";
56
80
  this.authorization = options.authorization;
57
81
  this.missingBaseUrlMessage = options.missingBaseUrlMessage;
58
82
  this.unavailableMessage = options.unavailableMessage;
@@ -70,9 +94,10 @@ class ApiClient {
70
94
  ...options.signal ? { signal: options.signal } : {}
71
95
  });
72
96
  }
73
- async upload(path, source, logicalPath) {
97
+ async upload(path, source, logicalPath, mediaType) {
74
98
  const form = new FormData;
75
- form.append("file", new Blob([new Uint8Array(await readFile(source))]), basename(source));
99
+ const bytes = new Uint8Array(await readFile(source));
100
+ form.append("file", new Blob([bytes], mediaType ? { type: mediaType } : undefined), basename(source));
76
101
  if (logicalPath)
77
102
  form.append("logicalPath", logicalPath);
78
103
  return this.json(this.url(path), { method: "POST", body: form });
@@ -117,7 +142,7 @@ class ApiClient {
117
142
  const headers = new Headers(init.headers);
118
143
  if (this.authorization)
119
144
  headers.set("authorization", this.authorization);
120
- response = await fetch(url, { ...init, headers });
145
+ response = await send(url, { ...init, headers }, this.socketPath);
121
146
  } catch (error) {
122
147
  if (init.signal?.aborted)
123
148
  throw error;
@@ -174,6 +199,48 @@ function stringField(record, key) {
174
199
  const value = record[key];
175
200
  return typeof value === "string" ? value : undefined;
176
201
  }
202
+ function send(url, init, socketPath) {
203
+ if (socketPath === undefined)
204
+ return fetch(url, init);
205
+ if (typeof Bun !== "undefined")
206
+ return fetch(url, { ...init, unix: socketPath });
207
+ return sendOverNodeSocket(url, init, socketPath);
208
+ }
209
+ async function sendOverNodeSocket(url, init, socketPath) {
210
+ const outgoing = new Request(url, init);
211
+ const body = outgoing.body ? Buffer.from(await outgoing.arrayBuffer()) : undefined;
212
+ const headers = Object.fromEntries(outgoing.headers);
213
+ if (body)
214
+ headers["content-length"] = String(body.byteLength);
215
+ return new Promise((resolve, reject) => {
216
+ const request = httpRequest({
217
+ socketPath,
218
+ method: outgoing.method,
219
+ path: `${url.pathname}${url.search}`,
220
+ headers: { ...headers, host: "localhost" },
221
+ ...init.signal ? { signal: init.signal } : {}
222
+ }, (response) => {
223
+ const responseHeaders = new Headers;
224
+ for (const [name, value] of Object.entries(response.headers)) {
225
+ for (const each of Array.isArray(value) ? value : value === undefined ? [] : [value]) {
226
+ responseHeaders.append(name, each);
227
+ }
228
+ }
229
+ const status = response.statusCode ?? 502;
230
+ const empty = status === 204 || status === 304 || outgoing.method === "HEAD";
231
+ resolve(new Response(empty ? null : nodeBody(response), {
232
+ status,
233
+ statusText: response.statusMessage ?? "",
234
+ headers: responseHeaders
235
+ }));
236
+ });
237
+ request.on("error", reject);
238
+ request.end(body);
239
+ });
240
+ }
241
+ function nodeBody(response) {
242
+ return Readable.toWeb(response);
243
+ }
177
244
  // src/arguments.ts
178
245
  function parseCommandArgs(args, kinds, command, positionalCount = 0) {
179
246
  const positionals = [];
@@ -193,8 +260,9 @@ function parseCommandArgs(args, kinds, command, positionalCount = 0) {
193
260
  const flag = equals < 0 ? argument : argument.slice(0, equals);
194
261
  if (!Object.hasOwn(kinds, flag))
195
262
  fail(`Unknown ${command} option '${flag}'.`);
196
- if (Object.hasOwn(options, flag))
263
+ if (Object.hasOwn(options, flag) && kinds[flag] !== "strings") {
197
264
  fail(`${flag} may only be provided once.`);
265
+ }
198
266
  if (kinds[flag] === "boolean") {
199
267
  if (equals >= 0)
200
268
  fail(`${flag} does not accept a value.`);
@@ -204,7 +272,8 @@ function parseCommandArgs(args, kinds, command, positionalCount = 0) {
204
272
  if (!value || equals < 0 && value.startsWith("--")) {
205
273
  fail(`${flag} requires a value.`);
206
274
  }
207
- options[flag] = value;
275
+ const previous = options[flag];
276
+ options[flag] = kinds[flag] === "strings" ? [...Array.isArray(previous) ? previous : [], value] : value;
208
277
  }
209
278
  }
210
279
  const [minimum, maximum] = typeof positionalCount === "number" ? [positionalCount, positionalCount] : positionalCount;
@@ -282,6 +351,9 @@ var CLI_LIMITS = {
282
351
  vectorSearchText: { maximum: 8000 },
283
352
  telemetryHistory: { default: 100, maximum: 1000 },
284
353
  linkPage: { default: 100, maximum: 1000 },
354
+ users: { default: 20, maximum: 200 },
355
+ datasetVersions: { default: 20, maximum: 100 },
356
+ datasetRows: { default: 100, maximum: 1000 },
285
357
  inspect: {
286
358
  depth: { default: 2, maximum: 3 },
287
359
  objects: { default: 20, maximum: 100 },
@@ -294,17 +366,17 @@ var DEFAULT_OBJECT_ORDER_BY = "updatedAt";
294
366
  var DEFAULT_TELEMETRY_ORDER = "desc";
295
367
 
296
368
  // src/commands/metadata.ts
297
- var SANDBOX_MAIN_HELP = `Sixb agent CLI
369
+ var SANDBOX_MAIN_HELP = `Sixb CLI
298
370
 
299
371
  Usage:
300
372
  sixb <command> [options]
301
373
 
302
- The CLI uses the run-scoped SIXB_API_BASE_URL. Do not configure authentication or another origin.
374
+ The CLI uses the run's SIXB_API_BASE_URL, which adds its credentials. Do not configure authentication or another origin.
303
375
  API commands emit JSON. Help, version, and examples emit text. Downloads write to --output and
304
376
  emit a JSON receipt.
305
377
 
306
378
  Discovery:
307
- sixb doctor Check the sandbox and API gateway
379
+ sixb status Check the sandbox and its API access
308
380
  sixb context Print the current run context
309
381
  sixb project show Show project metadata
310
382
  sixb ontology list Compact visible type and relationship catalog
@@ -317,6 +389,8 @@ Objects:
317
389
  sixb objects inspect <type> <id> Inspect an object and its related graph in one command
318
390
  sixb objects list [options] Browse materialized objects
319
391
  sixb objects get <type> <id>... Exact lookup through opaque object references
392
+ sixb objects get-by <type> <property> <value>
393
+ The object holding a value of a unique property
320
394
  sixb objects search <text> Search visible objects by id prefix and full text
321
395
  sixb objects search <text> --type <type>
322
396
  Semantic search through the type's vector profile
@@ -337,19 +411,27 @@ Actions and workflows:
337
411
  sixb workflows list|get|start ...
338
412
  sixb workflow-runs list|get ...
339
413
 
414
+ Data:
415
+ sixb datasets list|get|versions|rows ...
416
+ sixb syncs list|get ... Syncs you may run, each with its latest run
417
+ sixb sync-runs list ...
418
+ sixb pipelines list|get ... Pipelines you may run, each with its latest run
419
+ sixb pipeline-runs list|get ...
420
+
340
421
  Files:
341
422
  sixb files upload|download ...
342
423
 
424
+ Members:
425
+ sixb users search <text> Find members by name or email
426
+ sixb users list|get ...
427
+
428
+ A user parameter or property is {"type":"user","id":"<user-id>"}. Find the id with
429
+ \`sixb users search <name>\`.
430
+
343
431
  Run \`sixb <group> --help\` or \`sixb <group> <command> --help\` for exact arguments.
344
432
  For query IR, run \`sixb objects query --help\` and \`sixb objects query --example list\`.`;
345
- 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
346
- sixb context Print the current run context
347
- `, "").replace(`
348
- When you do not know a type's properties or links, read its file under $SIXB_ONTOLOGY_DIR. Use
349
- \`sixb ontology get\` for a type that has no file there.
350
- `, "");
351
- function renderInstanceHelp(mode) {
352
- return mode === "sandbox" ? SANDBOX_MAIN_HELP : LOCAL_MAIN_HELP;
433
+ function renderInstanceHelp() {
434
+ return SANDBOX_MAIN_HELP;
353
435
  }
354
436
  var SEARCH_DETAILS = `Without --type, search matches primary-id prefixes and full-text fields across visible types.
355
437
  With --type, the server embeds the text with the profile's model and ranks results by score,
@@ -364,10 +446,18 @@ var SEARCH_HELP = `Usage:
364
446
  profiles.
365
447
 
366
448
  ${SEARCH_DETAILS}`;
449
+ var GET_BY_HELP = `Usage:
450
+ sixb objects get-by <object-type> <property> <value> [--json]
451
+
452
+ Prints the object of exactly <object-type> holding <value> for a property marked unique in its
453
+ ontology doc, or null when no visible object holds it. Values match exactly, case included.
454
+ The value is a string; with --json it is parsed as JSON, for an integer (42) or a user
455
+ reference ({"type":"user","id":"usr_1"}).`;
367
456
  var OBJECTS_HELP = `Usage:
368
457
  sixb objects inspect <object-type> <primary-id> [options]
369
458
  sixb objects list [options]
370
459
  sixb objects get <object-type> <primary-id>...
460
+ sixb objects get-by <object-type> <property> <value> [--json]
371
461
  sixb objects search <text> [--type <object-type> [--vector-profile <name>]] [--limit <n>]
372
462
  sixb objects query --file <path|-> [--include-total|--no-total]
373
463
  sixb objects query --example <name>
@@ -434,7 +524,7 @@ Query nodes compose through input:
434
524
  limit {"kind":"limit","input":<query>,"limit":${CLI_LIMITS.list.default}}
435
525
  page {"kind":"page","input":<query>,"pageSize":${CLI_LIMITS.list.default},"pageToken":"from nextPageToken"}
436
526
  project {"kind":"project","input":<query>,"properties":["name","status"]}
437
- expand {"kind":"expand","input":<query>,"expansions":[{"linkId":"link","direction":"outgoing","limit":5}]}
527
+ expand {"kind":"expand","input":<query>,"expansions":[{"linkId":"link","direction":"outgoing","limit":5}],"users":["assignee"]}
438
528
  vector {"kind":"vector","input":<start|filter>,"vector":"text","profile":"name","k":10}
439
529
  rerank {"kind":"rerank","input":<vector>,"model":{"provider":"provider","modelId":"model"}}
440
530
 
@@ -445,11 +535,16 @@ Predicates use op, not kind:
445
535
  {"op":"in","propertyId":"status","values":["open","blocked"]}
446
536
  {"op":"exists","propertyId":"dueDate","value":true}
447
537
  {"op":"contains","propertyId":"tags","value":"urgent"}
538
+ {"op":"eq","propertyId":"assignee","value":{"type":"currentUser"}} the caller, on a user reference: neq, in, contains too
448
539
 
449
540
  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
541
+ direction, an expansion's limit, and users. Sort by relevance after text or vector with
451
542
  {"kind":"relevance"}. Wrap the outermost node in project to return only the properties you need.
452
543
 
544
+ An expand's users, or an expansion's, lists user-reference properties: each row then carries the
545
+ members' profiles under users (id, email, displayName, avatarUrl, status). Without access to the
546
+ member directory, users is empty.
547
+
453
548
  Traversal and expansion directions are outgoing or incoming. For incoming relationships, the link
454
549
  is declared on the child/source type; add sourceObjectTypeId when needed to disambiguate it.
455
550
 
@@ -460,7 +555,7 @@ identities. Put limits and pages inside the query tree.
460
555
  Vector profile names are the keys of the type's search.vectors. Vector input is a start node,
461
556
  optionally under filters; k bounds the ranked matches and results carry a score.`;
462
557
  var GROUP_HELP = {
463
- doctor: "Usage: sixb doctor",
558
+ status: "Usage: sixb status",
464
559
  context: "Usage: sixb context",
465
560
  project: "Usage: sixb project show",
466
561
  ontology: `Usage:
@@ -484,7 +579,8 @@ History options:
484
579
  \`actions get\` includes inputSchema, the exact JSON shape accepted by the Action. The JSON file
485
580
  contains that parameter object; use - to read standard input. \`actions request\` runs the Action
486
581
  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\`.`,
582
+ takes longer keeps running, and the error names the run to read with \`action-runs get\`. A request
583
+ that waits for the user's approval prints \`"status": "awaiting_approval"\` and exits with code 4.`,
488
584
  "action-runs": `Usage:
489
585
  sixb action-runs list [options]
490
586
  sixb action-runs get <run-id>
@@ -501,13 +597,54 @@ List options:
501
597
  files: `Usage:
502
598
  sixb files upload <local-path> [--logical-path <path>]
503
599
  sixb files download object <type> <id> --path <json-pointer> --output <local-path>
504
- sixb files download action-run|workflow-run <run-id> --path <json-pointer> --output <local-path>`,
600
+ sixb files download action-run|workflow-run <run-id> --path <json-pointer> --output <local-path>
601
+ sixb files download url <content-url> --output <local-path>`,
505
602
  workflows: `Usage:
506
603
  sixb workflows list
507
604
  sixb workflows get <workflow-id>
508
- sixb workflows start <workflow-id> [--file <path|->]
605
+ sixb workflows start <workflow-id> [--file <path|->] [--run-id <id>]
509
606
 
510
- The JSON file contains the workflow input object. Use - to read standard input.`,
607
+ The JSON file contains the workflow input object. Use - to read standard input. A start that waits
608
+ for the user's approval prints \`"status": "awaiting_approval"\` and exits with code 4.`,
609
+ users: `Usage:
610
+ sixb users list [options]
611
+ sixb users search <text> [options]
612
+ sixb users get <user-id|email>
613
+
614
+ Options:
615
+ --status active|suspended|all Defaults to active
616
+ --limit <1-${CLI_LIMITS.users.maximum}> Defaults to ${CLI_LIMITS.users.default}
617
+ --offset <n> Non-negative; defaults to 0
618
+
619
+ \`users search\` matches part of a display name or email, ignoring case; \`users get\` takes an
620
+ exact email, ignoring case, and finds suspended members too. Each member is
621
+ {"id","email","displayName","avatarUrl","status"}; a user parameter takes
622
+ {"type":"user","id":"<id>"}. Reading members requires can.view(project.users).`,
623
+ models: `Usage:
624
+ sixb models list [--kind language|image|speech|transcription]
625
+ sixb models get <model> [--kind <kind>]
626
+ sixb models text "<prompt>"|--file <path|-> [--model <tier|model>] [--instructions <text>]
627
+ [--max-output-tokens <n>] [--reasoning <level>]
628
+ sixb models image "<prompt>" --output <path> [--model <model>] [--n <n>]
629
+ [--size <size>] [--aspect-ratio <ratio>] [--quality <q>] [--style <s>]
630
+ [--seed <n>] [--image <file>]...
631
+ sixb models speech "<text>" --output <path> [--model <model>] [--voice <voice>]
632
+ [--format mp3|wav|ogg|aac|flac] [--instructions <text>]
633
+ [--speed <n>] [--language <code>]
634
+ sixb models transcribe <file> [--model <model>]
635
+ sixb models calls files <call-id> [--output <path>]
636
+
637
+ <model> is a model id, or provider:model-id when two providers share one; without --model, the
638
+ kind's default answers. \`list\` shows each model's kind, default, description and price; \`get\`
639
+ adds the sizes, voices and other options a request can use. Prices are in USD.
640
+
641
+ \`text\` also takes a tier (light, standard or heavy) for --model, and reads a long prompt from
642
+ --file: a path, or - for standard input.
643
+
644
+ --output is a file, numbered when a call returns several, or a directory ending in /. The
645
+ extension follows the file's type, and the result lists the paths written. --image uploads a
646
+ reference image; repeat it for several. \`calls files\` lists a past call's files, or downloads
647
+ them again.`,
511
648
  "workflow-runs": `Usage:
512
649
  sixb workflow-runs list [options]
513
650
  sixb workflow-runs get <run-id>
@@ -518,7 +655,57 @@ List options:
518
655
  --started-after|--started-before <RFC3339>
519
656
  --limit <1-${CLI_LIMITS.list.maximum}> Defaults to ${CLI_LIMITS.list.default}
520
657
  --offset <n> Non-negative; defaults to 0
521
- --order <asc|desc> Started-time order; defaults to ${DEFAULT_LIST_ORDER}`
658
+ --order <asc|desc> Started-time order; defaults to ${DEFAULT_LIST_ORDER}`,
659
+ datasets: `Usage:
660
+ sixb datasets list
661
+ sixb datasets get <dataset-id>
662
+ sixb datasets versions <dataset-id> [--limit <1-${CLI_LIMITS.datasetVersions.maximum}>]
663
+ sixb datasets rows <dataset-id> [options]
664
+
665
+ Rows options:
666
+ --version <version-id> Defaults to the latest version
667
+ --columns <a,b,...> Defaults to every column
668
+ --limit <1-${CLI_LIMITS.datasetRows.maximum}> Defaults to ${CLI_LIMITS.datasetRows.default}
669
+ --offset <n> Non-negative; defaults to 0
670
+
671
+ \`list\` and \`get\` show a dataset's schema, its latest version, and the syncs and pipelines you
672
+ may run that read or write it. \`versions\` lists the newest first. \`rows\` leaves out the
673
+ columns your clearances do not cover and names them under redactions.`,
674
+ syncs: `Usage:
675
+ sixb syncs list
676
+ sixb syncs get <sync-id>
677
+
678
+ Lists the syncs you may run, each with its latest run. \`sixb sync-runs list\` shows the history.`,
679
+ "sync-runs": `Usage:
680
+ sixb sync-runs list [options]
681
+
682
+ List options:
683
+ --sync <sync-id>
684
+ --dataset <dataset-id>
685
+ --status queued|running|succeeded|failed|cancelled
686
+ --started-after|--started-before <RFC3339>
687
+ --limit <1-${CLI_LIMITS.list.maximum}> Defaults to ${CLI_LIMITS.list.default}
688
+ --offset <n> Non-negative; defaults to 0
689
+ --order <asc|desc> Started-time order; defaults to ${DEFAULT_LIST_ORDER}`,
690
+ pipelines: `Usage:
691
+ sixb pipelines list
692
+ sixb pipelines get <pipeline-id>
693
+
694
+ Lists the pipelines you may run, each with its latest run. \`sixb pipeline-runs\` shows the
695
+ history and each run's steps.`,
696
+ "pipeline-runs": `Usage:
697
+ sixb pipeline-runs list [options]
698
+ sixb pipeline-runs get <run-id>
699
+
700
+ List options:
701
+ --pipeline <pipeline-id>
702
+ --status queued|running|succeeded|failed|cancelled
703
+ --started-after|--started-before <RFC3339>
704
+ --limit <1-${CLI_LIMITS.list.maximum}> Defaults to ${CLI_LIMITS.list.default}
705
+ --offset <n> Non-negative; defaults to 0
706
+ --order <asc|desc> Started-time order; defaults to ${DEFAULT_LIST_ORDER}
707
+
708
+ \`get\` includes the run's steps.`
522
709
  };
523
710
  var QUERY_EXAMPLES = {
524
711
  exact: '{"kind":"refs","refs":[{"objectTypeId":"RepositoryIssue","primaryId":"github:issue:owner/repo#297"}]}',
@@ -527,9 +714,11 @@ var QUERY_EXAMPLES = {
527
714
  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
715
  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
716
  exists: `{"kind":"limit","input":{"kind":"filter","input":{"kind":"start","objectTypeId":"Customer"},"predicate":{"op":"exists","propertyId":"accountManager","value":false}},"limit":${CLI_LIMITS.list.default}}`,
717
+ 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
718
  project: `{"kind":"project","input":{"kind":"limit","input":{"kind":"start","objectTypeId":"Customer"},"limit":${CLI_LIMITS.list.default}},"properties":["name","status"]}`,
531
719
  incoming: '{"kind":"traverse","input":{"kind":"refs","refs":[{"objectTypeId":"RepositoryIssue","primaryId":"github:issue:owner/repo#297"}]},"linkId":"issue","direction":"incoming","sourceObjectTypeId":"RepositoryComment"}',
532
720
  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}}]}`,
721
+ users: `{"kind":"expand","input":{"kind":"limit","input":{"kind":"start","objectTypeId":"Task"},"limit":${CLI_LIMITS.list.default}},"expansions":[],"users":["assignee"]}`,
533
722
  sort: `{"kind":"limit","input":{"kind":"sort","input":{"kind":"start","objectTypeId":"Customer"},"fields":[{"kind":"property","propertyId":"name","direction":"asc"}]},"limit":${CLI_LIMITS.list.default}}`,
534
723
  page: `{"kind":"page","input":{"kind":"start","objectTypeId":"Customer"},"pageSize":${CLI_LIMITS.list.default}}`,
535
724
  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}}`
@@ -538,6 +727,11 @@ var FACETS_EXAMPLE = '{"query":{"kind":"start","objectTypeId":"WorkOrder"},"face
538
727
 
539
728
  // src/commands/shared.ts
540
729
  import { readFile as readFile2 } from "fs/promises";
730
+ function idSegment(value) {
731
+ if (!value || value === "." || value === "..")
732
+ fail(`'${value ?? ""}' is not an id.`);
733
+ return encodeURIComponent(value);
734
+ }
541
735
  function parseQueryOptions(args, names, command) {
542
736
  const kinds = Object.fromEntries(Object.keys(names).map((flag) => [flag, "string"]));
543
737
  const { options } = parseCommandArgs(args, kinds, command);
@@ -567,20 +761,22 @@ function singleFileOption(args, command) {
567
761
  return options["--file"];
568
762
  }
569
763
  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
- }
764
+ const text = await readText(source, "JSON file");
578
765
  try {
579
766
  return JSON.parse(text);
580
767
  } catch {
581
768
  fail(source === "-" ? "Standard input is not valid JSON." : `JSON file '${source}' is not valid JSON.`, "invalid_json");
582
769
  }
583
770
  }
771
+ async function readText(source, noun) {
772
+ try {
773
+ return source === "-" ? await readStdin() : await readFile2(source, "utf8");
774
+ } catch (error) {
775
+ if (isFileError(error, "ENOENT"))
776
+ fail(`${noun} '${source}' does not exist.`);
777
+ throw error;
778
+ }
779
+ }
584
780
  function asRecord2(value) {
585
781
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
586
782
  }
@@ -609,7 +805,7 @@ async function actions(api, args, requestWaitMs = ACTION_REQUEST_WAIT_MS) {
609
805
  return writeText(GROUP_HELP.actions);
610
806
  if (sub === "get") {
611
807
  const { positionals } = parseCommandArgs(rest, {}, "actions get", 1);
612
- return writeJson(await api.get(`/api/actions/${encodeURIComponent(positionals[0] ?? "")}`));
808
+ return writeJson(await api.get(`/api/actions/${idSegment(positionals[0])}`));
613
809
  }
614
810
  if (sub === "list") {
615
811
  const options = parseQueryOptions(rest, { "--type": "objectTypeId" }, "actions list");
@@ -642,7 +838,7 @@ async function actions(api, args, requestWaitMs = ACTION_REQUEST_WAIT_MS) {
642
838
  const requestedRunId = runId ?? `act_${randomUUID2()}`;
643
839
  const waiting = AbortSignal.timeout(requestWaitMs);
644
840
  try {
645
- return writeJson(await api.post(`/api/actions/${encodeURIComponent(actionId)}`, {
841
+ return writeRequestResult(await api.post(`/api/actions/${idSegment(actionId)}`, {
646
842
  params,
647
843
  ...subjectType && subjectId ? { subject: { kind: "object", objectTypeId: subjectType, primaryId: subjectId } } : {},
648
844
  runId: requestedRunId
@@ -660,6 +856,59 @@ async function actions(api, args, requestWaitMs = ACTION_REQUEST_WAIT_MS) {
660
856
  fail(`Unknown actions command '${sub}'.`);
661
857
  }
662
858
 
859
+ // src/commands/data.ts
860
+ async function datasets(api, args) {
861
+ const [sub, ...rest] = args;
862
+ if (!sub || isHelp(sub) || requestsHelp(rest))
863
+ return writeText(GROUP_HELP.datasets);
864
+ if (sub === "list") {
865
+ parseCommandArgs(rest, {}, "datasets list");
866
+ return writeJson(await api.get("/api/datasets"));
867
+ }
868
+ if (sub === "get") {
869
+ const { positionals } = parseCommandArgs(rest, {}, "datasets get", 1);
870
+ return writeJson(await api.get(`/api/datasets/${idSegment(positionals[0])}`));
871
+ }
872
+ if (sub === "versions") {
873
+ const { positionals, options } = parseCommandArgs(rest, { "--limit": "string" }, "datasets versions", 1);
874
+ const { default: fallback, maximum } = CLI_LIMITS.datasetVersions;
875
+ const limit = integerInRange("--limit", options["--limit"] ?? String(fallback), 1, maximum);
876
+ return writeJson(await api.get(`/api/datasets/${idSegment(positionals[0])}/versions`, { limit: String(limit) }));
877
+ }
878
+ if (sub === "rows") {
879
+ const { positionals, options } = parseCommandArgs(rest, { "--version": "string", "--columns": "string", "--limit": "string", "--offset": "string" }, "datasets rows", 1);
880
+ const { default: fallback, maximum } = CLI_LIMITS.datasetRows;
881
+ const query = {
882
+ limit: String(integerInRange("--limit", options["--limit"] ?? String(fallback), 1, maximum)),
883
+ ...options["--version"] === undefined ? {} : { versionId: options["--version"] },
884
+ ...options["--columns"] === undefined ? {} : { columns: columnList(options["--columns"]) },
885
+ ...options["--offset"] === undefined ? {} : { offset: String(nonNegativeInteger("--offset", options["--offset"])) }
886
+ };
887
+ return writeJson(await api.get(`/api/datasets/${idSegment(positionals[0])}/rows`, query));
888
+ }
889
+ fail(`Unknown datasets command '${sub}'.`);
890
+ }
891
+ async function runnables(api, group, args) {
892
+ const [sub, ...rest] = args;
893
+ if (!sub || isHelp(sub) || requestsHelp(rest))
894
+ return writeText(GROUP_HELP[group]);
895
+ if (sub === "list") {
896
+ parseCommandArgs(rest, {}, `${group} list`);
897
+ return writeJson(await api.get(`/api/${group}`));
898
+ }
899
+ if (sub === "get") {
900
+ const { positionals } = parseCommandArgs(rest, {}, `${group} get`, 1);
901
+ return writeJson(await api.get(`/api/${group}/${idSegment(positionals[0])}`));
902
+ }
903
+ fail(`Unknown ${group} command '${sub}'.`);
904
+ }
905
+ function columnList(value) {
906
+ const columns = value.split(",").map((column) => column.trim());
907
+ if (columns.some((column) => !column))
908
+ fail("--columns takes column names separated by commas.");
909
+ return columns.join(",");
910
+ }
911
+
663
912
  // src/commands/files.ts
664
913
  import { access } from "fs/promises";
665
914
  async function files(api, args) {
@@ -679,20 +928,28 @@ async function files(api, args) {
679
928
  if (sub === "download") {
680
929
  const { positionals, options } = parseCommandArgs(rest, { "--path": "string", "--output": "string" }, "files download", [2, 3]);
681
930
  const context = positionals[0];
931
+ const output = options["--output"];
932
+ if (context === "url") {
933
+ requireExact(positionals, 2, "files download url requires exactly one content URL.");
934
+ const url = parseContentUrl(positionals[1] ?? "");
935
+ if (!output)
936
+ fail("files download requires --output <local-path>.");
937
+ await api.download(url.pathname, output, Object.fromEntries(url.searchParams));
938
+ return writeJson({ downloaded: true, output });
939
+ }
682
940
  let route;
683
941
  if (context === "object") {
684
942
  requireExact(positionals, 3, "files download object requires object type and primary id.");
685
943
  const type = positionals[1] ?? "";
686
944
  const id = positionals[2] ?? "";
687
- route = `/api/objects/${encodeURIComponent(type)}/${encodeURIComponent(id)}/files/content`;
945
+ route = `/api/objects/${idSegment(type)}/${idSegment(id)}/files/content`;
688
946
  } else if (context === "action-run" || context === "workflow-run") {
689
947
  requireExact(positionals, 2, `files download ${context} requires exactly one run id.`);
690
948
  const id = positionals[1] ?? "";
691
- route = `/api/${context}s/${encodeURIComponent(id)}/files/content`;
949
+ route = `/api/${context}s/${idSegment(id)}/files/content`;
692
950
  } else
693
951
  fail(`Unknown file download context '${context}'.`);
694
952
  const path = options["--path"];
695
- const output = options["--output"];
696
953
  if (!path)
697
954
  fail("files download requires --path <json-pointer>.");
698
955
  if (!output)
@@ -702,6 +959,352 @@ async function files(api, args) {
702
959
  }
703
960
  fail(`Unknown files command '${sub}'.`);
704
961
  }
962
+ function parseContentUrl(value) {
963
+ let url;
964
+ try {
965
+ url = new URL(value);
966
+ } catch {
967
+ fail(`'${value}' is not a URL.`);
968
+ }
969
+ if (!url.pathname.startsWith("/api/") || !url.pathname.endsWith("/files/content")) {
970
+ fail(`'${value}' is not a Sixb file content URL.`);
971
+ }
972
+ return url;
973
+ }
974
+
975
+ // src/commands/models.ts
976
+ import { access as access2, mkdir, stat } from "fs/promises";
977
+ import { dirname as dirname2, extname, join as join2, parse } from "path";
978
+ var MODEL_KINDS = ["language", "image", "speech", "transcription"];
979
+ async function models(api, args) {
980
+ const [sub, ...rest] = args;
981
+ if (!sub || isHelp(sub) || requestsHelp(rest))
982
+ return writeText(GROUP_HELP.models);
983
+ if (sub === "list") {
984
+ const { options } = parseCommandArgs(rest, { "--kind": "string" }, "models list", 0);
985
+ const kind = options["--kind"] === undefined ? undefined : modelKind(options["--kind"]);
986
+ const entries = catalogEntries(await api.get("/api/models"));
987
+ return writeJson(entries.filter((entry) => kind === undefined || entry.kind === kind).map(({ kind, entry }) => ({
988
+ kind,
989
+ model: entry.modelId,
990
+ provider: entry.provider,
991
+ ...entry.tier ? { tier: entry.tier } : {},
992
+ ...entry.isDefault ? { default: true } : {},
993
+ ...entry.description ? { description: entry.description } : {},
994
+ price: describePrice(entry.rateCard)
995
+ })));
996
+ }
997
+ if (sub === "get") {
998
+ const { positionals, options } = parseCommandArgs(rest, { "--kind": "string" }, "models get", 1);
999
+ const kind = options["--kind"] === undefined ? undefined : modelKind(options["--kind"]);
1000
+ const entries = catalogEntries(await api.get("/api/models"));
1001
+ const { kind: found, entry } = findModel(entries, positionals[0] ?? "", kind);
1002
+ const { provider, modelId, isDefault, rateCard, ...details } = entry;
1003
+ return writeJson({
1004
+ kind: found,
1005
+ model: modelId,
1006
+ provider,
1007
+ default: isDefault,
1008
+ ...details,
1009
+ price: describePrice(rateCard),
1010
+ ...rateCard ? { rateCard } : {}
1011
+ });
1012
+ }
1013
+ if (sub === "text")
1014
+ return generateText(api, rest);
1015
+ if (sub === "image")
1016
+ return generateImages(api, rest);
1017
+ if (sub === "speech")
1018
+ return generateSpeech(api, rest);
1019
+ if (sub === "transcribe")
1020
+ return transcribe(api, rest);
1021
+ if (sub === "calls")
1022
+ return calls(api, rest);
1023
+ fail(`Unknown models command '${sub}'.`);
1024
+ }
1025
+ var SPEECH_FORMATS = {
1026
+ mp3: "audio/mpeg",
1027
+ wav: "audio/wav",
1028
+ ogg: "audio/ogg",
1029
+ aac: "audio/aac",
1030
+ flac: "audio/flac"
1031
+ };
1032
+ var SPEECH_FORMAT_NAMES = Object.keys(SPEECH_FORMATS);
1033
+ var MEDIA_TYPES = {
1034
+ ".png": "image/png",
1035
+ ".jpg": "image/jpeg",
1036
+ ".jpeg": "image/jpeg",
1037
+ ".webp": "image/webp",
1038
+ ".gif": "image/gif",
1039
+ ".mp3": "audio/mpeg",
1040
+ ".wav": "audio/wav",
1041
+ ".ogg": "audio/ogg",
1042
+ ".aac": "audio/aac",
1043
+ ".flac": "audio/flac",
1044
+ ".opus": "audio/opus",
1045
+ ".m4a": "audio/mp4",
1046
+ ".webm": "audio/webm"
1047
+ };
1048
+ var EXTENSIONS = Object.fromEntries(Object.entries(MEDIA_TYPES).filter(([extension]) => extension !== ".jpeg").map(([extension, mediaType]) => [mediaType, extension]));
1049
+ async function generateText(api, args) {
1050
+ const { positionals, options } = parseCommandArgs(args, {
1051
+ "--model": "string",
1052
+ "--instructions": "string",
1053
+ "--max-output-tokens": "string",
1054
+ "--reasoning": "string",
1055
+ "--file": "string"
1056
+ }, "models text", [0, 1]);
1057
+ const prompt = await readPrompt(positionals[0], options["--file"]);
1058
+ const maxOutputTokens = options["--max-output-tokens"];
1059
+ const result = await api.post("/api/models/language/generations", {
1060
+ ...await languageModelRef(api, options["--model"]),
1061
+ prompt,
1062
+ ...options["--instructions"] === undefined ? {} : { instructions: options["--instructions"] },
1063
+ ...maxOutputTokens === undefined ? {} : { maxOutputTokens: integerInRange("--max-output-tokens", maxOutputTokens, 1, 1e6) },
1064
+ ...options["--reasoning"] === undefined ? {} : { reasoning: options["--reasoning"] }
1065
+ });
1066
+ writeJson({
1067
+ callId: result.callId,
1068
+ model: result.model.modelId,
1069
+ text: result.text,
1070
+ finishReason: result.finishReason,
1071
+ cost: describeCost(result.cost)
1072
+ });
1073
+ }
1074
+ async function generateImages(api, args) {
1075
+ const { positionals, options } = parseCommandArgs(args, {
1076
+ "--output": "string",
1077
+ "--model": "string",
1078
+ "--n": "string",
1079
+ "--size": "string",
1080
+ "--aspect-ratio": "string",
1081
+ "--quality": "string",
1082
+ "--style": "string",
1083
+ "--seed": "string",
1084
+ "--image": "strings"
1085
+ }, "models image", 1);
1086
+ const output = requireOutput(options["--output"], "models image");
1087
+ const seed = options["--seed"];
1088
+ if (seed !== undefined && !/^-?[0-9]+$/.test(seed))
1089
+ fail("--seed must be an integer.");
1090
+ const model = await modelRef(api, options["--model"], "image");
1091
+ const images = [];
1092
+ for (const path of options["--image"] ?? [])
1093
+ images.push(await uploadInput(api, path, "image"));
1094
+ const result = await api.post("/api/models/image/generations", {
1095
+ ...model,
1096
+ prompt: positionals[0],
1097
+ ...options["--n"] === undefined ? {} : { n: integerInRange("--n", options["--n"], 1, 100) },
1098
+ ...optional("size", options["--size"]),
1099
+ ...optional("aspectRatio", options["--aspect-ratio"]),
1100
+ ...optional("quality", options["--quality"]),
1101
+ ...optional("style", options["--style"]),
1102
+ ...seed === undefined ? {} : { seed: Number(seed) },
1103
+ ...images.length > 0 ? { images } : {}
1104
+ });
1105
+ writeJson(await savedResult(api, result, output));
1106
+ }
1107
+ async function generateSpeech(api, args) {
1108
+ const { positionals, options } = parseCommandArgs(args, {
1109
+ "--output": "string",
1110
+ "--model": "string",
1111
+ "--voice": "string",
1112
+ "--format": "string",
1113
+ "--instructions": "string",
1114
+ "--speed": "string",
1115
+ "--language": "string"
1116
+ }, "models speech", 1);
1117
+ const output = requireOutput(options["--output"], "models speech");
1118
+ const format = options["--format"];
1119
+ const speed = options["--speed"];
1120
+ if (speed !== undefined && !(Number(speed) > 0))
1121
+ fail("--speed must be a positive number.");
1122
+ const result = await api.post("/api/models/audio/speech", {
1123
+ ...await modelRef(api, options["--model"], "speech"),
1124
+ text: positionals[0],
1125
+ ...optional("voice", options["--voice"]),
1126
+ ...format === undefined ? {} : { mediaType: SPEECH_FORMATS[enumValue("--format", format, SPEECH_FORMAT_NAMES)] },
1127
+ ...optional("instructions", options["--instructions"]),
1128
+ ...speed === undefined ? {} : { speed: Number(speed) },
1129
+ ...optional("language", options["--language"])
1130
+ });
1131
+ writeJson(await savedResult(api, result, output));
1132
+ }
1133
+ async function transcribe(api, args) {
1134
+ const { positionals, options } = parseCommandArgs(args, { "--model": "string" }, "models transcribe", 1);
1135
+ const model = await modelRef(api, options["--model"], "transcription");
1136
+ const audio = await uploadInput(api, positionals[0] ?? "", "audio");
1137
+ const result = await api.post("/api/models/audio/transcriptions", {
1138
+ ...model,
1139
+ audio
1140
+ });
1141
+ writeJson({
1142
+ callId: result.callId,
1143
+ model: result.model.modelId,
1144
+ text: result.text,
1145
+ cost: describeCost(result.cost)
1146
+ });
1147
+ }
1148
+ async function calls(api, args) {
1149
+ const [sub, ...rest] = args;
1150
+ if (sub !== "files")
1151
+ fail(`Unknown models calls command '${sub ?? ""}'. Use 'models calls files'.`);
1152
+ const { positionals, options } = parseCommandArgs(rest, { "--output": "string" }, "models calls files", 1);
1153
+ const callId = positionals[0] ?? "";
1154
+ const call = await api.get(`/api/ai/model-calls/${idSegment(callId)}/files`);
1155
+ const output = options["--output"];
1156
+ if (output === undefined)
1157
+ return writeJson(call);
1158
+ writeJson({ callId: call.callId, files: await saveFiles(api, call.callId, call.files, output) });
1159
+ }
1160
+ async function modelRef(api, reference, kind) {
1161
+ if (reference === undefined)
1162
+ return {};
1163
+ const { entry } = findModel(catalogEntries(await api.get("/api/models")), reference, kind);
1164
+ return { model: { provider: entry.provider, modelId: entry.modelId } };
1165
+ }
1166
+ var LANGUAGE_TIERS = ["light", "standard", "heavy"];
1167
+ async function languageModelRef(api, reference) {
1168
+ const tier = LANGUAGE_TIERS.find((candidate) => candidate === reference);
1169
+ if (tier !== undefined)
1170
+ return { model: tier };
1171
+ return modelRef(api, reference, "language");
1172
+ }
1173
+ async function readPrompt(argument, source) {
1174
+ if (argument !== undefined && source !== undefined) {
1175
+ fail("models text takes its prompt as an argument or from --file, not both.");
1176
+ }
1177
+ const prompt = source === undefined ? argument : await readText(source, "Prompt file");
1178
+ if (prompt === undefined) {
1179
+ fail('models text requires a prompt: "<prompt>", or --file <path|-> to read it.');
1180
+ }
1181
+ if (!prompt.trim())
1182
+ fail("The prompt is empty.");
1183
+ return prompt;
1184
+ }
1185
+ function requireOutput(output, command) {
1186
+ if (!output)
1187
+ fail(`${command} requires --output <path>: a file, or a directory ending in /.`);
1188
+ return output;
1189
+ }
1190
+ function optional(field, value) {
1191
+ return value === undefined ? {} : { [field]: value };
1192
+ }
1193
+ async function uploadInput(api, path, kind) {
1194
+ try {
1195
+ await access2(path);
1196
+ } catch {
1197
+ fail(`Input file '${path}' does not exist.`);
1198
+ }
1199
+ const mediaType = MEDIA_TYPES[extname(path).toLowerCase()];
1200
+ if (!mediaType?.startsWith(`${kind}/`)) {
1201
+ const accepted = Object.entries(MEDIA_TYPES).filter(([, type]) => type.startsWith(`${kind}/`)).map(([extension]) => extension);
1202
+ fail(`'${path}' is not an ${kind} file. Use ${accepted.join(", ")}.`);
1203
+ }
1204
+ return api.upload("/api/files", path, undefined, mediaType);
1205
+ }
1206
+ async function savedResult(api, result, output) {
1207
+ return {
1208
+ callId: result.callId,
1209
+ model: result.model.modelId,
1210
+ files: await saveFiles(api, result.callId, result.files ?? [], output),
1211
+ ...result.warnings?.length ? { warnings: result.warnings } : {},
1212
+ cost: describeCost(result.cost)
1213
+ };
1214
+ }
1215
+ async function saveFiles(api, callId, files, output) {
1216
+ const directory = output.endsWith("/") || (await stat(output).catch(() => null))?.isDirectory();
1217
+ const paths = files.map((file, index) => {
1218
+ const number = files.length > 1 ? `-${index + 1}` : "";
1219
+ const extension = (file.mediaType && EXTENSIONS[file.mediaType]) ?? "";
1220
+ if (directory)
1221
+ return join2(output, `${callId}${number}${extension || ".bin"}`);
1222
+ const { dir, name, ext } = parse(output);
1223
+ return join2(dir, `${name}${number}${extension || ext}`);
1224
+ });
1225
+ for (const [index, path] of paths.entries()) {
1226
+ await mkdir(dirname2(path), { recursive: true });
1227
+ await api.download(`/api/ai/model-calls/${idSegment(callId)}/files/content`, path, {
1228
+ path: `/files/${index}`
1229
+ });
1230
+ }
1231
+ return paths;
1232
+ }
1233
+ function describeCost(cost) {
1234
+ if (!cost.money)
1235
+ return "unknown";
1236
+ const nanos = BigInt(cost.money.amountNanos);
1237
+ const whole = nanos / 1000000000n;
1238
+ const fraction = (nanos % 1000000000n).toString().padStart(9, "0").replace(/0+$/, "");
1239
+ return `$${whole}${fraction ? `.${fraction}` : ""}`;
1240
+ }
1241
+ function modelKind(value) {
1242
+ const kind = MODEL_KINDS.find((candidate) => candidate === value);
1243
+ if (!kind)
1244
+ fail(`Unknown model kind '${value}'. Use one of: ${MODEL_KINDS.join(", ")}.`);
1245
+ return kind;
1246
+ }
1247
+ function catalogEntries(catalog) {
1248
+ return [
1249
+ ...catalog.language.map((entry) => ({ kind: "language", entry })),
1250
+ ...catalog.image.map((entry) => ({ kind: "image", entry })),
1251
+ ...catalog.audio.speech.map((entry) => ({ kind: "speech", entry })),
1252
+ ...catalog.audio.transcription.map((entry) => ({ kind: "transcription", entry }))
1253
+ ];
1254
+ }
1255
+ function findModel(entries, reference, kind) {
1256
+ const ofKind = entries.filter((candidate) => kind === undefined || candidate.kind === kind);
1257
+ let matches = ofKind.filter((candidate) => candidate.entry.modelId === reference);
1258
+ const separator = reference.indexOf(":");
1259
+ if (matches.length === 0 && separator > 0) {
1260
+ const provider = reference.slice(0, separator);
1261
+ const modelId = reference.slice(separator + 1);
1262
+ matches = ofKind.filter((candidate) => candidate.entry.provider === provider && candidate.entry.modelId === modelId);
1263
+ }
1264
+ const [match] = matches;
1265
+ if (!match) {
1266
+ fail(`No ${kind === undefined ? "" : `${kind} `}model '${reference}' is configured. Run 'sixb models list'.`);
1267
+ }
1268
+ if (matches.length > 1) {
1269
+ const kinds = new Set(matches.map((candidate) => candidate.kind));
1270
+ 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}.`);
1271
+ }
1272
+ return match;
1273
+ }
1274
+ function describePrice(card) {
1275
+ if (!card)
1276
+ return "price unknown";
1277
+ const parts = [
1278
+ variantPrice(card.images, "per image", "at"),
1279
+ variantPrice(card.characters, "per 1M characters", "for"),
1280
+ variantPrice(card.audioInputSeconds, "per second of audio", "for"),
1281
+ card.input !== undefined && card.output !== undefined ? `${dollars(tokenPrice(card.input))} input \xB7 ${dollars(tokenPrice(card.output))} output per 1M tokens` : undefined,
1282
+ card.audioOutput === undefined ? undefined : `${dollars(tokenPrice(card.audioOutput))} per 1M audio tokens`
1283
+ ].filter((part) => part !== undefined);
1284
+ return parts.length === 0 ? "price unknown" : parts.join(" \xB7 ");
1285
+ }
1286
+ function variantPrice(price, unit, joiner) {
1287
+ if (price === undefined)
1288
+ return;
1289
+ if (typeof price === "string")
1290
+ return `${dollars(price)} ${unit}`;
1291
+ const only = price.default;
1292
+ if (only !== undefined && price.variants.every((variant) => variant.price === only)) {
1293
+ return `${dollars(only)} ${unit}`;
1294
+ }
1295
+ const variants = price.variants.map(({ price: amount, ...dimensions }) => {
1296
+ const label = Object.values(dimensions).join(" ");
1297
+ return `${dollars(amount)} ${joiner} ${label}`;
1298
+ });
1299
+ const otherwise = price.default === undefined ? [] : [`otherwise ${dollars(price.default)}`];
1300
+ return `${unit}: ${[...variants, ...otherwise].join(", ")}`;
1301
+ }
1302
+ function tokenPrice(price) {
1303
+ return typeof price === "string" ? price : price.default;
1304
+ }
1305
+ function dollars(amount) {
1306
+ return `$${amount}`;
1307
+ }
705
1308
 
706
1309
  // src/graph.ts
707
1310
  async function inspectGraph(api, objectTypeId, primaryId, options) {
@@ -968,6 +1571,8 @@ async function objects(api, args) {
968
1571
  return objectsList(api, rest);
969
1572
  case "get":
970
1573
  return objectsGet(api, rest);
1574
+ case "get-by":
1575
+ return objectsGetBy(api, rest);
971
1576
  case "search":
972
1577
  return objectsSearch(api, rest);
973
1578
  case "query":
@@ -1058,6 +1663,31 @@ async function objectsGet(api, args) {
1058
1663
  includeTotal: false
1059
1664
  }));
1060
1665
  }
1666
+ async function objectsGetBy(api, args) {
1667
+ if (requestsHelp(args))
1668
+ return writeText(GET_BY_HELP);
1669
+ const { positionals, options } = parseCommandArgs(args, { "--json": "boolean" }, "objects get-by", 3);
1670
+ const [objectTypeId = "", propertyId = "", text = ""] = positionals;
1671
+ const value = options["--json"] ? parseJsonValue(text) : text;
1672
+ const path = `/api/objects/${encodeURIComponent(objectTypeId)}/by/${encodeURIComponent(propertyId)}`;
1673
+ try {
1674
+ writeJson(await api.post(path, { value }));
1675
+ } catch (error) {
1676
+ if (!isObjectNotFound(error))
1677
+ throw error;
1678
+ writeJson(null);
1679
+ }
1680
+ }
1681
+ function parseJsonValue(text) {
1682
+ try {
1683
+ return JSON.parse(text);
1684
+ } catch {
1685
+ fail(`--json needs the value as JSON, such as 42 or '{"type":"user","id":"usr_1"}'.`);
1686
+ }
1687
+ }
1688
+ function isObjectNotFound(error) {
1689
+ return error instanceof CliError && error.body.status === 404 && error.body.code === "object.not_found";
1690
+ }
1061
1691
  async function objectsSearch(api, args) {
1062
1692
  if (requestsHelp(args))
1063
1693
  return writeText(SEARCH_HELP);
@@ -1238,7 +1868,7 @@ async function ontology(api, args) {
1238
1868
  if (requestsHelp(rest))
1239
1869
  return writeText("Usage: sixb ontology get <object-type>");
1240
1870
  const { positionals } = parseCommandArgs(rest, {}, "ontology get", 1);
1241
- return writeJson(await api.get(`/api/object-types/${encodeURIComponent(positionals[0] ?? "")}`));
1871
+ return writeJson(await api.get(`/api/object-types/${idSegment(positionals[0])}`));
1242
1872
  }
1243
1873
  fail(`Unknown ontology command '${sub}'.`);
1244
1874
  }
@@ -1264,12 +1894,25 @@ var WORKFLOW_RUN_STATUSES = [
1264
1894
  "failed",
1265
1895
  "cancelled"
1266
1896
  ];
1897
+ var DATA_RUN_STATUSES = ["queued", "running", "succeeded", "failed", "cancelled"];
1898
+ var STATUSES = {
1899
+ action: ACTION_RUN_STATUSES,
1900
+ workflow: WORKFLOW_RUN_STATUSES,
1901
+ sync: DATA_RUN_STATUSES,
1902
+ pipeline: DATA_RUN_STATUSES
1903
+ };
1904
+ var FILTERS = {
1905
+ action: { "--action": "actionId", "--type": "objectTypeId", "--id": "primaryId" },
1906
+ workflow: { "--workflow": "workflowId" },
1907
+ sync: { "--sync": "syncId", "--dataset": "datasetId" },
1908
+ pipeline: { "--pipeline": "pipelineId" }
1909
+ };
1267
1910
  async function runs(api, kind, args) {
1268
1911
  const [sub, ...rest] = args;
1269
1912
  const group = `${kind}-runs`;
1270
1913
  if (!sub || isHelp(sub) || requestsHelp(rest))
1271
1914
  return writeText(GROUP_HELP[group]);
1272
- if (sub === "get") {
1915
+ if (sub === "get" && kind !== "sync") {
1273
1916
  const { positionals } = parseCommandArgs(rest, {}, `${group} get`, 1);
1274
1917
  return writeJson(await api.get(`/api/${group}/${encodeURIComponent(positionals[0] ?? "")}`));
1275
1918
  }
@@ -1282,21 +1925,14 @@ async function runs(api, kind, args) {
1282
1925
  "--offset": "offset",
1283
1926
  "--order": "order"
1284
1927
  };
1285
- const action = {
1286
- ...common,
1287
- "--action": "actionId",
1288
- "--type": "objectTypeId",
1289
- "--id": "primaryId"
1290
- };
1291
- const workflow = { ...common, "--workflow": "workflowId" };
1292
- const options = normalizeWindowOptions(parseQueryOptions(rest, kind === "action" ? action : workflow, `${group} list`), {
1928
+ const options = normalizeWindowOptions(parseQueryOptions(rest, { ...common, ...FILTERS[kind] }, `${group} list`), {
1293
1929
  defaultLimit: CLI_LIMITS.list.default,
1294
1930
  maximumLimit: CLI_LIMITS.list.maximum,
1295
1931
  defaultOrder: DEFAULT_LIST_ORDER,
1296
1932
  offset: true
1297
1933
  });
1298
1934
  if (options.status !== undefined) {
1299
- options.status = enumValue("--status", options.status, kind === "action" ? ACTION_RUN_STATUSES : WORKFLOW_RUN_STATUSES);
1935
+ options.status = enumValue("--status", options.status, STATUSES[kind]);
1300
1936
  }
1301
1937
  for (const [name, flag] of [
1302
1938
  ["startedAfter", "--started-after"],
@@ -1350,7 +1986,7 @@ async function telemetry(api, args) {
1350
1986
  fail(`Unknown telemetry command '${sub}'.`);
1351
1987
  }
1352
1988
  function telemetryPath(args, terminal) {
1353
- return `/api/objects/${encodeURIComponent(args[0] ?? "")}/${encodeURIComponent(args[1] ?? "")}/telemetry/${encodeURIComponent(args[2] ?? "")}/${terminal}`;
1989
+ return `/api/objects/${idSegment(args[0])}/${idSegment(args[1])}/telemetry/${idSegment(args[2])}/${terminal}`;
1354
1990
  }
1355
1991
  function normalizeTelemetryQueryInput(input) {
1356
1992
  if (Array.isArray(input) || typeof input !== "object" || input === null) {
@@ -1379,7 +2015,59 @@ function normalizeTelemetryQueryInput(input) {
1379
2015
  return body;
1380
2016
  }
1381
2017
 
2018
+ // src/commands/users.ts
2019
+ var USER_STATUSES = ["active", "suspended", "all"];
2020
+ var LIST_FLAGS = {
2021
+ "--status": "string",
2022
+ "--limit": "string",
2023
+ "--offset": "string"
2024
+ };
2025
+ async function users(api, args) {
2026
+ const [sub, ...rest] = args;
2027
+ if (!sub || isHelp(sub) || requestsHelp(rest))
2028
+ return writeText(GROUP_HELP.users);
2029
+ if (sub === "get") {
2030
+ const { positionals } = parseCommandArgs(rest, {}, "users get", 1);
2031
+ const user = positionals[0]?.trim();
2032
+ if (!user)
2033
+ fail("users get requires a user id or email.");
2034
+ if (!user.includes("@")) {
2035
+ return writeJson(await api.get(`/api/users/${idSegment(user)}`));
2036
+ }
2037
+ const page = await api.get("/api/users", { email: user });
2038
+ const [member] = isRecord(page) && Array.isArray(page.users) ? page.users : [];
2039
+ if (member === undefined) {
2040
+ throw new CliError({ code: "not_found", message: `No member has the email '${user}'.` }, EXIT_API);
2041
+ }
2042
+ return writeJson(member);
2043
+ }
2044
+ if (sub === "list") {
2045
+ const { options } = parseCommandArgs(rest, LIST_FLAGS, "users list");
2046
+ return writeJson(await api.get("/api/users", listQuery(options)));
2047
+ }
2048
+ if (sub === "search") {
2049
+ const { positionals, options } = parseCommandArgs(rest, LIST_FLAGS, "users search", 1);
2050
+ const search = positionals[0]?.trim();
2051
+ if (!search)
2052
+ fail("users search requires the text to find in a name or email.");
2053
+ return writeJson(await api.get("/api/users", { ...listQuery(options), search }));
2054
+ }
2055
+ fail(`Unknown users command '${sub}'.`);
2056
+ }
2057
+ function listQuery(options) {
2058
+ const limit = integerInRange("--limit", options["--limit"] ?? String(CLI_LIMITS.users.default), 1, CLI_LIMITS.users.maximum);
2059
+ return {
2060
+ status: enumValue("--status", options["--status"] ?? "active", USER_STATUSES),
2061
+ limit: String(limit),
2062
+ ...options["--offset"] === undefined ? {} : { offset: String(nonNegativeInteger("--offset", options["--offset"])) }
2063
+ };
2064
+ }
2065
+ function isRecord(value) {
2066
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2067
+ }
2068
+
1382
2069
  // src/commands/workflows.ts
2070
+ import { randomUUID as randomUUID3 } from "crypto";
1383
2071
  async function workflows(api, args) {
1384
2072
  const [sub, ...rest] = args;
1385
2073
  if (!sub || isHelp(sub) || requestsHelp(rest))
@@ -1392,18 +2080,22 @@ async function workflows(api, args) {
1392
2080
  const {
1393
2081
  positionals: [workflowId]
1394
2082
  } = parseCommandArgs(rest, {}, "workflows get", 1);
1395
- return writeJson(await api.get(`/api/workflows/${encodeURIComponent(workflowId ?? "")}`));
2083
+ return writeJson(await api.get(`/api/workflows/${idSegment(workflowId)}`));
1396
2084
  }
1397
2085
  if (sub === "start") {
1398
2086
  const {
1399
2087
  positionals: [workflowId],
1400
2088
  options
1401
- } = parseCommandArgs(rest, { "--file": "string" }, "workflows start", 1);
2089
+ } = parseCommandArgs(rest, { "--file": "string", "--run-id": "string" }, "workflows start", 1);
1402
2090
  const input = options["--file"] ? await readJson(options["--file"]) : {};
1403
2091
  if (Array.isArray(input) || typeof input !== "object" || input === null) {
1404
2092
  fail("Workflow input must be a JSON object.");
1405
2093
  }
1406
- return writeJson(await api.post(`/api/workflows/${encodeURIComponent(workflowId ?? "")}/runs`, { input }));
2094
+ const runId = options["--run-id"] ?? `run_${randomUUID3()}`;
2095
+ return writeRequestResult(await api.post(`/api/workflows/${idSegment(workflowId)}/runs`, {
2096
+ input,
2097
+ runId
2098
+ }));
1407
2099
  }
1408
2100
  fail(`Unknown workflows command '${sub}'.`);
1409
2101
  }
@@ -1418,7 +2110,14 @@ var INSTANCE_COMMANDS = [
1418
2110
  "action-runs",
1419
2111
  "files",
1420
2112
  "workflows",
1421
- "workflow-runs"
2113
+ "workflow-runs",
2114
+ "users",
2115
+ "models",
2116
+ "datasets",
2117
+ "syncs",
2118
+ "sync-runs",
2119
+ "pipelines",
2120
+ "pipeline-runs"
1422
2121
  ];
1423
2122
  function isInstanceCommand(command) {
1424
2123
  return INSTANCE_COMMANDS.includes(command);
@@ -1443,6 +2142,20 @@ async function dispatch(api, command, args) {
1443
2142
  return workflows(api, args);
1444
2143
  case "workflow-runs":
1445
2144
  return runs(api, "workflow", args);
2145
+ case "users":
2146
+ return users(api, args);
2147
+ case "models":
2148
+ return models(api, args);
2149
+ case "datasets":
2150
+ return datasets(api, args);
2151
+ case "syncs":
2152
+ return runnables(api, "syncs", args);
2153
+ case "sync-runs":
2154
+ return runs(api, "sync", args);
2155
+ case "pipelines":
2156
+ return runnables(api, "pipelines", args);
2157
+ case "pipeline-runs":
2158
+ return runs(api, "pipeline", args);
1446
2159
  default:
1447
2160
  fail(`Unknown command '${command}'. Run 'sixb --help'.`);
1448
2161
  }
@@ -1457,29 +2170,54 @@ function createInstanceApiClient(mode) {
1457
2170
  baseUrl: mode.baseUrl,
1458
2171
  ...mode.kind === "local" && mode.token ? { authorization: `Bearer ${mode.token}` } : {},
1459
2172
  missingBaseUrlMessage: mode.kind === "sandbox" ? "SIXB_API_BASE_URL is not set." : "The selected Sixb profile has no API URL.",
1460
- unavailableMessage: mode.kind === "sandbox" ? "The Sixb API gateway could not be reached." : "The Sixb API could not be reached.",
1461
- unavailableHint: mode.kind === "sandbox" ? "Run 'sixb doctor' to verify the sandbox runtime and gateway." : "Run 'sixb status' to verify the current profile."
2173
+ unavailableMessage: mode.kind === "sandbox" ? "The Sixb API could not be reached from the sandbox." : "The Sixb API could not be reached.",
2174
+ unavailableHint: mode.kind === "sandbox" ? "Run 'sixb status' to verify the sandbox runtime and its API access." : "Run 'sixb status' to verify the current profile."
1462
2175
  });
1463
2176
  }
2177
+
2178
+ // src/commands/system.ts
2179
+ var AGENT_RUNTIME_PROFILE = "sixb-agent-runtime/v1";
2180
+ // src/memory.ts
2181
+ import { createHash } from "crypto";
2182
+ var AGENT_MEMORY_MANIFEST = "context/memory.json";
2183
+ var AGENT_MEMORY_ENTRY = "MEMORY.md";
2184
+ var AGENT_MEMORY_STARTER = `# Memory
2185
+
2186
+ `;
2187
+ function agentMemoryFileHash(contents) {
2188
+ return createHash("sha256").update(contents).digest("hex");
2189
+ }
2190
+ // src/sandbox-cli.ts
2191
+ import { readFile as readFile3 } from "fs/promises";
2192
+ var cachedAssets;
2193
+ function loadSandboxCliAssets() {
2194
+ cachedAssets ??= Promise.all([
2195
+ readFile3(new URL("./bin/sixb", import.meta.url), "utf8"),
2196
+ readFile3(new URL("./generated/sixb.mjs", import.meta.url))
2197
+ ]).then(([launcher, artifact]) => ({ launcher, artifact }));
2198
+ return cachedAssets;
2199
+ }
1464
2200
  export {
2201
+ AGENT_MEMORY_ENTRY,
2202
+ AGENT_MEMORY_MANIFEST,
2203
+ AGENT_MEMORY_STARTER,
2204
+ AGENT_RUNTIME_PROFILE,
1465
2205
  ApiClient,
1466
2206
  CliError,
1467
2207
  EXIT_API,
1468
2208
  EXIT_USAGE,
1469
2209
  INSTANCE_CLI_VERSION,
1470
2210
  INSTANCE_COMMANDS,
1471
- asRecord2 as asRecord,
2211
+ agentMemoryFileHash,
1472
2212
  createInstanceApiClient,
1473
2213
  fail,
1474
- isFileError,
1475
- isHelp,
1476
2214
  isInstanceCommand,
2215
+ loadSandboxCliAssets,
1477
2216
  renderInstanceHelp,
1478
2217
  reportError,
1479
2218
  requestsHelp,
1480
2219
  runInstanceCli,
1481
- writeJson,
1482
- writeText
2220
+ writeJson
1483
2221
  };
1484
2222
 
1485
- //# debugId=D8A3DF8338E3251F64756E2164756E21
2223
+ //# debugId=3808833041577FAB64756E2164756E21