@shelby-protocol/cli 0.0.4 → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +2 -15
  2. package/bin/entry.js +403 -111
  3. package/package.json +2 -4
package/README.md CHANGED
@@ -33,18 +33,5 @@ shelby <command> [options]
33
33
 
34
34
  ## Binary Release
35
35
 
36
- ### Install `Bun`
37
-
38
- On homebrew, you can do:
39
-
40
- ```bash
41
- brew tap oven-sh/bun
42
-
43
- brew install bun
44
- ```
45
-
46
- If you don't have homebrew, see https://bun.sh/get
47
-
48
- ### Run `scripts/bun-release.sh`
49
-
50
- From `apps/cli` run `bash scripts/bun-release.sh`
36
+ 1. Bump version
37
+ 2. Run `npm publish`
package/bin/entry.js CHANGED
@@ -4,7 +4,7 @@
4
4
  import { Command } from "commander";
5
5
 
6
6
  // package.json
7
- var version = "0.0.4";
7
+ var version = "0.0.6";
8
8
 
9
9
  // src/commands/init.tsx
10
10
  import { render } from "ink";
@@ -444,6 +444,135 @@ var ContextWizard = ({ onComplete }) => {
444
444
  return null;
445
445
  };
446
446
 
447
+ // src/components/UpdateContextWizard.tsx
448
+ import { Box as Box4, Text as Text4 } from "ink";
449
+ import TextInput4 from "ink-text-input";
450
+ import { useEffect as useEffect4, useState as useState4 } from "react";
451
+
452
+ // src/schemas/AptosNetworkSchema.ts
453
+ import { z } from "zod";
454
+ var AptosNetworkSchema = z.enum([
455
+ "mainnet",
456
+ "testnet",
457
+ "devnet",
458
+ "local"
459
+ ]);
460
+
461
+ // src/schemas/EndpointSchema.ts
462
+ import { z as z2 } from "zod";
463
+ var EndpointSchema = z2.string().url("Must be a valid URL");
464
+
465
+ // src/components/UpdateContextWizard.tsx
466
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
467
+ var UpdateContextWizard = ({
468
+ contextName,
469
+ currentShelbyRpc,
470
+ currentAptosNetwork,
471
+ onComplete
472
+ }) => {
473
+ const [step, setStep] = useState4("shelbyRpc");
474
+ const [shelbyRpcEndpoint, setShelbyRpcEndpoint] = useState4(currentShelbyRpc);
475
+ const [aptosNetwork, setAptosNetwork] = useState4(currentAptosNetwork);
476
+ const [error, setError] = useState4("");
477
+ useEffect4(() => {
478
+ if (step === "done") {
479
+ const updates = {};
480
+ if (shelbyRpcEndpoint !== currentShelbyRpc) {
481
+ updates.shelbyRpcEndpoint = shelbyRpcEndpoint;
482
+ }
483
+ if (aptosNetwork !== currentAptosNetwork) {
484
+ updates.aptosNetwork = aptosNetwork;
485
+ }
486
+ onComplete(updates);
487
+ }
488
+ }, [
489
+ step,
490
+ shelbyRpcEndpoint,
491
+ aptosNetwork,
492
+ currentShelbyRpc,
493
+ currentAptosNetwork,
494
+ onComplete
495
+ ]);
496
+ if (step === "shelbyRpc") {
497
+ return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
498
+ /* @__PURE__ */ jsxs4(Text4, { children: [
499
+ "Updating context: ",
500
+ contextName
501
+ ] }),
502
+ /* @__PURE__ */ jsxs4(Text4, { children: [
503
+ "Shelby RPC endpoint? (current: ",
504
+ currentShelbyRpc,
505
+ ")"
506
+ ] }),
507
+ error && /* @__PURE__ */ jsxs4(Text4, { color: "red", children: [
508
+ "Error: ",
509
+ error
510
+ ] }),
511
+ /* @__PURE__ */ jsx4(
512
+ TextInput4,
513
+ {
514
+ value: shelbyRpcEndpoint,
515
+ placeholder: currentShelbyRpc,
516
+ onChange: (value) => {
517
+ setShelbyRpcEndpoint(value);
518
+ setError("");
519
+ },
520
+ onSubmit: () => {
521
+ if (shelbyRpcEndpoint && shelbyRpcEndpoint !== currentShelbyRpc) {
522
+ const result = EndpointSchema.safeParse(shelbyRpcEndpoint);
523
+ if (!result.success) {
524
+ setError(result.error.errors[0].message);
525
+ return;
526
+ }
527
+ }
528
+ setError("");
529
+ setStep("aptosApi");
530
+ }
531
+ }
532
+ ),
533
+ /* @__PURE__ */ jsx4(Text4, { color: "gray", children: "Press Enter to continue" })
534
+ ] });
535
+ }
536
+ if (step === "aptosApi") {
537
+ return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
538
+ /* @__PURE__ */ jsxs4(Text4, { children: [
539
+ "Aptos network? (current: ",
540
+ currentAptosNetwork,
541
+ ")"
542
+ ] }),
543
+ error && /* @__PURE__ */ jsxs4(Text4, { color: "red", children: [
544
+ "Error: ",
545
+ error
546
+ ] }),
547
+ /* @__PURE__ */ jsx4(
548
+ TextInput4,
549
+ {
550
+ value: aptosNetwork,
551
+ placeholder: currentAptosNetwork,
552
+ onChange: (value) => {
553
+ setAptosNetwork(value);
554
+ setError("");
555
+ },
556
+ onSubmit: () => {
557
+ if (aptosNetwork && aptosNetwork !== currentAptosNetwork) {
558
+ const result = AptosNetworkSchema.safeParse(aptosNetwork);
559
+ if (!result.success) {
560
+ setError("Must be one of: mainnet, testnet, devnet, local");
561
+ return;
562
+ }
563
+ }
564
+ setError("");
565
+ setStep("done");
566
+ }
567
+ }
568
+ ),
569
+ /* @__PURE__ */ jsx4(Text4, { color: "gray", children: "Valid options: mainnet, testnet, devnet, local" }),
570
+ /* @__PURE__ */ jsx4(Text4, { color: "gray", children: "Press Enter to save changes" })
571
+ ] });
572
+ }
573
+ return null;
574
+ };
575
+
447
576
  // src/utils/config.ts
448
577
  import os from "os";
449
578
  import path from "path";
@@ -455,7 +584,7 @@ import {
455
584
  } from "@aptos-labs/ts-sdk";
456
585
  import fs from "fs-extra";
457
586
  import YAML from "yaml";
458
- import { z } from "zod";
587
+ import { z as z3 } from "zod";
459
588
 
460
589
  // tests/utils/mockConfig.ts
461
590
  var mockAccountAlice = generateEd25519Account();
@@ -493,20 +622,20 @@ var mockConfig = {
493
622
  var DEFAULT_CONFIG_PATH = "~/.shelby/config.yaml";
494
623
 
495
624
  // src/utils/config.ts
496
- var ContextSchema = z.object({
497
- aptos_network: z.enum(["mainnet", "testnet", "devnet", "local"]),
498
- shelby_rpc_endpoint: z.string().url()
625
+ var ContextSchema = z3.object({
626
+ aptos_network: AptosNetworkSchema,
627
+ shelby_rpc_endpoint: EndpointSchema
499
628
  });
500
- var ConfigSchema = z.object({
501
- contexts: z.record(ContextSchema),
502
- accounts: z.record(
503
- z.object({
504
- address: z.string().optional(),
505
- private_key: z.string({ message: "private_key must be provided" })
629
+ var ConfigSchema = z3.object({
630
+ contexts: z3.record(ContextSchema),
631
+ accounts: z3.record(
632
+ z3.object({
633
+ address: z3.string().optional(),
634
+ private_key: z3.string({ message: "private_key must be provided" })
506
635
  })
507
636
  ),
508
- default_context: z.string(),
509
- default_account: z.string()
637
+ default_context: z3.string(),
638
+ default_account: z3.string()
510
639
  });
511
640
  function resolveConfigPath(configPath) {
512
641
  if (configPath.startsWith("~")) {
@@ -618,7 +747,7 @@ function denormBlobName(pathModule, blobPrefix, blobName, outputPrefix) {
618
747
  }
619
748
 
620
749
  // src/commands/init.tsx
621
- import { jsx as jsx4 } from "react/jsx-runtime";
750
+ import { jsx as jsx5 } from "react/jsx-runtime";
622
751
  function initCommand(program) {
623
752
  program.command("init").description(
624
753
  "Bootstrap your Shelby config via an interactive wizard or flags"
@@ -690,7 +819,7 @@ function initCommand(program) {
690
819
  );
691
820
  } else {
692
821
  const { unmount } = render(
693
- /* @__PURE__ */ jsx4(
822
+ /* @__PURE__ */ jsx5(
694
823
  InitWizard,
695
824
  {
696
825
  onComplete: (config) => {
@@ -753,8 +882,8 @@ function getChunksetSizeBytes() {
753
882
 
754
883
  // ../../packages/sdk/dist/chunk-LATCKOZE.mjs
755
884
  import { AccountAddress as AccountAddress2 } from "@aptos-labs/ts-sdk";
756
- import { z as z2 } from "zod";
757
- var BlobNameSchema = z2.string().min(1, "Blob name path parameter cannot be empty.").max(1024, "Blob name cannot exceed 1024 characters.").refine((name2) => !name2.endsWith("/"), {
885
+ import { z as z4 } from "zod";
886
+ var BlobNameSchema = z4.string().min(1, "Blob name path parameter cannot be empty.").max(1024, "Blob name cannot exceed 1024 characters.").refine((name2) => !name2.endsWith("/"), {
758
887
  message: "Blob name cannot end with a slash"
759
888
  });
760
889
  function roundSize(size) {
@@ -1335,12 +1464,12 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1335
1464
  };
1336
1465
 
1337
1466
  // ../../packages/sdk/dist/chunk-OG3JQI44.mjs
1338
- import { z as z3 } from "zod";
1339
- var ChunksetCommitmentSchema = z3.object({
1467
+ import { z as z5 } from "zod";
1468
+ var ChunksetCommitmentSchema = z5.object({
1340
1469
  // Chunkset root (vector commitment of child chunks)
1341
- chunkset_root: z3.string().nullable(),
1470
+ chunkset_root: z5.string().nullable(),
1342
1471
  // the size is known statically from the current configuration
1343
- chunk_commitments: z3.array(z3.string())
1472
+ chunk_commitments: z5.array(z5.string())
1344
1473
  }).refine(
1345
1474
  (data) => {
1346
1475
  return data.chunk_commitments.length === ERASURE_K + ERASURE_M;
@@ -1353,13 +1482,13 @@ var ChunksetCommitmentSchema = z3.object({
1353
1482
  function expectedTotalChunksets(rawSize) {
1354
1483
  return roundSize(rawSize) / getChunksetSizeBytes();
1355
1484
  }
1356
- var BlobCommitmentsSchema = z3.object({
1357
- schema_version: z3.string(),
1358
- raw_data_size: z3.number(),
1485
+ var BlobCommitmentsSchema = z5.object({
1486
+ schema_version: z5.string(),
1487
+ raw_data_size: z5.number(),
1359
1488
  // FIXME I am not sure about this being here, or if it should be somewhere else
1360
1489
  // I deleted the blob_commitment. What's the difference?
1361
- blob_merkle_root: z3.string(),
1362
- chunkset_commitments: z3.array(ChunksetCommitmentSchema)
1490
+ blob_merkle_root: z5.string(),
1491
+ chunkset_commitments: z5.array(ChunksetCommitmentSchema)
1363
1492
  }).refine(
1364
1493
  (data) => {
1365
1494
  return expectedTotalChunksets(data.raw_data_size) === data.chunkset_commitments.length;
@@ -1377,20 +1506,20 @@ import { hashValues } from "@aptos-labs/ts-sdk";
1377
1506
  // src/commands/account.tsx
1378
1507
  import Table from "cli-table3";
1379
1508
  import { render as render2 } from "ink";
1380
- import { z as z4 } from "zod";
1381
- import { jsx as jsx5 } from "react/jsx-runtime";
1382
- var CreateAccountOptionsSchema = z4.object({
1383
- name: z4.string().optional(),
1384
- privateKey: z4.string().optional(),
1385
- address: z4.string().optional(),
1386
- scheme: z4.union([z4.literal(ED25519_NAME), z4.literal(SECP256K1_NAME)]).optional()
1509
+ import { z as z6 } from "zod";
1510
+ import { jsx as jsx6 } from "react/jsx-runtime";
1511
+ var CreateAccountOptionsSchema = z6.object({
1512
+ name: z6.string().optional(),
1513
+ privateKey: z6.string().optional(),
1514
+ address: z6.string().optional(),
1515
+ scheme: z6.union([z6.literal(ED25519_NAME), z6.literal(SECP256K1_NAME)]).optional()
1387
1516
  });
1388
- var ListAccountOptionsSchema = z4.object({});
1389
- var UseAccountOptionsSchema = z4.object({
1390
- accountName: z4.string()
1517
+ var ListAccountOptionsSchema = z6.object({});
1518
+ var UseAccountOptionsSchema = z6.object({
1519
+ accountName: z6.string()
1391
1520
  });
1392
- var DeleteAccountOptionsSchema = z4.object({
1393
- accountName: z4.string()
1521
+ var DeleteAccountOptionsSchema = z6.object({
1522
+ accountName: z6.string()
1394
1523
  });
1395
1524
  function accountCommand(program) {
1396
1525
  const account = program.command("account").description("Manage signing accounts (addresses & keys)");
@@ -1428,7 +1557,7 @@ function accountCommand(program) {
1428
1557
  } else {
1429
1558
  try {
1430
1559
  const { unmount } = render2(
1431
- /* @__PURE__ */ jsx5(
1560
+ /* @__PURE__ */ jsx6(
1432
1561
  AccountWizard,
1433
1562
  {
1434
1563
  onComplete: ({
@@ -1533,23 +1662,21 @@ function accountCommand(program) {
1533
1662
  const configPath = program.opts().configFile;
1534
1663
  try {
1535
1664
  const config = loadConfig(configPath);
1665
+ const accountName = program.opts().account || config.default_account;
1536
1666
  const shelbyConfig = getCurrentShelbyConfig(config, {
1537
1667
  context: program.opts().context
1538
1668
  });
1539
- const activeAccount = getCurrentAccount(
1540
- config,
1541
- program.opts().account
1542
- ).account;
1669
+ const activeAccount = getCurrentAccount(config, accountName).account;
1543
1670
  const aptos = new Aptos3(shelbyConfig.aptos.config);
1544
1671
  const blobClient = new ShelbyBlobClient({ aptos });
1545
1672
  console.log(
1546
- `\u{1F50D} Retrieving blobs for ${activeAccount.toString()} (${activeAccount.accountAddress.toString()})`
1673
+ `\u{1F50D} Retrieving blobs for ${accountName} (${activeAccount.accountAddress.toString()})`
1547
1674
  );
1548
1675
  const blobs = await blobClient.getAccountBlobs({
1549
1676
  account: activeAccount.accountAddress
1550
1677
  });
1551
1678
  console.log(
1552
- `\u2705 Retrieved ${blobs.length} blobs for ${activeAccount.toString()}`
1679
+ `\u2705 Retrieved ${blobs.length} blobs for ${accountName} (${activeAccount.accountAddress.toString()})`
1553
1680
  );
1554
1681
  const table = new Table({
1555
1682
  head: [
@@ -1594,13 +1721,18 @@ function accountCommand(program) {
1594
1721
  // src/commands/context.tsx
1595
1722
  import Table2 from "cli-table3";
1596
1723
  import { render as render3 } from "ink";
1597
- import { z as z5 } from "zod";
1598
- import { jsx as jsx6 } from "react/jsx-runtime";
1599
- var CreateContextOptionsSchema = z5.object({
1600
- name: z5.string().optional(),
1601
- shelbyRpcEndpoint: z5.string().optional(),
1602
- aptosNetwork: z5.enum(["mainnet", "testnet", "devnet", "local"]).optional()
1603
- // TODO maybe also indexer endpoints...
1724
+ import { z as z7 } from "zod";
1725
+ import { jsx as jsx7 } from "react/jsx-runtime";
1726
+ var CreateContextOptionsSchema = z7.object({
1727
+ name: z7.string().optional(),
1728
+ shelbyRpcEndpoint: EndpointSchema.optional(),
1729
+ aptosNetwork: AptosNetworkSchema.optional()
1730
+ // TODO: Add support for custom endpoints objects
1731
+ });
1732
+ var UpdateContextOptionsSchema = z7.object({
1733
+ shelbyRpcEndpoint: EndpointSchema.optional(),
1734
+ aptosNetwork: AptosNetworkSchema.optional()
1735
+ // TODO: Add support for custom endpoints objects
1604
1736
  });
1605
1737
  function contextCommand(program) {
1606
1738
  const context = program.command("context").description("Manage network contexts (Shelby RPC & Aptos endpoints)");
@@ -1629,7 +1761,7 @@ function contextCommand(program) {
1629
1761
  return;
1630
1762
  }
1631
1763
  const { unmount } = render3(
1632
- /* @__PURE__ */ jsx6(
1764
+ /* @__PURE__ */ jsx7(
1633
1765
  ContextWizard,
1634
1766
  {
1635
1767
  onComplete: ({
@@ -1650,6 +1782,59 @@ function contextCommand(program) {
1650
1782
  )
1651
1783
  );
1652
1784
  });
1785
+ context.command("update").description("Update an existing context").argument("<context-name>", "Name of the context to update").option("--shelby-rpc-endpoint <url>", "URL of the Shelby RPC service").option("--aptos-network <network>", "Aptos network").action((contextName, options) => {
1786
+ UpdateContextOptionsSchema.parse(options);
1787
+ const { shelbyRpcEndpoint, aptosNetwork } = options;
1788
+ const configPath = program.opts().configFile;
1789
+ let config;
1790
+ try {
1791
+ config = loadConfig(configPath);
1792
+ } catch (error) {
1793
+ console.error(`Error loading config: ${error.message}`);
1794
+ process.exit(1);
1795
+ }
1796
+ if (!config.contexts[contextName]) {
1797
+ console.error(`Error: Context '${contextName}' not found`);
1798
+ process.exit(1);
1799
+ }
1800
+ const currentContext = config.contexts[contextName];
1801
+ if (shelbyRpcEndpoint || aptosNetwork) {
1802
+ if (shelbyRpcEndpoint) {
1803
+ config.contexts[contextName].shelby_rpc_endpoint = shelbyRpcEndpoint;
1804
+ }
1805
+ if (aptosNetwork) {
1806
+ config.contexts[contextName].aptos_network = aptosNetwork;
1807
+ }
1808
+ saveConfig(config, configPath);
1809
+ console.log(`\u2705 Context '${contextName}' updated successfully`);
1810
+ return;
1811
+ }
1812
+ const { unmount } = render3(
1813
+ /* @__PURE__ */ jsx7(
1814
+ UpdateContextWizard,
1815
+ {
1816
+ contextName,
1817
+ currentShelbyRpc: currentContext.shelby_rpc_endpoint,
1818
+ currentAptosNetwork: currentContext.aptos_network,
1819
+ onComplete: ({
1820
+ shelbyRpcEndpoint: shelbyRpcEndpoint2,
1821
+ aptosNetwork: aptosNetwork2
1822
+ }) => {
1823
+ if (shelbyRpcEndpoint2) {
1824
+ config.contexts[contextName].shelby_rpc_endpoint = shelbyRpcEndpoint2;
1825
+ }
1826
+ if (aptosNetwork2) {
1827
+ config.contexts[contextName].aptos_network = aptosNetwork2;
1828
+ }
1829
+ saveConfig(config, configPath);
1830
+ console.log(`\u2705 Context '${contextName}' updated successfully`);
1831
+ unmount();
1832
+ process.exit(0);
1833
+ }
1834
+ }
1835
+ )
1836
+ );
1837
+ });
1653
1838
  context.command("list").description("List all contexts in a table").action(() => {
1654
1839
  const configPath = program.opts().configFile;
1655
1840
  let config;
@@ -1729,16 +1914,16 @@ import { Transform as Transform2 } from "stream";
1729
1914
  import { pipeline } from "stream/promises";
1730
1915
  import { Aptos as Aptos4 } from "@aptos-labs/ts-sdk";
1731
1916
  import ora from "ora";
1732
- import { z as z6 } from "zod";
1917
+ import { z as z8 } from "zod";
1733
1918
  var denormBlobName2 = (a, b, c) => denormBlobName(path3, a, b, c);
1734
1919
  var endsWithDirectorySeparator = (filePath) => {
1735
1920
  return filePath.endsWith("/") || filePath.endsWith("\\") || filePath.endsWith(path3.sep);
1736
1921
  };
1737
- var DownloadOptionsSchema = z6.object({
1738
- input: z6.string().min(1, "Input must be a valid blob name or directory prefix").describe("Blob name or directory prefix to download"),
1739
- output: z6.string().min(1, "Output must be a valid filepath").describe("Local path where to save the downloaded content"),
1740
- recursive: z6.boolean().default(false).describe("Download assuming canonical directory layout and recurse"),
1741
- force: z6.boolean().default(false).describe("Overwrite the output if it already exists")
1922
+ var DownloadOptionsSchema = z8.object({
1923
+ input: z8.string().min(1, "Input must be a valid blob name or directory prefix").describe("Blob name or directory prefix to download"),
1924
+ output: z8.string().min(1, "Output must be a valid filepath").describe("Local path where to save the downloaded content"),
1925
+ recursive: z8.boolean().default(false).describe("Download assuming canonical directory layout and recurse"),
1926
+ force: z8.boolean().default(false).describe("Overwrite the output if it already exists")
1742
1927
  }).refine(
1743
1928
  (data) => {
1744
1929
  if (data.recursive) {
@@ -1982,15 +2167,86 @@ function downloadCommand(program) {
1982
2167
  // src/commands/upload.tsx
1983
2168
  import * as fs3 from "fs/promises";
1984
2169
  import * as path4 from "path";
2170
+ import { Readable as Readable4 } from "stream";
1985
2171
  import { Aptos as Aptos5 } from "@aptos-labs/ts-sdk";
1986
2172
  import { glob } from "glob";
1987
- import { Box as Box4, Text as Text4, render as render4 } from "ink";
2173
+ import { Box as Box5, Text as Text5, render as render4 } from "ink";
1988
2174
  import SelectInput2 from "ink-select-input";
1989
2175
  import ora2 from "ora";
1990
- import { z as z7 } from "zod";
1991
- import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
2176
+ import { z as z9 } from "zod";
2177
+
2178
+ // src/utils/errors.ts
2179
+ var ERROR_CODES = {
2180
+ UNKNOWN_ERROR: "UNKNOWN_ERROR",
2181
+ EBLOB_WRITE_INSUFFICIENT_FUNDS: "EBLOB_WRITE_INSUFFICIENT_FUNDS",
2182
+ EBLOB_WRITE_CHUNKSET_ALREADY_EXISTS: "EBLOB_WRITE_CHUNKSET_ALREADY_EXISTS",
2183
+ ECONNREFUSED: "ECONNREFUSED",
2184
+ ENOTFOUND: "ENOTFOUND"
2185
+ };
2186
+ var DEFAULT_ERROR_RESPONSE = {
2187
+ displayMessage: "An unknown error occurred",
2188
+ code: ERROR_CODES.UNKNOWN_ERROR
2189
+ };
2190
+ var formatHostInfo = (error) => {
2191
+ if (error instanceof Error) {
2192
+ if ("hostname" in error) {
2193
+ return `(${error.hostname})`;
2194
+ }
2195
+ if ("address" in error && "port" in error) {
2196
+ return `(${error.address}:${error.port})`;
2197
+ }
2198
+ }
2199
+ return void 0;
2200
+ };
2201
+ var handleError = (error) => {
2202
+ if (error instanceof Error) {
2203
+ if (error.message.includes(ERROR_CODES.EBLOB_WRITE_INSUFFICIENT_FUNDS)) {
2204
+ return {
2205
+ displayMessage: "Insufficient Shelby tokens. Please fund your account with Shelby tokens to continue.",
2206
+ code: ERROR_CODES.EBLOB_WRITE_INSUFFICIENT_FUNDS
2207
+ };
2208
+ }
2209
+ if (error.message.includes(ERROR_CODES.EBLOB_WRITE_CHUNKSET_ALREADY_EXISTS)) {
2210
+ return {
2211
+ displayMessage: "The blob already exists on L1.",
2212
+ code: ERROR_CODES.EBLOB_WRITE_CHUNKSET_ALREADY_EXISTS
2213
+ };
2214
+ }
2215
+ if ("code" in error) {
2216
+ if (error.code === ERROR_CODES.ECONNREFUSED) {
2217
+ return {
2218
+ displayMessage: `Could not connect to the Shelby RPC endpoint. Please check that the endpoint you are using is correct ${formatHostInfo(error) ?? ""}.`,
2219
+ code: ERROR_CODES.ECONNREFUSED
2220
+ };
2221
+ }
2222
+ if (error.code === ERROR_CODES.ENOTFOUND) {
2223
+ return {
2224
+ displayMessage: `Could not connect to the Shelby RPC endpoint. Please check that the endpoint you are using is correct ${formatHostInfo(error) ?? ""}.`,
2225
+ code: ERROR_CODES.ENOTFOUND
2226
+ };
2227
+ }
2228
+ }
2229
+ if (error.cause instanceof AggregateError) {
2230
+ const firstKnownError = error.cause.errors.find((error2) => {
2231
+ const handledError = handleError(error2);
2232
+ return handledError.code !== ERROR_CODES.UNKNOWN_ERROR;
2233
+ });
2234
+ if (firstKnownError) return handleError(firstKnownError);
2235
+ }
2236
+ if (error.cause instanceof Error) {
2237
+ return handleError(error.cause);
2238
+ }
2239
+ }
2240
+ return {
2241
+ displayMessage: `An unknown error occurred ${error instanceof Error ? `: ${error.message}` : ""}`,
2242
+ code: ERROR_CODES.UNKNOWN_ERROR
2243
+ };
2244
+ };
2245
+
2246
+ // src/commands/upload.tsx
2247
+ import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
1992
2248
  var normBlobName2 = (i, f, b) => normBlobName(path4, i, f, b);
1993
- var flexibleDateSchema = z7.string().transform((val) => {
2249
+ var flexibleDateSchema = z9.string().transform((val) => {
1994
2250
  if (/^\d+$/.test(val)) {
1995
2251
  const timestamp = Number.parseInt(val, 10);
1996
2252
  if (timestamp > 0 && timestamp < 4102444800) {
@@ -2003,18 +2259,18 @@ var flexibleDateSchema = z7.string().transform((val) => {
2003
2259
  }
2004
2260
  return ret;
2005
2261
  });
2006
- var UploadOptionsSchema = z7.object({
2007
- input: z7.string().nonempty("`--input` is required"),
2008
- output: z7.string().nonempty("`output` is required"),
2262
+ var UploadOptionsSchema = z9.object({
2263
+ input: z9.string().nonempty("`--input` is required"),
2264
+ output: z9.string().nonempty("`output` is required"),
2009
2265
  expiration: flexibleDateSchema,
2010
- recursive: z7.boolean().optional().default(false),
2011
- assumeYes: z7.boolean().optional().default(false),
2012
- outputCommitments: z7.string().optional()
2266
+ recursive: z9.boolean().optional().default(false),
2267
+ assumeYes: z9.boolean().optional().default(false),
2268
+ outputCommitments: z9.string().optional()
2013
2269
  }).superRefine(async (data, ctx) => {
2014
2270
  const stats = await fs3.stat(data.input);
2015
2271
  if (!stats.isFile() && !stats.isDirectory()) {
2016
2272
  ctx.addIssue({
2017
- code: z7.ZodIssueCode.custom,
2273
+ code: z9.ZodIssueCode.custom,
2018
2274
  message: "`--input` must be a file or a directory",
2019
2275
  path: ["input"]
2020
2276
  });
@@ -2023,7 +2279,7 @@ var UploadOptionsSchema = z7.object({
2023
2279
  if (stats.isDirectory()) {
2024
2280
  if (!data.output.endsWith("/")) {
2025
2281
  ctx.addIssue({
2026
- code: z7.ZodIssueCode.custom,
2282
+ code: z9.ZodIssueCode.custom,
2027
2283
  message: "When input is a directory, output must end with '/'",
2028
2284
  path: ["output"]
2029
2285
  });
@@ -2032,7 +2288,7 @@ var UploadOptionsSchema = z7.object({
2032
2288
  const blobNameResult = BlobNameSchema.safeParse(data.output);
2033
2289
  if (!blobNameResult.success) {
2034
2290
  ctx.addIssue({
2035
- code: z7.ZodIssueCode.custom,
2291
+ code: z9.ZodIssueCode.custom,
2036
2292
  message: "When input is a file, output must be a valid blob name (cannot end with '/')",
2037
2293
  path: ["output"]
2038
2294
  });
@@ -2081,9 +2337,9 @@ function computeCost(filelist) {
2081
2337
  return 1e8 * filelist.length;
2082
2338
  }
2083
2339
  function uploadCommand(program) {
2084
- program.command("upload <input> <output>").description("Upload a file or directory to the shelby RPC in the config.").option(
2340
+ program.command("upload <input> <output>").description("Upload a file or directory to the shelby RPC in the config.").requiredOption(
2085
2341
  "-e, --expiration <datetime>",
2086
- "Time at which this blob will expire"
2342
+ "UNIX Timestamp at which this blob will expire"
2087
2343
  ).option("-r, --recursive", "If uploading a directory, recurse").option(
2088
2344
  "--assume-yes",
2089
2345
  "Do not prompt interactively, assume yes for any questions"
@@ -2117,13 +2373,13 @@ function uploadCommand(program) {
2117
2373
  if (!validatedOptions.assumeYes) {
2118
2374
  const shouldContinue = await new Promise((resolve2) => {
2119
2375
  const { unmount } = render4(
2120
- /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
2121
- /* @__PURE__ */ jsxs4(Text4, { children: [
2376
+ /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
2377
+ /* @__PURE__ */ jsxs5(Text5, { children: [
2122
2378
  "Upload will cost ",
2123
2379
  cost,
2124
2380
  ". Continue?"
2125
2381
  ] }),
2126
- /* @__PURE__ */ jsx7(
2382
+ /* @__PURE__ */ jsx8(
2127
2383
  SelectInput2,
2128
2384
  {
2129
2385
  items: [
@@ -2185,37 +2441,69 @@ function uploadCommand(program) {
2185
2441
  for (const entry of filelist) {
2186
2442
  totalSize += entry.sizeBytes;
2187
2443
  }
2444
+ let hasUploadedBlob = false;
2188
2445
  for (const entry of filelist) {
2189
2446
  spinner.text = `Reading ${entry.filename}.. (Overall: ${formatProgressPercent()}%, ${formatProgressRate()} MiB/s)`;
2190
- const blobData = await fs3.readFile(entry.filename);
2191
- if (blobData.length !== entry.sizeBytes) {
2192
- throw new Error(
2193
- `Size of file ${entry.filename} changed after initial scan. Original size was ${entry.sizeBytes} but it is now ${blobData.length}`
2447
+ try {
2448
+ const blobData = await fs3.readFile(entry.filename);
2449
+ if (blobData.length !== entry.sizeBytes) {
2450
+ throw new Error(
2451
+ `Size of file ${entry.filename} changed after initial scan. Original size was ${entry.sizeBytes} but it is now ${blobData.length}`
2452
+ );
2453
+ }
2454
+ spinner.text = `Committing ${entry.filename} to L1.. (Overall: ${formatProgressPercent()}%, ${formatProgressRate()} MiB/s)`;
2455
+ const existingBlobMetadata = await shelbyBlobClient.getBlobMetadata({
2456
+ account: activeAccount.accountAddress,
2457
+ name: entry.blobname
2458
+ });
2459
+ const blobCommitments = await generateCommitments(
2460
+ Readable4.from(blobData)
2194
2461
  );
2462
+ if (!existingBlobMetadata) {
2463
+ const { transaction: pendingWriteBlobCommitmentsTransaction } = await shelbyBlobClient.writeBlobCommitments({
2464
+ account: activeAccount,
2465
+ blobName: entry.blobname,
2466
+ lifetime: validatedOptions.expiration.getTime() * 1e3,
2467
+ blobCommitments
2468
+ });
2469
+ await aptos.waitForTransaction({
2470
+ transactionHash: pendingWriteBlobCommitmentsTransaction.hash
2471
+ });
2472
+ } else {
2473
+ spinner.text = `Blob ${entry.blobname} already exists on L1, skipping commitments and uploading to Shelby RPC...`;
2474
+ }
2475
+ if (validatedOptions.outputCommitments) {
2476
+ outputCommitments[entry.filename] = blobCommitments;
2477
+ }
2478
+ const existingBlobChunks = await shelbyBlobClient.getBlobChunks({
2479
+ account: activeAccount.accountAddress,
2480
+ name: entry.blobname
2481
+ });
2482
+ if (existingBlobChunks.length === 0) {
2483
+ console.error(
2484
+ `Blob ${entry.blobname} does not exist on L1, an error occurred when uploading commitments...`
2485
+ );
2486
+ process.exit(1);
2487
+ }
2488
+ if (!existingBlobChunks.every(
2489
+ (chunk) => chunk.location.variant === "stored"
2490
+ )) {
2491
+ spinner.text = `Uploading ${entry.filename} to Shelby RPC.. (Overall: ${formatProgressPercent()}%, ${formatProgressRate()} MiB/s)`;
2492
+ await shelbyNodeClient.putBlob(
2493
+ activeAccount.accountAddress,
2494
+ entry.blobname,
2495
+ blobData
2496
+ );
2497
+ hasUploadedBlob = true;
2498
+ } else {
2499
+ spinner.text = "All blob chunks have been stored on L1, skipping upload to Shelby RPC...";
2500
+ }
2501
+ amountUploaded += blobData.length;
2502
+ } catch (error) {
2503
+ const { displayMessage } = handleError(error);
2504
+ spinner.fail(displayMessage);
2505
+ process.exit(1);
2195
2506
  }
2196
- spinner.text = `Committing ${entry.filename} to L1.. (Overall: ${formatProgressPercent()}%, ${formatProgressRate()} MiB/s)`;
2197
- const {
2198
- blobCommitments,
2199
- transaction: pendingWriteBlobCommitmentsTransaction
2200
- } = await shelbyBlobClient.writeBlobCommitments({
2201
- account: activeAccount,
2202
- blobName: entry.blobname,
2203
- lifetime: validatedOptions.expiration.getTime() * 1e3,
2204
- data: blobData
2205
- });
2206
- if (validatedOptions.outputCommitments) {
2207
- outputCommitments[entry.filename] = blobCommitments;
2208
- }
2209
- await aptos.waitForTransaction({
2210
- transactionHash: pendingWriteBlobCommitmentsTransaction.hash
2211
- });
2212
- spinner.text = `Uploading ${entry.filename} to Shelby RPC.. (Overall: ${formatProgressPercent()}%, ${formatProgressRate()} MiB/s)`;
2213
- await shelbyNodeClient.putBlob(
2214
- activeAccount.accountAddress,
2215
- entry.blobname,
2216
- blobData
2217
- );
2218
- amountUploaded += blobData.length;
2219
2507
  }
2220
2508
  if (validatedOptions.outputCommitments) {
2221
2509
  await fs3.writeFile(
@@ -2223,18 +2511,22 @@ function uploadCommand(program) {
2223
2511
  JSON.stringify(outputCommitments)
2224
2512
  );
2225
2513
  }
2226
- spinner.succeed(
2227
- `Uploaded complete, ran at ${formatProgressRate()} MiB/s`
2228
- );
2514
+ if (hasUploadedBlob) {
2515
+ spinner.succeed(
2516
+ `Uploaded complete, ran at ${formatProgressRate()} MiB/s`
2517
+ );
2518
+ } else {
2519
+ spinner.succeed("Skipped upload, blob has already been uploaded.");
2520
+ }
2229
2521
  });
2230
2522
  }
2231
2523
 
2232
2524
  // src/commands/commitment.ts
2233
2525
  import * as fs4 from "fs";
2234
2526
  import * as fsP from "fs/promises";
2235
- import { z as z8 } from "zod";
2236
- var CommitmentOptionsSchema = z8.object({
2237
- input: z8.string().nonempty("`--input` is required").refine(
2527
+ import { z as z10 } from "zod";
2528
+ var CommitmentOptionsSchema = z10.object({
2529
+ input: z10.string().nonempty("`--input` is required").refine(
2238
2530
  async (path5) => {
2239
2531
  const stat4 = await fsP.stat(path5);
2240
2532
  return stat4.isFile();
@@ -2243,7 +2535,7 @@ var CommitmentOptionsSchema = z8.object({
2243
2535
  message: "`--input` must be a file"
2244
2536
  }
2245
2537
  ),
2246
- output: z8.string().nonempty("`--output` is required")
2538
+ output: z10.string().nonempty("`--output` is required")
2247
2539
  });
2248
2540
  function commitmentCommand(program) {
2249
2541
  program.command("commitment").description(
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@shelby-protocol/cli",
3
3
  "type": "module",
4
- "version": "0.0.4",
4
+ "version": "0.0.6",
5
5
  "private": false,
6
6
  "bin": {
7
7
  "shelby": "bin/entry.js"
@@ -9,9 +9,7 @@
9
9
  "publishConfig": {
10
10
  "access": "public"
11
11
  },
12
- "files": [
13
- "bin/entry.js"
14
- ],
12
+ "files": ["bin/entry.js"],
15
13
  "scripts": {
16
14
  "prepublishOnly": "pnpm run build",
17
15
  "lint": "biome check .",