@standards/cli 1.0.0-alpha.445 → 1.0.0-alpha.447

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 (2) hide show
  1. package/dist/bin.mjs +52 -31
  2. package/package.json +4 -4
package/dist/bin.mjs CHANGED
@@ -3,17 +3,17 @@
3
3
  import { t as discoverBundles } from "./discover-bundles-DPpEZVv0.mjs";
4
4
  import { createRequire } from "node:module";
5
5
  import { styleText } from "node:util";
6
- import { BundleConflictError, StandardsRequestError, createTransport, previewBundleSync, syncBundle } from "@standards/client";
6
+ import { BundleConflictError, StandardsRequestError, createTransport, previewBundleSync, syncBundle, uploadAll } from "@standards/client";
7
7
  import { Command, Option } from "commander";
8
8
  import { BUNDLE_LOCALES, COUNT_MODES, DECISION_TYPE_AGENT_SETUP, DECISION_TYPE_BUNDLE_SETUP, ENVIRONMENT_HARNESSES, ENVIRONMENT_IMAGES, ENVIRONMENT_SIZES, LINK_MAX_MS, NotFoundError, PROVIDER_NAMES, RECORD_ACCESS_ROLES, RESOURCE_VISIBILITIES, SchemaError, SchemaErrorCode, ValidationError, canonicalStringify, diffSchemaState, environmentHarness, formatByteSize, formatDecisionLink, isEnvironmentHarnessId, isPlainRecord, isSkillRepository, parseRecordAccessRole, parseSchemaState, parseSkillSource, schemaStateSourcePayload } from "@standards/schema";
9
- import { createWriteStream } from "node:fs";
9
+ import { createWriteStream, openAsBlob } from "node:fs";
10
10
  import { chmod, lstat, mkdir, readFile, readdir, readlink, stat, symlink, unlink, writeFile } from "node:fs/promises";
11
11
  import { basename, dirname, join, relative, resolve, sep } from "node:path";
12
12
  import Table from "cli-table3";
13
13
  import { StandardsRequestError as StandardsRequestError$1, TURN_END_TYPES, createAppClient, openAgentStream, schemaApiErrorCode } from "@standards/client/app";
14
- import { homedir, hostname } from "node:os";
15
14
  import { Readable } from "node:stream";
16
15
  import { pipeline } from "node:stream/promises";
16
+ import { homedir, hostname } from "node:os";
17
17
  import { buildOperationPath, recordsOperations } from "@standards/schema/api";
18
18
  import { stdin } from "node:process";
19
19
  import { createInterface } from "node:readline/promises";
@@ -960,6 +960,36 @@ function registerEnvironmentsCommand(program) {
960
960
  printDone(`Environment ${id} archived.`);
961
961
  });
962
962
  }
963
+ // src/commands/transfer.ts
964
+ /**
965
+ * Local files sent straight to storage, resumable, read from disk as they go
966
+ * (never whole in memory). One answer per path, in order; the first file the
967
+ * API refuses stops the command with its reason, and the files of the batch
968
+ * already stored are deleted.
969
+ */
970
+ async function uploadLocalFiles(client, paths, options = {}) {
971
+ const files = await Promise.all(paths.map(async (path) => Object.assign(await openAsBlob(path), { name: basename(path) })));
972
+ return await uploadAll(client, {
973
+ files,
974
+ protocol: "tus",
975
+ ...options.attachTo === void 0 ? {} : { attachTo: options.attachTo }
976
+ });
977
+ }
978
+ /**
979
+ * Streams a response's body to `output`, or to stdout without one. A file
980
+ * left half written is removed: a truncated file would pass for a whole one.
981
+ */
982
+ async function writeBody(response, output) {
983
+ if (!response.body) throw new StandardsRequestError(response.status, "The response had no body");
984
+ const source = Readable.fromWeb(response.body);
985
+ if (!output) return await pipeline(source, process.stdout);
986
+ try {
987
+ await pipeline(source, createWriteStream(output));
988
+ } catch (error) {
989
+ await unlink(output).catch(() => void 0);
990
+ throw error;
991
+ }
992
+ }
963
993
  // src/commands/files.ts
964
994
  function registerFilesCommand(program) {
965
995
  const files = program.command("files").description("Upload, list, download and delete files");
@@ -975,15 +1005,17 @@ function registerFilesCommand(program) {
975
1005
  files.command("get").description("Get a file by ID").argument("<id>", "file ID").action(async (id, _opts, cmd) => {
976
1006
  formatOutput(await getAppClientFromCommand(cmd).files.get({ id }), getFormat(cmd));
977
1007
  });
978
- files.command("upload").description("Upload local files (private); prints the stored files and their IDs").argument("<paths...>", "local file paths (at most 10)").action(async (paths, _opts, cmd) => {
979
- const uploads = await Promise.all(paths.map(async (path) => new File([await readFile(path)], basename(path))));
980
- formatOutput(await getAppClientFromCommand(cmd).files.upload({ files: uploads }), getFormat(cmd));
1008
+ files.command("upload").description("Upload local files (private), resuming after a dropped connection; prints the stored files and their IDs").argument("<paths...>", "local file paths").action(async (paths, _opts, cmd) => {
1009
+ formatOutput((await uploadLocalFiles(getAppClientFromCommand(cmd), paths)).map(({ file }) => file), getFormat(cmd));
981
1010
  });
982
1011
  files.command("content").description("Download a file's bytes (to stdout, or -o <file>)").argument("<id>", "file ID").option("-o, --output <file>", "write to a file instead of stdout").action(async (id, opts, cmd) => {
983
- const { blob } = await getAppClientFromCommand(cmd).files.content({ id });
984
- const bytes = new Uint8Array(await blob.arrayBuffer());
985
- if (opts.output) return await writeFile(opts.output, bytes);
986
- process.stdout.write(bytes);
1012
+ const { url } = await getAppClientFromCommand(cmd).files.signedUrl({
1013
+ id,
1014
+ download: true
1015
+ });
1016
+ const response = await fetch(url);
1017
+ if (!response.ok) throw new StandardsRequestError(response.status, `Storage refused the download (HTTP ${response.status})`);
1018
+ await writeBody(response, opts.output);
987
1019
  });
988
1020
  files.command("delete").description("Delete a file you uploaded and its stored bytes (not a file in a document pack)").argument("<id>", "file ID").option("--yes", "delete without confirmation").action(async (id, opts, cmd) => {
989
1021
  requireConfirmation(opts.yes, "Deleting a file purges its bytes. Re-run with \"--yes\" to confirm.");
@@ -1453,22 +1485,13 @@ function registerRecordsCommand(program) {
1453
1485
  filter: opts.filter,
1454
1486
  sort: opts.sort
1455
1487
  });
1456
- const response = await client.postRaw(buildOperationPath(recordsOperations.export, { objectName }), {
1488
+ await writeBody(await client.postRaw(buildOperationPath(recordsOperations.export, { objectName }), {
1457
1489
  q: opts.query ?? "",
1458
1490
  columns: parseCsvList(opts.columns),
1459
1491
  ...filters ? { filters } : {},
1460
1492
  ...sorts ? { sorts } : {},
1461
1493
  timezone: opts.timezone ?? Intl.DateTimeFormat().resolvedOptions().timeZone
1462
- });
1463
- if (!response.body) throw new StandardsRequestError(response.status, "The export response had no body");
1464
- const source = Readable.fromWeb(response.body);
1465
- if (!opts.output) return await pipeline(source, process.stdout);
1466
- try {
1467
- await pipeline(source, createWriteStream(opts.output));
1468
- } catch (error) {
1469
- await unlink(opts.output).catch(() => void 0);
1470
- throw error;
1471
- }
1494
+ }), opts.output);
1472
1495
  });
1473
1496
  records.command("set-logo").description("Set a record's image to the logo of a company's domain").argument("<object>", "object name (e.g. companies)").argument("<recordId>", "record ID").argument("<domain>", "website or bare domain (e.g. acme.com)").action(async (objectName, recordId, domain, _opts, cmd) => {
1474
1497
  formatOutput(await getAppClientFromCommand(cmd).records.setLogo({
@@ -1478,16 +1501,14 @@ function registerRecordsCommand(program) {
1478
1501
  }), getFormat(cmd));
1479
1502
  });
1480
1503
  records.command("attach-document").description("Upload and attach a local file to a record").argument("<object>", "object name (e.g. contacts)").argument("<recordId>", "record ID").requiredOption("--file <path>", "local file path to upload").option("--attribute <attr>", "attribute name to attach the document to").option("--parent-id <folderId>", "parent folder ID").option("--title <title>", "document title (defaults to filename)").action(async (objectName, recordId, opts, cmd) => {
1481
- const fileContent = await readFile(opts.file);
1482
- const fileName = basename(opts.file);
1483
- formatOutput(await getAppClientFromCommand(cmd).records.attachDocument({
1504
+ const [uploaded] = await uploadLocalFiles(getAppClientFromCommand(cmd), [opts.file], { attachTo: {
1484
1505
  objectName,
1485
- id: recordId,
1486
- title: opts.title ?? fileName,
1506
+ recordId,
1507
+ title: opts.title ?? basename(opts.file),
1487
1508
  ...opts.attribute ? { attributeName: opts.attribute } : {},
1488
- ...opts.parentId ? { parentId: opts.parentId } : {},
1489
- files: new File([fileContent], fileName)
1490
- }), getFormat(cmd));
1509
+ ...opts.parentId ? { parentId: opts.parentId } : {}
1510
+ } });
1511
+ formatOutput(uploaded?.attached, getFormat(cmd));
1491
1512
  });
1492
1513
  records.command("attach-file").description("Attach an already uploaded file to a record").argument("<object>", "object name (e.g. contacts)").argument("<recordId>", "record ID").requiredOption("--file-id <fileId>", "ID of a private uploaded file").option("--attribute <attr>", "document attribute (defaults to the record's attachments)").option("--document-id <documentId>", "append to this document's pack instead of creating one").option("--title <title>", "title of a new document (defaults to the file name without its extension)").action(async (objectName, recordId, opts, cmd) => {
1493
1514
  formatOutput(await getAppClientFromCommand(cmd).records.attachFile({
@@ -2586,11 +2607,11 @@ async function readFolder(client, dir, skipped) {
2586
2607
  });
2587
2608
  continue;
2588
2609
  }
2589
- const [stored] = await client.files.upload({ files: [new File([bytes], basename(absolute))] });
2610
+ const [stored] = await uploadLocalFiles(client, [absolute]);
2590
2611
  if (!stored) throw new Error(`The upload of ${absolute} answered no file`);
2591
2612
  files.push({
2592
2613
  path,
2593
- fileId: stored.id
2614
+ fileId: stored.file.id
2594
2615
  });
2595
2616
  }
2596
2617
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@standards/cli",
3
- "version": "1.0.0-alpha.445",
3
+ "version": "1.0.0-alpha.447",
4
4
  "description": "CLI tool to interact with Standards API",
5
5
  "type": "module",
6
6
  "bin": {
@@ -24,9 +24,9 @@
24
24
  "node": ">=22"
25
25
  },
26
26
  "dependencies": {
27
- "@standards/client": "1.0.0-alpha.445",
28
- "@standards/constants": "1.0.0-alpha.445",
29
- "@standards/schema": "1.0.0-alpha.445",
27
+ "@standards/client": "1.0.0-alpha.447",
28
+ "@standards/constants": "1.0.0-alpha.447",
29
+ "@standards/schema": "1.0.0-alpha.447",
30
30
  "cli-table3": "^0.6.5",
31
31
  "commander": "^13.1.0",
32
32
  "jiti": "2.7.0",