@secondlayer/cli 14.1.1 → 14.2.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
@@ -2483,11 +2483,24 @@ var require_commander = __commonJS((exports) => {
2483
2483
 
2484
2484
  // src/lib/api-url.ts
2485
2485
  function resolveApiUrl() {
2486
- return (process.env.SL_API_URL ?? process.env.SL_PLATFORM_API_URL ?? LOCAL_API_URL).replace(/\/+$/, "");
2486
+ return (process.env.SECONDLAYER_API_URL ?? process.env.SL_API_URL ?? process.env.SL_PLATFORM_API_URL ?? LOCAL_API_URL).replace(/\/+$/, "");
2487
2487
  }
2488
2488
  function resolveArchiveOpsUrl() {
2489
2489
  return (process.env.SL_CREDITS_API_URL ?? ARCHIVE_OPS_API_URL).replace(/\/+$/, "");
2490
2490
  }
2491
+ function isMerchantUrl(url = resolveApiUrl()) {
2492
+ try {
2493
+ return new URL(url).hostname === "api.secondlayer.tools";
2494
+ } catch {
2495
+ return false;
2496
+ }
2497
+ }
2498
+ function assertInstanceUrl(url = resolveApiUrl()) {
2499
+ if (!isMerchantUrl(url))
2500
+ return;
2501
+ throw new Error(`this command runs on your instance, not api.secondlayer.tools.
2502
+ unset SECONDLAYER_API_URL, or pass --api-url http://127.0.0.1:3800`);
2503
+ }
2491
2504
  var LOCAL_API_URL = "http://127.0.0.1:3800", ARCHIVE_OPS_API_URL = "https://api.secondlayer.tools";
2492
2505
 
2493
2506
  // src/lib/session.ts
@@ -2578,10 +2591,18 @@ var init_session = __esm(() => {
2578
2591
  });
2579
2592
 
2580
2593
  // src/lib/resolve-auth.ts
2581
- import { resolveApiKey } from "@secondlayer/sdk";
2594
+ import { resolveAccountKey, resolveApiKey } from "@secondlayer/sdk";
2582
2595
  function resolveEnvKey() {
2583
2596
  return resolveApiKey();
2584
2597
  }
2598
+ function applyApiKeyFlag(apiKey) {
2599
+ if (/^s[ks]-sl_/.test(apiKey)) {
2600
+ process.env.SECONDLAYER_API_KEY = apiKey;
2601
+ process.env.SL_API_KEY = apiKey;
2602
+ } else {
2603
+ process.env.INSTANCE_TOKEN = apiKey;
2604
+ }
2605
+ }
2585
2606
  async function resolveAuth() {
2586
2607
  const apiUrl = resolveApiUrl();
2587
2608
  const envKey = resolveEnvKey();
@@ -2600,17 +2621,19 @@ async function resolveAuth() {
2600
2621
  return { apiUrl, ephemeralKey: session.token, fromEnv: false };
2601
2622
  }
2602
2623
  function isOssMode() {
2603
- try {
2604
- return new URL(resolveApiUrl()).hostname !== "api.secondlayer.tools";
2605
- } catch {
2606
- return true;
2607
- }
2624
+ return !isMerchantUrl();
2625
+ }
2626
+ function resolveDataPlaneKey() {
2627
+ if (isMerchantUrl())
2628
+ return resolveAccountKey();
2629
+ return resolveApiKey();
2608
2630
  }
2609
2631
  var init_resolve_auth = __esm(() => {
2610
2632
  init_session();
2611
2633
  });
2612
2634
 
2613
2635
  // src/lib/http.ts
2636
+ import { resolveAccountKey as resolveAccountKey2, resolveApiKey as resolveApiKey2 } from "@secondlayer/sdk";
2614
2637
  async function request(url, opts) {
2615
2638
  const headers = {
2616
2639
  "content-type": "application/json",
@@ -2649,7 +2672,7 @@ async function httpArchiveOpsAnon(path2, opts = {}) {
2649
2672
  return httpAt(resolveArchiveOpsUrl(), path2, opts);
2650
2673
  }
2651
2674
  async function resolveArchiveOpsBearer() {
2652
- const envKey = resolveEnvKey();
2675
+ const envKey = resolveAccountKey2();
2653
2676
  const session = await readSession(resolveArchiveOpsUrl());
2654
2677
  if (session) {
2655
2678
  return {
@@ -2664,7 +2687,7 @@ async function resolveArchiveOpsBearer() {
2664
2687
  return {
2665
2688
  bearer: undefined,
2666
2689
  source: null,
2667
- ignoredEnvKey: envKey !== undefined
2690
+ ignoredEnvKey: resolveApiKey2() !== undefined && envKey === undefined
2668
2691
  };
2669
2692
  }
2670
2693
  function ignoredEnvKeyNote(ignored) {
@@ -16318,7 +16341,7 @@ class StacksApiClient {
16318
16341
  const statusCode = typeof error2 === "object" && error2 !== null && "response" in error2 && typeof error2.response === "object" && error2.response !== null && "statusCode" in error2.response ? error2.response.statusCode : undefined;
16319
16342
  const message = error2 instanceof Error ? error2.message : String(error2);
16320
16343
  if (statusCode === 401) {
16321
- throw new Error(isOssMode() ? "Authentication required. Set INSTANCE_TOKEN from `secondlayer init` (SL_API_KEY is a legacy alias)." : "Authentication required. Run: secondlayer login");
16344
+ throw new Error(isOssMode() ? "Authentication required. Set INSTANCE_TOKEN from `secondlayer init`." : "Authentication required. Run: secondlayer login");
16322
16345
  }
16323
16346
  if (statusCode === 404) {
16324
16347
  throw new Error(`${resourceType} not found: ${resourceId}`);
@@ -28610,7 +28633,7 @@ function handleApiError(err, action2) {
28610
28633
  const status = err instanceof ApiError || err instanceof CliHttpError ? err.status : undefined;
28611
28634
  if (err instanceof CliHttpError && err.code === "SESSION_EXPIRED" || status === 401) {
28612
28635
  printError("Authentication required.", {
28613
- hint: isOssMode() ? "Set INSTANCE_TOKEN from `secondlayer init` (SL_API_KEY is a legacy alias)." : "Run `secondlayer login` to re-authenticate."
28636
+ hint: isOssMode() ? "Set INSTANCE_TOKEN from `secondlayer init`." : "Run `secondlayer login` to re-authenticate."
28614
28637
  });
28615
28638
  process.exit(1);
28616
28639
  }
@@ -29097,143 +29120,6 @@ function inspectSourceGitState(absPath) {
29097
29120
  }
29098
29121
  var init_git_status = () => {};
29099
29122
 
29100
- // src/commands/login.ts
29101
- import { confirm as confirm2, input } from "@inquirer/prompts";
29102
- function sessionExpiry() {
29103
- return new Date(Date.now() + 90 * 24 * 60 * 60 * 1000).toISOString();
29104
- }
29105
- async function readStdin() {
29106
- const chunks = [];
29107
- for await (const chunk of process.stdin)
29108
- chunks.push(chunk);
29109
- return Buffer.concat(chunks).toString("utf8").trim();
29110
- }
29111
- function loginTarget(opts) {
29112
- return opts.credits ? resolveArchiveOpsUrl() : resolveApiUrl();
29113
- }
29114
- async function runTokenLogin(opts) {
29115
- const token = await readStdin();
29116
- if (!token) {
29117
- error('No token on stdin. Usage: echo "$KEY" | secondlayer login --with-token');
29118
- process.exit(1);
29119
- }
29120
- const target = loginTarget(opts);
29121
- let account;
29122
- try {
29123
- account = await httpAt(target, "/api/accounts/me", { bearer: token });
29124
- } catch (err) {
29125
- error(err instanceof CliHttpError ? `Token rejected by ${target}: ${err.message}` : err instanceof Error ? err.message : String(err));
29126
- process.exit(1);
29127
- }
29128
- await writeSession({
29129
- token,
29130
- email: account.email,
29131
- accountId: account.id,
29132
- expiresAt: sessionExpiry()
29133
- }, target);
29134
- success(`Logged in as ${account.email} (${target})`);
29135
- }
29136
- async function runLoginFlow(options3 = {}) {
29137
- const target = loginTarget(options3);
29138
- if (!options3.force) {
29139
- const existing = await readSession(target);
29140
- if (existing) {
29141
- info(`Already logged in as ${existing.email} (${target}).`);
29142
- if (!process.stdin.isTTY) {
29143
- info(dim("Run 'secondlayer logout' first, or re-run with --force to switch accounts."));
29144
- return;
29145
- }
29146
- try {
29147
- const proceed = await confirm2({
29148
- message: "Log in as a different user?",
29149
- default: false
29150
- });
29151
- if (!proceed) {
29152
- info(dim("Run 'secondlayer logout' to sign out."));
29153
- return;
29154
- }
29155
- } catch {
29156
- info(dim("Run 'secondlayer logout' first, or re-run with --force to switch accounts."));
29157
- return;
29158
- }
29159
- }
29160
- }
29161
- const email = await input({
29162
- message: "Email",
29163
- validate: (v2) => /^.+@.+\..+$/.test(v2) ? true : "Invalid email"
29164
- });
29165
- try {
29166
- const res = await httpAt(target, "/api/auth/magic-link", {
29167
- method: "POST",
29168
- body: { email }
29169
- });
29170
- info("Check your inbox for a 6-digit code.");
29171
- if (res.code) {
29172
- info(dim(`(DEV_MODE code: ${res.code})`));
29173
- }
29174
- } catch (err) {
29175
- if (err instanceof CliHttpError) {
29176
- error(err.message);
29177
- } else {
29178
- error(err instanceof Error ? err.message : String(err));
29179
- }
29180
- process.exit(1);
29181
- }
29182
- const code = await input({
29183
- message: "Enter the 6-digit code",
29184
- validate: (v2) => /^\d{6}$/.test(v2) ? true : "Expected 6 digits"
29185
- });
29186
- try {
29187
- const verified = await httpAt(target, "/api/auth/verify", {
29188
- method: "POST",
29189
- body: { email, code }
29190
- });
29191
- await writeSession({
29192
- token: verified.sessionToken,
29193
- email: verified.account.email,
29194
- accountId: verified.account.id,
29195
- expiresAt: sessionExpiry()
29196
- }, target);
29197
- success(`Logged in as ${verified.account.email}`);
29198
- info(dim(options3.credits ? "Run 'secondlayer credits balance' to see your archive credits." : "Run 'secondlayer whoami' to see your account status."));
29199
- } catch (err) {
29200
- if (err instanceof CliHttpError) {
29201
- error(err.message);
29202
- } else {
29203
- error(err instanceof Error ? err.message : String(err));
29204
- }
29205
- process.exit(1);
29206
- }
29207
- }
29208
- function registerLoginCommand(program2) {
29209
- 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)").option("--credits", "Log in to the archive credits account used by bootstrap, repair, and credits").addHelpText("after", `
29210
- Examples:
29211
- $ secondlayer login --credits
29212
- $ echo "$INSTANCE_TOKEN" | secondlayer login --with-token`).action((opts) => opts.withToken ? runTokenLogin({ credits: opts.credits }) : runLoginFlow({ force: opts.force, credits: opts.credits }));
29213
- }
29214
- var init_login = __esm(() => {
29215
- init_http();
29216
- init_output();
29217
- init_session();
29218
- });
29219
-
29220
- // src/lib/require-auth.ts
29221
- async function requireAuth() {
29222
- if (isOssMode())
29223
- return;
29224
- const session = await readSession();
29225
- if (session)
29226
- return;
29227
- info("You're not logged in. Starting login flow.");
29228
- await runLoginFlow();
29229
- }
29230
- var init_require_auth = __esm(() => {
29231
- init_login();
29232
- init_output();
29233
- init_resolve_auth();
29234
- init_session();
29235
- });
29236
-
29237
29123
  // src/templates/subgraph.ts
29238
29124
  function generateSubgraphStarter(name) {
29239
29125
  return `${nextStepsHeader(name)}${starter(name)}`;
@@ -29323,7 +29209,7 @@ __export(exports_subgraph_test, {
29323
29209
  import { createHash as createHash2 } from "node:crypto";
29324
29210
  import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "node:fs";
29325
29211
  import { dirname as dirname4, resolve as resolve2 } from "node:path";
29326
- import { Index, resolveApiKey as resolveApiKey2, resolveBaseUrl } from "@secondlayer/sdk";
29212
+ import { Index, resolveApiKey as resolveApiKey3, resolveBaseUrl } from "@secondlayer/sdk";
29327
29213
  import {
29328
29214
  DECODED_EVENT_TYPES
29329
29215
  } from "@secondlayer/stacks/filters";
@@ -29376,7 +29262,7 @@ function indexReadFailure(err, ctx) {
29376
29262
  };
29377
29263
  }
29378
29264
  if (status === 401 || status === 403) {
29379
- const credential = resolveApiKey2() ? "INSTANCE_TOKEN is set in this shell" : "no INSTANCE_TOKEN is set in this shell";
29265
+ const credential = resolveApiKey3() ? "INSTANCE_TOKEN is set in this shell" : "no INSTANCE_TOKEN is set in this shell";
29380
29266
  return {
29381
29267
  message: `Index read rejected (HTTP ${status}) while ${where}.`,
29382
29268
  hint: `${credential}. This read uses the same SL_API_URL + INSTANCE_TOKEN pair as \`secondlayer subgraphs deploy\` — confirm both point at the instance you deployed to (\`secondlayer status\`), then retry. ${offlineHint}`
@@ -29611,7 +29497,7 @@ import {
29611
29497
  } from "node:fs";
29612
29498
  import { basename, dirname as dirname5, extname, join as join4, resolve as resolve3 } from "node:path";
29613
29499
  import { fileURLToPath as fileURLToPath3 } from "node:url";
29614
- import { confirm as confirm3 } from "@inquirer/prompts";
29500
+ import { confirm as confirm2 } from "@inquirer/prompts";
29615
29501
  import {
29616
29502
  generatePrintPayloadTypes,
29617
29503
  generatePrintSchemaSubgraph
@@ -29627,7 +29513,7 @@ async function loadSubgraphWithDepCheck(absPath) {
29627
29513
  if (!missingSdk)
29628
29514
  throw err;
29629
29515
  warn("Missing dependency: @secondlayer/subgraphs");
29630
- const install = await confirm3({
29516
+ const install = await confirm2({
29631
29517
  message: "Install with `bun add @secondlayer/subgraphs`?",
29632
29518
  default: true
29633
29519
  });
@@ -29797,9 +29683,9 @@ async function specFromLocalFile(absPath, format2, specOptions) {
29797
29683
  });
29798
29684
  return format2 === "openapi" ? generateSubgraphOpenApi(detail, specOptions) : format2 === "agent" ? generateSubgraphAgentSchema(detail, specOptions) : generateSubgraphMarkdown(detail, specOptions);
29799
29685
  }
29800
- function createLocalSubgraphDetail(input2) {
29686
+ function createLocalSubgraphDetail(input) {
29801
29687
  const tables = {};
29802
- for (const [tableName, rawTable] of Object.entries(input2.schema)) {
29688
+ for (const [tableName, rawTable] of Object.entries(input.schema)) {
29803
29689
  const table = rawTable;
29804
29690
  const columns = {};
29805
29691
  for (const [columnName, column] of Object.entries(table.columns ?? {})) {
@@ -29816,22 +29702,22 @@ function createLocalSubgraphDetail(input2) {
29816
29702
  columns._tx_id = { type: "text" };
29817
29703
  columns._created_at = { type: "timestamp" };
29818
29704
  tables[tableName] = {
29819
- endpoint: `/subgraphs/${input2.name}/${tableName}`,
29705
+ endpoint: `/subgraphs/${input.name}/${tableName}`,
29820
29706
  columns,
29821
29707
  rowCount: 0,
29822
- example: `/subgraphs/${input2.name}/${tableName}?_sort=_block_height&_order=desc&_limit=10`,
29708
+ example: `/subgraphs/${input.name}/${tableName}?_sort=_block_height&_order=desc&_limit=10`,
29823
29709
  ...table.indexes && { indexes: table.indexes },
29824
29710
  ...table.uniqueKeys && { uniqueKeys: table.uniqueKeys }
29825
29711
  };
29826
29712
  }
29827
29713
  return {
29828
- name: input2.name,
29829
- version: input2.version ?? "0.0.0",
29830
- schemaHash: input2.schemaHash,
29714
+ name: input.name,
29715
+ version: input.version ?? "0.0.0",
29716
+ schemaHash: input.schemaHash,
29831
29717
  status: "local",
29832
29718
  lastProcessedBlock: 0,
29833
- ...input2.description && { description: input2.description },
29834
- sources: input2.sources,
29719
+ ...input.description && { description: input.description },
29720
+ sources: input.sources,
29835
29721
  health: {
29836
29722
  totalProcessed: 0,
29837
29723
  totalErrors: 0,
@@ -30050,6 +29936,7 @@ function formatSubgraphSync(sync) {
30050
29936
  }
30051
29937
  function registerSubgraphsCommand(program2) {
30052
29938
  const subgraphs = program2.command("subgraphs").description("Manage materialized subgraphs");
29939
+ subgraphs.hook("preAction", () => assertInstanceUrl());
30053
29940
  subgraphs.command("create <name>").description("Start here: create a new subgraph definition file (see also: subgraphs scaffold)").option("--from-contract <contractId>", "Generate sources/schema/handlers from the contract's observed print events (requires network)").option("--table-per-topic", "With --from-contract: one table per print topic instead of a single wide table").addHelpText("after", `
30054
29941
  create is the default way to start a subgraph. It writes subgraphs/<name>.ts —
30055
29942
  an empty starter, or with --from-contract a schema inferred from the print
@@ -30186,7 +30073,7 @@ otherwise the deployed definition's only copy is the row in the database. Pass
30186
30073
  const message = `${file} is ${reason}. Deploy it and the only copy of this definition is a row in the database.`;
30187
30074
  if (process.stdin.isTTY) {
30188
30075
  warn(message);
30189
- const confirmed = await confirm3({
30076
+ const confirmed = await confirm2({
30190
30077
  message: "Deploy anyway?",
30191
30078
  default: false
30192
30079
  });
@@ -30202,9 +30089,6 @@ otherwise the deployed definition's only copy is the row in the database. Pass
30202
30089
  }
30203
30090
  }
30204
30091
  const config = await loadConfig2();
30205
- if (config.network !== "local" && !isOssMode()) {
30206
- await requireAuth();
30207
- }
30208
30092
  const dryRun = options3.dryRun;
30209
30093
  const startBlock = parseStartBlockOption(options3.startBlock);
30210
30094
  if (startBlock !== undefined) {
@@ -30927,8 +30811,6 @@ var init_subgraphs2 = __esm(() => {
30927
30811
  init_fs();
30928
30812
  init_git_status();
30929
30813
  init_output();
30930
- init_require_auth();
30931
- init_resolve_auth();
30932
30814
  init_resolve_auth();
30933
30815
  init_clarity();
30934
30816
  init_api();
@@ -30973,14 +30855,14 @@ function parseIndexEventType(eventType, types5) {
30973
30855
  function client() {
30974
30856
  return new Index2({
30975
30857
  baseUrl: resolveApiUrl(),
30976
- apiKey: resolveEnvKey()
30858
+ apiKey: resolveDataPlaneKey()
30977
30859
  });
30978
30860
  }
30979
30861
  function fail(action2, err) {
30980
30862
  const message = err instanceof Error ? err.message : String(err);
30981
30863
  error(`Failed to ${action2}: ${message}`);
30982
30864
  if (/\b403\b/.test(message) || /forbidden/i.test(message)) {
30983
- note("The local API refused this read. Check INSTANCE_TOKEN.");
30865
+ note(isMerchantUrl() ? "The API refused this read. Check SECONDLAYER_API_KEY." : "The local API refused this read. Check INSTANCE_TOKEN.");
30984
30866
  }
30985
30867
  process.exit(1);
30986
30868
  }
@@ -34578,7 +34460,7 @@ __export(exports_node_impl, {
34578
34460
  runSetupWizard: () => runSetupWizard,
34579
34461
  restartNode: () => restartNode
34580
34462
  });
34581
- import { confirm as confirm5, input as input3, select as select4 } from "@inquirer/prompts";
34463
+ import { confirm as confirm4, input as input2, select as select4 } from "@inquirer/prompts";
34582
34464
  async function runSetupWizard() {
34583
34465
  console.log("");
34584
34466
  console.log(blue("Stacks Node Setup Wizard"));
@@ -34596,7 +34478,7 @@ async function runSetupWizard() {
34596
34478
  }
34597
34479
  success("Docker is running");
34598
34480
  console.log("");
34599
- const installPath = await input3({
34481
+ const installPath = await input2({
34600
34482
  message: "Where is stacks-blockchain-docker installed?",
34601
34483
  validate: async (value) => {
34602
34484
  if (!value.trim())
@@ -34615,18 +34497,18 @@ async function runSetupWizard() {
34615
34497
  ],
34616
34498
  default: "mainnet"
34617
34499
  });
34618
- const autoStartIndexer = await confirm5({
34500
+ const autoStartIndexer = await confirm4({
34619
34501
  message: "Auto-start indexer when node starts?",
34620
34502
  default: true
34621
34503
  });
34622
34504
  let indexerPort = 3700;
34623
34505
  if (autoStartIndexer) {
34624
- const customPort = await confirm5({
34506
+ const customPort = await confirm4({
34625
34507
  message: "Use default indexer port (3700)?",
34626
34508
  default: true
34627
34509
  });
34628
34510
  if (!customPort) {
34629
- const portInput = await input3({
34511
+ const portInput = await input2({
34630
34512
  message: "Enter custom indexer port:",
34631
34513
  default: "3700",
34632
34514
  validate: (value) => {
@@ -34727,7 +34609,7 @@ async function stopNode(_pathOverride, force, _wait) {
34727
34609
  return;
34728
34610
  }
34729
34611
  if (!force) {
34730
- const proceed = await confirm5({
34612
+ const proceed = await confirm4({
34731
34613
  message: "Stop the Stacks node?",
34732
34614
  default: false
34733
34615
  });
@@ -34757,7 +34639,7 @@ async function restartNode(pathOverride, force, _wait) {
34757
34639
  const wasRunning = await isNodeRunning();
34758
34640
  if (wasRunning) {
34759
34641
  if (!force) {
34760
- const proceed = await confirm5({
34642
+ const proceed = await confirm4({
34761
34643
  message: "Restart the Stacks node?",
34762
34644
  default: false
34763
34645
  });
@@ -34963,14 +34845,13 @@ var init_node_impl = __esm(() => {
34963
34845
  // src/lib/archive-reference.ts
34964
34846
  import { createHash as createHash4 } from "node:crypto";
34965
34847
  import { readFile as readFile5 } from "node:fs/promises";
34966
- import { dirname as dirname16, join as join13, resolve as resolve13, sep as sep2 } from "node:path";
34848
+ import { join as join13, resolve as resolve13, sep as sep2 } from "node:path";
34967
34849
  import { parseRetryAfter } from "@secondlayer/sdk";
34968
- import { ARCHIVE_ROOT_PUBLIC_KEY_PEM } from "@secondlayer/shared/archive/root-key";
34969
- import { verifyStreamsBulkManifestSignature } from "@secondlayer/shared/streams-bulk-manifest";
34970
- function isLatestPointer(value) {
34971
- const pointer = value;
34972
- return typeof pointer.snapshot_path === "string" && (value.partitions === undefined || value.partitions.length === 0);
34973
- }
34850
+ import {
34851
+ checkSignature,
34852
+ loadReference,
34853
+ resolveArchivePublicKey
34854
+ } from "@secondlayer/shared/archive/reference";
34974
34855
  function assertPathWithinRoot(path11, root) {
34975
34856
  const segments = path11.split("/");
34976
34857
  const leaves = path11.startsWith("/") || path11.includes("\\") || /^[a-z][a-z0-9+.-]*:/i.test(path11) || /[%?#]/.test(path11) || segments.some((part) => part === "" || part === "." || part === "..");
@@ -34985,109 +34866,6 @@ function assertPathWithinRoot(path11, root) {
34985
34866
  }
34986
34867
  }
34987
34868
  }
34988
- async function fetchJson(url) {
34989
- const response2 = await fetch(url, { signal: AbortSignal.timeout(30000) });
34990
- if (!response2.ok) {
34991
- throw new Error(`could not fetch manifest (${response2.status} ${response2.statusText})`);
34992
- }
34993
- return response2.json();
34994
- }
34995
- async function loadReference(source, options3 = {}) {
34996
- const isRemote = /^https?:\/\//.test(source);
34997
- let manifest;
34998
- let root;
34999
- if (isRemote) {
35000
- manifest = await fetchJson(source);
35001
- const url = new URL(source);
35002
- url.pathname = url.pathname.replace(/\/snapshots\/[^/]+$/, "").replace(/\/[^/]+\.json$/, "");
35003
- root = url.toString();
35004
- } else {
35005
- const path11 = resolve13(source);
35006
- manifest = JSON.parse(await readFile5(path11, "utf8"));
35007
- root = isLatestPointer(manifest) ? dirname16(path11) : dirname16(dirname16(path11));
35008
- }
35009
- if (!isLatestPointer(manifest)) {
35010
- return { manifest, origin: source, root, isRemote };
35011
- }
35012
- const pointer = manifest;
35013
- const snapshotPath = pointer.snapshot_path;
35014
- if (!SNAPSHOT_PATH_PATTERN.test(snapshotPath)) {
35015
- throw new Error(`pointer names an invalid snapshot path: ${snapshotPath} (expected snapshots/<sha256>.json)`);
35016
- }
35017
- if (typeof pointer.snapshot_digest !== "string" || !/^[0-9a-f]{64}$/.test(pointer.snapshot_digest)) {
35018
- throw new Error("pointer carries no snapshot_digest, so it cannot prove which snapshot is current");
35019
- }
35020
- if (pointer.signature !== undefined) {
35021
- if (!options3.publicKeyPem) {
35022
- throw new Error("pointer is signed but no public key is available to verify it");
35023
- }
35024
- let verified = false;
35025
- try {
35026
- verified = verifyStreamsBulkManifestSignature(pointer, options3.publicKeyPem);
35027
- } catch {
35028
- verified = false;
35029
- }
35030
- if (!verified) {
35031
- throw new Error("pointer signature did not verify against the archive key");
35032
- }
35033
- }
35034
- const resolved = isRemote ? await fetchJson(`${root.replace(/\/$/, "")}/${snapshotPath}`) : JSON.parse(await readFile5(join13(root, snapshotPath), "utf8"));
35035
- const digest = createHash4("sha256").update(JSON.stringify((({ signature: _s, key_id: _k, ...rest }) => rest)(resolved))).digest("hex");
35036
- if (digest !== pointer.snapshot_digest) {
35037
- throw new Error(`pointer/snapshot mismatch: latest.json names ${pointer.snapshot_digest}, resolved manifest hashes to ${digest}`);
35038
- }
35039
- return { manifest: resolved, origin: source, root, isRemote };
35040
- }
35041
- function checkSignature(manifest, publicKeyPem, insecure) {
35042
- if (insecure)
35043
- return { verified: false, reason: "signature check skipped" };
35044
- if (!manifest.signature) {
35045
- return { verified: false, reason: "manifest carries no signature" };
35046
- }
35047
- if (!publicKeyPem) {
35048
- return {
35049
- verified: false,
35050
- reason: "no public key available to verify with"
35051
- };
35052
- }
35053
- try {
35054
- const verified = verifyStreamsBulkManifestSignature(manifest, publicKeyPem);
35055
- return {
35056
- verified,
35057
- reason: verified ? undefined : "signature did not verify"
35058
- };
35059
- } catch (err) {
35060
- return {
35061
- verified: false,
35062
- reason: err instanceof Error ? err.message : "signature check failed"
35063
- };
35064
- }
35065
- }
35066
- async function fetchHostedKey(url) {
35067
- if (!url.startsWith("https://"))
35068
- return;
35069
- try {
35070
- const response2 = await fetch(url, { signal: AbortSignal.timeout(1e4) });
35071
- if (!response2.ok)
35072
- return;
35073
- const body = await response2.json();
35074
- return typeof body.public_key_pem === "string" ? body.public_key_pem : undefined;
35075
- } catch {
35076
- return;
35077
- }
35078
- }
35079
- async function resolveArchivePublicKey(input4) {
35080
- if (input4.explicitPem)
35081
- return input4.explicitPem;
35082
- if (input4.envPem)
35083
- return input4.envPem;
35084
- if (input4.allowHostedApi) {
35085
- const hosted = await fetchHostedKey(input4.hostedKeyUrl ?? HOSTED_SIGNING_KEY_URL);
35086
- if (hosted)
35087
- return hosted;
35088
- }
35089
- return ARCHIVE_ROOT_PUBLIC_KEY_PEM;
35090
- }
35091
34869
  function isTransientCode(code) {
35092
34870
  return typeof code === "string" && (TRANSIENT_ERROR_CODES.has(code) || code.startsWith("UND_ERR_"));
35093
34871
  }
@@ -35178,9 +34956,8 @@ async function fetchVerifiedPartition(reference, partition, gate, fetchOpts) {
35178
34956
  }
35179
34957
  return bytes;
35180
34958
  }
35181
- var SNAPSHOT_PATH_PATTERN, HOSTED_SIGNING_KEY_URL = "https://api.secondlayer.tools/public/streams/signing-key", ArchiveFetchError, TRANSIENT_ERROR_CODES, PARTITION_FETCH_RETRIES = 3, PARTITION_FETCH_TIMEOUT_MS = 300000, RETRY_BASE_MS = 1000, RETRY_AFTER_CAP_MS = 60000;
34959
+ var ArchiveFetchError, TRANSIENT_ERROR_CODES, PARTITION_FETCH_RETRIES = 3, PARTITION_FETCH_TIMEOUT_MS = 300000, RETRY_BASE_MS = 1000, RETRY_AFTER_CAP_MS = 60000;
35182
34960
  var init_archive_reference = __esm(() => {
35183
- SNAPSHOT_PATH_PATTERN = /^snapshots\/[0-9a-f]{64}\.json$/;
35184
34961
  ArchiveFetchError = class ArchiveFetchError extends Error {
35185
34962
  transient;
35186
34963
  attempts;
@@ -35207,9 +34984,12 @@ var init_archive_reference = __esm(() => {
35207
34984
  import { randomBytes as randomBytes3 } from "node:crypto";
35208
34985
  import { existsSync as existsSync8, readFileSync as readFileSync7, writeFileSync as writeFileSync7 } from "node:fs";
35209
34986
  import { resolve as resolve14 } from "node:path";
35210
- import { ARCHIVE_ROOT_PUBLIC_KEY_PEM as ARCHIVE_ROOT_PUBLIC_KEY_PEM2 } from "@secondlayer/shared/archive/root-key";
34987
+ import { ARCHIVE_ROOT_PUBLIC_KEY_PEM } from "@secondlayer/shared/archive/root-key";
35211
34988
  import { generateEd25519KeyPair } from "@secondlayer/shared/crypto/ed25519";
35212
34989
  import { INSTANCE_NETWORKS } from "@secondlayer/shared/db/queries/instance";
34990
+ function isHostedAccountKey(value) {
34991
+ return value.startsWith("sk-sl_") || value.startsWith("ss-sl_");
34992
+ }
35213
34993
  function parseInstanceNetwork(value) {
35214
34994
  const network = value.trim().toLowerCase();
35215
34995
  if (INSTANCE_NETWORKS.includes(network)) {
@@ -35257,11 +35037,15 @@ function readEnvValue(envPath, key) {
35257
35037
  return match ? unescapeEnvValue(match[1]) : null;
35258
35038
  }
35259
35039
  function buildInstanceEnv(input4) {
35260
- const token = input4.existing?.INSTANCE_TOKEN || input4.existing?.SL_API_KEY || generateInstanceToken();
35040
+ const existingAlias = input4.existing?.SL_API_KEY;
35041
+ const aliasIsHex = existingAlias && !existingAlias.startsWith("sk-sl_") && !existingAlias.startsWith("ss-sl_");
35042
+ const token = input4.existing?.INSTANCE_TOKEN || (aliasIsHex ? existingAlias : undefined) || generateInstanceToken();
35261
35043
  const secrets = input4.existing?.SECONDLAYER_SECRETS_KEY || generateSecretsKey();
35262
35044
  const signing = input4.existing?.STREAMS_SIGNING_PRIVATE_KEY || input4.existing?.SECONDLAYER_WEBHOOK_SIGNING_PRIVATE_KEY || generateSigningPrivateKey();
35263
- const apiUrl = input4.apiUrl ?? "http://127.0.0.1:3800";
35264
- const archiveKey = input4.existing?.ARCHIVE_SIGNING_PUBLIC_KEY || input4.archivePublicKeyPem || ARCHIVE_ROOT_PUBLIC_KEY_PEM2;
35045
+ const apiUrl = input4.existing?.SECONDLAYER_API_URL || input4.existing?.SL_API_URL || input4.apiUrl || "http://127.0.0.1:3800";
35046
+ const archiveKey = input4.existing?.ARCHIVE_SIGNING_PUBLIC_KEY || input4.archivePublicKeyPem || ARCHIVE_ROOT_PUBLIC_KEY_PEM;
35047
+ const hostedCandidate = input4.existing?.SECONDLAYER_API_KEY || input4.existing?.SL_API_KEY || undefined;
35048
+ const hostedKey = hostedCandidate && isHostedAccountKey(hostedCandidate) ? hostedCandidate : undefined;
35265
35049
  return {
35266
35050
  INSTANCE_MODE: "oss",
35267
35051
  STACKS_NETWORK: input4.network,
@@ -35270,8 +35054,9 @@ function buildInstanceEnv(input4) {
35270
35054
  STREAMS_SIGNING_PRIVATE_KEY: signing,
35271
35055
  SECONDLAYER_WEBHOOK_SIGNING_PRIVATE_KEY: signing,
35272
35056
  ALLOW_UNSIGNED_WEBHOOKS: "false",
35273
- SL_API_URL: input4.existing?.SL_API_URL || apiUrl,
35274
- SL_API_KEY: token,
35057
+ SECONDLAYER_API_URL: apiUrl,
35058
+ SL_API_URL: apiUrl,
35059
+ ...hostedKey ? { SECONDLAYER_API_KEY: hostedKey, SL_API_KEY: hostedKey } : {},
35275
35060
  ARCHIVE_SIGNING_PUBLIC_KEY: archiveKey
35276
35061
  };
35277
35062
  }
@@ -35287,11 +35072,12 @@ function renderInstanceEnv(env2) {
35287
35072
  `STREAMS_SIGNING_PRIVATE_KEY=${escapeEnvValue(env2.STREAMS_SIGNING_PRIVATE_KEY)}`,
35288
35073
  `SECONDLAYER_WEBHOOK_SIGNING_PRIVATE_KEY=${escapeEnvValue(env2.SECONDLAYER_WEBHOOK_SIGNING_PRIVATE_KEY)}`,
35289
35074
  `ALLOW_UNSIGNED_WEBHOOKS=${env2.ALLOW_UNSIGNED_WEBHOOKS}`,
35290
- `SL_API_URL=${env2.SL_API_URL}`,
35291
- "# Legacy alias of INSTANCE_TOKEN, same value. Kept for existing setups;",
35292
- "# INSTANCE_TOKEN wins if the two ever disagree.",
35293
- `SL_API_KEY=${env2.SL_API_KEY}`
35075
+ `SECONDLAYER_API_URL=${env2.SECONDLAYER_API_URL}`,
35076
+ `SL_API_URL=${env2.SL_API_URL}`
35294
35077
  ];
35078
+ if (env2.SECONDLAYER_API_KEY) {
35079
+ lines2.push("# Hosted account key for archive credits (sk-sl_*).", `SECONDLAYER_API_KEY=${env2.SECONDLAYER_API_KEY}`, `SL_API_KEY=${env2.SL_API_KEY ?? env2.SECONDLAYER_API_KEY}`);
35080
+ }
35295
35081
  lines2.push("# Archive trust root: bootstrap, verify, and repair check manifests", "# against this key. Replace it to pin a different archive.", `ARCHIVE_SIGNING_PUBLIC_KEY=${escapeEnvValue(env2.ARCHIVE_SIGNING_PUBLIC_KEY)}`, "");
35296
35082
  return lines2.join(`
35297
35083
  `);
@@ -35308,7 +35094,9 @@ function loadExistingInstanceEnv(cwd, file = INSTANCE_ENV_FILE) {
35308
35094
  SECONDLAYER_SECRETS_KEY: readEnvValue(path11, "SECONDLAYER_SECRETS_KEY"),
35309
35095
  STREAMS_SIGNING_PRIVATE_KEY: readEnvValue(path11, "STREAMS_SIGNING_PRIVATE_KEY"),
35310
35096
  SECONDLAYER_WEBHOOK_SIGNING_PRIVATE_KEY: readEnvValue(path11, "SECONDLAYER_WEBHOOK_SIGNING_PRIVATE_KEY"),
35097
+ SECONDLAYER_API_URL: readEnvValue(path11, "SECONDLAYER_API_URL"),
35311
35098
  SL_API_URL: readEnvValue(path11, "SL_API_URL"),
35099
+ SECONDLAYER_API_KEY: readEnvValue(path11, "SECONDLAYER_API_KEY"),
35312
35100
  SL_API_KEY: readEnvValue(path11, "SL_API_KEY"),
35313
35101
  ARCHIVE_SIGNING_PUBLIC_KEY: readEnvValue(path11, "ARCHIVE_SIGNING_PUBLIC_KEY")
35314
35102
  };
@@ -35693,7 +35481,9 @@ function resolveSecrets(config) {
35693
35481
  SECONDLAYER_SECRETS_KEY: existing.SECONDLAYER_SECRETS_KEY,
35694
35482
  STREAMS_SIGNING_PRIVATE_KEY: existing.STREAMS_SIGNING_PRIVATE_KEY,
35695
35483
  SECONDLAYER_WEBHOOK_SIGNING_PRIVATE_KEY: existing.SECONDLAYER_WEBHOOK_SIGNING_PRIVATE_KEY,
35484
+ SECONDLAYER_API_URL: existing.SECONDLAYER_API_URL,
35696
35485
  SL_API_URL: existing.SL_API_URL,
35486
+ SECONDLAYER_API_KEY: existing.SECONDLAYER_API_KEY,
35697
35487
  SL_API_KEY: existing.SL_API_KEY,
35698
35488
  ARCHIVE_SIGNING_PUBLIC_KEY: existing.ARCHIVE_SIGNING_PUBLIC_KEY
35699
35489
  },
@@ -35761,10 +35551,12 @@ function renderSetupEnv(config, secrets) {
35761
35551
  `STREAMS_SIGNING_PRIVATE_KEY=${escapeEnvValue(i3.STREAMS_SIGNING_PRIVATE_KEY)}`,
35762
35552
  `SECONDLAYER_WEBHOOK_SIGNING_PRIVATE_KEY=${escapeEnvValue(i3.SECONDLAYER_WEBHOOK_SIGNING_PRIVATE_KEY)}`,
35763
35553
  `ALLOW_UNSIGNED_WEBHOOKS=${i3.ALLOW_UNSIGNED_WEBHOOKS}`,
35764
- "# Legacy alias of INSTANCE_TOKEN, same value.",
35765
- `SL_API_KEY=${i3.SL_API_KEY}`,
35554
+ `SECONDLAYER_API_URL=${i3.SECONDLAYER_API_URL}`,
35766
35555
  `SL_API_URL=${i3.SL_API_URL}`
35767
35556
  ];
35557
+ if (i3.SECONDLAYER_API_KEY) {
35558
+ lines2.push("# Hosted account key for archive credits (sk-sl_*).", `SECONDLAYER_API_KEY=${i3.SECONDLAYER_API_KEY}`, `SL_API_KEY=${i3.SL_API_KEY ?? i3.SECONDLAYER_API_KEY}`);
35559
+ }
35768
35560
  lines2.push("# Archive trust root: bootstrap, verify, and repair check manifests", "# against this key. Replace it to pin a different archive.", `ARCHIVE_SIGNING_PUBLIC_KEY=${escapeEnvValue(i3.ARCHIVE_SIGNING_PUBLIC_KEY)}`);
35769
35561
  if (config.nodeMode === "full") {
35770
35562
  lines2.push("BITCOIN_RPC_USER=stacks", `BITCOIN_RPC_PASSWORD=${secrets.bitcoinRpcPassword}`);
@@ -36792,7 +36584,7 @@ var {
36792
36584
  // package.json
36793
36585
  var package_default = {
36794
36586
  name: "@secondlayer/cli",
36795
- version: "14.1.1",
36587
+ version: "14.2.0",
36796
36588
  description: "Run a Stacks index on your own hardware — set it up, restore verified history, and keep it current.",
36797
36589
  repository: {
36798
36590
  type: "git",
@@ -36837,8 +36629,8 @@ var package_default = {
36837
36629
  "@opentui/react": "^0.5.3",
36838
36630
  "@secondlayer/bundler": "^0.5.1",
36839
36631
  "@secondlayer/scaffold": "^1.5.5",
36840
- "@secondlayer/sdk": "^10.1.1",
36841
- "@secondlayer/shared": "^11.2.1",
36632
+ "@secondlayer/sdk": "^10.3.0",
36633
+ "@secondlayer/shared": "^11.4.0",
36842
36634
  "@secondlayer/stacks": "^5.1.0",
36843
36635
  "@secondlayer/subgraphs": "^4.1.5",
36844
36636
  esbuild: "^0.27.4",
@@ -37431,6 +37223,7 @@ function openBrowser(url) {
37431
37223
  }
37432
37224
  function registerConsoleCommand(program2) {
37433
37225
  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) => {
37226
+ assertInstanceUrl();
37434
37227
  const url = resolveConsoleUrl(options3.url);
37435
37228
  if (options3.open === false) {
37436
37229
  writeData(url);
@@ -37456,112 +37249,14 @@ import {
37456
37249
  } from "@secondlayer/sdk";
37457
37250
 
37458
37251
  // src/lib/instance-diagnosis.ts
37459
- var BOOTSTRAP_STEP = "Restore verified history: secondlayer bootstrap --against <manifest>";
37460
- var OBSERVER_STEP = "Confirm your node streams into this instance — secondlayer observer --mode indexer --endpoint secondlayer:3700 prints the stanza its config needs";
37461
- var SELF_HOST_DOCS_STEP = "Docs: https://www.secondlayer.tools/docs/self-host";
37462
- function hasNoChainData(status) {
37463
- const integrity = status.chainIntegrity;
37464
- if (!integrity || integrity.reason === "check_failed")
37465
- return false;
37466
- if (integrity.maxHeight !== 0)
37467
- return false;
37468
- return (status.chainTip ?? null) === null;
37469
- }
37470
- function emptyIndexIssue(status) {
37471
- const decoders = status.index?.decoders ?? [];
37472
- const stalled = decoders.filter((d2) => d2.status !== "ok").length;
37473
- const decoderClause = stalled > 0 ? ` and ${stalled} decoder${stalled === 1 ? "" : "s"} report unavailable because there is nothing to decode` : "";
37474
- return {
37475
- title: "No blocks indexed yet — this instance holds 0 blocks.",
37476
- detail: `Streams has no tip${decoderClause}, which is what makes the overall status DEGRADED. This is the expected state for a fresh instance: nothing is broken, but no read returns data until history lands.`,
37477
- nextSteps: [BOOTSTRAP_STEP, OBSERVER_STEP, SELF_HOST_DOCS_STEP]
37478
- };
37479
- }
37480
- var SERVICE_STEPS = [
37481
- "See the raw per-service report: secondlayer status --json",
37482
- "Check the containers from docker/oss: docker compose ps, then docker compose logs --tail=50 secondlayer"
37483
- ];
37484
- function diagnoseInstanceStatus(status) {
37485
- const overall = String(status.status ?? "unknown");
37486
- if (hasNoChainData(status)) {
37487
- return { state: "empty-index", overall, issues: [emptyIndexIssue(status)] };
37488
- }
37489
- const issues = [];
37490
- for (const service of status.services ?? []) {
37491
- if (service.status === "ok")
37492
- continue;
37493
- issues.push({
37494
- title: `Service ${service.name} reports ${service.status}.`,
37495
- nextSteps: SERVICE_STEPS
37496
- });
37497
- }
37498
- const integrity = status.chainIntegrity;
37499
- if (integrity && integrity.ok === false) {
37500
- issues.push({
37501
- title: "Chain-data integrity check failed.",
37502
- detail: integrity.reason ?? "The tip the instance serves is not backed by the history it implies.",
37503
- nextSteps: [
37504
- "Verify against the archive: secondlayer verify all --against <manifest>",
37505
- "Repair what diverged: secondlayer repair --against <manifest> --apply"
37506
- ]
37507
- });
37508
- }
37509
- if (issues.length === 0 && overall !== "healthy") {
37510
- issues.push({
37511
- title: `The API reports status=${overall} but names no failing service.`,
37512
- detail: "Every service in the report is ok, so the degradation is in a surface this command does not summarize.",
37513
- nextSteps: SERVICE_STEPS
37514
- });
37515
- }
37516
- return {
37517
- state: issues.length > 0 ? "degraded" : "healthy",
37518
- overall,
37519
- issues
37520
- };
37521
- }
37522
- var CONTEXT_FIELDS = [
37523
- "account",
37524
- "streamsTip",
37525
- "indexTip",
37526
- "subgraphs",
37527
- "subscriptions",
37528
- "activeOperations"
37529
- ];
37530
- function explainContextNulls(snapshot, probe) {
37531
- const nullFields = CONTEXT_FIELDS.filter((field) => (snapshot[field] ?? null) === null);
37532
- if (nullFields.length === 0) {
37533
- return { summary: "Every field resolved.", nulls: {} };
37534
- }
37535
- if (!probe.status) {
37536
- const reason = `No instance answered at ${probe.apiUrl} (${probe.statusError ?? "unreachable"}), so this read never reached an API.`;
37537
- return {
37538
- summary: `${reason} Start it from docker/oss with: docker compose up -d — or point SL_API_URL at the instance you meant.`,
37539
- nulls: Object.fromEntries(nullFields.map((field) => [field, reason]))
37540
- };
37541
- }
37542
- const emptyIndex = hasNoChainData(probe.status);
37543
- const blocks = probe.status.chainIntegrity?.maxHeight ?? null;
37544
- const accountReason = probe.selfHosted ? "Expected: a self-hosted instance has no account system. Accounts exist only on the metered archive deployment at api.secondlayer.tools, so null here is normal and not a failure." : "Not authenticated — run `secondlayer login`, or export INSTANCE_TOKEN.";
37545
- const listReason = unresolvedReadReason(probe, blocks);
37546
- const chainReason = emptyIndex ? `No blocks indexed yet — this instance holds 0 blocks, so there is no tip to report. ${BOOTSTRAP_STEP}` : listReason;
37547
- const nulls = {};
37548
- for (const field of nullFields) {
37549
- if (field === "account")
37550
- nulls[field] = accountReason;
37551
- else if (field === "streamsTip" || field === "indexTip")
37552
- nulls[field] = chainReason;
37553
- else if (field === "activeOperations")
37554
- nulls[field] = (snapshot.subgraphs ?? null) === null ? "Not probed: in-flight operations are read per subgraph, and the subgraph list did not resolve." : listReason;
37555
- else
37556
- nulls[field] = listReason;
37557
- }
37558
- const summary = emptyIndex ? `Instance at ${probe.apiUrl} is reachable but holds 0 blocks — the chain fields are null because nothing has been indexed yet. ${BOOTSTRAP_STEP}` : nullFields.length === 1 && nullFields[0] === "account" ? `Instance at ${probe.apiUrl} is reachable and indexed to block ${blocks ?? "unknown"}; only \`account\` is null, which is normal when self-hosting.` : `Instance at ${probe.apiUrl} is reachable, but ${nullFields.length} field${nullFields.length === 1 ? "" : "s"} did not resolve — see \`nulls\` below for each one.`;
37559
- return { summary, nulls };
37560
- }
37561
- function unresolvedReadReason(probe, blocks) {
37562
- const where = `The instance at ${probe.apiUrl} is reachable${blocks !== null ? ` and indexed to block ${blocks}` : ""}, but this read returned nothing.`;
37563
- return probe.hasCredential ? `${where} Check the surface directly: secondlayer status --json` : `${where} No credential resolved: export INSTANCE_TOKEN — an instance published past loopback requires it on every /v1 read.`;
37564
- }
37252
+ import {
37253
+ BOOTSTRAP_STEP,
37254
+ OBSERVER_STEP,
37255
+ SELF_HOST_DOCS_STEP,
37256
+ diagnoseInstanceStatus,
37257
+ explainContextNulls,
37258
+ hasNoChainData
37259
+ } from "@secondlayer/shared/archive/instance-diagnosis";
37565
37260
 
37566
37261
  // src/commands/context.ts
37567
37262
  init_output();
@@ -37705,18 +37400,19 @@ function printPublicStatus(status, diagnosis) {
37705
37400
  init_api_client();
37706
37401
  init_filter_params();
37707
37402
  init_output();
37403
+ init_resolve_auth();
37708
37404
  import { sign } from "@secondlayer/shared/crypto/standard-webhooks";
37709
37405
 
37710
37406
  // src/lib/subscription-validation.ts
37711
37407
  import {
37712
37408
  validateSubscriptionFilterForTable
37713
37409
  } from "@secondlayer/shared/schemas/subscriptions";
37714
- async function validateSubscriptionTargetFromApi(client2, input2) {
37715
- const subgraph = await client2.subgraphs.status(input2.subgraphName);
37410
+ async function validateSubscriptionTargetFromApi(client2, input) {
37411
+ const subgraph = await client2.subgraphs.status(input.subgraphName);
37716
37412
  const errors2 = validateSubscriptionFilterForTable({
37717
- subgraphName: input2.subgraphName,
37718
- tableName: input2.tableName,
37719
- filter: input2.filter,
37413
+ subgraphName: input.subgraphName,
37414
+ tableName: input.tableName,
37415
+ filter: input.filter,
37720
37416
  tables: subgraph.tables
37721
37417
  });
37722
37418
  if (errors2.length > 0) {
@@ -37741,7 +37437,7 @@ import {
37741
37437
  } from "node:fs";
37742
37438
  import { dirname as dirname6, join as join6, relative, resolve as resolve5 } from "node:path";
37743
37439
  import { fileURLToPath as fileURLToPath4 } from "node:url";
37744
- import { input as input2, select as select3 } from "@inquirer/prompts";
37440
+ import { input, select as select3 } from "@inquirer/prompts";
37745
37441
  import { SecondLayer as SecondLayer3 } from "@secondlayer/sdk";
37746
37442
  import {
37747
37443
  ChainTriggerSchema
@@ -37835,7 +37531,7 @@ async function promptFor(_name, opts) {
37835
37531
  let subgraph = opts.subgraph;
37836
37532
  if (subgraph === undefined) {
37837
37533
  requireTtyFor("--subgraph");
37838
- subgraph = await input2({
37534
+ subgraph = await input({
37839
37535
  message: "Subgraph name (must already be deployed):",
37840
37536
  validate: (v2) => v2.trim().length > 0 ? true : "required"
37841
37537
  });
@@ -37843,7 +37539,7 @@ async function promptFor(_name, opts) {
37843
37539
  let table = opts.table;
37844
37540
  if (table === undefined) {
37845
37541
  requireTtyFor("--table");
37846
- table = await input2({
37542
+ table = await input({
37847
37543
  message: "Table to subscribe to:",
37848
37544
  validate: (v2) => v2.trim().length > 0 ? true : "required"
37849
37545
  });
@@ -37851,7 +37547,7 @@ async function promptFor(_name, opts) {
37851
37547
  let url = opts.url;
37852
37548
  if (url === undefined) {
37853
37549
  requireTtyFor("--url");
37854
- url = await input2({
37550
+ url = await input({
37855
37551
  message: runtime === "inngest" ? "Inngest event endpoint URL (e.g. https://inn.gs/e/<KEY> or http://localhost:8288/e/<KEY>):" : runtime === "trigger" ? "Trigger.dev task URL (e.g. https://api.trigger.dev/api/v1/tasks/<TASK_ID>/trigger):" : runtime === "cloudflare" ? "Cloudflare Workflows instances URL:" : "Your HTTP receiver URL (e.g. https://yourapp.example/webhook):",
37856
37552
  validate: (v2) => v2.startsWith("http://") || v2.startsWith("https://") ? true : "must be http(s) URL"
37857
37553
  });
@@ -38184,52 +37880,52 @@ Auth config:`));
38184
37880
  console.log(JSON.stringify(sub.authConfig, null, 2));
38185
37881
  }
38186
37882
  }
38187
- function buildDoctorReport(input3) {
38188
- const successful = input3.deliveries.filter(isSuccessDelivery).length;
38189
- const failed = input3.deliveries.length - successful;
38190
- const subgraph = input3.subgraph ? {
38191
- name: input3.subgraph.name,
38192
- status: input3.subgraph.status,
38193
- syncStatus: input3.subgraph.sync.status,
38194
- lastProcessedBlock: input3.subgraph.sync.lastProcessedBlock,
38195
- chainTip: input3.subgraph.sync.chainTip,
38196
- gapCount: input3.subgraph.sync.gaps.count,
38197
- integrity: input3.subgraph.sync.integrity
37883
+ function buildDoctorReport(input2) {
37884
+ const successful = input2.deliveries.filter(isSuccessDelivery).length;
37885
+ const failed = input2.deliveries.length - successful;
37886
+ const subgraph = input2.subgraph ? {
37887
+ name: input2.subgraph.name,
37888
+ status: input2.subgraph.status,
37889
+ syncStatus: input2.subgraph.sync.status,
37890
+ lastProcessedBlock: input2.subgraph.sync.lastProcessedBlock,
37891
+ chainTip: input2.subgraph.sync.chainTip,
37892
+ gapCount: input2.subgraph.sync.gaps.count,
37893
+ integrity: input2.subgraph.sync.integrity
38198
37894
  } : null;
38199
37895
  const hints = [];
38200
- if (input3.subscription.status === "paused") {
38201
- hints.push(`Resume when the receiver is healthy: secondlayer subscriptions resume ${input3.subscription.id}`);
37896
+ if (input2.subscription.status === "paused") {
37897
+ hints.push(`Resume when the receiver is healthy: secondlayer subscriptions resume ${input2.subscription.id}`);
38202
37898
  }
38203
- if (input3.subscription.lastError) {
37899
+ if (input2.subscription.lastError) {
38204
37900
  hints.push("Run secondlayer subscriptions test to reproduce the receiver request.");
38205
37901
  }
38206
- if (input3.subscription.circuitOpenedAt || input3.subscription.circuitFailures > 0) {
37902
+ if (input2.subscription.circuitOpenedAt || input2.subscription.circuitFailures > 0) {
38207
37903
  hints.push("Circuit breaker has failures; inspect receiver logs and delivery status codes.");
38208
37904
  }
38209
- if (input3.dead.length > 0) {
38210
- hints.push(`Dead-letter rows exist; inspect with secondlayer subscriptions dead ${input3.subscription.id} and requeue selected rows.`);
37905
+ if (input2.dead.length > 0) {
37906
+ hints.push(`Dead-letter rows exist; inspect with secondlayer subscriptions dead ${input2.subscription.id} and requeue selected rows.`);
38211
37907
  }
38212
37908
  if (subgraph?.gapCount && subgraph.gapCount > 0) {
38213
- hints.push(`Linked subgraph has gaps; run secondlayer subgraphs gaps ${input3.subscription.subgraphName}.`);
37909
+ hints.push(`Linked subgraph has gaps; run secondlayer subgraphs gaps ${input2.subscription.subgraphName}.`);
38214
37910
  }
38215
37911
  if (subgraph?.syncStatus === "catching_up") {
38216
37912
  hints.push("Linked subgraph is still catching up; new matching rows may arrive later.");
38217
37913
  }
38218
- if (input3.deliveries.length === 0) {
37914
+ if (input2.deliveries.length === 0) {
38219
37915
  hints.push("No deliveries yet; confirm the table is receiving inserted rows that match the filter.");
38220
37916
  }
38221
37917
  if (hints.length === 0) {
38222
37918
  hints.push("No immediate action needed.");
38223
37919
  }
38224
37920
  return {
38225
- subscription: input3.subscription,
37921
+ subscription: input2.subscription,
38226
37922
  deliverySummary: {
38227
- total: input3.deliveries.length,
37923
+ total: input2.deliveries.length,
38228
37924
  successful,
38229
37925
  failed,
38230
- last: input3.deliveries[0] ?? null
37926
+ last: input2.deliveries[0] ?? null
38231
37927
  },
38232
- deadCount: input3.dead.length,
37928
+ deadCount: input2.dead.length,
38233
37929
  subgraph,
38234
37930
  hints
38235
37931
  };
@@ -38322,9 +38018,9 @@ function resolveSigningSecret(options3, env2 = process.env) {
38322
38018
  function shellQuote(value) {
38323
38019
  return `'${value.replace(/'/g, "'\\''")}'`;
38324
38020
  }
38325
- function buildSubscriptionTestFixture(input3) {
38326
- const nowSeconds = input3.nowSeconds ?? Math.floor(Date.now() / 1000);
38327
- const sub = input3.subscription;
38021
+ function buildSubscriptionTestFixture(input2) {
38022
+ const nowSeconds = input2.nowSeconds ?? Math.floor(Date.now() / 1000);
38023
+ const sub = input2.subscription;
38328
38024
  const isChain = sub.kind === "chain";
38329
38025
  const type = isChain ? `chain.${sub.triggers?.[0]?.type ?? "event"}.apply` : `${sub.subgraphName}.${sub.tableName}.created`;
38330
38026
  const data = isChain ? {
@@ -38332,10 +38028,10 @@ function buildSubscriptionTestFixture(input3) {
38332
38028
  canonical: true,
38333
38029
  block_hash: "0xexampleblock",
38334
38030
  block_height: 0,
38335
- tx_id: input3.row.tx_id ?? "0xexampletransaction",
38031
+ tx_id: input2.row.tx_id ?? "0xexampletransaction",
38336
38032
  trigger: sub.triggers?.[0]?.type ?? "event",
38337
- event: input3.row
38338
- } : input3.row;
38033
+ event: input2.row
38034
+ } : input2.row;
38339
38035
  const body = JSON.stringify({
38340
38036
  type,
38341
38037
  timestamp: new Date(nowSeconds * 1000).toISOString(),
@@ -38343,15 +38039,15 @@ function buildSubscriptionTestFixture(input3) {
38343
38039
  });
38344
38040
  const headers = {
38345
38041
  "content-type": "application/json",
38346
- ...sign(body, input3.signingSecret, {
38347
- id: input3.id ?? `test-${input3.subscription.id}`,
38042
+ ...sign(body, input2.signingSecret, {
38043
+ id: input2.id ?? `test-${input2.subscription.id}`,
38348
38044
  timestampSeconds: nowSeconds
38349
38045
  })
38350
38046
  };
38351
38047
  const headerArgs = Object.entries(headers).map(([key, value]) => ` -H ${shellQuote(`${key}: ${value}`)} \\`).join(`
38352
38048
  `);
38353
38049
  const curl = [
38354
- `curl -X POST ${shellQuote(input3.subscription.url)} \\`,
38050
+ `curl -X POST ${shellQuote(input2.subscription.url)} \\`,
38355
38051
  headerArgs,
38356
38052
  ` --data ${shellQuote(body)}`
38357
38053
  ].join(`
@@ -38414,6 +38110,7 @@ async function confirmOrExit(message, yes) {
38414
38110
  }
38415
38111
  function registerSubscriptionsCommand(program2) {
38416
38112
  const subscriptions = program2.command("subscriptions").alias("subs").description("Manage subgraph table subscriptions");
38113
+ subscriptions.hook("preAction", () => assertInstanceUrl());
38417
38114
  addSubscriptionsCreateCommand(subscriptions);
38418
38115
  subscriptions.command("list").alias("ls").description("List subscriptions").option("--json", "Output as JSON").action(async (options3) => {
38419
38116
  try {
@@ -39149,7 +38846,7 @@ var VALID_TYPES = STREAMS_EVENT_TYPES;
39149
38846
  function client2() {
39150
38847
  return createStreamsClient({
39151
38848
  baseUrl: resolveApiUrl(),
39152
- apiKey: resolveEnvKey() ?? ""
38849
+ apiKey: resolveDataPlaneKey() ?? ""
39153
38850
  });
39154
38851
  }
39155
38852
  function dumpsClient(dumpsUrl) {
@@ -39160,7 +38857,7 @@ function dumpsClient(dumpsUrl) {
39160
38857
  }
39161
38858
  return createStreamsClient({
39162
38859
  baseUrl: resolveApiUrl(),
39163
- apiKey: resolveEnvKey() ?? "",
38860
+ apiKey: resolveDataPlaneKey() ?? "",
39164
38861
  dumpsBaseUrl
39165
38862
  });
39166
38863
  }
@@ -39789,7 +39486,7 @@ function markFrozen(cmd) {
39789
39486
  init_config2();
39790
39487
  init_dev_state();
39791
39488
  init_output();
39792
- import { confirm as confirm4 } from "@inquirer/prompts";
39489
+ import { confirm as confirm3 } from "@inquirer/prompts";
39793
39490
  import { getDb, sql } from "@secondlayer/shared/db";
39794
39491
  import {
39795
39492
  countMissingBlocks,
@@ -40009,7 +39706,7 @@ async function resetDatabase(skipConfirm) {
40009
39706
  console.log(dim("Note: Subgraph configurations will be preserved."));
40010
39707
  console.log("");
40011
39708
  if (!skipConfirm) {
40012
- const confirmed = await confirm4({
39709
+ const confirmed = await confirm3({
40013
39710
  message: "Are you sure you want to reset the database?",
40014
39711
  default: false
40015
39712
  });
@@ -40046,7 +39743,7 @@ async function resyncDatabase(skipConfirm, backfill) {
40046
39743
  console.log(dim("Note: Subgraph configurations will be preserved."));
40047
39744
  console.log("");
40048
39745
  if (!skipConfirm) {
40049
- const confirmed = await confirm4({
39746
+ const confirmed = await confirm3({
40050
39747
  message: "Are you sure you want to resync?",
40051
39748
  default: false
40052
39749
  });
@@ -40614,6 +40311,7 @@ function formatLogLine3(service, line) {
40614
40311
  init_config2();
40615
40312
  init_http();
40616
40313
  init_output();
40314
+ import { resolveApiKey as resolveApiKey4 } from "@secondlayer/sdk";
40617
40315
 
40618
40316
  // src/lib/project-file.ts
40619
40317
  import { existsSync as existsSync7 } from "node:fs";
@@ -40658,58 +40356,188 @@ async function readActiveProject(cwd, globalDefault) {
40658
40356
  }
40659
40357
 
40660
40358
  // src/commands/whoami.ts
40359
+ async function runWhoami() {
40360
+ const instanceUrl = resolveApiUrl();
40361
+ const merchantUrl = resolveArchiveOpsUrl();
40362
+ const instanceSet = resolveApiKey4() !== undefined;
40363
+ const { source } = await resolveArchiveOpsBearer();
40364
+ let email = null;
40365
+ let merchantOk = false;
40366
+ try {
40367
+ const account = await httpArchiveOps("/api/accounts/me");
40368
+ email = account.email;
40369
+ merchantOk = true;
40370
+ } catch {
40371
+ merchantOk = false;
40372
+ }
40373
+ const merchantCredential = source === "env" ? "SECONDLAYER_API_KEY" : source === "session" ? "session" : null;
40374
+ const config = await loadConfig2();
40375
+ const active = await readActiveProject(process.cwd(), config.defaultProject);
40376
+ return {
40377
+ instance: {
40378
+ url: instanceUrl,
40379
+ credential: "INSTANCE_TOKEN",
40380
+ status: instanceSet ? "set" : "missing"
40381
+ },
40382
+ merchant: {
40383
+ url: merchantUrl,
40384
+ credential: merchantCredential,
40385
+ status: merchantOk ? "set" : "missing",
40386
+ email
40387
+ },
40388
+ project: active ? { slug: active.slug, source: active.resolvedFrom } : null
40389
+ };
40390
+ }
40661
40391
  function registerWhoamiCommand(program2) {
40662
- program2.command("whoami", { hidden: true }).description("Show the archive credits account, credential source, and project").option("--json", "Output as JSON").action(async (options3) => {
40663
- const creditsUrl = resolveArchiveOpsUrl();
40664
- const { source } = await resolveArchiveOpsBearer();
40665
- let account;
40666
- try {
40667
- account = await httpArchiveOps("/api/accounts/me");
40668
- } catch (err) {
40669
- if (err instanceof CliHttpError) {
40670
- error(err.message);
40671
- process.exit(1);
40672
- }
40673
- throw err;
40674
- }
40675
- const config = await loadConfig2();
40676
- const active = await readActiveProject(process.cwd(), config.defaultProject);
40677
- const authSource = source === "env" ? "API key (env)" : `session (${ARCHIVE_LOGIN_COMMAND})`;
40392
+ program2.command("whoami").description("Show instance and merchant targets.").option("--json", "Output as JSON").action(async (options3) => {
40393
+ const result = await runWhoami();
40678
40394
  output({
40679
40395
  json: options3.json,
40680
- data: {
40681
- email: account.email,
40682
- creditsUrl,
40683
- apiUrl: resolveApiUrl(),
40684
- authSource,
40685
- project: active ? { slug: active.slug, source: active.resolvedFrom } : null
40686
- },
40396
+ data: result,
40687
40397
  human: () => {
40688
40398
  const rows = [];
40689
- rows.push(["Email", account.email]);
40690
- rows.push(["Credits API", creditsUrl]);
40691
- rows.push(["Instance API", resolveApiUrl()]);
40692
- rows.push(["Auth", dim(authSource)]);
40693
- if (active) {
40694
- rows.push(["Project", active.slug]);
40695
- rows.push(["Project source", dim(active.resolvedFrom)]);
40399
+ rows.push([
40400
+ "instance",
40401
+ `${result.instance.url} ${result.instance.credential} ${result.instance.status}`
40402
+ ]);
40403
+ const merchantCred = result.merchant.credential ?? "SECONDLAYER_API_KEY|session";
40404
+ const merchantValue = result.merchant.email ? `${result.merchant.url} ${merchantCred} ${result.merchant.status} (${result.merchant.email})` : `${result.merchant.url} ${merchantCred} ${result.merchant.status}`;
40405
+ rows.push(["merchant", merchantValue]);
40406
+ if (result.project) {
40407
+ rows.push(["project", result.project.slug]);
40408
+ rows.push(["project source", dim(result.project.source)]);
40696
40409
  } else {
40697
- rows.push(["Project", dim("(none)")]);
40410
+ rows.push(["project", dim("(none)")]);
40698
40411
  }
40699
40412
  console.log(formatKeyValue(rows));
40413
+ if (result.merchant.status === "missing") {
40414
+ note(`merchant identity missing - run \`${ARCHIVE_LOGIN_COMMAND}\``);
40415
+ }
40700
40416
  }
40701
40417
  });
40702
40418
  });
40703
40419
  }
40704
-
40705
- // src/commands/index.ts
40706
- init_login();
40707
-
40420
+ // src/commands/login.ts
40421
+ init_http();
40422
+ init_output();
40423
+ init_session();
40424
+ import { confirm as confirm5, input as input3 } from "@inquirer/prompts";
40425
+ function sessionExpiry() {
40426
+ return new Date(Date.now() + 90 * 24 * 60 * 60 * 1000).toISOString();
40427
+ }
40428
+ async function readStdin() {
40429
+ const chunks = [];
40430
+ for await (const chunk of process.stdin)
40431
+ chunks.push(chunk);
40432
+ return Buffer.concat(chunks).toString("utf8").trim();
40433
+ }
40434
+ function loginTarget(opts) {
40435
+ return opts.credits ? resolveArchiveOpsUrl() : resolveApiUrl();
40436
+ }
40437
+ async function runTokenLogin(opts) {
40438
+ const token = await readStdin();
40439
+ if (!token) {
40440
+ error('No token on stdin. Usage: echo "$KEY" | secondlayer login --with-token');
40441
+ process.exit(1);
40442
+ }
40443
+ const target = loginTarget(opts);
40444
+ let account;
40445
+ try {
40446
+ account = await httpAt(target, "/api/accounts/me", { bearer: token });
40447
+ } catch (err) {
40448
+ error(err instanceof CliHttpError ? `Token rejected by ${target}: ${err.message}` : err instanceof Error ? err.message : String(err));
40449
+ process.exit(1);
40450
+ }
40451
+ await writeSession({
40452
+ token,
40453
+ email: account.email,
40454
+ accountId: account.id,
40455
+ expiresAt: sessionExpiry()
40456
+ }, target);
40457
+ success(`Logged in as ${account.email} (${target})`);
40458
+ }
40459
+ async function runLoginFlow(options3 = {}) {
40460
+ const target = loginTarget(options3);
40461
+ if (!options3.force) {
40462
+ const existing = await readSession(target);
40463
+ if (existing) {
40464
+ info(`Already logged in as ${existing.email} (${target}).`);
40465
+ if (!process.stdin.isTTY) {
40466
+ info(dim("Run 'secondlayer logout' first, or re-run with --force to switch accounts."));
40467
+ return;
40468
+ }
40469
+ try {
40470
+ const proceed = await confirm5({
40471
+ message: "Log in as a different user?",
40472
+ default: false
40473
+ });
40474
+ if (!proceed) {
40475
+ info(dim("Run 'secondlayer logout' to sign out."));
40476
+ return;
40477
+ }
40478
+ } catch {
40479
+ info(dim("Run 'secondlayer logout' first, or re-run with --force to switch accounts."));
40480
+ return;
40481
+ }
40482
+ }
40483
+ }
40484
+ const email = await input3({
40485
+ message: "Email",
40486
+ validate: (v2) => /^.+@.+\..+$/.test(v2) ? true : "Invalid email"
40487
+ });
40488
+ try {
40489
+ const res = await httpAt(target, "/api/auth/magic-link", {
40490
+ method: "POST",
40491
+ body: { email }
40492
+ });
40493
+ info("Check your inbox for a 6-digit code.");
40494
+ if (res.code) {
40495
+ info(dim(`(DEV_MODE code: ${res.code})`));
40496
+ }
40497
+ } catch (err) {
40498
+ if (err instanceof CliHttpError) {
40499
+ error(err.message);
40500
+ } else {
40501
+ error(err instanceof Error ? err.message : String(err));
40502
+ }
40503
+ process.exit(1);
40504
+ }
40505
+ const code = await input3({
40506
+ message: "Enter the 6-digit code",
40507
+ validate: (v2) => /^\d{6}$/.test(v2) ? true : "Expected 6 digits"
40508
+ });
40509
+ try {
40510
+ const verified = await httpAt(target, "/api/auth/verify", {
40511
+ method: "POST",
40512
+ body: { email, code }
40513
+ });
40514
+ await writeSession({
40515
+ token: verified.sessionToken,
40516
+ email: verified.account.email,
40517
+ accountId: verified.account.id,
40518
+ expiresAt: sessionExpiry()
40519
+ }, target);
40520
+ success(`Logged in as ${verified.account.email}`);
40521
+ info(dim(options3.credits ? "Run 'secondlayer credits balance' to see your archive credits." : "Run 'secondlayer whoami' to see your account status."));
40522
+ } catch (err) {
40523
+ if (err instanceof CliHttpError) {
40524
+ error(err.message);
40525
+ } else {
40526
+ error(err instanceof Error ? err.message : String(err));
40527
+ }
40528
+ process.exit(1);
40529
+ }
40530
+ }
40531
+ function registerLoginCommand(program2) {
40532
+ 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)").option("--credits", "Log in to the archive credits account used by bootstrap, repair, and credits").addHelpText("after", `
40533
+ Examples:
40534
+ $ secondlayer login --credits
40535
+ $ echo "$SECONDLAYER_API_KEY" | secondlayer login --with-token`).action((opts) => opts.withToken ? runTokenLogin({ credits: opts.credits }) : runLoginFlow({ force: opts.force, credits: opts.credits }));
40536
+ }
40708
40537
  // src/commands/logout.ts
40709
40538
  init_http();
40710
40539
  init_output();
40711
40540
  init_session();
40712
- init_login();
40713
40541
  async function runLogout(opts = {}) {
40714
40542
  const target = loginTarget(opts);
40715
40543
  const session = await readSession(target);
@@ -41988,6 +41816,7 @@ function registerBootstrapCommand(program2) {
41988
41816
  // src/commands/uninstall.ts
41989
41817
  init_instance_init();
41990
41818
  init_output();
41819
+ init_resolve_auth();
41991
41820
  import { spawnSync as spawnSync4 } from "node:child_process";
41992
41821
  import { existsSync as existsSync9, readFileSync as readFileSync8 } from "node:fs";
41993
41822
  import { join as join16, resolve as resolve15 } from "node:path";
@@ -42042,6 +41871,7 @@ function composeDownArgs(plan, layout) {
42042
41871
  }
42043
41872
  function registerUninstallCommand(program2) {
42044
41873
  program2.command("uninstall").description("Stop the stack; the index, chainstate, and keys survive").option("--compose <file>", `compose file to tear down (default: ./docker-compose.yml from setup, else ${REPO_COMPOSE})`).option("--purge", "also destroy the volumes (wipes the index)").option("--yes", "confirm a purge").option("--backup <dir>", "a bundle proving the keys exist elsewhere (required to purge)").option("--apply", "actually run it (default is a dry run)").option("--json", "Output as JSON").action(async (opts) => {
41874
+ assertInstanceUrl();
42045
41875
  const dataDir = process.env.DATA_DIR ?? "./data";
42046
41876
  const layout = resolveUninstallLayout(process.cwd(), opts.compose);
42047
41877
  const secretsPresent = !!process.env.SECONDLAYER_SECRETS_KEY || !!layout.secretsFile;
@@ -42116,6 +41946,7 @@ function registerUninstallCommand(program2) {
42116
41946
  // src/commands/backup.ts
42117
41947
  init_fs();
42118
41948
  init_output();
41949
+ init_resolve_auth();
42119
41950
  import {
42120
41951
  chmodSync as chmodSync2,
42121
41952
  existsSync as existsSync10,
@@ -42425,10 +42256,10 @@ async function runRestore(opts) {
42425
42256
  });
42426
42257
  }
42427
42258
  function registerBackupCommand(program2) {
42428
- attachBackupCommand(program2.command("backup").description("Write a restorable bundle: index, keys, and scope"));
42259
+ attachBackupCommand(program2.command("backup").description("Write a restorable bundle: index, keys, and scope").hook("preAction", () => assertInstanceUrl()));
42429
42260
  }
42430
42261
  function registerRestoreCommand(program2) {
42431
- attachRestoreCommand(program2.command("restore").description("Restore an instance from a backup bundle"));
42262
+ attachRestoreCommand(program2.command("restore").description("Restore an instance from a backup bundle").hook("preAction", () => assertInstanceUrl()));
42432
42263
  }
42433
42264
  // src/commands/init.ts
42434
42265
  init_archive_reference();
@@ -42441,6 +42272,7 @@ function registerInitCommand(program2) {
42441
42272
  --network <mainnet|testnet|devnet> and --api-url <url> are top-level global
42442
42273
  flags (see \`secondlayer --help\`). Defaults: mainnet, http://127.0.0.1:3800.
42443
42274
  `).action(async (opts) => {
42275
+ assertInstanceUrl();
42444
42276
  const network = instanceNetworkFromEnv();
42445
42277
  const existing = opts.force ? {} : loadExistingInstanceEnv(process.cwd());
42446
42278
  const env2 = buildInstanceEnv({
@@ -42466,11 +42298,13 @@ flags (see \`secondlayer --help\`). Defaults: mainnet, http://127.0.0.1:3800.
42466
42298
  init_instance_init();
42467
42299
  init_observer_stanza();
42468
42300
  init_output();
42301
+ init_resolve_auth();
42469
42302
  function registerObserverCommand(program2) {
42470
42303
  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", `
42471
42304
  --network <mainnet|testnet|devnet> is the top-level global flag (see
42472
42305
  \`secondlayer --help\`) — default mainnet, same as before.
42473
42306
  `).action((opts) => {
42307
+ assertInstanceUrl();
42474
42308
  const mode = parseObserverMode(opts.mode);
42475
42309
  const network = instanceNetworkFromEnv();
42476
42310
  const endpoint = opts.endpoint ?? defaultObserverEndpoint(network);
@@ -42483,6 +42317,7 @@ function registerObserverCommand(program2) {
42483
42317
  }
42484
42318
  // src/commands/setup.ts
42485
42319
  init_output();
42320
+ init_resolve_auth();
42486
42321
 
42487
42322
  // src/lib/setup-prompts.ts
42488
42323
  init_instance_init();
@@ -42689,6 +42524,7 @@ Without a TTY (piped, CI, an agent), or with --yes, the TUI is skipped: every
42689
42524
  decision with no safe default (--network, --node-mode, and --against unless
42690
42525
  --skip-bootstrap) must come from a flag or the command fails fast naming it.
42691
42526
  `).action(async (opts) => {
42527
+ assertInstanceUrl();
42692
42528
  const flags = {
42693
42529
  ...opts,
42694
42530
  network: opts.network ?? process.env.STACKS_NETWORK
@@ -42712,18 +42548,19 @@ decision with no safe default (--network, --node-mode, and --against unless
42712
42548
  });
42713
42549
  }
42714
42550
  // src/cli.ts
42551
+ init_resolve_auth();
42715
42552
  var { version } = package_default;
42716
- program.name("secondlayer").alias("sl").description("Secondlayer CLI — run a Stacks index on your own hardware").version(version).option("--network <network>", "Override network (mainnet, testnet, devnet)").option("--api-key <key>", "Instance or archive credential; prefer INSTANCE_TOKEN in env, a flag lands in shell history and ps").option("--api-url <url>", "API endpoint (overrides SL_API_URL)").showSuggestionAfterError(true).showHelpAfterError("(run `secondlayer --help` to see available commands)");
42553
+ program.name("secondlayer").alias("sl").description("Secondlayer CLI — run a Stacks index on your own hardware").version(version).option("--network <network>", "Override network (mainnet, testnet, devnet)").option("--api-key <key>", "Credential for this invocation: INSTANCE_TOKEN (hex) for the instance, or sk-sl_* for hosted/archive. Prefer env. A flag lands in shell history and ps").option("--api-url <url>", "API endpoint (overrides SECONDLAYER_API_URL)").showSuggestionAfterError(true).showHelpAfterError("(run `secondlayer --help` to see available commands)");
42717
42554
  program.hook("preAction", (thisCommand) => {
42718
42555
  const { network, apiKey, apiUrl } = thisCommand.opts();
42719
42556
  if (network)
42720
42557
  process.env.STACKS_NETWORK = network;
42721
- if (apiKey) {
42722
- process.env.INSTANCE_TOKEN = apiKey;
42723
- process.env.SL_API_KEY = apiKey;
42724
- }
42725
- if (apiUrl)
42558
+ if (apiKey)
42559
+ applyApiKeyFlag(apiKey);
42560
+ if (apiUrl) {
42561
+ process.env.SECONDLAYER_API_URL = apiUrl;
42726
42562
  process.env.SL_API_URL = apiUrl;
42563
+ }
42727
42564
  });
42728
42565
  program.addHelpText("after", `
42729
42566
  Quickstart:
@@ -42769,5 +42606,5 @@ program.commandsGroup("Archive:");
42769
42606
  registerCreditsCommand(program);
42770
42607
  program.parse();
42771
42608
 
42772
- //# debugId=A5FE446DEB77888264756E2164756E21
42609
+ //# debugId=CAE75BE93848EF8E64756E2164756E21
42773
42610
  //# sourceMappingURL=cli.js.map