@secondlayer/cli 15.0.0 → 16.0.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/README.md CHANGED
@@ -82,7 +82,7 @@ No account. Writes `.env.local`, restores history, prints the Stacks observer st
82
82
  | `secondlayer setup [--network …] [--node-mode external\|stacks\|full] [--api-port <spec>] [--dir <path>] [--against <manifest>] [--skip-bootstrap] [--skip-verify] [--yes] [--force]` | Guided self-host onboarding — secrets, compose + `.env`, docker up, observer stanza, bootstrap, verify. TUI when interactive; flags-only (no prompts) with `--yes` or no TTY |
83
83
  | `secondlayer init [--network mainnet\|testnet\|devnet] [--api-url <url>] [--force]` | Write `.env.local` (token, secrets key, webhook signing key). `--network` and `--api-url` are the global flags. Idempotent |
84
84
  | `secondlayer bootstrap --against <manifest> [--from-block <n>] [--to-block <n>] [--verify all\|blocks] [--public-key <pem>] [-y] [--json]` | Restore chain history from a verified archive into an empty database. A run that died mid-way resumes per dataset on re-run. After the load, digests for blocks, transactions, and events are checked over the restored range (`--verify blocks` skips the child datasets and the minutes they cost). Partition fetches retry three times with backoff on resets, timeouts, 429 (honoring `Retry-After`) and 5xx; a link that stays down exits `1` with a re-run hint, and the re-run resumes. Exit `0` restored, `1` diverged or interrupted, `2` refused |
85
- | `secondlayer observer [--mode indexer\|signer-shared] [--endpoint host:port] [--recovery journal\|archive] [--network …]` | Print the `[[events_observer]]` stanza. Signer-shared requires `--recovery` |
85
+ | `secondlayer observer [--mode indexer\|signer-shared] [--endpoint host:port] [--network …]` | Print the `[[events_observer]]` stanza. Signer-shared skips retries; a missed block stays a gap until `repair` |
86
86
  | `secondlayer verify [all\|raw\|decode:<name>\|subgraph:<name>] --against <manifest> [--quick\|--deep\|--anchor]` | Compare local data to a signed archive. Default target `raw`. Exit `0` clean, `1` diverged, `2` unanchored |
87
87
  | `secondlayer repair --against <archive> [--apply] [-y]` | Plan (default) or apply an archive repair. A fixed block is rewritten with its transactions and events from the archive; when the reference has no child partition for a height, the block is rewritten alone, the height is named with a `bootstrap --from-block H --to-block H` remedy, and the exit is `1`. Partition fetches retry like `bootstrap`; a link that stays down exits `1` with a re-run hint, and heights already repaired are kept |
88
88
 
package/dist/cli.js CHANGED
@@ -197993,7 +197993,8 @@ import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync3, wr
197993
197993
  import { dirname as dirname4, resolve as resolve3 } from "node:path";
197994
197994
  import { Index, resolveApiKey as resolveApiKey3, resolveBaseUrl } from "@secondlayer/sdk";
197995
197995
  import {
197996
- DECODED_EVENT_TYPES
197996
+ DECODED_EVENT_TYPES,
197997
+ VM_EVENT_TYPES
197997
197998
  } from "@secondlayer/stacks/filters";
197998
197999
  import {
197999
198000
  runSubgraphTest as applySubgraphTest,
@@ -198030,7 +198031,13 @@ function eventTypeFor(filter) {
198030
198031
  return null;
198031
198032
  }
198032
198033
  const candidate = filter.type === "print_event" ? "print" : filter.type;
198033
- return DECODED_EVENT_TYPES.includes(candidate) ? candidate : null;
198034
+ if (DECODED_EVENT_TYPES.includes(candidate)) {
198035
+ return candidate;
198036
+ }
198037
+ if (VM_EVENT_TYPES.includes(candidate)) {
198038
+ return candidate;
198039
+ }
198040
+ return null;
198034
198041
  }
198035
198042
  function indexApiUrl() {
198036
198043
  return resolveBaseUrl();
@@ -202446,13 +202453,6 @@ function parseObserverMode(value) {
202446
202453
  }
202447
202454
  throw new UnsupportedObserverError(`observer mode must be indexer or signer-shared (got ${value})`);
202448
202455
  }
202449
- function parseRecoverySource(value) {
202450
- const source = value.trim().toLowerCase();
202451
- if (RECOVERY_SOURCES.includes(source)) {
202452
- return source;
202453
- }
202454
- throw new UnsupportedObserverError(`recovery source must be journal or archive (got ${value})`);
202455
- }
202456
202456
  function parseObserverEndpoint(value) {
202457
202457
  const trimmed = value.trim();
202458
202458
  if (/^[a-z][a-z0-9+.-]*:\/\//i.test(trimmed) || trimmed.startsWith("unix:")) {
@@ -202474,9 +202474,6 @@ function validateObserverStanza(input3) {
202474
202474
  if (input3.network !== "devnet" && LOOPBACK_HOSTS.has(host.toLowerCase())) {
202475
202475
  throw new UnsupportedObserverError(`loopback observer endpoint is not container-visible on ${input3.network}; use a docker DNS name such as indexer:3700`);
202476
202476
  }
202477
- if (input3.mode === "signer-shared" && !input3.recovery) {
202478
- throw new UnsupportedObserverError("signer-shared mode requires --recovery journal or --recovery archive");
202479
- }
202480
202477
  return { endpoint };
202481
202478
  }
202482
202479
  function renderObserverStanza(input3) {
@@ -202484,7 +202481,7 @@ function renderObserverStanza(input3) {
202484
202481
  const indexer = input3.mode === "indexer";
202485
202482
  const timeoutMs = indexer ? 2000 : 500;
202486
202483
  const disableRetries = !indexer;
202487
- 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.";
202484
+ const comment = indexer ? "# Pure indexer: retry delivery. A slow observer can stall the node." : "# Signer-shared: do not retry. A missed block stays a gap until `secondlayer repair`.";
202488
202485
  return [
202489
202486
  comment,
202490
202487
  "[[events_observer]]",
@@ -202501,10 +202498,9 @@ function defaultObserverEndpoint(network) {
202501
202498
  return "127.0.0.1:3700";
202502
202499
  return "indexer:3700";
202503
202500
  }
202504
- var OBSERVER_MODES, RECOVERY_SOURCES, UnsupportedObserverError, LOOPBACK_HOSTS;
202501
+ var OBSERVER_MODES, UnsupportedObserverError, LOOPBACK_HOSTS;
202505
202502
  var init_observer_stanza = __esm(() => {
202506
202503
  OBSERVER_MODES = ["indexer", "signer-shared"];
202507
- RECOVERY_SOURCES = ["journal", "archive"];
202508
202504
  UnsupportedObserverError = class UnsupportedObserverError extends Error {
202509
202505
  name = "UnsupportedObserverError";
202510
202506
  };
@@ -202516,11 +202512,10 @@ function buildOssCompose(opts = {}) {
202516
202512
  const owner = opts.owner ?? DEFAULT_IMAGE_OWNER;
202517
202513
  const tag = opts.imageTag ?? DEFAULT_IMAGE_TAG;
202518
202514
  const runtimeImage = `ghcr.io/${owner}/secondlayer-runtime:${tag}`;
202519
- const consoleImage = `ghcr.io/${owner}/secondlayer-console:${tag}`;
202520
202515
  return `# Generated by \`secondlayer setup\` — do not edit by hand.
202521
202516
  # Mirrors docker/oss/docker-compose.yml; see that file for the source of truth
202522
- # on env vars, profiles, and third-party digest pins. \`secondlayer\`/\`console\`
202523
- # are swapped from a repo build to the published OSS images (no checkout here).
202517
+ # on env vars, profiles, and third-party digest pins. \`secondlayer\` is
202518
+ # swapped from a repo build to the published OSS image (no checkout here).
202524
202519
  name: secondlayer
202525
202520
 
202526
202521
  services:
@@ -202597,27 +202592,6 @@ services:
202597
202592
  retries: 3
202598
202593
  restart: unless-stopped
202599
202594
 
202600
- console:
202601
- image: ${consoleImage}
202602
- environment:
202603
- SL_API_URL: http://secondlayer:3800
202604
- INSTANCE_TOKEN: \${INSTANCE_TOKEN:-}
202605
- CONSOLE_TOKEN: \${CONSOLE_TOKEN:-}
202606
- NODE_ENV: production
202607
- ports:
202608
- - "\${CONSOLE_PORT:-3801}:3801"
202609
- depends_on:
202610
- secondlayer:
202611
- condition: service_started
202612
- healthcheck:
202613
- test: ["CMD", "curl", "-f", "http://localhost:3801/console"]
202614
- interval: 30s
202615
- timeout: 5s
202616
- start_period: 15s
202617
- retries: 3
202618
- restart: unless-stopped
202619
- profiles: ["console"]
202620
-
202621
202595
  bitcoind:
202622
202596
  # Digest = kylemanna/bitcoind:latest as published 2026-07-20 — see
202623
202597
  # docker/oss/docker-compose.yml for why the digest is the real pin here.
@@ -202705,7 +202679,7 @@ dbcache=4096
202705
202679
  var init_oss_compose = () => {};
202706
202680
 
202707
202681
  // src/lib/setup-wizard.ts
202708
- import { spawn as spawn4, spawnSync as spawnSync5 } from "node:child_process";
202682
+ import { spawn as spawn3, spawnSync as spawnSync5 } from "node:child_process";
202709
202683
  import { randomBytes as randomBytes4 } from "node:crypto";
202710
202684
  import { existsSync as existsSync12 } from "node:fs";
202711
202685
  import { writeFile as writeFile6 } from "node:fs/promises";
@@ -202728,7 +202702,7 @@ function resolveNonInteractiveConfig(flags) {
202728
202702
  throw new MissingSetupFlagError("--node-mode", "Missing --node-mode <external|stacks|full>. Non-interactive setup requires it explicitly — there is no safe default.");
202729
202703
  }
202730
202704
  if (!flags.against && !flags.skipBootstrap) {
202731
- throw new MissingSetupFlagError("--against", "Missing --against <manifest-url>. Pass an archive manifest to bootstrap from, or --skip-bootstrap to sync from genesis instead.");
202705
+ throw new MissingSetupFlagError("--against", "Missing --against <manifest-url>. Pass an archive manifest to bootstrap from, or --skip-bootstrap to index only what your node sends from now on.");
202732
202706
  }
202733
202707
  const network = parseInstanceNetwork(flags.network);
202734
202708
  const nodeMode = parseSetupNodeMode(flags.nodeMode);
@@ -202925,7 +202899,7 @@ function composeProfileArgs(nodeMode) {
202925
202899
  }
202926
202900
  function runStreaming(command, args, opts) {
202927
202901
  return new Promise((resolvePromise, reject) => {
202928
- const child = spawn4(command, args, {
202902
+ const child = spawn3(command, args, {
202929
202903
  cwd: opts.cwd,
202930
202904
  env: opts.env ?? process.env,
202931
202905
  stdio: ["ignore", "pipe", "pipe"]
@@ -203916,7 +203890,7 @@ var {
203916
203890
  // package.json
203917
203891
  var package_default = {
203918
203892
  name: "@secondlayer/cli",
203919
- version: "15.0.0",
203893
+ version: "16.0.0",
203920
203894
  description: "Run a Stacks index on your own hardware — set it up, restore verified history, and keep it current.",
203921
203895
  repository: {
203922
203896
  type: "git",
@@ -203957,10 +203931,10 @@ var package_default = {
203957
203931
  "@opentui/react": "^0.5.3",
203958
203932
  "@secondlayer/bundler": "^0.5.3",
203959
203933
  "@secondlayer/scaffold": "^1.5.7",
203960
- "@secondlayer/sdk": "^10.4.2",
203961
- "@secondlayer/shared": "^11.6.0",
203962
- "@secondlayer/stacks": "^6.0.0",
203963
- "@secondlayer/subgraphs": "^4.3.4",
203934
+ "@secondlayer/sdk": "^10.4.3",
203935
+ "@secondlayer/shared": "^11.7.0",
203936
+ "@secondlayer/stacks": "^6.1.0",
203937
+ "@secondlayer/subgraphs": "^4.4.0",
203964
203938
  esbuild: "^0.28.2",
203965
203939
  react: "^19.1.0",
203966
203940
  "smol-toml": "^1.8.0",
@@ -204537,10 +204511,13 @@ Exit codes:
204537
204511
  console.error(dim(` Scope starts at ${startHeight.toLocaleString()} — earlier history is declared out of scope, not missing.`));
204538
204512
  }
204539
204513
  if (seam) {
204540
- console.error(dim(`The chain advanced to ${seam.nodeTip.toLocaleString()} while restoring — ${seam.gap.toLocaleString()} blocks to catch up.`));
204541
- console.error(dim(" Start the indexer; it backfills that range before following the tip."));
204514
+ console.error(dim(`Your node was at ${seam.nodeTip.toLocaleString()} when the restore started, ${seam.gap.toLocaleString()} blocks past the archive tip.`));
204515
+ console.error(dim(` The index has a gap from ${(tipHeight + 1).toLocaleString()} to ${seam.nodeTip.toLocaleString()}. Blocks your node sent during the restore are ingested; nothing fills the gap on its own.`));
204516
+ console.error(dim(` After the next archive publish covers it: secondlayer repair --against ${opts.against} --from-block ${tipHeight + 1} --to-block ${seam.nodeTip} --apply`));
204517
+ } else if (nodeTipAtStart === null) {
204518
+ console.error(dim(" Start the indexer to continue from there. Node unreachable, so any gap past the archive tip is unknown; `secondlayer verify` finds it once the archive covers it."));
204542
204519
  } else {
204543
- console.error(dim(" Start the indexer to continue from there. (Node unreachable — catch-up range unknown.)"));
204520
+ console.error(dim(" Start the indexer to continue from there."));
204544
204521
  }
204545
204522
  } else {
204546
204523
  const byDataset = verifyDatasets.map((d) => `${divergentRanges.filter((r) => r.dataset === d).length} ${d}`).join(", ");
@@ -205927,66 +205904,6 @@ async function validateDatabaseConnection(url) {
205927
205904
  console.log(dim("The URL was saved but connection failed. Check your database settings."));
205928
205905
  }
205929
205906
  }
205930
- // src/commands/console.ts
205931
- init_output();
205932
- init_resolve_auth();
205933
- import { spawn as spawn3 } from "node:child_process";
205934
- var DEFAULT_CONSOLE_URL = "http://localhost:3801/console";
205935
- var CONSOLE_PORT = 3801;
205936
- function resolveConsoleUrl(override) {
205937
- if (override)
205938
- return override.replace(/\/+$/, "");
205939
- const apiUrl = process.env.SL_API_URL;
205940
- if (apiUrl && isOssMode()) {
205941
- try {
205942
- const { protocol, hostname } = new URL(apiUrl);
205943
- return `${protocol}//${hostname}:${CONSOLE_PORT}/console`;
205944
- } catch {}
205945
- }
205946
- return DEFAULT_CONSOLE_URL;
205947
- }
205948
- async function isReachable(url) {
205949
- try {
205950
- await fetch(url, { signal: AbortSignal.timeout(2000) });
205951
- return true;
205952
- } catch {
205953
- return false;
205954
- }
205955
- }
205956
- function openBrowser(url) {
205957
- const opener = process.platform === "darwin" ? "open" : process.platform === "linux" ? "xdg-open" : undefined;
205958
- if (!opener)
205959
- return Promise.resolve(false);
205960
- return new Promise((resolve7) => {
205961
- const child = spawn3(opener, [url], { stdio: "ignore", detached: true });
205962
- child.once("error", () => resolve7(false));
205963
- child.once("spawn", () => {
205964
- child.unref();
205965
- resolve7(true);
205966
- });
205967
- });
205968
- }
205969
- function registerConsoleCommand(program2) {
205970
- program2.command("console").description("Open the console UI in a browser").option("--url <url>", `Console URL (default ${DEFAULT_CONSOLE_URL})`).option("--no-open", "Print the console URL instead of opening a browser").action(async (options3) => {
205971
- assertInstanceUrl();
205972
- const url = resolveConsoleUrl(options3.url);
205973
- if (options3.open === false) {
205974
- writeData(url);
205975
- return;
205976
- }
205977
- if (!await isReachable(url)) {
205978
- printError(`Console not reachable at ${url}`, {
205979
- hint: "docker compose --profile console up -d"
205980
- });
205981
- process.exit(1);
205982
- }
205983
- if (await openBrowser(url)) {
205984
- success(`console: ${url}`);
205985
- } else {
205986
- writeData(url);
205987
- }
205988
- });
205989
- }
205990
205907
  // src/commands/context.ts
205991
205908
  init_http();
205992
205909
  import {
@@ -209043,7 +208960,7 @@ init_observer_stanza();
209043
208960
  init_output();
209044
208961
  init_resolve_auth();
209045
208962
  function registerObserverCommand(program2) {
209046
- 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").addHelpText("after", `
208963
+ 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").addHelpText("after", `
209047
208964
  --network <mainnet|testnet|devnet> is the top-level global flag (see
209048
208965
  \`secondlayer --help\`) — default mainnet, same as before.
209049
208966
  `).action((opts) => {
@@ -209051,11 +208968,10 @@ function registerObserverCommand(program2) {
209051
208968
  const mode = parseObserverMode(opts.mode);
209052
208969
  const network = instanceNetworkFromEnv();
209053
208970
  const endpoint = opts.endpoint ?? defaultObserverEndpoint(network);
209054
- const recovery = opts.recovery ? parseRecoverySource(opts.recovery) : undefined;
209055
208971
  if (mode === "signer-shared") {
209056
- note("Signer-shared nodes skip observer retries. Pair with a journal or archive for completeness.");
208972
+ note("Signer-shared nodes skip observer retries. A missed block stays a gap until `secondlayer repair` fills it from the archive.");
209057
208973
  }
209058
- writeData(renderObserverStanza({ mode, endpoint, network, recovery }));
208974
+ writeData(renderObserverStanza({ mode, endpoint, network }));
209059
208975
  });
209060
208976
  }
209061
208977
  // src/commands/setup.ts
@@ -209138,9 +209054,9 @@ async function promptSetupConfig(flags) {
209138
209054
  description: "Enter your own"
209139
209055
  },
209140
209056
  {
209141
- name: "Sync from genesis",
209057
+ name: "Skip the archive",
209142
209058
  value: "skip",
209143
- description: "Skip bootstrap — slower, no download"
209059
+ description: "Index only what your node sends from now on"
209144
209060
  }
209145
209061
  ]
209146
209062
  });
@@ -209175,7 +209091,7 @@ async function promptSetupConfig(flags) {
209175
209091
  console.error(` node mode ${config.nodeMode}`);
209176
209092
  console.error(` dir ${config.dir}`);
209177
209093
  console.error(` api port ${config.apiPort}`);
209178
- console.error(` bootstrap ${config.skipBootstrap ? "skip (sync from genesis)" : config.against}`);
209094
+ console.error(` bootstrap ${config.skipBootstrap ? "skip (from the node's tip)" : config.against}`);
209179
209095
  console.error("");
209180
209096
  const proceed = await confirm4({ message: "Start setup?", default: true });
209181
209097
  if (!proceed)
@@ -209254,7 +209170,7 @@ async function runPromptedInteractive(flags) {
209254
209170
  await executeAndRender(config);
209255
209171
  }
209256
209172
  function registerSetupCommand(program2) {
209257
- program2.command("setup").description("Guided self-host onboarding: secrets, config, docker, bootstrap, verify — one command").option("--node-mode <mode>", "external, stacks, or full (required)").option("--api-port <spec>", "API publish spec", "127.0.0.1:3800").option("--dir <path>", "Target directory for compose + .env", ".").option("--against <manifest>", `Archive manifest to bootstrap from (suggested default: ${DEFAULT_ARCHIVE_MANIFEST})`).option("--skip-bootstrap", "Sync from genesis instead of restoring an archive").option("--skip-verify", "Skip the post-bootstrap verify pass").option("--yes", "Non-interactive: skip the TUI, require flags, never prompt").option("--force", "Regenerate secrets even if a .env already exists in --dir").option("--owner <owner>", "ghcr image owner (namespace) to pull from").option("--image-tag <tag>", "Published image tag to run").addHelpText("after", `
209173
+ program2.command("setup").description("Guided self-host onboarding: secrets, config, docker, bootstrap, verify — one command").option("--node-mode <mode>", "external, stacks, or full (required)").option("--api-port <spec>", "API publish spec", "127.0.0.1:3800").option("--dir <path>", "Target directory for compose + .env", ".").option("--against <manifest>", `Archive manifest to bootstrap from (suggested default: ${DEFAULT_ARCHIVE_MANIFEST})`).option("--skip-bootstrap", "Skip the archive restore; index only what your node sends from now on").option("--skip-verify", "Skip the post-bootstrap verify pass").option("--yes", "Non-interactive: skip the TUI, require flags, never prompt").option("--force", "Regenerate secrets even if a .env already exists in --dir").option("--owner <owner>", "ghcr image owner (namespace) to pull from").option("--image-tag <tag>", "Published image tag to run").addHelpText("after", `
209258
209174
  --network <mainnet|testnet|devnet> is the top-level global flag (see
209259
209175
  \`secondlayer --help\`) — required here too, just parsed one level up.
209260
209176
 
@@ -209321,7 +209237,6 @@ registerSetupCommand(program);
209321
209237
  registerInitCommand(program);
209322
209238
  registerBootstrapCommand(program);
209323
209239
  registerObserverCommand(program);
209324
- registerConsoleCommand(program);
209325
209240
  registerLoginCommand(program);
209326
209241
  registerLogoutCommand(program);
209327
209242
  registerWhoamiCommand(program);
@@ -209349,5 +209264,5 @@ registerCreditsCommand(program);
209349
209264
  registerArchiveCommand(program);
209350
209265
  program.parse();
209351
209266
 
209352
- //# debugId=400188075261398364756E2164756E21
209267
+ //# debugId=F035865DCA2A5BEE64756E2164756E21
209353
209268
  //# sourceMappingURL=cli.js.map