@secondlayer/cli 23.4.3 → 23.5.1

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
@@ -2129,12 +2129,16 @@ Expecting one of '${allowedValues.join("', '")}'`);
2129
2129
  });
2130
2130
 
2131
2131
  // src/lib/api-url.ts
2132
- import { isHostedApiUrl, resolveBaseUrl } from "@secondlayer/sdk";
2132
+ import {
2133
+ isHostedApiUrl,
2134
+ resolveAccountUrl,
2135
+ resolveBaseUrl
2136
+ } from "@secondlayer/sdk";
2133
2137
  function resolveApiUrl() {
2134
2138
  return resolveBaseUrl();
2135
2139
  }
2136
2140
  function resolveArchiveOpsUrl() {
2137
- return (process.env.SL_CREDITS_API_URL ?? ARCHIVE_OPS_API_URL).replace(/\/+$/, "");
2141
+ return resolveAccountUrl();
2138
2142
  }
2139
2143
  function isMerchantUrl(url = resolveApiUrl()) {
2140
2144
  return isHostedApiUrl(url);
@@ -2160,6 +2164,7 @@ import {
2160
2164
  } from "node:fs/promises";
2161
2165
  import { homedir } from "node:os";
2162
2166
  import { dirname, join } from "node:path";
2167
+ import { isHostedApiUrl as isHostedApiUrl2 } from "@secondlayer/sdk";
2163
2168
  import { z } from "zod";
2164
2169
  function getSessionPath() {
2165
2170
  return join(process.env.HOME || homedir(), ".secondlayer", "session.json");
@@ -2193,9 +2198,22 @@ async function readSessionFile(path) {
2193
2198
  return { sessions: {} };
2194
2199
  }
2195
2200
  }
2196
- async function readSession(apiUrl = resolveApiUrl(), path = getSessionPath()) {
2201
+ async function readSession(apiUrl = resolveArchiveOpsUrl(), path = getSessionPath()) {
2197
2202
  const file = await readSessionFile(path);
2198
- return file.sessions[sessionKey(apiUrl)] ?? null;
2203
+ const key = sessionKey(apiUrl);
2204
+ const found = file.sessions[key];
2205
+ if (found)
2206
+ return found;
2207
+ if (key !== sessionKey(resolveArchiveOpsUrl()) || !isHostedApiUrl2(key)) {
2208
+ return null;
2209
+ }
2210
+ const legacy = [ARCHIVE_OPS_API_URL, resolveApiUrl()].map(sessionKey).filter((candidate) => candidate !== key && isHostedApiUrl2(candidate)).map((candidate) => file.sessions[candidate]).find((session) => session !== undefined);
2211
+ if (!legacy)
2212
+ return null;
2213
+ try {
2214
+ await writeSessionFile({ sessions: { ...file.sessions, [key]: legacy } }, path);
2215
+ } catch {}
2216
+ return legacy;
2199
2217
  }
2200
2218
  async function writeSessionFile(file, path) {
2201
2219
  const dir = dirname(path);
@@ -2209,12 +2227,12 @@ async function writeSessionFile(file, path) {
2209
2227
  await chmod(tmp, 384);
2210
2228
  await rename(tmp, path);
2211
2229
  }
2212
- async function writeSession(session, apiUrl = resolveApiUrl(), path = getSessionPath()) {
2230
+ async function writeSession(session, apiUrl = resolveArchiveOpsUrl(), path = getSessionPath()) {
2213
2231
  const file = await readSessionFile(path);
2214
2232
  file.sessions[sessionKey(apiUrl)] = session;
2215
2233
  await writeSessionFile(file, path);
2216
2234
  }
2217
- async function clearSession(apiUrl = resolveApiUrl(), path = getSessionPath()) {
2235
+ async function clearSession(apiUrl = resolveArchiveOpsUrl(), path = getSessionPath()) {
2218
2236
  const file = await readSessionFile(path);
2219
2237
  delete file.sessions[sessionKey(apiUrl)];
2220
2238
  if (Object.keys(file.sessions).length === 0) {
@@ -2258,7 +2276,7 @@ async function resolveAuth() {
2258
2276
  if (isOssMode()) {
2259
2277
  return { apiUrl, ephemeralKey: "", fromEnv: true };
2260
2278
  }
2261
- const session = await readSession();
2279
+ const session = sessionKey(apiUrl) === sessionKey(resolveArchiveOpsUrl()) ? await readSession() : null;
2262
2280
  if (!session) {
2263
2281
  const err = new Error("Not logged in — run `secondlayer login`");
2264
2282
  err.code = "SESSION_EXPIRED";
@@ -2299,7 +2317,11 @@ var init_resolve_auth = __esm(() => {
2299
2317
  });
2300
2318
 
2301
2319
  // src/lib/http.ts
2302
- import { resolveAccountKey as resolveAccountKey2, resolveApiKey as resolveApiKey2 } from "@secondlayer/sdk";
2320
+ import {
2321
+ resolveAccountKey as resolveAccountKey2,
2322
+ resolveApiKey as resolveApiKey2,
2323
+ resolveIdentity
2324
+ } from "@secondlayer/sdk";
2303
2325
  async function request(url, opts) {
2304
2326
  const headers = {
2305
2327
  "content-type": "application/json",
@@ -2340,20 +2362,11 @@ async function httpArchiveOpsAnon(path, opts = {}) {
2340
2362
  async function resolveArchiveOpsBearer() {
2341
2363
  const envKey = resolveAccountKey2();
2342
2364
  const session = await readSession(resolveArchiveOpsUrl());
2343
- if (session) {
2344
- return {
2345
- bearer: session.token,
2346
- source: "session",
2347
- ignoredEnvKey: false
2348
- };
2349
- }
2350
- if (envKey && ARCHIVE_CREDENTIAL_RE.test(envKey)) {
2351
- return { bearer: envKey, source: "env", ignoredEnvKey: false };
2352
- }
2365
+ const identity = resolveIdentity("account", { session: session?.token });
2353
2366
  return {
2354
- bearer: undefined,
2355
- source: null,
2356
- ignoredEnvKey: resolveApiKey2() !== undefined && envKey === undefined
2367
+ bearer: identity.token,
2368
+ source: identity.source === "none" ? null : identity.source,
2369
+ ignoredEnvKey: identity.source === "none" && resolveApiKey2() !== undefined && envKey === undefined
2357
2370
  };
2358
2371
  }
2359
2372
  function ignoredEnvKeyNote(ignored) {
@@ -2371,13 +2384,13 @@ async function httpArchiveOps(path, opts = {}) {
2371
2384
  });
2372
2385
  } catch (err) {
2373
2386
  if (err instanceof CliHttpError && err.status === 401) {
2374
- const remedy = source === "session" ? `Your saved login was rejected (${err.message}). Run \`secondlayer login\`, or \`secondlayer logout\` to use SECONDLAYER_API_KEY.` : `Your account rejected the credential (${err.message}). ${ARCHIVE_AUTH_HINT}${ignoredEnvKeyNote(ignoredEnvKey)}`;
2387
+ const remedy = source === "session" ? `Your saved login was rejected (${err.message}). Run \`secondlayer login\`, or set SECONDLAYER_API_KEY (it takes precedence over a saved login).` : `Your account rejected the credential (${err.message}). ${ARCHIVE_AUTH_HINT}${ignoredEnvKeyNote(ignoredEnvKey)}`;
2375
2388
  throw new CliHttpError(401, err.code, err.body, remedy);
2376
2389
  }
2377
2390
  throw err;
2378
2391
  }
2379
2392
  }
2380
- var CliHttpError, REQUEST_TIMEOUT_MS = 30000, ARCHIVE_AUTH_HINT = "Set SECONDLAYER_API_KEY to your account key (sk-sl_…, from /account/keys), or run `secondlayer login`.", ARCHIVE_CREDENTIAL_RE;
2393
+ var CliHttpError, REQUEST_TIMEOUT_MS = 30000, ARCHIVE_AUTH_HINT = "Set SECONDLAYER_API_KEY to your account key (sk-sl_…, from /account/keys), or run `secondlayer login`.";
2381
2394
  var init_http = __esm(() => {
2382
2395
  init_api_url();
2383
2396
  init_resolve_auth();
@@ -2395,7 +2408,6 @@ var init_http = __esm(() => {
2395
2408
  this.body = body;
2396
2409
  }
2397
2410
  };
2398
- ARCHIVE_CREDENTIAL_RE = /^s[ks]-sl_/;
2399
2411
  });
2400
2412
 
2401
2413
  // src/lib/output.ts
@@ -29022,6 +29034,25 @@ var init_subgraphs = __esm(() => {
29022
29034
  init_format();
29023
29035
  });
29024
29036
 
29037
+ // src/lib/coalesced-runner.ts
29038
+ function createCoalescedRunner(task) {
29039
+ let state = "idle";
29040
+ const trigger = async () => {
29041
+ if (state !== "idle") {
29042
+ state = "deployingDirty";
29043
+ return;
29044
+ }
29045
+ do {
29046
+ state = "deploying";
29047
+ try {
29048
+ await task();
29049
+ } catch {}
29050
+ } while (state === "deployingDirty");
29051
+ state = "idle";
29052
+ };
29053
+ return { trigger, state: () => state };
29054
+ }
29055
+
29025
29056
  // src/lib/derive-abi.ts
29026
29057
  import {
29027
29058
  extractSubgraphDefinition,
@@ -30006,14 +30037,26 @@ import {
30006
30037
  generatePrintSchemaSubgraph as generatePrintSchemaSubgraph2,
30007
30038
  generateTraitSubgraph as generateTraitSubgraph2
30008
30039
  } from "@secondlayer/scaffold";
30009
- import {
30010
- billingPausedCode
30011
- } from "@secondlayer/shared/schemas";
30012
30040
  import { TRAIT_STANDARDS } from "@secondlayer/stacks/clarity";
30013
30041
  import {
30014
30042
  deriveVerification as deriveVerification2,
30015
30043
  isDeterminismViolation
30016
30044
  } from "@secondlayer/subgraphs/verification";
30045
+ function watchVerdict(subgraph) {
30046
+ if (subgraph.health.billingPausedCode) {
30047
+ return {
30048
+ kind: "stopped",
30049
+ message: `Subgraph is paused: ${subgraph.health.billingPausedCode.replace(/_/g, " ")}. Fix billing, then re-run.`
30050
+ };
30051
+ }
30052
+ if (subgraph.status === "error" || subgraph.sync?.status === "error") {
30053
+ return {
30054
+ kind: "stopped",
30055
+ message: `Subgraph is in error: ${subgraph.health.lastError ?? "no error recorded"}`
30056
+ };
30057
+ }
30058
+ return subgraph.sync?.status === "synced" ? { kind: "synced" } : { kind: "running" };
30059
+ }
30017
30060
  async function loadSubgraphDefinition(source, fileName) {
30018
30061
  const { bundleSubgraphCode } = await import("@secondlayer/bundler");
30019
30062
  const bundled = await bundleSubgraphCode(source, { fileName });
@@ -30379,9 +30422,8 @@ function formatOperationRange(op) {
30379
30422
  return "whole subgraph";
30380
30423
  return `${op.fromBlock ?? "start"} → ${op.toBlock ?? "tip"}`;
30381
30424
  }
30382
- function formatSubgraphStatus(status, lastError) {
30383
- const code = billingPausedCode(lastError);
30384
- return code ? `${status} (paused: ${code.replace(/_/g, " ")})` : status;
30425
+ function formatSubgraphStatus(status, billingPausedCode) {
30426
+ return billingPausedCode ? `${status} (paused: ${billingPausedCode.replace(/_/g, " ")})` : status;
30385
30427
  }
30386
30428
  function formatOperationProgress(progress) {
30387
30429
  if (progress === null)
@@ -30627,7 +30669,8 @@ Check the contract id, and that the contract has emitted print events.`);
30627
30669
  error(`[${new Date().toLocaleTimeString()}] ${err}`);
30628
30670
  }
30629
30671
  };
30630
- await deploySubgraph();
30672
+ const runner = createCoalescedRunner(deploySubgraph);
30673
+ await runner.trigger();
30631
30674
  let timeout = null;
30632
30675
  const watcher = watch(absPath, () => {
30633
30676
  if (timeout)
@@ -30635,7 +30678,7 @@ Check the contract id, and that the contract has emitted print events.`);
30635
30678
  timeout = setTimeout(async () => {
30636
30679
  console.log("");
30637
30680
  info("File changed, redeploying...");
30638
- await deploySubgraph();
30681
+ await runner.trigger();
30639
30682
  }, 300);
30640
30683
  });
30641
30684
  process.on("SIGINT", () => {
@@ -30892,7 +30935,7 @@ ${data.length} subgraph(s) total`));
30892
30935
  handleApiError(err, "list subgraphs");
30893
30936
  }
30894
30937
  });
30895
- subgraphs.command("status <name>").alias("get").description("Show detailed subgraph status").option("-w, --watch", "Refresh every 2s until synced or Ctrl-C").action(async (name, options) => {
30938
+ subgraphs.command("status <name>").alias("get").description("Show detailed subgraph status").option("-w, --watch", "Refresh every 2s until synced; exits non-zero on error or a billing pause, or Ctrl-C").action(async (name, options) => {
30896
30939
  const renderOnce = async (opts = {}) => {
30897
30940
  const subgraph = await getSubgraphApi(name);
30898
30941
  if (opts.clearScreen)
@@ -30915,7 +30958,7 @@ ${data.length} subgraph(s) total`));
30915
30958
  ["Verification", formatVerificationStatus(subgraph.verification)],
30916
30959
  [
30917
30960
  "Status",
30918
- formatSubgraphStatus(subgraph.status, subgraph.health.lastError)
30961
+ formatSubgraphStatus(subgraph.status, subgraph.health.billingPausedCode)
30919
30962
  ],
30920
30963
  ["Sync", syncDisplay.line],
30921
30964
  [syncDisplay.remainingLabel, syncDisplay.remaining],
@@ -30964,11 +31007,16 @@ Table endpoints:`));
30964
31007
  await new Promise((res) => setTimeout(res, 2000));
30965
31008
  continue;
30966
31009
  }
30967
- if (sg?.sync?.status === "synced") {
31010
+ const verdict = watchVerdict(sg);
31011
+ if (verdict.kind === "synced") {
30968
31012
  console.log(dim(`
30969
31013
  Synced, exiting watch.`));
30970
31014
  return;
30971
31015
  }
31016
+ if (verdict.kind === "stopped") {
31017
+ error(verdict.message);
31018
+ process.exit(1);
31019
+ }
30972
31020
  await new Promise((res) => setTimeout(res, 2000));
30973
31021
  }
30974
31022
  } catch (err) {
@@ -31868,7 +31916,7 @@ var {
31868
31916
  // package.json
31869
31917
  var package_default = {
31870
31918
  name: "@secondlayer/cli",
31871
- version: "23.4.3",
31919
+ version: "23.5.1",
31872
31920
  description: "Run a Stacks index on your own hardware — set it up, restore verified history, and keep it current.",
31873
31921
  repository: {
31874
31922
  type: "git",
@@ -31917,10 +31965,10 @@ var package_default = {
31917
31965
  "@inquirer/prompts": "^8.2.0",
31918
31966
  "@secondlayer/bundler": "^0.7.3",
31919
31967
  "@secondlayer/scaffold": "^1.6.3",
31920
- "@secondlayer/sdk": "^16.2.0",
31921
- "@secondlayer/shared": "^15.2.1",
31968
+ "@secondlayer/sdk": "^16.3.2",
31969
+ "@secondlayer/shared": "^15.3.2",
31922
31970
  "@secondlayer/stacks": "^6.6.0",
31923
- "@secondlayer/subgraphs": "^8.0.4",
31971
+ "@secondlayer/subgraphs": "^8.1.0",
31924
31972
  "@secondlayer/verify": "^0.6.3",
31925
31973
  esbuild: "^0.28.2",
31926
31974
  "smol-toml": "^1.9.1",
@@ -34489,7 +34537,7 @@ function registerCreditsCommand(program) {
34489
34537
  credits.command("buy").description("Open a one-time Stripe checkout for credits").requiredOption("--email <email>", "Receipt email; becomes the account").option("--pack <usd>", `One of ${PACKS.join(", ")}`, "25").option("--json", "Output as JSON").action((opts) => runBuy(opts));
34490
34538
  credits.command("balance").description("Show prepaid archive credit balance").option("--json", "Output as JSON").action((opts) => runBalance(opts));
34491
34539
  credits.command("usage").description("Rows delivered per day: free vs paid, by source").option("--days <n>", "Days to show (max 30)", "30").option("--json", "Output as JSON").action((opts) => runUsage(opts));
34492
- credits.command("refill").description("Opt-in auto-refill when the balance drops under a threshold").option("--below <usd>", "Trigger when balance is under this USD amount").option("--pack <usd>", `Pack to buy (${PACKS.join(", ")})`, "25").option("--off", "Turn auto-refill off").option("--json", "Output as JSON").action((opts) => runRefill(opts));
34540
+ credits.command("refill").description("Opt-in auto-refill when the balance drops under a threshold. Needs a signed-in session (`secondlayer login`), not an API key").option("--below <usd>", "Trigger when balance is under this USD amount").option("--pack <usd>", `Pack to buy (${PACKS.join(", ")})`, "25").option("--off", "Turn auto-refill off").option("--json", "Output as JSON").action((opts) => runRefill(opts));
34493
34541
  }
34494
34542
  async function runRefill(opts) {
34495
34543
  if (!opts.off && opts.below === undefined) {
@@ -35317,7 +35365,7 @@ function attachWebhookSubcommands(parent) {
35317
35365
  console.log(formatTable(["Name", "ID", "Status", "Target", "Format", "Last Success"], data.map((sub) => [
35318
35366
  sub.name,
35319
35367
  sub.id,
35320
- sub.status === "active" ? green(sub.status) : sub.status === "paused" ? yellow(sub.status) : redStatus(sub.status),
35368
+ sub.status === "active" ? green(sub.status) : yellow(sub.status),
35321
35369
  formatWebhookTarget(sub),
35322
35370
  sub.format,
35323
35371
  formatMaybeDate(sub.lastSuccessAt)
@@ -37083,7 +37131,9 @@ function registerLoginCommand(program) {
37083
37131
  Examples:
37084
37132
  $ secondlayer login
37085
37133
  $ secondlayer login --email
37086
- $ echo "$SECONDLAYER_API_KEY" | secondlayer login --with-token`).action((opts) => opts.withToken ? runTokenLogin() : opts.email ? runLoginFlow({ force: opts.force }) : runDeviceLogin({ force: opts.force }));
37134
+ $ echo "$SECONDLAYER_API_KEY" | secondlayer login --with-token
37135
+
37136
+ Precedence: SECONDLAYER_API_KEY (when set) beats the saved login. Unset it to act as the logged-in account.`).action((opts) => opts.withToken ? runTokenLogin() : opts.email ? runLoginFlow({ force: opts.force }) : runDeviceLogin({ force: opts.force }));
37087
37137
  }
37088
37138
  // src/commands/logout.ts
37089
37139
  init_http();
@@ -37718,5 +37768,5 @@ registerCreditsCommand(program);
37718
37768
  registerArchiveCommand(program);
37719
37769
  program.parse();
37720
37770
 
37721
- //# debugId=CCB6C7D1DE3F411864756E2164756E21
37771
+ //# debugId=2C10A69C0E77EE3B64756E2164756E21
37722
37772
  //# sourceMappingURL=cli.js.map