@secondlayer/cli 22.0.0 → 22.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
@@ -2567,12 +2567,27 @@ function nextStepHint(status) {
2567
2567
  return "Server error — retry shortly, or check `secondlayer status`.";
2568
2568
  return;
2569
2569
  }
2570
+ function authFailureMessage(code, status, ossMode) {
2571
+ if (code === "KEY_REVOKED") {
2572
+ return {
2573
+ message: "Your saved login was revoked.",
2574
+ hint: "Run `secondlayer login` to sign in again. If you set SECONDLAYER_API_KEY, replace it with a key from /account/keys."
2575
+ };
2576
+ }
2577
+ if (code === "SESSION_EXPIRED" || status === 401) {
2578
+ return {
2579
+ message: "Authentication required.",
2580
+ hint: ossMode ? "Set INSTANCE_TOKEN from `secondlayer init`." : "Run `secondlayer login` to re-authenticate."
2581
+ };
2582
+ }
2583
+ return null;
2584
+ }
2570
2585
  function handleApiError(err, action) {
2571
2586
  const status = err instanceof ApiError || err instanceof CliHttpError ? err.status : undefined;
2572
- if (err instanceof CliHttpError && err.code === "SESSION_EXPIRED" || status === 401) {
2573
- printError("Authentication required.", {
2574
- hint: isOssMode() ? "Set INSTANCE_TOKEN from `secondlayer init`." : "Run `secondlayer login` to re-authenticate."
2575
- });
2587
+ const code = err instanceof ApiError || err instanceof CliHttpError ? err.code : undefined;
2588
+ const auth = authFailureMessage(code, status, isOssMode());
2589
+ if (auth) {
2590
+ printError(auth.message, { hint: auth.hint });
2576
2591
  process.exit(1);
2577
2592
  }
2578
2593
  const detail = err instanceof Error ? err.message : String(err);
@@ -16181,7 +16196,11 @@ class StacksApiClient {
16181
16196
  return response.body;
16182
16197
  } catch (error) {
16183
16198
  const statusCode = typeof error === "object" && error !== null && "response" in error && typeof error.response === "object" && error.response !== null && "statusCode" in error.response ? error.response.statusCode : undefined;
16199
+ const responseBody = typeof error === "object" && error !== null && "response" in error && typeof error.response === "object" && error.response !== null && "body" in error.response ? error.response.body : undefined;
16184
16200
  const message = error instanceof Error ? error.message : String(error);
16201
+ if (statusCode === 401 && typeof responseBody === "object" && responseBody !== null && "code" in responseBody && responseBody.code === "KEY_REVOKED") {
16202
+ throw new Error("Your saved login was revoked. Run `secondlayer login` to sign in again.");
16203
+ }
16185
16204
  if (statusCode === 401) {
16186
16205
  throw new Error(isOssMode() ? "Authentication required. Set INSTANCE_TOKEN from `secondlayer init`." : "Authentication required. Run: secondlayer login");
16187
16206
  }
@@ -29469,7 +29488,7 @@ var init_git_status = () => {};
29469
29488
 
29470
29489
  // ../../node_modules/typescript/lib/typescript.js
29471
29490
  var require_typescript = __commonJS(function(exports, module) {
29472
- var __dirname = "/Users/ryan/projects/secondlayer/node_modules/typescript/lib", __filename = "/Users/ryan/projects/secondlayer/node_modules/typescript/lib/typescript.js";
29491
+ var __dirname = "/Users/ryan/projects/secondlayer/.claude/worktrees/rel313/node_modules/typescript/lib", __filename = "/Users/ryan/projects/secondlayer/.claude/worktrees/rel313/node_modules/typescript/lib/typescript.js";
29473
29492
  /*! *****************************************************************************
29474
29493
  Copyright (c) Microsoft Corporation. All rights reserved.
29475
29494
  Licensed under the Apache License, Version 2.0 (the "License"); you may not use
@@ -202692,7 +202711,7 @@ var {
202692
202711
  // package.json
202693
202712
  var package_default = {
202694
202713
  name: "@secondlayer/cli",
202695
- version: "22.0.0",
202714
+ version: "22.1.0",
202696
202715
  description: "Run a Stacks index on your own hardware — set it up, restore verified history, and keep it current.",
202697
202716
  repository: {
202698
202717
  type: "git",
@@ -204165,6 +204184,126 @@ import {
204165
204184
  reportVerify
204166
204185
  } from "@secondlayer/shared/coverage";
204167
204186
  import { getDb as getDb3 } from "@secondlayer/shared/db";
204187
+
204188
+ // src/lib/archive-heights.ts
204189
+ import {
204190
+ PerHeightDigestAccumulator
204191
+ } from "@secondlayer/shared/archive/per-height-digest";
204192
+ import {
204193
+ streamHeightDigests
204194
+ } from "@secondlayer/shared/archive/semantic-digest-builder";
204195
+ var PART_FOR_DATASET = {
204196
+ blocks: "block",
204197
+ transactions: "transactions",
204198
+ events: "events"
204199
+ };
204200
+ async function localPerHeightDigests(db, fromBlock, toBlock, datasets = ["blocks", "transactions", "events"]) {
204201
+ const accumulator = new PerHeightDigestAccumulator;
204202
+ for await (const { height, digest } of streamHeightDigests(db, "blocks", fromBlock, toBlock)) {
204203
+ accumulator.setBlockDigest(height, digest);
204204
+ }
204205
+ if (datasets.includes("transactions")) {
204206
+ for await (const { height, digest } of streamHeightDigests(db, "transactions", fromBlock, toBlock)) {
204207
+ accumulator.appendTransactionDigest(height, digest);
204208
+ }
204209
+ }
204210
+ if (datasets.includes("events")) {
204211
+ for await (const { height, digest } of streamHeightDigests(db, "events", fromBlock, toBlock)) {
204212
+ accumulator.appendEventDigest(height, digest);
204213
+ }
204214
+ }
204215
+ const rows = new Map;
204216
+ for (const row of accumulator.drain(fromBlock, toBlock)) {
204217
+ rows.set(row.height, row);
204218
+ }
204219
+ return rows;
204220
+ }
204221
+ function sidecarsCovering(digestIndex, fromBlock, toBlock) {
204222
+ return (digestIndex ?? []).filter((p) => p.from_block <= toBlock && p.to_block >= fromBlock);
204223
+ }
204224
+ async function fetchSidecarRows(reference, partition) {
204225
+ const bytes = await fetchVerifiedPartition(reference, partition);
204226
+ const rows = [];
204227
+ for await (const raw of readPartitionRows(bytes, `digests-${partition.from_block}-${partition.to_block}`)) {
204228
+ const row = raw;
204229
+ rows.push({
204230
+ height: Number(row.height),
204231
+ block_digest: String(row.block_digest),
204232
+ transactions_rollup: row.transactions_rollup == null ? null : String(row.transactions_rollup),
204233
+ events_rollup: row.events_rollup == null ? null : String(row.events_rollup)
204234
+ });
204235
+ }
204236
+ return rows;
204237
+ }
204238
+ function compareHeights(params) {
204239
+ const { dataset, fromBlock, toBlock, local, archive, covered } = params;
204240
+ const inCoverage = (h) => covered.some((c) => h >= c.from_block && h <= c.to_block);
204241
+ const heights = new Set;
204242
+ for (const h of archive.keys())
204243
+ heights.add(h);
204244
+ for (const h of local.keys())
204245
+ if (inCoverage(h))
204246
+ heights.add(h);
204247
+ const out = [];
204248
+ for (const height of [...heights].sort((a, b) => a - b)) {
204249
+ if (height < fromBlock || height > toBlock)
204250
+ continue;
204251
+ const mine = local.get(height);
204252
+ const theirs = archive.get(height);
204253
+ if (!mine && theirs) {
204254
+ out.push({ height, parts: ["missing-local"] });
204255
+ } else if (mine && !theirs) {
204256
+ out.push({ height, parts: ["missing-archive"] });
204257
+ } else if (mine && theirs && !partsMatch(dataset, mine, theirs)) {
204258
+ out.push({ height, parts: [PART_FOR_DATASET[dataset]] });
204259
+ }
204260
+ }
204261
+ return out;
204262
+ }
204263
+ function partsMatch(dataset, a, b) {
204264
+ if (dataset === "blocks")
204265
+ return a.block_digest === b.block_digest;
204266
+ if (dataset === "transactions") {
204267
+ return a.transactions_rollup === b.transactions_rollup;
204268
+ }
204269
+ return a.events_rollup === b.events_rollup;
204270
+ }
204271
+ async function divergentHeightsForRange(params) {
204272
+ const { db, reference, dataset, fromBlock, toBlock } = params;
204273
+ const sidecars = sidecarsCovering(params.digestIndex, fromBlock, toBlock);
204274
+ if (sidecars.length === 0)
204275
+ return null;
204276
+ const archive = new Map;
204277
+ for (const sidecar of sidecars) {
204278
+ for (const row of await fetchSidecarRows(reference, sidecar)) {
204279
+ archive.set(row.height, row);
204280
+ }
204281
+ }
204282
+ const local = await localPerHeightDigests(db, fromBlock, toBlock, [dataset]);
204283
+ return compareHeights({
204284
+ dataset,
204285
+ fromBlock,
204286
+ toBlock,
204287
+ local,
204288
+ archive,
204289
+ covered: sidecars
204290
+ });
204291
+ }
204292
+ function mergeRepairRanges(ranges) {
204293
+ const sorted = [...ranges].sort((a, b) => a.from_block - b.from_block || a.to_block - b.to_block);
204294
+ const merged = [];
204295
+ for (const range of sorted) {
204296
+ const last = merged[merged.length - 1];
204297
+ if (last && range.from_block <= last.to_block + 1) {
204298
+ last.to_block = Math.max(last.to_block, range.to_block);
204299
+ } else {
204300
+ merged.push({ from_block: range.from_block, to_block: range.to_block });
204301
+ }
204302
+ }
204303
+ return merged;
204304
+ }
204305
+
204306
+ // src/commands/verify.ts
204168
204307
  init_output();
204169
204308
  init_resolve_auth();
204170
204309
 
@@ -204559,6 +204698,7 @@ function parseHeight3(value, flag) {
204559
204698
  }
204560
204699
  return parsed;
204561
204700
  }
204701
+ var MAX_HEIGHTS_SHOWN = 20;
204562
204702
  function statusLabel(status) {
204563
204703
  if (status === "match")
204564
204704
  return green("ok");
@@ -204597,9 +204737,10 @@ Exit codes:
204597
204737
  envPem: process.env.ARCHIVE_SIGNING_PUBLIC_KEY ?? process.env.STREAMS_SIGNING_PUBLIC_KEY,
204598
204738
  allowHostedApi: !isOssMode()
204599
204739
  });
204600
- const { manifest, origin } = await loadReference(opts.against, {
204740
+ const loaded = await loadReference(opts.against, {
204601
204741
  publicKeyPem: publicKey
204602
204742
  });
204743
+ const { manifest, origin } = loaded;
204603
204744
  const signature = checkSignature(manifest, publicKey, !!opts.insecure);
204604
204745
  if (!signature.verified && !opts.insecure) {
204605
204746
  printError(`Cannot trust the reference: ${signature.reason}.`, {
@@ -204732,6 +204873,30 @@ Exit codes:
204732
204873
  }))
204733
204874
  ];
204734
204875
  const diverged = all.filter((c) => c.status !== "match");
204876
+ const digestIndex = manifest.digest_index;
204877
+ const heightNotes = [];
204878
+ if (diverged.length > 0 && !digestIndex?.length) {
204879
+ heightNotes.push("This snapshot has no per-height digests; exact diverging heights need a newer snapshot.");
204880
+ } else {
204881
+ for (const range of diverged) {
204882
+ try {
204883
+ const heights = await divergentHeightsForRange({
204884
+ db,
204885
+ reference: loaded,
204886
+ digestIndex,
204887
+ dataset: range.dataset,
204888
+ fromBlock: range.from_block,
204889
+ toBlock: range.to_block
204890
+ });
204891
+ if (heights)
204892
+ range.divergent_heights = heights;
204893
+ else
204894
+ heightNotes.push(`No per-height digests cover ${range.from_block}-${range.to_block}.`);
204895
+ } catch (err) {
204896
+ heightNotes.push(`Could not narrow ${range.from_block}-${range.to_block} to heights: ${err instanceof Error ? err.message : String(err)}`);
204897
+ }
204898
+ }
204899
+ }
204735
204900
  const verdict = reportVerify({
204736
204901
  target,
204737
204902
  mode,
@@ -204744,7 +204909,8 @@ Exit codes:
204744
204909
  signature_verified: signature.verified,
204745
204910
  ranges_checked: all.length,
204746
204911
  semantic_checks: semanticChecks.length,
204747
- divergent_ranges: diverged
204912
+ divergent_ranges: diverged,
204913
+ ...heightNotes.length > 0 ? { note: heightNotes.join(" ") } : {}
204748
204914
  };
204749
204915
  output({
204750
204916
  json: opts.json,
@@ -204764,11 +204930,28 @@ Exit codes:
204764
204930
  String(d.expected_rows),
204765
204931
  String(d.actual_rows)
204766
204932
  ])));
204933
+ for (const range of diverged) {
204934
+ const heights = range.divergent_heights;
204935
+ if (!heights)
204936
+ continue;
204937
+ console.error("");
204938
+ console.error(`${range.from_block}-${range.to_block} ${range.dataset}: ${heights.length} diverging height${heights.length === 1 ? "" : "s"}`);
204939
+ for (const h of heights.slice(0, MAX_HEIGHTS_SHOWN)) {
204940
+ console.error(` ${h.height} ${h.parts.join(", ")}`);
204941
+ }
204942
+ if (heights.length > MAX_HEIGHTS_SHOWN) {
204943
+ console.error(dim(` and ${heights.length - MAX_HEIGHTS_SHOWN} more (see --json)`));
204944
+ }
204945
+ }
204946
+ if (heightNotes.length > 0) {
204947
+ console.error("");
204948
+ for (const line of heightNotes)
204949
+ console.error(dim(` ${line}`));
204950
+ }
204767
204951
  console.error("");
204768
204952
  console.error(dim("These ranges hold different data than the signed archive. Repair with:"));
204769
- const first = diverged[0];
204770
- if (first) {
204771
- console.error(dim(` secondlayer repair --from-block ${first.from_block} --to-block ${first.to_block}`));
204953
+ for (const range of mergeRepairRanges(diverged)) {
204954
+ console.error(dim(` secondlayer repair --from-block ${range.from_block} --to-block ${range.to_block}`));
204772
204955
  }
204773
204956
  }
204774
204957
  });
@@ -207861,12 +208044,24 @@ async function runTokenLogin() {
207861
208044
  }, target);
207862
208045
  success(`Logged in as ${account.email} (${target})`);
207863
208046
  }
207864
- async function shouldContinueLogin(target, options) {
208047
+ var defaultLoginCheckDeps = {
208048
+ readSession,
208049
+ verify: (target, token) => httpAt(target, "/api/accounts/me", { bearer: token })
208050
+ };
208051
+ async function shouldContinueLogin(target, options, deps = defaultLoginCheckDeps) {
207865
208052
  if (options.force)
207866
208053
  return true;
207867
- const existing = await readSession(target);
208054
+ const existing = await deps.readSession(target);
207868
208055
  if (!existing)
207869
208056
  return true;
208057
+ try {
208058
+ await deps.verify(target, existing.token);
208059
+ } catch (err) {
208060
+ if (err instanceof CliHttpError && (err.status === 401 || err.status === 403)) {
208061
+ info(`Your saved login for ${existing.email} stopped working (revoked or expired). Signing in again.`);
208062
+ return true;
208063
+ }
208064
+ }
207870
208065
  info(`Already logged in as ${existing.email} (${target}).`);
207871
208066
  const hint = "Run 'secondlayer logout' first, or re-run with --force to switch accounts.";
207872
208067
  if (!process.stdin.isTTY) {
@@ -207936,10 +208131,10 @@ async function runDeviceLogin(options = {}) {
207936
208131
  error(err instanceof Error ? err.message : String(err));
207937
208132
  process.exit(1);
207938
208133
  }
207939
- info(`First copy your one-time code: ${start.userCode}`);
208134
+ info(`Your one-time code: ${start.userCode}`);
207940
208135
  const readline = process.stdin.isTTY ? createInterface({ input: process.stdin }) : null;
207941
208136
  if (readline) {
207942
- info("Press Enter to open secondlayer.tools in your browser...");
208137
+ info("Press Enter to open the sign-in page, then check the code matches.");
207943
208138
  readline.once("line", () => openBrowser(start.verificationUrl));
207944
208139
  }
207945
208140
  info(dim(`Or open ${start.verificationUrl}`));
@@ -208659,5 +208854,5 @@ registerCreditsCommand(program);
208659
208854
  registerArchiveCommand(program);
208660
208855
  program.parse();
208661
208856
 
208662
- //# debugId=32DC3B7BA2B6602D64756E2164756E21
208857
+ //# debugId=32F86A2D9F7E084964756E2164756E21
208663
208858
  //# sourceMappingURL=cli.js.map