@secondlayer/cli 9.10.0 → 10.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -2481,6 +2481,25 @@ var require_commander = __commonJS((exports) => {
2481
2481
  exports.InvalidOptionArgumentError = InvalidArgumentError;
2482
2482
  });
2483
2483
 
2484
+ // ../../node_modules/commander/esm.mjs
2485
+ var import__, program, createCommand, createArgument, createOption, CommanderError, InvalidArgumentError, InvalidOptionArgumentError, Command, Argument, Option, Help;
2486
+ var init_esm = __esm(() => {
2487
+ import__ = __toESM(require_commander(), 1);
2488
+ ({
2489
+ program,
2490
+ createCommand,
2491
+ createArgument,
2492
+ createOption,
2493
+ CommanderError,
2494
+ InvalidArgumentError,
2495
+ InvalidOptionArgumentError,
2496
+ Command,
2497
+ Argument,
2498
+ Option,
2499
+ Help
2500
+ } = import__.default);
2501
+ });
2502
+
2484
2503
  // src/lib/session.ts
2485
2504
  import {
2486
2505
  chmod,
@@ -2543,6 +2562,9 @@ async function resolveAuth() {
2543
2562
  if (envKey) {
2544
2563
  return { apiUrl, ephemeralKey: envKey, fromEnv: true };
2545
2564
  }
2565
+ if (isOssMode()) {
2566
+ return { apiUrl, ephemeralKey: "", fromEnv: true };
2567
+ }
2546
2568
  const session = await readSession();
2547
2569
  if (!session) {
2548
2570
  const err = new Error("Not logged in — run `sl login`");
@@ -2551,6 +2573,9 @@ async function resolveAuth() {
2551
2573
  }
2552
2574
  return { apiUrl, ephemeralKey: session.token, fromEnv: false };
2553
2575
  }
2576
+ function isOssMode() {
2577
+ return !!process.env.SL_API_URL;
2578
+ }
2554
2579
  var DEFAULT_API_URL = "https://api.secondlayer.tools";
2555
2580
  var init_resolve_auth = __esm(() => {
2556
2581
  init_session();
@@ -2590,7 +2615,7 @@ async function httpPlatform(path2, opts = {}) {
2590
2615
  try {
2591
2616
  auth = await resolveAuth();
2592
2617
  } catch {
2593
- throw new CliHttpError(401, "SESSION_EXPIRED", { error: "Not logged in" }, "Not logged in — run `sl login`");
2618
+ throw new CliHttpError(401, "SESSION_EXPIRED", { error: "Not logged in" }, isOssMode() ? "Not authenticated — set SL_API_KEY from `sl init`" : "Not logged in — run `sl login`");
2594
2619
  }
2595
2620
  return request(`${auth.apiUrl}${path2}`, {
2596
2621
  ...opts,
@@ -2745,7 +2770,7 @@ function handleApiError(err, action) {
2745
2770
  const status = err instanceof ApiError || err instanceof CliHttpError ? err.status : undefined;
2746
2771
  if (err instanceof CliHttpError && err.code === "SESSION_EXPIRED" || status === 401) {
2747
2772
  printError("Authentication required.", {
2748
- hint: "Run `sl login` to re-authenticate."
2773
+ hint: isOssMode() ? "Set SL_API_KEY / INSTANCE_TOKEN from `sl init`." : "Run `sl login` to re-authenticate."
2749
2774
  });
2750
2775
  process.exit(1);
2751
2776
  }
@@ -28826,7 +28851,7 @@ async function runLoginFlow(options3 = {}) {
28826
28851
  }
28827
28852
  }
28828
28853
  function registerLoginCommand(program2) {
28829
- program2.command("login").description("Log in to Secondlayer (magic-link email)").option("--force", "Skip the already-logged-in check and re-run the flow").option("--with-token", "Read an API key from stdin (non-interactive)").addHelpText("after", `
28854
+ program2.command("login", { hidden: true }).description("Log in to Secondlayer (magic-link email)").option("--force", "Skip the already-logged-in check and re-run the flow").option("--with-token", "Read an API key from stdin (non-interactive)").addHelpText("after", `
28830
28855
  Examples:
28831
28856
  $ sl login
28832
28857
  $ echo "$SL_API_KEY" | sl login --with-token`).action((opts) => opts.withToken ? runTokenLogin() : runLoginFlow({ force: opts.force }));
@@ -28839,6 +28864,8 @@ var init_login = __esm(() => {
28839
28864
 
28840
28865
  // src/lib/require-auth.ts
28841
28866
  async function requireAuth() {
28867
+ if (isOssMode())
28868
+ return;
28842
28869
  const session = await readSession();
28843
28870
  if (session)
28844
28871
  return;
@@ -28848,6 +28875,7 @@ async function requireAuth() {
28848
28875
  var init_require_auth = __esm(() => {
28849
28876
  init_login();
28850
28877
  init_output();
28878
+ init_resolve_auth();
28851
28879
  init_session();
28852
28880
  });
28853
28881
 
@@ -30104,7 +30132,7 @@ Stopped watching.`);
30104
30132
  });
30105
30133
  await new Promise(() => {});
30106
30134
  });
30107
- subgraphs.command("deploy <file>").alias("update").description("Deploy a subgraph definition file (alias: update — deploy is create-or-update)").option("--start-block <n>", "Override the subgraph definition startBlock for this deploy").option("--tip-first", "Go live at chain tip immediately; history backfills in the background (requires order-tolerant handlers — commutative or insert-only writes)").option("--dry-run", "Validate and preview deploy without writing changes").option("-y, --yes", "Skip the reindex confirmation prompt").option("--database-url <url>", "BYO data plane: write the subgraph's schema/rows to your own Postgres. With --dry-run, prints the DDL + grant script and verifies the connection.").option("--strict", "Run `tsc --noEmit` against the handler before deploy (slower; catches TS type errors)").option("--visibility <visibility>", "Read visibility: public (anon /v1 reads, global name claim) or private (your key only). Defaults: managed → public, BYO → private.").option("--allow-uncommitted", "Deploy even though the source file is not committed to git (the deployed definition will exist only in the database)").action(async (file, options3) => {
30135
+ subgraphs.command("deploy <file>").alias("update").description("Deploy a subgraph definition file (alias: update — deploy is create-or-update)").option("--start-block <n>", "Override the subgraph definition startBlock for this deploy").option("--tip-first", "Go live at chain tip immediately; history backfills in the background (requires order-tolerant handlers — commutative or insert-only writes)").option("--dry-run", "Validate and preview deploy without writing changes").option("-y, --yes", "Skip the reindex confirmation prompt").option("--database-url <url>", "BYO data plane: write the subgraph's schema/rows to your own Postgres. With --dry-run, prints the DDL + grant script and verifies the connection.").option("--strict", "Run `tsc --noEmit` against the handler before deploy (slower; catches TS type errors)").addOption(new Option("--visibility <visibility>", "Hosted only. Public/private namespace.").hideHelp()).option("--allow-uncommitted", "Deploy even though the source file is not committed to git (the deployed definition will exist only in the database)").action(async (file, options3) => {
30108
30136
  try {
30109
30137
  const absPath = resolve3(file);
30110
30138
  if (!options3.dryRun) {
@@ -30132,7 +30160,7 @@ Stopped watching.`);
30132
30160
  }
30133
30161
  }
30134
30162
  const config = await loadConfig2();
30135
- if (config.network !== "local") {
30163
+ if (config.network !== "local" && !isOssMode()) {
30136
30164
  await requireAuth();
30137
30165
  }
30138
30166
  const dryRun = options3.dryRun;
@@ -30666,7 +30694,7 @@ ${rows.length} row(s)`));
30666
30694
  handleApiError(err, "delete subgraph");
30667
30695
  }
30668
30696
  });
30669
- subgraphs.command("publish <name>").description("Make a subgraph publicly readable on /v1/subgraphs/<name> (claims the name globally, no key needed to read)").action(async (name) => {
30697
+ subgraphs.command("publish <name>", { hidden: true }).description("Make a subgraph publicly readable on /v1/subgraphs/<name> (claims the name globally, no key needed to read)").action(async (name) => {
30670
30698
  try {
30671
30699
  await requireAuth();
30672
30700
  const result = await publishSubgraphApi(name);
@@ -30684,7 +30712,7 @@ ${rows.length} row(s)`));
30684
30712
  handleApiError(err, "publish subgraph");
30685
30713
  }
30686
30714
  });
30687
- subgraphs.command("unpublish <name>").description("Make a subgraph private again — reads require your API key, the public name claim is released").action(async (name) => {
30715
+ subgraphs.command("unpublish <name>", { hidden: true }).description("Make a subgraph private again — reads require your API key, the public name claim is released").action(async (name) => {
30688
30716
  try {
30689
30717
  await requireAuth();
30690
30718
  await unpublishSubgraphApi(name);
@@ -30839,6 +30867,7 @@ async function runSubgraphSchemaCodegen(file, options3, optionSource, defaultTar
30839
30867
  }
30840
30868
  var ORM_ONLY_CODEGEN_FLAGS;
30841
30869
  var init_subgraphs2 = __esm(() => {
30870
+ init_esm();
30842
30871
  init_subgraph_scaffold();
30843
30872
  init_subgraphs();
30844
30873
  init_api_client();
@@ -30850,6 +30879,7 @@ var init_subgraphs2 = __esm(() => {
30850
30879
  init_output();
30851
30880
  init_require_auth();
30852
30881
  init_resolve_auth();
30882
+ init_resolve_auth();
30853
30883
  init_clarity();
30854
30884
  init_subgraph();
30855
30885
  init_api();
@@ -34749,75 +34779,12 @@ var init_node_impl = __esm(() => {
34749
34779
  init_output();
34750
34780
  });
34751
34781
 
34752
- // src/commands/init.ts
34753
- var exports_init = {};
34754
- __export(exports_init, {
34755
- init: () => init2
34756
- });
34757
- import { promises as fs7 } from "node:fs";
34758
- import path11 from "node:path";
34759
- async function init2() {
34760
- const configPath = path11.join(process.cwd(), "secondlayer.config.ts");
34761
- try {
34762
- await fs7.access(configPath);
34763
- console.log(source_default.yellow("⚠ secondlayer.config.ts already exists"));
34764
- return;
34765
- } catch {}
34766
- const hasClarinetProject = await fileExists2("./Clarinet.toml");
34767
- let config;
34768
- if (hasClarinetProject) {
34769
- config = `import { defineConfig } from '@secondlayer/cli';
34770
- import { clarinet } from '@secondlayer/cli/plugins';
34771
-
34772
- export default defineConfig({
34773
- out: './src/generated/contracts.ts',
34774
- plugins: [
34775
- clarinet() // Found Clarinet.toml in current directory
34776
- ]
34777
- });`;
34778
- } else {
34779
- config = `import { defineConfig } from '@secondlayer/cli';
34780
-
34781
- export default defineConfig({
34782
- out: './src/generated/contracts.ts',
34783
- plugins: [],
34784
- });`;
34785
- }
34786
- await fs7.writeFile(configPath, config);
34787
- console.log(source_default.green("✓ Created `secondlayer.config.ts`"));
34788
- console.log("\nRun `secondlayer generate` to generate type-safe interfaces, functions, and hooks!");
34789
- }
34790
- async function fileExists2(filePath) {
34791
- try {
34792
- await fs7.access(filePath);
34793
- return true;
34794
- } catch {
34795
- return false;
34796
- }
34797
- }
34798
- var init_init = __esm(() => {
34799
- init_source();
34800
- });
34801
-
34802
- // ../../node_modules/commander/esm.mjs
34803
- var import__ = __toESM(require_commander(), 1);
34804
- var {
34805
- program,
34806
- createCommand,
34807
- createArgument,
34808
- createOption,
34809
- CommanderError,
34810
- InvalidArgumentError,
34811
- InvalidOptionArgumentError,
34812
- Command,
34813
- Argument,
34814
- Option,
34815
- Help
34816
- } = import__.default;
34782
+ // src/cli.ts
34783
+ init_esm();
34817
34784
  // package.json
34818
34785
  var package_default = {
34819
34786
  name: "@secondlayer/cli",
34820
- version: "9.10.0",
34787
+ version: "10.1.0",
34821
34788
  repository: {
34822
34789
  type: "git",
34823
34790
  url: "git+https://github.com/ryanwaits/secondlayer.git",
@@ -34858,10 +34825,10 @@ var package_default = {
34858
34825
  "@biomejs/wasm-nodejs": "^1.9.0",
34859
34826
  "@dsnp/parquetjs": "^1.8.8",
34860
34827
  "@inquirer/prompts": "^8.2.0",
34861
- "@secondlayer/bundler": "^0.4.1",
34828
+ "@secondlayer/bundler": "^0.5.0",
34862
34829
  "@secondlayer/scaffold": "^1.5.1",
34863
34830
  "@secondlayer/sdk": "^6.51.0",
34864
- "@secondlayer/shared": "^7.8.0",
34831
+ "@secondlayer/shared": "^7.10.0",
34865
34832
  "@secondlayer/stacks": "^3.3.1",
34866
34833
  "@secondlayer/subgraphs": "^3.26.4",
34867
34834
  esbuild: "^0.27.4",
@@ -35003,7 +34970,7 @@ async function applyProfileUpdate(options, getOptionValueSource) {
35003
34970
  });
35004
34971
  }
35005
34972
  function registerAccountCommand(program2) {
35006
- const account = program2.command("account").description("Manage your account profile");
34973
+ const account = program2.command("account", { hidden: true }).description("Manage your account profile");
35007
34974
  addBillingCommand(account);
35008
34975
  account.command("get").description("Show your account profile").option("--json", "Output as JSON").action(withErrorHandling(async (options) => showProfile(options.json), { action: "show profile" }));
35009
34976
  account.command("update").description("Update your public profile").option("--name <name>", "Set display name").option("--bio <bio>", "Set bio").option("--slug <slug>", "Set public URL slug").option("--notify-reindex-complete", "Email me when a subgraph reindex finishes").option("--no-notify-reindex-complete", "Don't email me when a subgraph reindex finishes").option("--json", "Output as JSON").action(withErrorHandling(async (options, command) => applyProfileUpdate(options, (key) => command.getOptionValueSource(key)), {
@@ -36536,6 +36503,7 @@ function buildDevnetCompose(opts = {}) {
36536
36503
  const indexerImage = `ghcr.io/${owner}/secondlayer-indexer:${tag}`;
36537
36504
  const dbUrl = "postgres://secondlayer:secondlayer@postgres:5432/secondlayer";
36538
36505
  const DEV_SECRETS_KEY = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef";
36506
+ const DEV_INSTANCE_TOKEN = "dev-instance-token";
36539
36507
  return `# Generated by \`sl devnet connect\` — do not edit by hand.
36540
36508
  # Secondlayer app services for local clarinet devnet development.
36541
36509
  name: secondlayer-devnet
@@ -36572,6 +36540,8 @@ services:
36572
36540
  INSTANCE_MODE: oss
36573
36541
  DATABASE_URL: ${dbUrl}
36574
36542
  PORT: 3800
36543
+ LISTEN_HOST: "0.0.0.0"
36544
+ INSTANCE_TOKEN: ${DEV_INSTANCE_TOKEN}
36575
36545
  NODE_ENV: production
36576
36546
  DATA_DIR: /data
36577
36547
  # Shared across api + subgraph-processor so the secret the api encrypts
@@ -37317,7 +37287,7 @@ init_resolve_auth();
37317
37287
  import { SecondLayer as SecondLayer4 } from "@secondlayer/sdk";
37318
37288
  var DEFAULT_BASE_URL4 = "https://api.secondlayer.tools";
37319
37289
  function registerKeysCommand(program2) {
37320
- const keys = program2.command("keys").description("Manage API keys");
37290
+ const keys = program2.command("keys", { hidden: true }).description("Manage API keys");
37321
37291
  keys.command("create").description("Mint a scoped streams/index read key (requires an account-level key)").option("--product <product>", "Key scope: streams or index", "streams").option("--name <name>", "Optional label for the key").option("--json", "Print the full response as JSON").action(async (o3) => {
37322
37292
  if (o3.product && o3.product !== "streams" && o3.product !== "index") {
37323
37293
  error("--product must be 'streams' or 'index'");
@@ -38607,7 +38577,7 @@ async function writeActiveProject(slug, cwd) {
38607
38577
  // src/commands/whoami.ts
38608
38578
  init_resolve_auth();
38609
38579
  function registerWhoamiCommand(program2) {
38610
- program2.command("whoami").description("Show the active account, credential source, and project").option("--json", "Output as JSON").action(async (options3) => {
38580
+ program2.command("whoami", { hidden: true }).description("Show the active account, credential source, and project").option("--json", "Output as JSON").action(async (options3) => {
38611
38581
  let auth;
38612
38582
  try {
38613
38583
  auth = await resolveAuth();
@@ -38663,7 +38633,7 @@ init_http();
38663
38633
  init_output();
38664
38634
  init_session();
38665
38635
  function registerLogoutCommand(program2) {
38666
- program2.command("logout").description("Log out and revoke the local session").action(async () => {
38636
+ program2.command("logout", { hidden: true }).description("Log out and revoke the local session").action(async () => {
38667
38637
  const session = await readSession();
38668
38638
  if (!session) {
38669
38639
  info("Not logged in.");
@@ -38685,7 +38655,7 @@ init_config2();
38685
38655
  init_http();
38686
38656
  init_output();
38687
38657
  function registerProjectCommand(program2) {
38688
- const project = program2.command("projects").alias("project").description("Manage your Secondlayer project");
38658
+ const project = program2.command("projects", { hidden: true }).alias("project").description("Manage your Secondlayer project");
38689
38659
  project.command("list").alias("ls").description("List projects in your account").option("--json", "Output as JSON").action(async (options3) => {
38690
38660
  try {
38691
38661
  const res = await httpPlatform("/api/projects");
@@ -38764,6 +38734,15 @@ import {
38764
38734
  compareRangeDigests,
38765
38735
  computeRangeDigest
38766
38736
  } from "@secondlayer/shared/archive/range-digest";
38737
+ import {
38738
+ comparePartitionSemanticDigests,
38739
+ computePartitionSemanticDigest
38740
+ } from "@secondlayer/shared/archive/semantic-digest-builder";
38741
+ import {
38742
+ datasetMatchesTarget,
38743
+ parseVerifyTarget,
38744
+ reportVerify
38745
+ } from "@secondlayer/shared/coverage";
38767
38746
  import { getDb as getDb2 } from "@secondlayer/shared/db";
38768
38747
 
38769
38748
  // src/lib/archive-reference.ts
@@ -38840,6 +38819,7 @@ function checkSignature(manifest, publicKeyPem, insecure) {
38840
38819
  };
38841
38820
  }
38842
38821
  }
38822
+ var HOSTED_API = "https://api.secondlayer.tools";
38843
38823
  async function resolvePublicKey(explicitPem, apiUrl) {
38844
38824
  if (explicitPem)
38845
38825
  return explicitPem;
@@ -38853,6 +38833,16 @@ async function resolvePublicKey(explicitPem, apiUrl) {
38853
38833
  return;
38854
38834
  }
38855
38835
  }
38836
+ async function resolveArchivePublicKey(input4) {
38837
+ if (input4.explicitPem)
38838
+ return input4.explicitPem;
38839
+ if (input4.envPem)
38840
+ return input4.envPem;
38841
+ if (input4.allowHostedApi) {
38842
+ return resolvePublicKey(undefined, HOSTED_API);
38843
+ }
38844
+ return;
38845
+ }
38856
38846
  async function fetchVerifiedPartition(reference, partition) {
38857
38847
  let bytes;
38858
38848
  if (reference.isRemote) {
@@ -38896,17 +38886,21 @@ function statusLabel(status2) {
38896
38886
  return yellow("missing");
38897
38887
  }
38898
38888
  function registerVerifyCommand(program2) {
38899
- program2.command("verify").description("Compare local chain data against a signed archive (read-only; nothing is uploaded)").requiredOption("--against <manifest>", "archive manifest: an https URL or a local file path").option("--from-block <n>", "first height to check").option("--to-block <n>", "last height to check").option("--counts", "also compare transaction/event row counts (slower)").option("--public-key <pem>", "pin the signing key instead of fetching it").option("--insecure", "skip the manifest signature check (not recommended)").option("--json", "Output as JSON").addHelpText("after", `
38889
+ program2.command("verify").description("Compare local chain data against a signed archive (read-only; nothing is uploaded)").argument("[target]", "all | raw | decode:<name> | subgraph:<name>", "raw").option("--quick", "coverage/identity only (default)").option("--deep", "include semantic / scratch replay where available").option("--anchor", "require a verified archive signature").requiredOption("--against <manifest>", "archive manifest: an https URL or a local file path").option("--from-block <n>", "first height to check").option("--to-block <n>", "last height to check").option("--counts", "also compare transaction/event row counts (slower)").option("--semantic", "also recompute per-partition semantic digests locally and compare (slow: full re-stream)").option("--public-key <pem>", "pin the signing key instead of fetching it").option("--insecure", "skip the manifest signature check (not recommended)").option("--json", "Output as JSON").addHelpText("after", `
38900
38890
  Examples:
38901
38891
  $ sl verify --against https://archive.secondlayer.tools/.../snapshots/<digest>.json
38902
- $ sl verify --against ./snapshot.json --from-block 8000000 --to-block 8499999
38903
- $ sl verify --against ./snapshot.json --counts --json
38892
+ $ sl verify raw --against ./snapshot.json --from-block 8000000 --to-block 8499999
38893
+ $ sl verify all --against ./snapshot.json --json
38894
+ $ sl verify decode:ft_transfer --against ./snapshot.json --quick
38895
+ $ sl verify subgraph:sbtc --against ./snapshot.json --deep
38904
38896
 
38905
38897
  Exit codes:
38906
38898
  0 local data matches the archive
38907
38899
  1 divergence found (divergent ranges are listed)
38908
- 2 unanchored — reference unavailable or unverifiable`).action(async (opts) => {
38900
+ 2 unanchored — reference unavailable or unverifiable`).action(async (targetArg, opts) => {
38909
38901
  try {
38902
+ const target = parseVerifyTarget(targetArg);
38903
+ const mode = opts.deep ? "deep" : opts.anchor ? "anchor" : "quick";
38910
38904
  const fromBlock = opts.fromBlock === undefined ? undefined : parseHeight2(opts.fromBlock, "--from-block");
38911
38905
  const toBlock = opts.toBlock === undefined ? undefined : parseHeight2(opts.toBlock, "--to-block");
38912
38906
  if (fromBlock !== undefined && toBlock !== undefined && toBlock < fromBlock) {
@@ -38922,6 +38916,12 @@ Exit codes:
38922
38916
  output({
38923
38917
  json: opts.json,
38924
38918
  data: {
38919
+ ...reportVerify({
38920
+ target,
38921
+ mode,
38922
+ anchored: false,
38923
+ detail: signature.reason
38924
+ }),
38925
38925
  status: "unanchored",
38926
38926
  reference: origin,
38927
38927
  reason: signature.reason
@@ -38931,6 +38931,8 @@ Exit codes:
38931
38931
  process.exit(VERIFY_EXIT.UNANCHORED);
38932
38932
  }
38933
38933
  const reference = (manifest.range_digests ?? []).filter((d2) => {
38934
+ if (!datasetMatchesTarget(d2.dataset, target))
38935
+ return false;
38934
38936
  if (fromBlock !== undefined && d2.to_block < fromBlock)
38935
38937
  return false;
38936
38938
  if (toBlock !== undefined && d2.from_block > toBlock)
@@ -38976,13 +38978,60 @@ Exit codes:
38976
38978
  });
38977
38979
  }
38978
38980
  }
38979
- const all2 = [...comparisons, ...countChecks];
38981
+ const semanticChecks = [];
38982
+ const referenceSemantic = (manifest.partition_semantic_digests ?? []).filter((d2) => {
38983
+ if (!datasetMatchesTarget(d2.dataset, target))
38984
+ return false;
38985
+ if (fromBlock !== undefined && d2.to_block < fromBlock)
38986
+ return false;
38987
+ if (toBlock !== undefined && d2.from_block > toBlock)
38988
+ return false;
38989
+ return true;
38990
+ });
38991
+ if (opts.semantic || opts.deep) {
38992
+ if (referenceSemantic.length === 0) {
38993
+ warn("Reference publishes no semantic digests; --semantic had nothing to compare.");
38994
+ } else {
38995
+ note(`Recomputing ${referenceSemantic.length} partition semantic digests (this is the slow pass).`);
38996
+ const localSemantic = [];
38997
+ let semanticDone = 0;
38998
+ for (const partition of referenceSemantic) {
38999
+ localSemantic.push(await computePartitionSemanticDigest(db, partition.dataset, partition.from_block, partition.to_block));
39000
+ semanticDone++;
39001
+ if (semanticDone % 5 === 0 || semanticDone === referenceSemantic.length) {
39002
+ note(` checked ${semanticDone}/${referenceSemantic.length} partitions`);
39003
+ }
39004
+ }
39005
+ semanticChecks.push(...comparePartitionSemanticDigests(localSemantic, referenceSemantic));
39006
+ }
39007
+ }
39008
+ const all2 = [
39009
+ ...comparisons,
39010
+ ...countChecks,
39011
+ ...semanticChecks.map((s2) => ({
39012
+ dataset: s2.dataset,
39013
+ from_block: s2.from_block,
39014
+ to_block: s2.to_block,
39015
+ status: s2.status,
39016
+ expected_digest: s2.expected_digest,
39017
+ actual_digest: s2.actual_digest,
39018
+ expected_rows: s2.expected_rows,
39019
+ actual_rows: s2.actual_rows
39020
+ }))
39021
+ ];
38980
39022
  const diverged = all2.filter((c4) => c4.status !== "match");
39023
+ const verdict = reportVerify({
39024
+ target,
39025
+ mode,
39026
+ diverged: diverged.length > 0
39027
+ });
38981
39028
  const report = {
38982
- status: diverged.length === 0 ? "clean" : "diverged",
39029
+ ...verdict,
39030
+ status: verdict.status,
38983
39031
  reference: origin,
38984
39032
  signature_verified: signature.verified,
38985
39033
  ranges_checked: all2.length,
39034
+ semantic_checks: semanticChecks.length,
38986
39035
  divergent_ranges: diverged
38987
39036
  };
38988
39037
  output({
@@ -39281,7 +39330,35 @@ import {
39281
39330
  } from "@secondlayer/shared/archive/copy-loader";
39282
39331
  import { computeRangeDigest as computeRangeDigest3 } from "@secondlayer/shared/archive/range-digest";
39283
39332
  import { getDb as getDb4, getRawClient, sql as sql3 } from "@secondlayer/shared/db";
39333
+
39334
+ // src/lib/bootstrap-resume.ts
39335
+ function planTornImport(input4) {
39336
+ if (input4.hasIndexProgress) {
39337
+ return {
39338
+ action: "refuse",
39339
+ reason: "This database already holds a completed bootstrap."
39340
+ };
39341
+ }
39342
+ if (input4.maxBlockHeight === null || input4.maxBlockHeight <= 0) {
39343
+ return { action: "fresh" };
39344
+ }
39345
+ const blockPartitions = input4.partitions.filter((p2) => p2.dataset === "blocks").sort((a4, b3) => a4.from_block - b3.from_block);
39346
+ const sealed = blockPartitions.filter((p2) => p2.to_block <= (input4.maxBlockHeight ?? 0));
39347
+ const skipThrough = sealed.length > 0 ? Math.max(...sealed.map((p2) => p2.to_block)) : 0;
39348
+ const torn = blockPartitions.find((p2) => p2.from_block <= (input4.maxBlockHeight ?? 0) && p2.to_block > (input4.maxBlockHeight ?? 0));
39349
+ return {
39350
+ action: "resume",
39351
+ truncateFrom: torn ? torn.from_block : null,
39352
+ skipThrough
39353
+ };
39354
+ }
39355
+ function partitionIsLoaded(partition, skipThrough) {
39356
+ return partition.to_block <= skipThrough;
39357
+ }
39358
+
39359
+ // src/commands/bootstrap.ts
39284
39360
  init_output();
39361
+ init_resolve_auth();
39285
39362
  var BOOTSTRAP_EXIT = {
39286
39363
  OK: 0,
39287
39364
  INCOMPLETE: 1,
@@ -39342,8 +39419,8 @@ async function loadPartition(reference, partition, rawClient) {
39342
39419
  }
39343
39420
  return written;
39344
39421
  }
39345
- function registerBootstrapCommand(program2) {
39346
- program2.command("bootstrap").description("Restore chain history from a verified archive instead of syncing from genesis").requiredOption("--against <manifest>", "archive manifest: an https URL or a local file path").option("--to-block <n>", "stop at this height instead of the archive tip").option("--public-key <pem>", "pin the signing key instead of fetching it").option("-y, --yes", "skip the confirmation prompt").option("--json", "Output as JSON").addHelpText("after", `
39422
+ function attachBootstrapCommand(cmd) {
39423
+ return cmd.requiredOption("--against <manifest>", "archive manifest: an https URL or a local file path").option("--to-block <n>", "stop at this height instead of the archive tip").option("--public-key <pem>", "pin the signing key instead of fetching it").option("-y, --yes", "skip the confirmation prompt").option("--json", "Output as JSON").addHelpText("after", `
39347
39424
  Examples:
39348
39425
  $ sl bootstrap --against https://archive.secondlayer.tools/.../snapshots/<digest>.json
39349
39426
  $ sl bootstrap --against ./snapshot.json --to-block 4000000 --yes
@@ -39354,7 +39431,11 @@ Exit codes:
39354
39431
  2 refused (non-empty target, or an untrusted reference)`).action(async (opts) => {
39355
39432
  try {
39356
39433
  const reference = await loadReference(opts.against);
39357
- const publicKey = await resolvePublicKey(opts.publicKey, process.env.SL_API_URL ?? "https://api.secondlayer.tools");
39434
+ const publicKey = await resolveArchivePublicKey({
39435
+ explicitPem: opts.publicKey,
39436
+ envPem: process.env.ARCHIVE_SIGNING_PUBLIC_KEY ?? process.env.STREAMS_SIGNING_PUBLIC_KEY,
39437
+ allowHostedApi: !isOssMode()
39438
+ });
39358
39439
  const signature = checkSignature(reference.manifest, publicKey, false);
39359
39440
  if (!signature.verified) {
39360
39441
  printError(`Refusing to bootstrap from an untrusted archive: ${signature.reason}.`, {
@@ -39363,15 +39444,31 @@ Exit codes:
39363
39444
  process.exit(BOOTSTRAP_EXIT.REFUSED);
39364
39445
  }
39365
39446
  const db = getDb4();
39366
- const existing = await db.selectFrom("blocks").select(({ fn }) => fn.countAll().as("count")).executeTakeFirst();
39367
- if (Number(existing?.count ?? 0) > 0) {
39368
- printError("This database already holds blocks.", {
39447
+ const progress = await db.selectFrom("index_progress").select("network").executeTakeFirst();
39448
+ const maxHeightRow = await db.selectFrom("blocks").select(({ fn }) => fn.max("height").as("max")).executeTakeFirst();
39449
+ const maxBlockHeight = maxHeightRow?.max === null || maxHeightRow?.max === undefined ? null : Number(maxHeightRow.max);
39450
+ const toBlock = opts.toBlock === undefined ? undefined : Number(opts.toBlock);
39451
+ const declared = (reference.manifest.partitions ?? []).filter((p2) => toBlock === undefined || p2.to_block <= toBlock);
39452
+ const resume = planTornImport({
39453
+ hasIndexProgress: !!progress,
39454
+ maxBlockHeight,
39455
+ partitions: declared
39456
+ });
39457
+ if (resume.action === "refuse") {
39458
+ printError(resume.reason, {
39369
39459
  hint: "Bootstrap is for a fresh instance. To fix an existing one, use `sl repair`."
39370
39460
  });
39371
39461
  process.exit(BOOTSTRAP_EXIT.REFUSED);
39372
39462
  }
39373
- const toBlock = opts.toBlock === undefined ? undefined : Number(opts.toBlock);
39374
- const partitions = (reference.manifest.partitions ?? []).filter((p2) => toBlock === undefined || p2.to_block <= toBlock);
39463
+ if (resume.action === "resume" && resume.truncateFrom !== null) {
39464
+ await sql3`DELETE FROM events WHERE block_height >= ${resume.truncateFrom}`.execute(db);
39465
+ await sql3`DELETE FROM transactions WHERE block_height >= ${resume.truncateFrom}`.execute(db);
39466
+ await sql3`DELETE FROM blocks WHERE height >= ${resume.truncateFrom}`.execute(db);
39467
+ note(`Resuming torn import: truncated from height ${resume.truncateFrom}.`);
39468
+ } else if (resume.action === "resume") {
39469
+ note(`Resuming import after sealed height ${resume.skipThrough.toLocaleString()}.`);
39470
+ }
39471
+ const partitions = declared.filter((p2) => resume.action === "fresh" || !partitionIsLoaded(p2, resume.skipThrough));
39375
39472
  if (partitions.length === 0) {
39376
39473
  printError("The archive has no partitions for that range.", {
39377
39474
  hint: "Check --to-block against the manifest's coverage."
@@ -39496,9 +39593,217 @@ Exit codes:
39496
39593
  }
39497
39594
  });
39498
39595
  }
39596
+ function registerBootstrapCommand(program2) {
39597
+ attachBootstrapCommand(program2.command("bootstrap").description("Restore chain history from a verified archive instead of syncing from genesis"));
39598
+ }
39599
+ // src/lib/instance-init.ts
39600
+ import { randomBytes as randomBytes3 } from "node:crypto";
39601
+ import { existsSync as existsSync8, readFileSync as readFileSync7, writeFileSync as writeFileSync7 } from "node:fs";
39602
+ import { resolve as resolve13 } from "node:path";
39603
+ import { generateEd25519KeyPair } from "@secondlayer/shared/crypto/ed25519";
39604
+ import { INSTANCE_NETWORKS } from "@secondlayer/shared/db/queries/instance";
39605
+ var INSTANCE_ENV_FILE = ".env.local";
39606
+ function parseInstanceNetwork(value) {
39607
+ const network = value.trim().toLowerCase();
39608
+ if (INSTANCE_NETWORKS.includes(network)) {
39609
+ return network;
39610
+ }
39611
+ throw new Error(`network must be mainnet, testnet, or devnet (got ${value})`);
39612
+ }
39613
+ function generateInstanceToken() {
39614
+ return randomBytes3(32).toString("hex");
39615
+ }
39616
+ function generateSecretsKey() {
39617
+ return randomBytes3(32).toString("hex");
39618
+ }
39619
+ function generateSigningPrivateKey() {
39620
+ return generateEd25519KeyPair().privateKeyPem;
39621
+ }
39622
+ function escapeEnvValue(value) {
39623
+ if (!/[\s#"']/.test(value) && !value.includes("\\"))
39624
+ return value;
39625
+ return `"${value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\n/g, "\\n")}"`;
39626
+ }
39627
+ function unescapeEnvValue(raw) {
39628
+ let value = raw.trim();
39629
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
39630
+ value = value.slice(1, -1);
39631
+ }
39632
+ return value.replace(/\\n/g, `
39633
+ `).replace(/\\"/g, '"').replace(/\\\\/g, "\\");
39634
+ }
39635
+ function readEnvValue(envPath, key) {
39636
+ if (!existsSync8(envPath))
39637
+ return null;
39638
+ const match = readFileSync7(envPath, "utf8").match(new RegExp(`^${key}=(.*)$`, "m"));
39639
+ return match ? unescapeEnvValue(match[1]) : null;
39640
+ }
39641
+ function buildInstanceEnv(input4) {
39642
+ const token = input4.existing?.INSTANCE_TOKEN || input4.existing?.SL_API_KEY || generateInstanceToken();
39643
+ const secrets = input4.existing?.SECONDLAYER_SECRETS_KEY || generateSecretsKey();
39644
+ const signing = input4.existing?.STREAMS_SIGNING_PRIVATE_KEY || input4.existing?.SECONDLAYER_WEBHOOK_SIGNING_PRIVATE_KEY || generateSigningPrivateKey();
39645
+ const apiUrl = input4.apiUrl ?? "http://127.0.0.1:3800";
39646
+ const archiveKey = input4.existing?.ARCHIVE_SIGNING_PUBLIC_KEY;
39647
+ return {
39648
+ INSTANCE_MODE: "oss",
39649
+ STACKS_NETWORK: input4.network,
39650
+ INSTANCE_TOKEN: token,
39651
+ SECONDLAYER_SECRETS_KEY: secrets,
39652
+ STREAMS_SIGNING_PRIVATE_KEY: signing,
39653
+ SECONDLAYER_WEBHOOK_SIGNING_PRIVATE_KEY: signing,
39654
+ ALLOW_UNSIGNED_WEBHOOKS: "false",
39655
+ SL_API_URL: input4.existing?.SL_API_URL || apiUrl,
39656
+ SL_API_KEY: token,
39657
+ ...archiveKey ? { ARCHIVE_SIGNING_PUBLIC_KEY: archiveKey } : {}
39658
+ };
39659
+ }
39660
+ function renderInstanceEnv(env2) {
39661
+ const lines2 = [
39662
+ "# Generated by sl init. Do not commit.",
39663
+ `INSTANCE_MODE=${env2.INSTANCE_MODE}`,
39664
+ `STACKS_NETWORK=${env2.STACKS_NETWORK}`,
39665
+ `INSTANCE_TOKEN=${env2.INSTANCE_TOKEN}`,
39666
+ `SECONDLAYER_SECRETS_KEY=${env2.SECONDLAYER_SECRETS_KEY}`,
39667
+ `STREAMS_SIGNING_PRIVATE_KEY=${escapeEnvValue(env2.STREAMS_SIGNING_PRIVATE_KEY)}`,
39668
+ `SECONDLAYER_WEBHOOK_SIGNING_PRIVATE_KEY=${escapeEnvValue(env2.SECONDLAYER_WEBHOOK_SIGNING_PRIVATE_KEY)}`,
39669
+ `ALLOW_UNSIGNED_WEBHOOKS=${env2.ALLOW_UNSIGNED_WEBHOOKS}`,
39670
+ `SL_API_URL=${env2.SL_API_URL}`,
39671
+ `SL_API_KEY=${env2.SL_API_KEY}`
39672
+ ];
39673
+ if (env2.ARCHIVE_SIGNING_PUBLIC_KEY) {
39674
+ lines2.push(`ARCHIVE_SIGNING_PUBLIC_KEY=${escapeEnvValue(env2.ARCHIVE_SIGNING_PUBLIC_KEY)}`);
39675
+ }
39676
+ lines2.push("");
39677
+ return lines2.join(`
39678
+ `);
39679
+ }
39680
+ function writeInstanceEnv(cwd, env2, file = INSTANCE_ENV_FILE) {
39681
+ const path11 = resolve13(cwd, file);
39682
+ writeFileSync7(path11, renderInstanceEnv(env2), { mode: 384 });
39683
+ return path11;
39684
+ }
39685
+ function loadExistingInstanceEnv(cwd, file = INSTANCE_ENV_FILE) {
39686
+ const path11 = resolve13(cwd, file);
39687
+ return {
39688
+ INSTANCE_TOKEN: readEnvValue(path11, "INSTANCE_TOKEN"),
39689
+ SECONDLAYER_SECRETS_KEY: readEnvValue(path11, "SECONDLAYER_SECRETS_KEY"),
39690
+ STREAMS_SIGNING_PRIVATE_KEY: readEnvValue(path11, "STREAMS_SIGNING_PRIVATE_KEY"),
39691
+ SECONDLAYER_WEBHOOK_SIGNING_PRIVATE_KEY: readEnvValue(path11, "SECONDLAYER_WEBHOOK_SIGNING_PRIVATE_KEY"),
39692
+ SL_API_URL: readEnvValue(path11, "SL_API_URL"),
39693
+ SL_API_KEY: readEnvValue(path11, "SL_API_KEY"),
39694
+ ARCHIVE_SIGNING_PUBLIC_KEY: readEnvValue(path11, "ARCHIVE_SIGNING_PUBLIC_KEY")
39695
+ };
39696
+ }
39697
+
39698
+ // src/commands/init.ts
39699
+ init_output();
39700
+ function registerInitCommand(program2) {
39701
+ program2.command("init").description("Write local env (token, secrets key, webhook signing key). Idempotent.").option("--network <network>", "mainnet, testnet, or devnet", process.env.STACKS_NETWORK ?? "mainnet").option("--api-url <url>", "Local API URL", "http://127.0.0.1:3800").option("--force", "Overwrite generated values even if .env.local exists").action((opts) => {
39702
+ const network = parseInstanceNetwork(opts.network);
39703
+ const existing = opts.force ? {} : loadExistingInstanceEnv(process.cwd());
39704
+ const env2 = buildInstanceEnv({
39705
+ network,
39706
+ existing,
39707
+ apiUrl: opts.apiUrl
39708
+ });
39709
+ const path11 = writeInstanceEnv(process.cwd(), env2);
39710
+ success(`Wrote ${INSTANCE_ENV_FILE}`);
39711
+ note(formatKeyValue([
39712
+ ["network", env2.STACKS_NETWORK],
39713
+ ["api", env2.SL_API_URL],
39714
+ ["env", path11]
39715
+ ]));
39716
+ note("Export these or pass --env-file to compose. Do not commit the file.");
39717
+ });
39718
+ }
39719
+ // src/lib/observer-stanza.ts
39720
+ var OBSERVER_MODES = ["indexer", "signer-shared"];
39721
+ var RECOVERY_SOURCES = ["journal", "archive"];
39722
+
39723
+ class UnsupportedObserverError extends Error {
39724
+ name = "UnsupportedObserverError";
39725
+ }
39726
+ function parseObserverMode(value) {
39727
+ const mode = value.trim().toLowerCase();
39728
+ if (OBSERVER_MODES.includes(mode)) {
39729
+ return mode;
39730
+ }
39731
+ throw new UnsupportedObserverError(`observer mode must be indexer or signer-shared (got ${value})`);
39732
+ }
39733
+ function parseRecoverySource(value) {
39734
+ const source = value.trim().toLowerCase();
39735
+ if (RECOVERY_SOURCES.includes(source)) {
39736
+ return source;
39737
+ }
39738
+ throw new UnsupportedObserverError(`recovery source must be journal or archive (got ${value})`);
39739
+ }
39740
+ function parseObserverEndpoint(value) {
39741
+ const trimmed = value.trim();
39742
+ if (/^[a-z][a-z0-9+.-]*:\/\//i.test(trimmed) || trimmed.startsWith("unix:")) {
39743
+ throw new UnsupportedObserverError(`observer endpoint must be host:port, not a URL (${value})`);
39744
+ }
39745
+ const match = trimmed.match(/^([a-zA-Z0-9._-]+):(\d+)$/);
39746
+ if (!match) {
39747
+ throw new UnsupportedObserverError(`observer endpoint must be host:port (got ${value})`);
39748
+ }
39749
+ const port = Number(match[2]);
39750
+ if (port < 1 || port > 65535) {
39751
+ throw new UnsupportedObserverError(`observer port out of range (${port})`);
39752
+ }
39753
+ return trimmed;
39754
+ }
39755
+ var LOOPBACK_HOSTS = new Set(["127.0.0.1", "localhost", "::1"]);
39756
+ function validateObserverStanza(input4) {
39757
+ const endpoint = parseObserverEndpoint(input4.endpoint);
39758
+ const host = endpoint.slice(0, endpoint.lastIndexOf(":"));
39759
+ if (input4.network !== "devnet" && LOOPBACK_HOSTS.has(host.toLowerCase())) {
39760
+ throw new UnsupportedObserverError(`loopback observer endpoint is not container-visible on ${input4.network}; use a docker DNS name such as indexer:3700`);
39761
+ }
39762
+ if (input4.mode === "signer-shared" && !input4.recovery) {
39763
+ throw new UnsupportedObserverError("signer-shared mode requires --recovery journal or --recovery archive");
39764
+ }
39765
+ return { endpoint };
39766
+ }
39767
+ function renderObserverStanza(input4) {
39768
+ const { endpoint } = validateObserverStanza(input4);
39769
+ const indexer = input4.mode === "indexer";
39770
+ const timeoutMs = indexer ? 2000 : 500;
39771
+ const disableRetries = !indexer;
39772
+ const comment = indexer ? "# Pure indexer: retry delivery. A slow observer can stall the node." : "# Signer-shared: do not retry. Missed blocks refill from the journal/archive.";
39773
+ return [
39774
+ comment,
39775
+ "[[events_observer]]",
39776
+ `endpoint = "${endpoint}"`,
39777
+ 'events_keys = ["*"]',
39778
+ `timeout_ms = ${timeoutMs}`,
39779
+ `disable_retries = ${disableRetries}`,
39780
+ ""
39781
+ ].join(`
39782
+ `);
39783
+ }
39784
+ function defaultObserverEndpoint(network) {
39785
+ if (network === "devnet")
39786
+ return "127.0.0.1:3700";
39787
+ return "indexer:3700";
39788
+ }
39789
+
39790
+ // src/commands/observer.ts
39791
+ init_output();
39792
+ function registerObserverCommand(program2) {
39793
+ program2.command("observer").description("Print the Stacks events_observer stanza for this node mode").option("--mode <mode>", "indexer (retry) or signer-shared (no retry)", "indexer").option("--endpoint <host:port>", "Observer callback the node will POST to").option("--recovery <source>", "Required for signer-shared: journal or archive").option("--network <network>", "mainnet, testnet, or devnet", process.env.STACKS_NETWORK ?? "mainnet").action((opts) => {
39794
+ const mode = parseObserverMode(opts.mode);
39795
+ const network = parseInstanceNetwork(opts.network);
39796
+ const endpoint = opts.endpoint ?? defaultObserverEndpoint(network);
39797
+ const recovery = opts.recovery ? parseRecoverySource(opts.recovery) : undefined;
39798
+ if (mode === "signer-shared") {
39799
+ note("Signer-shared nodes skip observer retries. Pair with a journal or archive for completeness.");
39800
+ }
39801
+ writeData(renderObserverStanza({ mode, endpoint, network, recovery }));
39802
+ });
39803
+ }
39499
39804
  // src/cli.ts
39500
39805
  var { version } = package_default;
39501
- program.name("secondlayer").alias("sl").description("SecondLayer CLI — dedicated Stacks indexing + real-time subgraphs").version(version).option("--network <network>", "Override network (local, testnet, mainnet)").option("--api-key <key>", "API credential (overrides SL_API_KEY)").option("--api-url <url>", "API endpoint (overrides SL_API_URL)").showSuggestionAfterError(true).showHelpAfterError("(run `sl --help` to see available commands)");
39806
+ program.name("secondlayer").alias("sl").description("Secondlayer CLI — self-hosted Stacks data runtime").version(version).option("--network <network>", "Override network (local, testnet, mainnet)").option("--api-key <key>", "API credential (overrides SL_API_KEY)").option("--api-url <url>", "API endpoint (overrides SL_API_URL)").showSuggestionAfterError(true).showHelpAfterError("(run `sl --help` to see available commands)");
39502
39807
  program.hook("preAction", (thisCommand) => {
39503
39808
  const { network, apiKey, apiUrl } = thisCommand.opts();
39504
39809
  if (network)
@@ -39510,16 +39815,15 @@ program.hook("preAction", (thisCommand) => {
39510
39815
  });
39511
39816
  program.addHelpText("after", `
39512
39817
  Quickstart:
39513
- $ sl login # Authenticate (magic-link email)
39818
+ $ sl init --network mainnet
39819
+ $ sl bootstrap --against <manifest>
39514
39820
  $ sl subgraphs create my-watcher --template sip-010-balances
39515
39821
  $ sl subgraphs deploy subgraphs/my-watcher.ts
39516
- $ sl subgraphs status my-watcher
39517
39822
  `);
39518
39823
  program.commandsGroup("Getting started:");
39519
- program.command("init").description("Initialize a new secondlayer.config.ts file").action(async () => {
39520
- const { init: init3 } = await Promise.resolve().then(() => (init_init(), exports_init));
39521
- await init3();
39522
- });
39824
+ registerInitCommand(program);
39825
+ registerBootstrapCommand(program);
39826
+ registerObserverCommand(program);
39523
39827
  registerLoginCommand(program);
39524
39828
  registerLogoutCommand(program);
39525
39829
  registerWhoamiCommand(program);
@@ -39552,7 +39856,6 @@ registerStatusCommand(program);
39552
39856
  registerDoctorCommand(program);
39553
39857
  registerConfigCommand(program);
39554
39858
  registerContextCommand(program);
39555
- registerBootstrapCommand(program);
39556
39859
  registerVerifyCommand(program);
39557
39860
  registerRepairCommand(program);
39558
39861
  program.commandsGroup("Account:");
@@ -39560,5 +39863,5 @@ registerAccountCommand(program);
39560
39863
  registerKeysCommand(program);
39561
39864
  program.parse();
39562
39865
 
39563
- //# debugId=D839AA90F21AC3D964756E2164756E21
39866
+ //# debugId=43937D51F562F20264756E2164756E21
39564
39867
  //# sourceMappingURL=cli.js.map