@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/README.md +3 -2
- package/dist/cli.js +337 -500
- package/dist/cli.js.map +26 -27
- package/package.json +3 -3
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
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
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 =
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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(
|
|
29686
|
+
function createLocalSubgraphDetail(input) {
|
|
29801
29687
|
const tables = {};
|
|
29802
|
-
for (const [tableName, rawTable] of Object.entries(
|
|
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/${
|
|
29705
|
+
endpoint: `/subgraphs/${input.name}/${tableName}`,
|
|
29820
29706
|
columns,
|
|
29821
29707
|
rowCount: 0,
|
|
29822
|
-
example: `/subgraphs/${
|
|
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:
|
|
29829
|
-
version:
|
|
29830
|
-
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
|
-
...
|
|
29834
|
-
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
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 {
|
|
34848
|
+
import { join as join13, resolve as resolve13, sep as sep2 } from "node:path";
|
|
34967
34849
|
import { parseRetryAfter } from "@secondlayer/sdk";
|
|
34968
|
-
import {
|
|
34969
|
-
|
|
34970
|
-
|
|
34971
|
-
|
|
34972
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
35264
|
-
const archiveKey = input4.existing?.ARCHIVE_SIGNING_PUBLIC_KEY || input4.archivePublicKeyPem ||
|
|
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
|
-
|
|
35274
|
-
|
|
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
|
-
`
|
|
35291
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
36841
|
-
"@secondlayer/shared": "^11.
|
|
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
|
-
|
|
37460
|
-
|
|
37461
|
-
|
|
37462
|
-
|
|
37463
|
-
|
|
37464
|
-
|
|
37465
|
-
|
|
37466
|
-
|
|
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,
|
|
37715
|
-
const subgraph = await client2.subgraphs.status(
|
|
37410
|
+
async function validateSubscriptionTargetFromApi(client2, input) {
|
|
37411
|
+
const subgraph = await client2.subgraphs.status(input.subgraphName);
|
|
37716
37412
|
const errors2 = validateSubscriptionFilterForTable({
|
|
37717
|
-
subgraphName:
|
|
37718
|
-
tableName:
|
|
37719
|
-
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
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
38188
|
-
const successful =
|
|
38189
|
-
const failed =
|
|
38190
|
-
const subgraph =
|
|
38191
|
-
name:
|
|
38192
|
-
status:
|
|
38193
|
-
syncStatus:
|
|
38194
|
-
lastProcessedBlock:
|
|
38195
|
-
chainTip:
|
|
38196
|
-
gapCount:
|
|
38197
|
-
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 (
|
|
38201
|
-
hints.push(`Resume when the receiver is healthy: secondlayer subscriptions resume ${
|
|
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 (
|
|
37899
|
+
if (input2.subscription.lastError) {
|
|
38204
37900
|
hints.push("Run secondlayer subscriptions test to reproduce the receiver request.");
|
|
38205
37901
|
}
|
|
38206
|
-
if (
|
|
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 (
|
|
38210
|
-
hints.push(`Dead-letter rows exist; inspect with secondlayer subscriptions dead ${
|
|
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 ${
|
|
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 (
|
|
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:
|
|
37921
|
+
subscription: input2.subscription,
|
|
38226
37922
|
deliverySummary: {
|
|
38227
|
-
total:
|
|
37923
|
+
total: input2.deliveries.length,
|
|
38228
37924
|
successful,
|
|
38229
37925
|
failed,
|
|
38230
|
-
last:
|
|
37926
|
+
last: input2.deliveries[0] ?? null
|
|
38231
37927
|
},
|
|
38232
|
-
deadCount:
|
|
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(
|
|
38326
|
-
const nowSeconds =
|
|
38327
|
-
const sub =
|
|
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:
|
|
38031
|
+
tx_id: input2.row.tx_id ?? "0xexampletransaction",
|
|
38336
38032
|
trigger: sub.triggers?.[0]?.type ?? "event",
|
|
38337
|
-
event:
|
|
38338
|
-
} :
|
|
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,
|
|
38347
|
-
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(
|
|
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:
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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"
|
|
40663
|
-
const
|
|
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([
|
|
40690
|
-
|
|
40691
|
-
|
|
40692
|
-
|
|
40693
|
-
|
|
40694
|
-
|
|
40695
|
-
|
|
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(["
|
|
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
|
-
|
|
40706
|
-
|
|
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>", "
|
|
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
|
-
|
|
42723
|
-
|
|
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=
|
|
42609
|
+
//# debugId=CAE75BE93848EF8E64756E2164756E21
|
|
42773
42610
|
//# sourceMappingURL=cli.js.map
|