@secondlayer/cli 14.1.2 → 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
  });
@@ -35105,6 +34987,9 @@ import { resolve as resolve14 } from "node:path";
35105
34987
  import { ARCHIVE_ROOT_PUBLIC_KEY_PEM } from "@secondlayer/shared/archive/root-key";
35106
34988
  import { generateEd25519KeyPair } from "@secondlayer/shared/crypto/ed25519";
35107
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
+ }
35108
34993
  function parseInstanceNetwork(value) {
35109
34994
  const network = value.trim().toLowerCase();
35110
34995
  if (INSTANCE_NETWORKS.includes(network)) {
@@ -35152,11 +35037,15 @@ function readEnvValue(envPath, key) {
35152
35037
  return match ? unescapeEnvValue(match[1]) : null;
35153
35038
  }
35154
35039
  function buildInstanceEnv(input4) {
35155
- 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();
35156
35043
  const secrets = input4.existing?.SECONDLAYER_SECRETS_KEY || generateSecretsKey();
35157
35044
  const signing = input4.existing?.STREAMS_SIGNING_PRIVATE_KEY || input4.existing?.SECONDLAYER_WEBHOOK_SIGNING_PRIVATE_KEY || generateSigningPrivateKey();
35158
- const apiUrl = input4.apiUrl ?? "http://127.0.0.1:3800";
35045
+ const apiUrl = input4.existing?.SECONDLAYER_API_URL || input4.existing?.SL_API_URL || input4.apiUrl || "http://127.0.0.1:3800";
35159
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;
35160
35049
  return {
35161
35050
  INSTANCE_MODE: "oss",
35162
35051
  STACKS_NETWORK: input4.network,
@@ -35165,8 +35054,9 @@ function buildInstanceEnv(input4) {
35165
35054
  STREAMS_SIGNING_PRIVATE_KEY: signing,
35166
35055
  SECONDLAYER_WEBHOOK_SIGNING_PRIVATE_KEY: signing,
35167
35056
  ALLOW_UNSIGNED_WEBHOOKS: "false",
35168
- SL_API_URL: input4.existing?.SL_API_URL || apiUrl,
35169
- SL_API_KEY: token,
35057
+ SECONDLAYER_API_URL: apiUrl,
35058
+ SL_API_URL: apiUrl,
35059
+ ...hostedKey ? { SECONDLAYER_API_KEY: hostedKey, SL_API_KEY: hostedKey } : {},
35170
35060
  ARCHIVE_SIGNING_PUBLIC_KEY: archiveKey
35171
35061
  };
35172
35062
  }
@@ -35182,11 +35072,12 @@ function renderInstanceEnv(env2) {
35182
35072
  `STREAMS_SIGNING_PRIVATE_KEY=${escapeEnvValue(env2.STREAMS_SIGNING_PRIVATE_KEY)}`,
35183
35073
  `SECONDLAYER_WEBHOOK_SIGNING_PRIVATE_KEY=${escapeEnvValue(env2.SECONDLAYER_WEBHOOK_SIGNING_PRIVATE_KEY)}`,
35184
35074
  `ALLOW_UNSIGNED_WEBHOOKS=${env2.ALLOW_UNSIGNED_WEBHOOKS}`,
35185
- `SL_API_URL=${env2.SL_API_URL}`,
35186
- "# Legacy alias of INSTANCE_TOKEN, same value. Kept for existing setups;",
35187
- "# INSTANCE_TOKEN wins if the two ever disagree.",
35188
- `SL_API_KEY=${env2.SL_API_KEY}`
35075
+ `SECONDLAYER_API_URL=${env2.SECONDLAYER_API_URL}`,
35076
+ `SL_API_URL=${env2.SL_API_URL}`
35189
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
+ }
35190
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)}`, "");
35191
35082
  return lines2.join(`
35192
35083
  `);
@@ -35203,7 +35094,9 @@ function loadExistingInstanceEnv(cwd, file = INSTANCE_ENV_FILE) {
35203
35094
  SECONDLAYER_SECRETS_KEY: readEnvValue(path11, "SECONDLAYER_SECRETS_KEY"),
35204
35095
  STREAMS_SIGNING_PRIVATE_KEY: readEnvValue(path11, "STREAMS_SIGNING_PRIVATE_KEY"),
35205
35096
  SECONDLAYER_WEBHOOK_SIGNING_PRIVATE_KEY: readEnvValue(path11, "SECONDLAYER_WEBHOOK_SIGNING_PRIVATE_KEY"),
35097
+ SECONDLAYER_API_URL: readEnvValue(path11, "SECONDLAYER_API_URL"),
35206
35098
  SL_API_URL: readEnvValue(path11, "SL_API_URL"),
35099
+ SECONDLAYER_API_KEY: readEnvValue(path11, "SECONDLAYER_API_KEY"),
35207
35100
  SL_API_KEY: readEnvValue(path11, "SL_API_KEY"),
35208
35101
  ARCHIVE_SIGNING_PUBLIC_KEY: readEnvValue(path11, "ARCHIVE_SIGNING_PUBLIC_KEY")
35209
35102
  };
@@ -35588,7 +35481,9 @@ function resolveSecrets(config) {
35588
35481
  SECONDLAYER_SECRETS_KEY: existing.SECONDLAYER_SECRETS_KEY,
35589
35482
  STREAMS_SIGNING_PRIVATE_KEY: existing.STREAMS_SIGNING_PRIVATE_KEY,
35590
35483
  SECONDLAYER_WEBHOOK_SIGNING_PRIVATE_KEY: existing.SECONDLAYER_WEBHOOK_SIGNING_PRIVATE_KEY,
35484
+ SECONDLAYER_API_URL: existing.SECONDLAYER_API_URL,
35591
35485
  SL_API_URL: existing.SL_API_URL,
35486
+ SECONDLAYER_API_KEY: existing.SECONDLAYER_API_KEY,
35592
35487
  SL_API_KEY: existing.SL_API_KEY,
35593
35488
  ARCHIVE_SIGNING_PUBLIC_KEY: existing.ARCHIVE_SIGNING_PUBLIC_KEY
35594
35489
  },
@@ -35656,10 +35551,12 @@ function renderSetupEnv(config, secrets) {
35656
35551
  `STREAMS_SIGNING_PRIVATE_KEY=${escapeEnvValue(i3.STREAMS_SIGNING_PRIVATE_KEY)}`,
35657
35552
  `SECONDLAYER_WEBHOOK_SIGNING_PRIVATE_KEY=${escapeEnvValue(i3.SECONDLAYER_WEBHOOK_SIGNING_PRIVATE_KEY)}`,
35658
35553
  `ALLOW_UNSIGNED_WEBHOOKS=${i3.ALLOW_UNSIGNED_WEBHOOKS}`,
35659
- "# Legacy alias of INSTANCE_TOKEN, same value.",
35660
- `SL_API_KEY=${i3.SL_API_KEY}`,
35554
+ `SECONDLAYER_API_URL=${i3.SECONDLAYER_API_URL}`,
35661
35555
  `SL_API_URL=${i3.SL_API_URL}`
35662
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
+ }
35663
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)}`);
35664
35561
  if (config.nodeMode === "full") {
35665
35562
  lines2.push("BITCOIN_RPC_USER=stacks", `BITCOIN_RPC_PASSWORD=${secrets.bitcoinRpcPassword}`);
@@ -36687,7 +36584,7 @@ var {
36687
36584
  // package.json
36688
36585
  var package_default = {
36689
36586
  name: "@secondlayer/cli",
36690
- version: "14.1.2",
36587
+ version: "14.2.0",
36691
36588
  description: "Run a Stacks index on your own hardware — set it up, restore verified history, and keep it current.",
36692
36589
  repository: {
36693
36590
  type: "git",
@@ -36732,8 +36629,8 @@ var package_default = {
36732
36629
  "@opentui/react": "^0.5.3",
36733
36630
  "@secondlayer/bundler": "^0.5.1",
36734
36631
  "@secondlayer/scaffold": "^1.5.5",
36735
- "@secondlayer/sdk": "^10.2.0",
36736
- "@secondlayer/shared": "^11.3.0",
36632
+ "@secondlayer/sdk": "^10.3.0",
36633
+ "@secondlayer/shared": "^11.4.0",
36737
36634
  "@secondlayer/stacks": "^5.1.0",
36738
36635
  "@secondlayer/subgraphs": "^4.1.5",
36739
36636
  esbuild: "^0.27.4",
@@ -37326,6 +37223,7 @@ function openBrowser(url) {
37326
37223
  }
37327
37224
  function registerConsoleCommand(program2) {
37328
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();
37329
37227
  const url = resolveConsoleUrl(options3.url);
37330
37228
  if (options3.open === false) {
37331
37229
  writeData(url);
@@ -37502,18 +37400,19 @@ function printPublicStatus(status, diagnosis) {
37502
37400
  init_api_client();
37503
37401
  init_filter_params();
37504
37402
  init_output();
37403
+ init_resolve_auth();
37505
37404
  import { sign } from "@secondlayer/shared/crypto/standard-webhooks";
37506
37405
 
37507
37406
  // src/lib/subscription-validation.ts
37508
37407
  import {
37509
37408
  validateSubscriptionFilterForTable
37510
37409
  } from "@secondlayer/shared/schemas/subscriptions";
37511
- async function validateSubscriptionTargetFromApi(client2, input2) {
37512
- const subgraph = await client2.subgraphs.status(input2.subgraphName);
37410
+ async function validateSubscriptionTargetFromApi(client2, input) {
37411
+ const subgraph = await client2.subgraphs.status(input.subgraphName);
37513
37412
  const errors2 = validateSubscriptionFilterForTable({
37514
- subgraphName: input2.subgraphName,
37515
- tableName: input2.tableName,
37516
- filter: input2.filter,
37413
+ subgraphName: input.subgraphName,
37414
+ tableName: input.tableName,
37415
+ filter: input.filter,
37517
37416
  tables: subgraph.tables
37518
37417
  });
37519
37418
  if (errors2.length > 0) {
@@ -37538,7 +37437,7 @@ import {
37538
37437
  } from "node:fs";
37539
37438
  import { dirname as dirname6, join as join6, relative, resolve as resolve5 } from "node:path";
37540
37439
  import { fileURLToPath as fileURLToPath4 } from "node:url";
37541
- import { input as input2, select as select3 } from "@inquirer/prompts";
37440
+ import { input, select as select3 } from "@inquirer/prompts";
37542
37441
  import { SecondLayer as SecondLayer3 } from "@secondlayer/sdk";
37543
37442
  import {
37544
37443
  ChainTriggerSchema
@@ -37632,7 +37531,7 @@ async function promptFor(_name, opts) {
37632
37531
  let subgraph = opts.subgraph;
37633
37532
  if (subgraph === undefined) {
37634
37533
  requireTtyFor("--subgraph");
37635
- subgraph = await input2({
37534
+ subgraph = await input({
37636
37535
  message: "Subgraph name (must already be deployed):",
37637
37536
  validate: (v2) => v2.trim().length > 0 ? true : "required"
37638
37537
  });
@@ -37640,7 +37539,7 @@ async function promptFor(_name, opts) {
37640
37539
  let table = opts.table;
37641
37540
  if (table === undefined) {
37642
37541
  requireTtyFor("--table");
37643
- table = await input2({
37542
+ table = await input({
37644
37543
  message: "Table to subscribe to:",
37645
37544
  validate: (v2) => v2.trim().length > 0 ? true : "required"
37646
37545
  });
@@ -37648,7 +37547,7 @@ async function promptFor(_name, opts) {
37648
37547
  let url = opts.url;
37649
37548
  if (url === undefined) {
37650
37549
  requireTtyFor("--url");
37651
- url = await input2({
37550
+ url = await input({
37652
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):",
37653
37552
  validate: (v2) => v2.startsWith("http://") || v2.startsWith("https://") ? true : "must be http(s) URL"
37654
37553
  });
@@ -37981,52 +37880,52 @@ Auth config:`));
37981
37880
  console.log(JSON.stringify(sub.authConfig, null, 2));
37982
37881
  }
37983
37882
  }
37984
- function buildDoctorReport(input3) {
37985
- const successful = input3.deliveries.filter(isSuccessDelivery).length;
37986
- const failed = input3.deliveries.length - successful;
37987
- const subgraph = input3.subgraph ? {
37988
- name: input3.subgraph.name,
37989
- status: input3.subgraph.status,
37990
- syncStatus: input3.subgraph.sync.status,
37991
- lastProcessedBlock: input3.subgraph.sync.lastProcessedBlock,
37992
- chainTip: input3.subgraph.sync.chainTip,
37993
- gapCount: input3.subgraph.sync.gaps.count,
37994
- 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
37995
37894
  } : null;
37996
37895
  const hints = [];
37997
- if (input3.subscription.status === "paused") {
37998
- 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}`);
37999
37898
  }
38000
- if (input3.subscription.lastError) {
37899
+ if (input2.subscription.lastError) {
38001
37900
  hints.push("Run secondlayer subscriptions test to reproduce the receiver request.");
38002
37901
  }
38003
- if (input3.subscription.circuitOpenedAt || input3.subscription.circuitFailures > 0) {
37902
+ if (input2.subscription.circuitOpenedAt || input2.subscription.circuitFailures > 0) {
38004
37903
  hints.push("Circuit breaker has failures; inspect receiver logs and delivery status codes.");
38005
37904
  }
38006
- if (input3.dead.length > 0) {
38007
- 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.`);
38008
37907
  }
38009
37908
  if (subgraph?.gapCount && subgraph.gapCount > 0) {
38010
- 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}.`);
38011
37910
  }
38012
37911
  if (subgraph?.syncStatus === "catching_up") {
38013
37912
  hints.push("Linked subgraph is still catching up; new matching rows may arrive later.");
38014
37913
  }
38015
- if (input3.deliveries.length === 0) {
37914
+ if (input2.deliveries.length === 0) {
38016
37915
  hints.push("No deliveries yet; confirm the table is receiving inserted rows that match the filter.");
38017
37916
  }
38018
37917
  if (hints.length === 0) {
38019
37918
  hints.push("No immediate action needed.");
38020
37919
  }
38021
37920
  return {
38022
- subscription: input3.subscription,
37921
+ subscription: input2.subscription,
38023
37922
  deliverySummary: {
38024
- total: input3.deliveries.length,
37923
+ total: input2.deliveries.length,
38025
37924
  successful,
38026
37925
  failed,
38027
- last: input3.deliveries[0] ?? null
37926
+ last: input2.deliveries[0] ?? null
38028
37927
  },
38029
- deadCount: input3.dead.length,
37928
+ deadCount: input2.dead.length,
38030
37929
  subgraph,
38031
37930
  hints
38032
37931
  };
@@ -38119,9 +38018,9 @@ function resolveSigningSecret(options3, env2 = process.env) {
38119
38018
  function shellQuote(value) {
38120
38019
  return `'${value.replace(/'/g, "'\\''")}'`;
38121
38020
  }
38122
- function buildSubscriptionTestFixture(input3) {
38123
- const nowSeconds = input3.nowSeconds ?? Math.floor(Date.now() / 1000);
38124
- const sub = input3.subscription;
38021
+ function buildSubscriptionTestFixture(input2) {
38022
+ const nowSeconds = input2.nowSeconds ?? Math.floor(Date.now() / 1000);
38023
+ const sub = input2.subscription;
38125
38024
  const isChain = sub.kind === "chain";
38126
38025
  const type = isChain ? `chain.${sub.triggers?.[0]?.type ?? "event"}.apply` : `${sub.subgraphName}.${sub.tableName}.created`;
38127
38026
  const data = isChain ? {
@@ -38129,10 +38028,10 @@ function buildSubscriptionTestFixture(input3) {
38129
38028
  canonical: true,
38130
38029
  block_hash: "0xexampleblock",
38131
38030
  block_height: 0,
38132
- tx_id: input3.row.tx_id ?? "0xexampletransaction",
38031
+ tx_id: input2.row.tx_id ?? "0xexampletransaction",
38133
38032
  trigger: sub.triggers?.[0]?.type ?? "event",
38134
- event: input3.row
38135
- } : input3.row;
38033
+ event: input2.row
38034
+ } : input2.row;
38136
38035
  const body = JSON.stringify({
38137
38036
  type,
38138
38037
  timestamp: new Date(nowSeconds * 1000).toISOString(),
@@ -38140,15 +38039,15 @@ function buildSubscriptionTestFixture(input3) {
38140
38039
  });
38141
38040
  const headers = {
38142
38041
  "content-type": "application/json",
38143
- ...sign(body, input3.signingSecret, {
38144
- id: input3.id ?? `test-${input3.subscription.id}`,
38042
+ ...sign(body, input2.signingSecret, {
38043
+ id: input2.id ?? `test-${input2.subscription.id}`,
38145
38044
  timestampSeconds: nowSeconds
38146
38045
  })
38147
38046
  };
38148
38047
  const headerArgs = Object.entries(headers).map(([key, value]) => ` -H ${shellQuote(`${key}: ${value}`)} \\`).join(`
38149
38048
  `);
38150
38049
  const curl = [
38151
- `curl -X POST ${shellQuote(input3.subscription.url)} \\`,
38050
+ `curl -X POST ${shellQuote(input2.subscription.url)} \\`,
38152
38051
  headerArgs,
38153
38052
  ` --data ${shellQuote(body)}`
38154
38053
  ].join(`
@@ -38211,6 +38110,7 @@ async function confirmOrExit(message, yes) {
38211
38110
  }
38212
38111
  function registerSubscriptionsCommand(program2) {
38213
38112
  const subscriptions = program2.command("subscriptions").alias("subs").description("Manage subgraph table subscriptions");
38113
+ subscriptions.hook("preAction", () => assertInstanceUrl());
38214
38114
  addSubscriptionsCreateCommand(subscriptions);
38215
38115
  subscriptions.command("list").alias("ls").description("List subscriptions").option("--json", "Output as JSON").action(async (options3) => {
38216
38116
  try {
@@ -38946,7 +38846,7 @@ var VALID_TYPES = STREAMS_EVENT_TYPES;
38946
38846
  function client2() {
38947
38847
  return createStreamsClient({
38948
38848
  baseUrl: resolveApiUrl(),
38949
- apiKey: resolveEnvKey() ?? ""
38849
+ apiKey: resolveDataPlaneKey() ?? ""
38950
38850
  });
38951
38851
  }
38952
38852
  function dumpsClient(dumpsUrl) {
@@ -38957,7 +38857,7 @@ function dumpsClient(dumpsUrl) {
38957
38857
  }
38958
38858
  return createStreamsClient({
38959
38859
  baseUrl: resolveApiUrl(),
38960
- apiKey: resolveEnvKey() ?? "",
38860
+ apiKey: resolveDataPlaneKey() ?? "",
38961
38861
  dumpsBaseUrl
38962
38862
  });
38963
38863
  }
@@ -39586,7 +39486,7 @@ function markFrozen(cmd) {
39586
39486
  init_config2();
39587
39487
  init_dev_state();
39588
39488
  init_output();
39589
- import { confirm as confirm4 } from "@inquirer/prompts";
39489
+ import { confirm as confirm3 } from "@inquirer/prompts";
39590
39490
  import { getDb, sql } from "@secondlayer/shared/db";
39591
39491
  import {
39592
39492
  countMissingBlocks,
@@ -39806,7 +39706,7 @@ async function resetDatabase(skipConfirm) {
39806
39706
  console.log(dim("Note: Subgraph configurations will be preserved."));
39807
39707
  console.log("");
39808
39708
  if (!skipConfirm) {
39809
- const confirmed = await confirm4({
39709
+ const confirmed = await confirm3({
39810
39710
  message: "Are you sure you want to reset the database?",
39811
39711
  default: false
39812
39712
  });
@@ -39843,7 +39743,7 @@ async function resyncDatabase(skipConfirm, backfill) {
39843
39743
  console.log(dim("Note: Subgraph configurations will be preserved."));
39844
39744
  console.log("");
39845
39745
  if (!skipConfirm) {
39846
- const confirmed = await confirm4({
39746
+ const confirmed = await confirm3({
39847
39747
  message: "Are you sure you want to resync?",
39848
39748
  default: false
39849
39749
  });
@@ -40411,6 +40311,7 @@ function formatLogLine3(service, line) {
40411
40311
  init_config2();
40412
40312
  init_http();
40413
40313
  init_output();
40314
+ import { resolveApiKey as resolveApiKey4 } from "@secondlayer/sdk";
40414
40315
 
40415
40316
  // src/lib/project-file.ts
40416
40317
  import { existsSync as existsSync7 } from "node:fs";
@@ -40455,58 +40356,188 @@ async function readActiveProject(cwd, globalDefault) {
40455
40356
  }
40456
40357
 
40457
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
+ }
40458
40391
  function registerWhoamiCommand(program2) {
40459
- program2.command("whoami", { hidden: true }).description("Show the archive credits account, credential source, and project").option("--json", "Output as JSON").action(async (options3) => {
40460
- const creditsUrl = resolveArchiveOpsUrl();
40461
- const { source } = await resolveArchiveOpsBearer();
40462
- let account;
40463
- try {
40464
- account = await httpArchiveOps("/api/accounts/me");
40465
- } catch (err) {
40466
- if (err instanceof CliHttpError) {
40467
- error(err.message);
40468
- process.exit(1);
40469
- }
40470
- throw err;
40471
- }
40472
- const config = await loadConfig2();
40473
- const active = await readActiveProject(process.cwd(), config.defaultProject);
40474
- 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();
40475
40394
  output({
40476
40395
  json: options3.json,
40477
- data: {
40478
- email: account.email,
40479
- creditsUrl,
40480
- apiUrl: resolveApiUrl(),
40481
- authSource,
40482
- project: active ? { slug: active.slug, source: active.resolvedFrom } : null
40483
- },
40396
+ data: result,
40484
40397
  human: () => {
40485
40398
  const rows = [];
40486
- rows.push(["Email", account.email]);
40487
- rows.push(["Credits API", creditsUrl]);
40488
- rows.push(["Instance API", resolveApiUrl()]);
40489
- rows.push(["Auth", dim(authSource)]);
40490
- if (active) {
40491
- rows.push(["Project", active.slug]);
40492
- 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)]);
40493
40409
  } else {
40494
- rows.push(["Project", dim("(none)")]);
40410
+ rows.push(["project", dim("(none)")]);
40495
40411
  }
40496
40412
  console.log(formatKeyValue(rows));
40413
+ if (result.merchant.status === "missing") {
40414
+ note(`merchant identity missing - run \`${ARCHIVE_LOGIN_COMMAND}\``);
40415
+ }
40497
40416
  }
40498
40417
  });
40499
40418
  });
40500
40419
  }
40501
-
40502
- // src/commands/index.ts
40503
- init_login();
40504
-
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
+ }
40505
40537
  // src/commands/logout.ts
40506
40538
  init_http();
40507
40539
  init_output();
40508
40540
  init_session();
40509
- init_login();
40510
40541
  async function runLogout(opts = {}) {
40511
40542
  const target = loginTarget(opts);
40512
40543
  const session = await readSession(target);
@@ -41785,6 +41816,7 @@ function registerBootstrapCommand(program2) {
41785
41816
  // src/commands/uninstall.ts
41786
41817
  init_instance_init();
41787
41818
  init_output();
41819
+ init_resolve_auth();
41788
41820
  import { spawnSync as spawnSync4 } from "node:child_process";
41789
41821
  import { existsSync as existsSync9, readFileSync as readFileSync8 } from "node:fs";
41790
41822
  import { join as join16, resolve as resolve15 } from "node:path";
@@ -41839,6 +41871,7 @@ function composeDownArgs(plan, layout) {
41839
41871
  }
41840
41872
  function registerUninstallCommand(program2) {
41841
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();
41842
41875
  const dataDir = process.env.DATA_DIR ?? "./data";
41843
41876
  const layout = resolveUninstallLayout(process.cwd(), opts.compose);
41844
41877
  const secretsPresent = !!process.env.SECONDLAYER_SECRETS_KEY || !!layout.secretsFile;
@@ -41913,6 +41946,7 @@ function registerUninstallCommand(program2) {
41913
41946
  // src/commands/backup.ts
41914
41947
  init_fs();
41915
41948
  init_output();
41949
+ init_resolve_auth();
41916
41950
  import {
41917
41951
  chmodSync as chmodSync2,
41918
41952
  existsSync as existsSync10,
@@ -42222,10 +42256,10 @@ async function runRestore(opts) {
42222
42256
  });
42223
42257
  }
42224
42258
  function registerBackupCommand(program2) {
42225
- 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()));
42226
42260
  }
42227
42261
  function registerRestoreCommand(program2) {
42228
- 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()));
42229
42263
  }
42230
42264
  // src/commands/init.ts
42231
42265
  init_archive_reference();
@@ -42238,6 +42272,7 @@ function registerInitCommand(program2) {
42238
42272
  --network <mainnet|testnet|devnet> and --api-url <url> are top-level global
42239
42273
  flags (see \`secondlayer --help\`). Defaults: mainnet, http://127.0.0.1:3800.
42240
42274
  `).action(async (opts) => {
42275
+ assertInstanceUrl();
42241
42276
  const network = instanceNetworkFromEnv();
42242
42277
  const existing = opts.force ? {} : loadExistingInstanceEnv(process.cwd());
42243
42278
  const env2 = buildInstanceEnv({
@@ -42263,11 +42298,13 @@ flags (see \`secondlayer --help\`). Defaults: mainnet, http://127.0.0.1:3800.
42263
42298
  init_instance_init();
42264
42299
  init_observer_stanza();
42265
42300
  init_output();
42301
+ init_resolve_auth();
42266
42302
  function registerObserverCommand(program2) {
42267
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", `
42268
42304
  --network <mainnet|testnet|devnet> is the top-level global flag (see
42269
42305
  \`secondlayer --help\`) — default mainnet, same as before.
42270
42306
  `).action((opts) => {
42307
+ assertInstanceUrl();
42271
42308
  const mode = parseObserverMode(opts.mode);
42272
42309
  const network = instanceNetworkFromEnv();
42273
42310
  const endpoint = opts.endpoint ?? defaultObserverEndpoint(network);
@@ -42280,6 +42317,7 @@ function registerObserverCommand(program2) {
42280
42317
  }
42281
42318
  // src/commands/setup.ts
42282
42319
  init_output();
42320
+ init_resolve_auth();
42283
42321
 
42284
42322
  // src/lib/setup-prompts.ts
42285
42323
  init_instance_init();
@@ -42486,6 +42524,7 @@ Without a TTY (piped, CI, an agent), or with --yes, the TUI is skipped: every
42486
42524
  decision with no safe default (--network, --node-mode, and --against unless
42487
42525
  --skip-bootstrap) must come from a flag or the command fails fast naming it.
42488
42526
  `).action(async (opts) => {
42527
+ assertInstanceUrl();
42489
42528
  const flags = {
42490
42529
  ...opts,
42491
42530
  network: opts.network ?? process.env.STACKS_NETWORK
@@ -42509,18 +42548,19 @@ decision with no safe default (--network, --node-mode, and --against unless
42509
42548
  });
42510
42549
  }
42511
42550
  // src/cli.ts
42551
+ init_resolve_auth();
42512
42552
  var { version } = package_default;
42513
- 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)");
42514
42554
  program.hook("preAction", (thisCommand) => {
42515
42555
  const { network, apiKey, apiUrl } = thisCommand.opts();
42516
42556
  if (network)
42517
42557
  process.env.STACKS_NETWORK = network;
42518
- if (apiKey) {
42519
- process.env.INSTANCE_TOKEN = apiKey;
42520
- process.env.SL_API_KEY = apiKey;
42521
- }
42522
- if (apiUrl)
42558
+ if (apiKey)
42559
+ applyApiKeyFlag(apiKey);
42560
+ if (apiUrl) {
42561
+ process.env.SECONDLAYER_API_URL = apiUrl;
42523
42562
  process.env.SL_API_URL = apiUrl;
42563
+ }
42524
42564
  });
42525
42565
  program.addHelpText("after", `
42526
42566
  Quickstart:
@@ -42566,5 +42606,5 @@ program.commandsGroup("Archive:");
42566
42606
  registerCreditsCommand(program);
42567
42607
  program.parse();
42568
42608
 
42569
- //# debugId=54476D3169B18AA964756E2164756E21
42609
+ //# debugId=CAE75BE93848EF8E64756E2164756E21
42570
42610
  //# sourceMappingURL=cli.js.map