@shelby-protocol/cli 0.0.4 → 0.0.5

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 +316 -100
  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.5";
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,16 @@ 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
+ import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
1992
2178
  var normBlobName2 = (i, f, b) => normBlobName(path4, i, f, b);
1993
- var flexibleDateSchema = z7.string().transform((val) => {
2179
+ var flexibleDateSchema = z9.string().transform((val) => {
1994
2180
  if (/^\d+$/.test(val)) {
1995
2181
  const timestamp = Number.parseInt(val, 10);
1996
2182
  if (timestamp > 0 && timestamp < 4102444800) {
@@ -2003,18 +2189,18 @@ var flexibleDateSchema = z7.string().transform((val) => {
2003
2189
  }
2004
2190
  return ret;
2005
2191
  });
2006
- var UploadOptionsSchema = z7.object({
2007
- input: z7.string().nonempty("`--input` is required"),
2008
- output: z7.string().nonempty("`output` is required"),
2192
+ var UploadOptionsSchema = z9.object({
2193
+ input: z9.string().nonempty("`--input` is required"),
2194
+ output: z9.string().nonempty("`output` is required"),
2009
2195
  expiration: flexibleDateSchema,
2010
- recursive: z7.boolean().optional().default(false),
2011
- assumeYes: z7.boolean().optional().default(false),
2012
- outputCommitments: z7.string().optional()
2196
+ recursive: z9.boolean().optional().default(false),
2197
+ assumeYes: z9.boolean().optional().default(false),
2198
+ outputCommitments: z9.string().optional()
2013
2199
  }).superRefine(async (data, ctx) => {
2014
2200
  const stats = await fs3.stat(data.input);
2015
2201
  if (!stats.isFile() && !stats.isDirectory()) {
2016
2202
  ctx.addIssue({
2017
- code: z7.ZodIssueCode.custom,
2203
+ code: z9.ZodIssueCode.custom,
2018
2204
  message: "`--input` must be a file or a directory",
2019
2205
  path: ["input"]
2020
2206
  });
@@ -2023,7 +2209,7 @@ var UploadOptionsSchema = z7.object({
2023
2209
  if (stats.isDirectory()) {
2024
2210
  if (!data.output.endsWith("/")) {
2025
2211
  ctx.addIssue({
2026
- code: z7.ZodIssueCode.custom,
2212
+ code: z9.ZodIssueCode.custom,
2027
2213
  message: "When input is a directory, output must end with '/'",
2028
2214
  path: ["output"]
2029
2215
  });
@@ -2032,7 +2218,7 @@ var UploadOptionsSchema = z7.object({
2032
2218
  const blobNameResult = BlobNameSchema.safeParse(data.output);
2033
2219
  if (!blobNameResult.success) {
2034
2220
  ctx.addIssue({
2035
- code: z7.ZodIssueCode.custom,
2221
+ code: z9.ZodIssueCode.custom,
2036
2222
  message: "When input is a file, output must be a valid blob name (cannot end with '/')",
2037
2223
  path: ["output"]
2038
2224
  });
@@ -2081,9 +2267,9 @@ function computeCost(filelist) {
2081
2267
  return 1e8 * filelist.length;
2082
2268
  }
2083
2269
  function uploadCommand(program) {
2084
- program.command("upload <input> <output>").description("Upload a file or directory to the shelby RPC in the config.").option(
2270
+ program.command("upload <input> <output>").description("Upload a file or directory to the shelby RPC in the config.").requiredOption(
2085
2271
  "-e, --expiration <datetime>",
2086
- "Time at which this blob will expire"
2272
+ "UNIX Timestamp at which this blob will expire"
2087
2273
  ).option("-r, --recursive", "If uploading a directory, recurse").option(
2088
2274
  "--assume-yes",
2089
2275
  "Do not prompt interactively, assume yes for any questions"
@@ -2117,13 +2303,13 @@ function uploadCommand(program) {
2117
2303
  if (!validatedOptions.assumeYes) {
2118
2304
  const shouldContinue = await new Promise((resolve2) => {
2119
2305
  const { unmount } = render4(
2120
- /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
2121
- /* @__PURE__ */ jsxs4(Text4, { children: [
2306
+ /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
2307
+ /* @__PURE__ */ jsxs5(Text5, { children: [
2122
2308
  "Upload will cost ",
2123
2309
  cost,
2124
2310
  ". Continue?"
2125
2311
  ] }),
2126
- /* @__PURE__ */ jsx7(
2312
+ /* @__PURE__ */ jsx8(
2127
2313
  SelectInput2,
2128
2314
  {
2129
2315
  items: [
@@ -2185,6 +2371,7 @@ function uploadCommand(program) {
2185
2371
  for (const entry of filelist) {
2186
2372
  totalSize += entry.sizeBytes;
2187
2373
  }
2374
+ let hasUploadedBlob = false;
2188
2375
  for (const entry of filelist) {
2189
2376
  spinner.text = `Reading ${entry.filename}.. (Overall: ${formatProgressPercent()}%, ${formatProgressRate()} MiB/s)`;
2190
2377
  const blobData = await fs3.readFile(entry.filename);
@@ -2194,27 +2381,52 @@ function uploadCommand(program) {
2194
2381
  );
2195
2382
  }
2196
2383
  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
2384
+ const existingBlobMetadata = await shelbyBlobClient.getBlobMetadata({
2385
+ account: activeAccount.accountAddress,
2386
+ name: entry.blobname
2205
2387
  });
2388
+ const blobCommitments = await generateCommitments(
2389
+ Readable4.from(blobData)
2390
+ );
2391
+ if (!existingBlobMetadata) {
2392
+ const { transaction: pendingWriteBlobCommitmentsTransaction } = await shelbyBlobClient.writeBlobCommitments({
2393
+ account: activeAccount,
2394
+ blobName: entry.blobname,
2395
+ lifetime: validatedOptions.expiration.getTime() * 1e3,
2396
+ blobCommitments
2397
+ });
2398
+ await aptos.waitForTransaction({
2399
+ transactionHash: pendingWriteBlobCommitmentsTransaction.hash
2400
+ });
2401
+ } else {
2402
+ spinner.text = `Blob ${entry.blobname} already exists on L1, skipping commitments and uploading to Shelby RPC...`;
2403
+ }
2206
2404
  if (validatedOptions.outputCommitments) {
2207
2405
  outputCommitments[entry.filename] = blobCommitments;
2208
2406
  }
2209
- await aptos.waitForTransaction({
2210
- transactionHash: pendingWriteBlobCommitmentsTransaction.hash
2407
+ const existingBlobChunks = await shelbyBlobClient.getBlobChunks({
2408
+ account: activeAccount.accountAddress,
2409
+ name: entry.blobname
2211
2410
  });
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
- );
2411
+ if (existingBlobChunks.length === 0) {
2412
+ console.error(
2413
+ `Blob ${entry.blobname} does not exist on L1, an error occurred when uploading commitments...`
2414
+ );
2415
+ process.exit(1);
2416
+ }
2417
+ if (!existingBlobChunks.every(
2418
+ (chunk) => chunk.location.variant === "stored"
2419
+ )) {
2420
+ spinner.text = `Uploading ${entry.filename} to Shelby RPC.. (Overall: ${formatProgressPercent()}%, ${formatProgressRate()} MiB/s)`;
2421
+ await shelbyNodeClient.putBlob(
2422
+ activeAccount.accountAddress,
2423
+ entry.blobname,
2424
+ blobData
2425
+ );
2426
+ hasUploadedBlob = true;
2427
+ } else {
2428
+ spinner.text = "All blob chunks have been stored on L1, skipping upload to Shelby RPC...";
2429
+ }
2218
2430
  amountUploaded += blobData.length;
2219
2431
  }
2220
2432
  if (validatedOptions.outputCommitments) {
@@ -2223,18 +2435,22 @@ function uploadCommand(program) {
2223
2435
  JSON.stringify(outputCommitments)
2224
2436
  );
2225
2437
  }
2226
- spinner.succeed(
2227
- `Uploaded complete, ran at ${formatProgressRate()} MiB/s`
2228
- );
2438
+ if (hasUploadedBlob) {
2439
+ spinner.succeed(
2440
+ `Uploaded complete, ran at ${formatProgressRate()} MiB/s`
2441
+ );
2442
+ } else {
2443
+ spinner.succeed("Skipped upload, blob has already been uploaded.");
2444
+ }
2229
2445
  });
2230
2446
  }
2231
2447
 
2232
2448
  // src/commands/commitment.ts
2233
2449
  import * as fs4 from "fs";
2234
2450
  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(
2451
+ import { z as z10 } from "zod";
2452
+ var CommitmentOptionsSchema = z10.object({
2453
+ input: z10.string().nonempty("`--input` is required").refine(
2238
2454
  async (path5) => {
2239
2455
  const stat4 = await fsP.stat(path5);
2240
2456
  return stat4.isFile();
@@ -2243,7 +2459,7 @@ var CommitmentOptionsSchema = z8.object({
2243
2459
  message: "`--input` must be a file"
2244
2460
  }
2245
2461
  ),
2246
- output: z8.string().nonempty("`--output` is required")
2462
+ output: z10.string().nonempty("`--output` is required")
2247
2463
  });
2248
2464
  function commitmentCommand(program) {
2249
2465
  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.5",
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 .",