@secondlayer/cli 21.2.0 → 22.0.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 +2 -2
- package/dist/cli.js +161 -45
- package/dist/cli.js.map +13 -12
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -15,8 +15,8 @@ secondlayer --version
|
|
|
15
15
|
Default API is `http://127.0.0.1:3800`. Override with `SECONDLAYER_API_URL`.
|
|
16
16
|
|
|
17
17
|
Writes against a published bind use `INSTANCE_TOKEN` from `secondlayer init`.
|
|
18
|
-
Loopback reads need no token.
|
|
19
|
-
`SECONDLAYER_API_KEY` (`sk-sl_*`).
|
|
18
|
+
Loopback reads need no token. Your hosted account (credits, archive fetches)
|
|
19
|
+
uses `secondlayer login` or `SECONDLAYER_API_KEY` (`sk-sl_*`).
|
|
20
20
|
|
|
21
21
|
```bash
|
|
22
22
|
export SECONDLAYER_API_URL=http://127.0.0.1:3800
|
package/dist/cli.js
CHANGED
|
@@ -2369,12 +2369,12 @@ async function resolveArchiveOpsBearer() {
|
|
|
2369
2369
|
};
|
|
2370
2370
|
}
|
|
2371
2371
|
function ignoredEnvKeyNote(ignored) {
|
|
2372
|
-
return ignored ? " INSTANCE_TOKEN was ignored: it unlocks this instance, not
|
|
2372
|
+
return ignored ? " INSTANCE_TOKEN was ignored: it unlocks this instance, not your account." : "";
|
|
2373
2373
|
}
|
|
2374
2374
|
async function httpArchiveOps(path, opts = {}) {
|
|
2375
2375
|
const { bearer, ignoredEnvKey } = await resolveArchiveOpsBearer();
|
|
2376
2376
|
if (!bearer) {
|
|
2377
|
-
throw new CliHttpError(401, "SESSION_EXPIRED", { error: "Not logged in" }, `
|
|
2377
|
+
throw new CliHttpError(401, "SESSION_EXPIRED", { error: "Not logged in" }, `This needs your Secondlayer account. ${ARCHIVE_AUTH_HINT}${ignoredEnvKeyNote(ignoredEnvKey)}`);
|
|
2378
2378
|
}
|
|
2379
2379
|
try {
|
|
2380
2380
|
return await request(`${resolveArchiveOpsUrl()}${path}`, {
|
|
@@ -2383,12 +2383,12 @@ async function httpArchiveOps(path, opts = {}) {
|
|
|
2383
2383
|
});
|
|
2384
2384
|
} catch (err) {
|
|
2385
2385
|
if (err instanceof CliHttpError && err.status === 401) {
|
|
2386
|
-
throw new CliHttpError(401, err.code, err.body, `
|
|
2386
|
+
throw new CliHttpError(401, err.code, err.body, `Your account rejected the credential (${err.message}). ${ARCHIVE_AUTH_HINT}${ignoredEnvKeyNote(ignoredEnvKey)}`);
|
|
2387
2387
|
}
|
|
2388
2388
|
throw err;
|
|
2389
2389
|
}
|
|
2390
2390
|
}
|
|
2391
|
-
var CliHttpError, REQUEST_TIMEOUT_MS = 30000, ARCHIVE_AUTH_HINT = "Set SECONDLAYER_API_KEY to your account key (sk-sl_…, from /account/keys), or run `secondlayer login
|
|
2391
|
+
var CliHttpError, REQUEST_TIMEOUT_MS = 30000, ARCHIVE_AUTH_HINT = "Set SECONDLAYER_API_KEY to your account key (sk-sl_…, from /account/keys), or run `secondlayer login`.", ARCHIVE_CREDENTIAL_RE;
|
|
2392
2392
|
var init_http = __esm(() => {
|
|
2393
2393
|
init_api_url();
|
|
2394
2394
|
init_resolve_auth();
|
|
@@ -202692,7 +202692,7 @@ var {
|
|
|
202692
202692
|
// package.json
|
|
202693
202693
|
var package_default = {
|
|
202694
202694
|
name: "@secondlayer/cli",
|
|
202695
|
-
version: "
|
|
202695
|
+
version: "22.0.0",
|
|
202696
202696
|
description: "Run a Stacks index on your own hardware — set it up, restore verified history, and keep it current.",
|
|
202697
202697
|
repository: {
|
|
202698
202698
|
type: "git",
|
|
@@ -202732,7 +202732,7 @@ var package_default = {
|
|
|
202732
202732
|
"@secondlayer/bundler": "^0.7.1",
|
|
202733
202733
|
"@secondlayer/scaffold": "^1.6.2",
|
|
202734
202734
|
"@secondlayer/sdk": "^15.1.0",
|
|
202735
|
-
"@secondlayer/shared": "^13.
|
|
202735
|
+
"@secondlayer/shared": "^13.7.0",
|
|
202736
202736
|
"@secondlayer/stacks": "^6.4.0",
|
|
202737
202737
|
"@secondlayer/subgraphs": "^7.2.0",
|
|
202738
202738
|
"@secondlayer/verify": "^0.6.0",
|
|
@@ -202811,7 +202811,7 @@ var BUY_CREDITS_COMMAND = "secondlayer credits buy";
|
|
|
202811
202811
|
function formatInsufficientMessage(quote) {
|
|
202812
202812
|
const shortfallUsd = ((quote.usdMicros - quote.balanceUsdMicros) / 1e6).toFixed(2);
|
|
202813
202813
|
const balanceUsd = (quote.balanceUsdMicros / 1e6).toFixed(2);
|
|
202814
|
-
return `Insufficient
|
|
202814
|
+
return `Insufficient credits: quote $${quote.usd}, balance $${balanceUsd}, short $${shortfallUsd}. Buy more with \`${BUY_CREDITS_COMMAND}\`.`;
|
|
202815
202815
|
}
|
|
202816
202816
|
var ARCHIVE_GATE_NOT_CONFIGURED_MESSAGE = "the archive gate is not configured on the server; contact the operator";
|
|
202817
202817
|
var ALLOWANCE_MONTHLY_BUNDLES = 6;
|
|
@@ -204986,7 +204986,7 @@ async function runBuy(opts) {
|
|
|
204986
204986
|
human: () => {
|
|
204987
204987
|
success(`Checkout for $${amount} → ${email}`);
|
|
204988
204988
|
console.log(res.url);
|
|
204989
|
-
console.log(dim("After payment: set SECONDLAYER_API_KEY (or run `secondlayer login
|
|
204989
|
+
console.log(dim("After payment: set SECONDLAYER_API_KEY (or run `secondlayer login`), then run `secondlayer credits balance`"));
|
|
204990
204990
|
}
|
|
204991
204991
|
});
|
|
204992
204992
|
}
|
|
@@ -205061,8 +205061,8 @@ async function runUsage(opts) {
|
|
|
205061
205061
|
});
|
|
205062
205062
|
}
|
|
205063
205063
|
function registerCreditsCommand(program) {
|
|
205064
|
-
const credits = program.command("credits").description("Buy and check
|
|
205065
|
-
credits.command("buy").description("Open a one-time Stripe checkout for
|
|
205064
|
+
const credits = program.command("credits").description("Buy and check your credits");
|
|
205065
|
+
credits.command("buy").description("Open a one-time Stripe checkout for credits").requiredOption("--email <email>", "Receipt email; becomes the account").option("--pack <usd>", `One of ${PACKS.join(", ")}`, "25").option("--json", "Output as JSON").action((opts) => runBuy(opts));
|
|
205066
205066
|
credits.command("balance").description("Show prepaid archive credit balance").option("--json", "Output as JSON").action((opts) => runBalance(opts));
|
|
205067
205067
|
credits.command("usage").description("Rows delivered per day: free vs paid, by source").option("--days <n>", "Days to show (max 30)", "30").option("--json", "Output as JSON").action((opts) => runUsage(opts));
|
|
205068
205068
|
credits.command("refill").description("Opt-in auto-refill when the balance drops under a threshold").option("--below <usd>", "Trigger when balance is under this USD amount").option("--pack <usd>", `Pack to buy (${PACKS.join(", ")})`, "25").option("--off", "Turn auto-refill off").option("--json", "Output as JSON").action((opts) => runRefill(opts));
|
|
@@ -207801,9 +207801,32 @@ function registerWhoamiCommand(program) {
|
|
|
207801
207801
|
// src/commands/login.ts
|
|
207802
207802
|
init_api_url();
|
|
207803
207803
|
init_http();
|
|
207804
|
+
import { hostname } from "node:os";
|
|
207805
|
+
import { createInterface } from "node:readline";
|
|
207806
|
+
import { confirm as confirm3, input } from "@inquirer/prompts";
|
|
207807
|
+
|
|
207808
|
+
// src/lib/open-browser.ts
|
|
207809
|
+
import { spawn as spawn3 } from "node:child_process";
|
|
207810
|
+
function browserCommand(url, platform = process.platform) {
|
|
207811
|
+
if (platform === "darwin")
|
|
207812
|
+
return { cmd: "open", args: [url] };
|
|
207813
|
+
if (platform === "win32") {
|
|
207814
|
+
return { cmd: "cmd", args: ["/c", "start", "", url] };
|
|
207815
|
+
}
|
|
207816
|
+
return { cmd: "xdg-open", args: [url] };
|
|
207817
|
+
}
|
|
207818
|
+
function openBrowser(url) {
|
|
207819
|
+
try {
|
|
207820
|
+
const { cmd, args } = browserCommand(url);
|
|
207821
|
+
const child = spawn3(cmd, args, { detached: true, stdio: "ignore" });
|
|
207822
|
+
child.on("error", () => {});
|
|
207823
|
+
child.unref();
|
|
207824
|
+
} catch {}
|
|
207825
|
+
}
|
|
207826
|
+
|
|
207827
|
+
// src/commands/login.ts
|
|
207804
207828
|
init_output();
|
|
207805
207829
|
init_session();
|
|
207806
|
-
import { confirm as confirm3, input } from "@inquirer/prompts";
|
|
207807
207830
|
function sessionExpiry() {
|
|
207808
207831
|
return new Date(Date.now() + 90 * 24 * 60 * 60 * 1000).toISOString();
|
|
207809
207832
|
}
|
|
@@ -207813,16 +207836,16 @@ async function readStdin() {
|
|
|
207813
207836
|
chunks.push(chunk);
|
|
207814
207837
|
return Buffer.concat(chunks).toString("utf8").trim();
|
|
207815
207838
|
}
|
|
207816
|
-
function loginTarget(
|
|
207817
|
-
return
|
|
207839
|
+
function loginTarget() {
|
|
207840
|
+
return resolveArchiveOpsUrl();
|
|
207818
207841
|
}
|
|
207819
|
-
async function runTokenLogin(
|
|
207842
|
+
async function runTokenLogin() {
|
|
207820
207843
|
const token = await readStdin();
|
|
207821
207844
|
if (!token) {
|
|
207822
207845
|
error('No token on stdin. Usage: echo "$KEY" | secondlayer login --with-token');
|
|
207823
207846
|
process.exit(1);
|
|
207824
207847
|
}
|
|
207825
|
-
const target = loginTarget(
|
|
207848
|
+
const target = loginTarget();
|
|
207826
207849
|
let account;
|
|
207827
207850
|
try {
|
|
207828
207851
|
account = await httpAt(target, "/api/accounts/me", { bearer: token });
|
|
@@ -207838,31 +207861,123 @@ async function runTokenLogin(opts) {
|
|
|
207838
207861
|
}, target);
|
|
207839
207862
|
success(`Logged in as ${account.email} (${target})`);
|
|
207840
207863
|
}
|
|
207841
|
-
async function
|
|
207842
|
-
|
|
207843
|
-
|
|
207844
|
-
|
|
207845
|
-
|
|
207846
|
-
|
|
207847
|
-
|
|
207848
|
-
|
|
207849
|
-
|
|
207850
|
-
|
|
207851
|
-
|
|
207852
|
-
|
|
207853
|
-
|
|
207854
|
-
|
|
207855
|
-
|
|
207856
|
-
|
|
207857
|
-
|
|
207858
|
-
|
|
207864
|
+
async function shouldContinueLogin(target, options) {
|
|
207865
|
+
if (options.force)
|
|
207866
|
+
return true;
|
|
207867
|
+
const existing = await readSession(target);
|
|
207868
|
+
if (!existing)
|
|
207869
|
+
return true;
|
|
207870
|
+
info(`Already logged in as ${existing.email} (${target}).`);
|
|
207871
|
+
const hint = "Run 'secondlayer logout' first, or re-run with --force to switch accounts.";
|
|
207872
|
+
if (!process.stdin.isTTY) {
|
|
207873
|
+
info(dim(hint));
|
|
207874
|
+
return false;
|
|
207875
|
+
}
|
|
207876
|
+
try {
|
|
207877
|
+
const proceed = await confirm3({
|
|
207878
|
+
message: "Log in as a different user?",
|
|
207879
|
+
default: false
|
|
207880
|
+
});
|
|
207881
|
+
if (!proceed) {
|
|
207882
|
+
info(dim("Run 'secondlayer logout' to sign out."));
|
|
207883
|
+
return false;
|
|
207884
|
+
}
|
|
207885
|
+
} catch {
|
|
207886
|
+
info(dim(hint));
|
|
207887
|
+
return false;
|
|
207888
|
+
}
|
|
207889
|
+
return true;
|
|
207890
|
+
}
|
|
207891
|
+
async function pollDeviceToken(start, deps) {
|
|
207892
|
+
const deadline = deps.now() + start.expiresIn * 1000;
|
|
207893
|
+
let failures = 0;
|
|
207894
|
+
while (deps.now() < deadline) {
|
|
207895
|
+
await deps.sleep(start.interval * 1000);
|
|
207896
|
+
try {
|
|
207897
|
+
const res = await deps.post("/api/cli/device/token", { deviceCode: start.deviceCode });
|
|
207898
|
+
return {
|
|
207899
|
+
kind: "approved",
|
|
207900
|
+
sessionToken: res.sessionToken,
|
|
207901
|
+
account: res.account
|
|
207902
|
+
};
|
|
207903
|
+
} catch (err) {
|
|
207904
|
+
if (err instanceof CliHttpError) {
|
|
207905
|
+
if (err.status === 428) {
|
|
207906
|
+
failures = 0;
|
|
207907
|
+
continue;
|
|
207859
207908
|
}
|
|
207860
|
-
|
|
207861
|
-
|
|
207862
|
-
|
|
207909
|
+
if (err.status === 403 && err.body.error === "access_denied") {
|
|
207910
|
+
return { kind: "denied" };
|
|
207911
|
+
}
|
|
207912
|
+
if (err.status === 410)
|
|
207913
|
+
return { kind: "expired" };
|
|
207914
|
+
throw err;
|
|
207863
207915
|
}
|
|
207916
|
+
if (++failures >= 3)
|
|
207917
|
+
throw err;
|
|
207864
207918
|
}
|
|
207865
207919
|
}
|
|
207920
|
+
return { kind: "expired" };
|
|
207921
|
+
}
|
|
207922
|
+
function clientName() {
|
|
207923
|
+
return hostname().trim().slice(0, 64) || "this machine";
|
|
207924
|
+
}
|
|
207925
|
+
async function runDeviceLogin(options = {}) {
|
|
207926
|
+
const target = loginTarget();
|
|
207927
|
+
if (!await shouldContinueLogin(target, options))
|
|
207928
|
+
return;
|
|
207929
|
+
let start;
|
|
207930
|
+
try {
|
|
207931
|
+
start = await httpAt(target, "/api/cli/device", {
|
|
207932
|
+
method: "POST",
|
|
207933
|
+
body: { clientName: clientName() }
|
|
207934
|
+
});
|
|
207935
|
+
} catch (err) {
|
|
207936
|
+
error(err instanceof Error ? err.message : String(err));
|
|
207937
|
+
process.exit(1);
|
|
207938
|
+
}
|
|
207939
|
+
info(`First copy your one-time code: ${start.userCode}`);
|
|
207940
|
+
const readline = process.stdin.isTTY ? createInterface({ input: process.stdin }) : null;
|
|
207941
|
+
if (readline) {
|
|
207942
|
+
info("Press Enter to open secondlayer.tools in your browser...");
|
|
207943
|
+
readline.once("line", () => openBrowser(start.verificationUrl));
|
|
207944
|
+
}
|
|
207945
|
+
info(dim(`Or open ${start.verificationUrl}`));
|
|
207946
|
+
info(dim("Waiting for you to approve in the browser..."));
|
|
207947
|
+
let outcome;
|
|
207948
|
+
try {
|
|
207949
|
+
outcome = await pollDeviceToken(start, {
|
|
207950
|
+
post: (path, body) => httpAt(target, path, { method: "POST", body }),
|
|
207951
|
+
sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
207952
|
+
now: Date.now
|
|
207953
|
+
});
|
|
207954
|
+
} catch (err) {
|
|
207955
|
+
readline?.close();
|
|
207956
|
+
error(err instanceof Error ? err.message : String(err));
|
|
207957
|
+
process.exit(1);
|
|
207958
|
+
}
|
|
207959
|
+
readline?.close();
|
|
207960
|
+
if (outcome.kind === "denied") {
|
|
207961
|
+
error("Cancelled in the browser.");
|
|
207962
|
+
process.exit(1);
|
|
207963
|
+
}
|
|
207964
|
+
if (outcome.kind === "expired") {
|
|
207965
|
+
error("The code expired. Run secondlayer login again.");
|
|
207966
|
+
process.exit(1);
|
|
207967
|
+
}
|
|
207968
|
+
await writeSession({
|
|
207969
|
+
token: outcome.sessionToken,
|
|
207970
|
+
email: outcome.account.email,
|
|
207971
|
+
accountId: outcome.account.id,
|
|
207972
|
+
expiresAt: sessionExpiry()
|
|
207973
|
+
}, target);
|
|
207974
|
+
success(`Logged in as ${outcome.account.email} on ${clientName()}`);
|
|
207975
|
+
info(dim("Run 'secondlayer logout' to sign this machine out."));
|
|
207976
|
+
}
|
|
207977
|
+
async function runLoginFlow(options = {}) {
|
|
207978
|
+
const target = loginTarget();
|
|
207979
|
+
if (!await shouldContinueLogin(target, options))
|
|
207980
|
+
return;
|
|
207866
207981
|
const email = await input({
|
|
207867
207982
|
message: "Email",
|
|
207868
207983
|
validate: (v) => /^.+@.+\..+$/.test(v) ? true : "Invalid email"
|
|
@@ -207900,7 +208015,7 @@ async function runLoginFlow(options = {}) {
|
|
|
207900
208015
|
expiresAt: sessionExpiry()
|
|
207901
208016
|
}, target);
|
|
207902
208017
|
success(`Logged in as ${verified.account.email}`);
|
|
207903
|
-
info(dim(
|
|
208018
|
+
info(dim("Run 'secondlayer whoami' to see your account status."));
|
|
207904
208019
|
} catch (err) {
|
|
207905
208020
|
if (err instanceof CliHttpError) {
|
|
207906
208021
|
error(err.message);
|
|
@@ -207911,17 +208026,18 @@ async function runLoginFlow(options = {}) {
|
|
|
207911
208026
|
}
|
|
207912
208027
|
}
|
|
207913
208028
|
function registerLoginCommand(program) {
|
|
207914
|
-
program.command("login").description("Log in to Secondlayer
|
|
208029
|
+
program.command("login").description("Log in to Secondlayer through your browser").option("--force", "Skip the already-logged-in check and re-run the flow").option("--email", "Sign in with an emailed 6-digit code instead of a browser").option("--with-token", "Read an API key from stdin (non-interactive)").addHelpText("after", `
|
|
207915
208030
|
Examples:
|
|
207916
|
-
$ secondlayer login
|
|
207917
|
-
$
|
|
208031
|
+
$ secondlayer login
|
|
208032
|
+
$ secondlayer login --email
|
|
208033
|
+
$ echo "$SECONDLAYER_API_KEY" | secondlayer login --with-token`).action((opts) => opts.withToken ? runTokenLogin() : opts.email ? runLoginFlow({ force: opts.force }) : runDeviceLogin({ force: opts.force }));
|
|
207918
208034
|
}
|
|
207919
208035
|
// src/commands/logout.ts
|
|
207920
208036
|
init_http();
|
|
207921
208037
|
init_output();
|
|
207922
208038
|
init_session();
|
|
207923
|
-
async function runLogout(
|
|
207924
|
-
const target = loginTarget(
|
|
208039
|
+
async function runLogout() {
|
|
208040
|
+
const target = loginTarget();
|
|
207925
208041
|
const session = await readSession(target);
|
|
207926
208042
|
if (!session) {
|
|
207927
208043
|
info(`Not logged in (${target}).`);
|
|
@@ -207941,7 +208057,7 @@ async function runLogout(opts = {}) {
|
|
|
207941
208057
|
success(`Logged out (${target}).`);
|
|
207942
208058
|
}
|
|
207943
208059
|
function registerLogoutCommand(program) {
|
|
207944
|
-
program.command("logout").description("
|
|
208060
|
+
program.command("logout").description("Sign this machine out of your Secondlayer account").action(() => runLogout());
|
|
207945
208061
|
}
|
|
207946
208062
|
// src/commands/backup.ts
|
|
207947
208063
|
init_fs();
|
|
@@ -208355,7 +208471,7 @@ function renderInstanceEnv(env) {
|
|
|
208355
208471
|
`SECONDLAYER_API_URL=${env.SECONDLAYER_API_URL}`
|
|
208356
208472
|
];
|
|
208357
208473
|
if (env.SECONDLAYER_API_KEY) {
|
|
208358
|
-
lines.push("# Hosted account key for archive
|
|
208474
|
+
lines.push("# Hosted account key for credits and archive fetches (sk-sl_*).", `SECONDLAYER_API_KEY=${env.SECONDLAYER_API_KEY}`);
|
|
208359
208475
|
}
|
|
208360
208476
|
lines.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(env.ARCHIVE_SIGNING_PUBLIC_KEY)}`, "");
|
|
208361
208477
|
return lines.join(`
|
|
@@ -208543,5 +208659,5 @@ registerCreditsCommand(program);
|
|
|
208543
208659
|
registerArchiveCommand(program);
|
|
208544
208660
|
program.parse();
|
|
208545
208661
|
|
|
208546
|
-
//# debugId=
|
|
208662
|
+
//# debugId=32DC3B7BA2B6602D64756E2164756E21
|
|
208547
208663
|
//# sourceMappingURL=cli.js.map
|