@secondlayer/cli 21.3.0 → 22.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/cli.js +69 -43
- package/dist/cli.js.map +13 -13
- package/package.json +1 -1
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();
|
|
@@ -2567,12 +2567,27 @@ function nextStepHint(status) {
|
|
|
2567
2567
|
return "Server error — retry shortly, or check `secondlayer status`.";
|
|
2568
2568
|
return;
|
|
2569
2569
|
}
|
|
2570
|
+
function authFailureMessage(code, status, ossMode) {
|
|
2571
|
+
if (code === "KEY_REVOKED") {
|
|
2572
|
+
return {
|
|
2573
|
+
message: "Your saved login was revoked.",
|
|
2574
|
+
hint: "Run `secondlayer login` to sign in again. If you set SECONDLAYER_API_KEY, replace it with a key from /account/keys."
|
|
2575
|
+
};
|
|
2576
|
+
}
|
|
2577
|
+
if (code === "SESSION_EXPIRED" || status === 401) {
|
|
2578
|
+
return {
|
|
2579
|
+
message: "Authentication required.",
|
|
2580
|
+
hint: ossMode ? "Set INSTANCE_TOKEN from `secondlayer init`." : "Run `secondlayer login` to re-authenticate."
|
|
2581
|
+
};
|
|
2582
|
+
}
|
|
2583
|
+
return null;
|
|
2584
|
+
}
|
|
2570
2585
|
function handleApiError(err, action) {
|
|
2571
2586
|
const status = err instanceof ApiError || err instanceof CliHttpError ? err.status : undefined;
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
});
|
|
2587
|
+
const code = err instanceof ApiError || err instanceof CliHttpError ? err.code : undefined;
|
|
2588
|
+
const auth = authFailureMessage(code, status, isOssMode());
|
|
2589
|
+
if (auth) {
|
|
2590
|
+
printError(auth.message, { hint: auth.hint });
|
|
2576
2591
|
process.exit(1);
|
|
2577
2592
|
}
|
|
2578
2593
|
const detail = err instanceof Error ? err.message : String(err);
|
|
@@ -16181,7 +16196,11 @@ class StacksApiClient {
|
|
|
16181
16196
|
return response.body;
|
|
16182
16197
|
} catch (error) {
|
|
16183
16198
|
const statusCode = typeof error === "object" && error !== null && "response" in error && typeof error.response === "object" && error.response !== null && "statusCode" in error.response ? error.response.statusCode : undefined;
|
|
16199
|
+
const responseBody = typeof error === "object" && error !== null && "response" in error && typeof error.response === "object" && error.response !== null && "body" in error.response ? error.response.body : undefined;
|
|
16184
16200
|
const message = error instanceof Error ? error.message : String(error);
|
|
16201
|
+
if (statusCode === 401 && typeof responseBody === "object" && responseBody !== null && "code" in responseBody && responseBody.code === "KEY_REVOKED") {
|
|
16202
|
+
throw new Error("Your saved login was revoked. Run `secondlayer login` to sign in again.");
|
|
16203
|
+
}
|
|
16185
16204
|
if (statusCode === 401) {
|
|
16186
16205
|
throw new Error(isOssMode() ? "Authentication required. Set INSTANCE_TOKEN from `secondlayer init`." : "Authentication required. Run: secondlayer login");
|
|
16187
16206
|
}
|
|
@@ -202692,7 +202711,7 @@ var {
|
|
|
202692
202711
|
// package.json
|
|
202693
202712
|
var package_default = {
|
|
202694
202713
|
name: "@secondlayer/cli",
|
|
202695
|
-
version: "
|
|
202714
|
+
version: "22.0.1",
|
|
202696
202715
|
description: "Run a Stacks index on your own hardware — set it up, restore verified history, and keep it current.",
|
|
202697
202716
|
repository: {
|
|
202698
202717
|
type: "git",
|
|
@@ -202811,7 +202830,7 @@ var BUY_CREDITS_COMMAND = "secondlayer credits buy";
|
|
|
202811
202830
|
function formatInsufficientMessage(quote) {
|
|
202812
202831
|
const shortfallUsd = ((quote.usdMicros - quote.balanceUsdMicros) / 1e6).toFixed(2);
|
|
202813
202832
|
const balanceUsd = (quote.balanceUsdMicros / 1e6).toFixed(2);
|
|
202814
|
-
return `Insufficient
|
|
202833
|
+
return `Insufficient credits: quote $${quote.usd}, balance $${balanceUsd}, short $${shortfallUsd}. Buy more with \`${BUY_CREDITS_COMMAND}\`.`;
|
|
202815
202834
|
}
|
|
202816
202835
|
var ARCHIVE_GATE_NOT_CONFIGURED_MESSAGE = "the archive gate is not configured on the server; contact the operator";
|
|
202817
202836
|
var ALLOWANCE_MONTHLY_BUNDLES = 6;
|
|
@@ -204986,7 +205005,7 @@ async function runBuy(opts) {
|
|
|
204986
205005
|
human: () => {
|
|
204987
205006
|
success(`Checkout for $${amount} → ${email}`);
|
|
204988
205007
|
console.log(res.url);
|
|
204989
|
-
console.log(dim("After payment: set SECONDLAYER_API_KEY (or run `secondlayer login
|
|
205008
|
+
console.log(dim("After payment: set SECONDLAYER_API_KEY (or run `secondlayer login`), then run `secondlayer credits balance`"));
|
|
204990
205009
|
}
|
|
204991
205010
|
});
|
|
204992
205011
|
}
|
|
@@ -205061,8 +205080,8 @@ async function runUsage(opts) {
|
|
|
205061
205080
|
});
|
|
205062
205081
|
}
|
|
205063
205082
|
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
|
|
205083
|
+
const credits = program.command("credits").description("Buy and check your credits");
|
|
205084
|
+
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
205085
|
credits.command("balance").description("Show prepaid archive credit balance").option("--json", "Output as JSON").action((opts) => runBalance(opts));
|
|
205067
205086
|
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
205087
|
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));
|
|
@@ -207836,16 +207855,16 @@ async function readStdin() {
|
|
|
207836
207855
|
chunks.push(chunk);
|
|
207837
207856
|
return Buffer.concat(chunks).toString("utf8").trim();
|
|
207838
207857
|
}
|
|
207839
|
-
function loginTarget(
|
|
207840
|
-
return
|
|
207858
|
+
function loginTarget() {
|
|
207859
|
+
return resolveArchiveOpsUrl();
|
|
207841
207860
|
}
|
|
207842
|
-
async function runTokenLogin(
|
|
207861
|
+
async function runTokenLogin() {
|
|
207843
207862
|
const token = await readStdin();
|
|
207844
207863
|
if (!token) {
|
|
207845
207864
|
error('No token on stdin. Usage: echo "$KEY" | secondlayer login --with-token');
|
|
207846
207865
|
process.exit(1);
|
|
207847
207866
|
}
|
|
207848
|
-
const target = loginTarget(
|
|
207867
|
+
const target = loginTarget();
|
|
207849
207868
|
let account;
|
|
207850
207869
|
try {
|
|
207851
207870
|
account = await httpAt(target, "/api/accounts/me", { bearer: token });
|
|
@@ -207861,12 +207880,24 @@ async function runTokenLogin(opts) {
|
|
|
207861
207880
|
}, target);
|
|
207862
207881
|
success(`Logged in as ${account.email} (${target})`);
|
|
207863
207882
|
}
|
|
207864
|
-
|
|
207883
|
+
var defaultLoginCheckDeps = {
|
|
207884
|
+
readSession,
|
|
207885
|
+
verify: (target, token) => httpAt(target, "/api/accounts/me", { bearer: token })
|
|
207886
|
+
};
|
|
207887
|
+
async function shouldContinueLogin(target, options, deps = defaultLoginCheckDeps) {
|
|
207865
207888
|
if (options.force)
|
|
207866
207889
|
return true;
|
|
207867
|
-
const existing = await readSession(target);
|
|
207890
|
+
const existing = await deps.readSession(target);
|
|
207868
207891
|
if (!existing)
|
|
207869
207892
|
return true;
|
|
207893
|
+
try {
|
|
207894
|
+
await deps.verify(target, existing.token);
|
|
207895
|
+
} catch (err) {
|
|
207896
|
+
if (err instanceof CliHttpError && (err.status === 401 || err.status === 403)) {
|
|
207897
|
+
info(`Your saved login for ${existing.email} stopped working (revoked or expired). Signing in again.`);
|
|
207898
|
+
return true;
|
|
207899
|
+
}
|
|
207900
|
+
}
|
|
207870
207901
|
info(`Already logged in as ${existing.email} (${target}).`);
|
|
207871
207902
|
const hint = "Run 'secondlayer logout' first, or re-run with --force to switch accounts.";
|
|
207872
207903
|
if (!process.stdin.isTTY) {
|
|
@@ -207895,7 +207926,11 @@ async function pollDeviceToken(start, deps) {
|
|
|
207895
207926
|
await deps.sleep(start.interval * 1000);
|
|
207896
207927
|
try {
|
|
207897
207928
|
const res = await deps.post("/api/cli/device/token", { deviceCode: start.deviceCode });
|
|
207898
|
-
return {
|
|
207929
|
+
return {
|
|
207930
|
+
kind: "approved",
|
|
207931
|
+
sessionToken: res.sessionToken,
|
|
207932
|
+
account: res.account
|
|
207933
|
+
};
|
|
207899
207934
|
} catch (err) {
|
|
207900
207935
|
if (err instanceof CliHttpError) {
|
|
207901
207936
|
if (err.status === 428) {
|
|
@@ -207905,9 +207940,6 @@ async function pollDeviceToken(start, deps) {
|
|
|
207905
207940
|
if (err.status === 403 && err.body.error === "access_denied") {
|
|
207906
207941
|
return { kind: "denied" };
|
|
207907
207942
|
}
|
|
207908
|
-
if (err.status === 409 && err.body.error === "key_limit") {
|
|
207909
|
-
return { kind: "key_limit" };
|
|
207910
|
-
}
|
|
207911
207943
|
if (err.status === 410)
|
|
207912
207944
|
return { kind: "expired" };
|
|
207913
207945
|
throw err;
|
|
@@ -207918,12 +207950,11 @@ async function pollDeviceToken(start, deps) {
|
|
|
207918
207950
|
}
|
|
207919
207951
|
return { kind: "expired" };
|
|
207920
207952
|
}
|
|
207921
|
-
var KEY_LIMIT_MESSAGE = "You have 50 keys. Revoke one under Keys, then run secondlayer login again.";
|
|
207922
207953
|
function clientName() {
|
|
207923
207954
|
return hostname().trim().slice(0, 64) || "this machine";
|
|
207924
207955
|
}
|
|
207925
207956
|
async function runDeviceLogin(options = {}) {
|
|
207926
|
-
const target = loginTarget(
|
|
207957
|
+
const target = loginTarget();
|
|
207927
207958
|
if (!await shouldContinueLogin(target, options))
|
|
207928
207959
|
return;
|
|
207929
207960
|
let start;
|
|
@@ -207936,10 +207967,10 @@ async function runDeviceLogin(options = {}) {
|
|
|
207936
207967
|
error(err instanceof Error ? err.message : String(err));
|
|
207937
207968
|
process.exit(1);
|
|
207938
207969
|
}
|
|
207939
|
-
info(`
|
|
207970
|
+
info(`Your one-time code: ${start.userCode}`);
|
|
207940
207971
|
const readline = process.stdin.isTTY ? createInterface({ input: process.stdin }) : null;
|
|
207941
207972
|
if (readline) {
|
|
207942
|
-
info("Press Enter to open
|
|
207973
|
+
info("Press Enter to open the sign-in page, then check the code matches.");
|
|
207943
207974
|
readline.once("line", () => openBrowser(start.verificationUrl));
|
|
207944
207975
|
}
|
|
207945
207976
|
info(dim(`Or open ${start.verificationUrl}`));
|
|
@@ -207961,25 +207992,21 @@ async function runDeviceLogin(options = {}) {
|
|
|
207961
207992
|
error("Cancelled in the browser.");
|
|
207962
207993
|
process.exit(1);
|
|
207963
207994
|
}
|
|
207964
|
-
if (outcome.kind === "key_limit") {
|
|
207965
|
-
error(KEY_LIMIT_MESSAGE);
|
|
207966
|
-
process.exit(1);
|
|
207967
|
-
}
|
|
207968
207995
|
if (outcome.kind === "expired") {
|
|
207969
207996
|
error("The code expired. Run secondlayer login again.");
|
|
207970
207997
|
process.exit(1);
|
|
207971
207998
|
}
|
|
207972
207999
|
await writeSession({
|
|
207973
|
-
token: outcome.
|
|
208000
|
+
token: outcome.sessionToken,
|
|
207974
208001
|
email: outcome.account.email,
|
|
207975
208002
|
accountId: outcome.account.id,
|
|
207976
208003
|
expiresAt: sessionExpiry()
|
|
207977
208004
|
}, target);
|
|
207978
208005
|
success(`Logged in as ${outcome.account.email} on ${clientName()}`);
|
|
207979
|
-
info(dim("
|
|
208006
|
+
info(dim("Run 'secondlayer logout' to sign this machine out."));
|
|
207980
208007
|
}
|
|
207981
208008
|
async function runLoginFlow(options = {}) {
|
|
207982
|
-
const target = loginTarget(
|
|
208009
|
+
const target = loginTarget();
|
|
207983
208010
|
if (!await shouldContinueLogin(target, options))
|
|
207984
208011
|
return;
|
|
207985
208012
|
const email = await input({
|
|
@@ -208019,7 +208046,7 @@ async function runLoginFlow(options = {}) {
|
|
|
208019
208046
|
expiresAt: sessionExpiry()
|
|
208020
208047
|
}, target);
|
|
208021
208048
|
success(`Logged in as ${verified.account.email}`);
|
|
208022
|
-
info(dim(
|
|
208049
|
+
info(dim("Run 'secondlayer whoami' to see your account status."));
|
|
208023
208050
|
} catch (err) {
|
|
208024
208051
|
if (err instanceof CliHttpError) {
|
|
208025
208052
|
error(err.message);
|
|
@@ -208030,19 +208057,18 @@ async function runLoginFlow(options = {}) {
|
|
|
208030
208057
|
}
|
|
208031
208058
|
}
|
|
208032
208059
|
function registerLoginCommand(program) {
|
|
208033
|
-
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)").
|
|
208060
|
+
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", `
|
|
208034
208061
|
Examples:
|
|
208035
208062
|
$ secondlayer login
|
|
208036
208063
|
$ secondlayer login --email
|
|
208037
|
-
$ secondlayer login --
|
|
208038
|
-
$ echo "$SECONDLAYER_API_KEY" | secondlayer login --with-token`).action((opts) => opts.withToken ? runTokenLogin({ credits: opts.credits }) : opts.email ? runLoginFlow({ force: opts.force, credits: opts.credits }) : runDeviceLogin({ force: opts.force, credits: opts.credits }));
|
|
208064
|
+
$ echo "$SECONDLAYER_API_KEY" | secondlayer login --with-token`).action((opts) => opts.withToken ? runTokenLogin() : opts.email ? runLoginFlow({ force: opts.force }) : runDeviceLogin({ force: opts.force }));
|
|
208039
208065
|
}
|
|
208040
208066
|
// src/commands/logout.ts
|
|
208041
208067
|
init_http();
|
|
208042
208068
|
init_output();
|
|
208043
208069
|
init_session();
|
|
208044
|
-
async function runLogout(
|
|
208045
|
-
const target = loginTarget(
|
|
208070
|
+
async function runLogout() {
|
|
208071
|
+
const target = loginTarget();
|
|
208046
208072
|
const session = await readSession(target);
|
|
208047
208073
|
if (!session) {
|
|
208048
208074
|
info(`Not logged in (${target}).`);
|
|
@@ -208062,7 +208088,7 @@ async function runLogout(opts = {}) {
|
|
|
208062
208088
|
success(`Logged out (${target}).`);
|
|
208063
208089
|
}
|
|
208064
208090
|
function registerLogoutCommand(program) {
|
|
208065
|
-
program.command("logout").description("
|
|
208091
|
+
program.command("logout").description("Sign this machine out of your Secondlayer account").action(() => runLogout());
|
|
208066
208092
|
}
|
|
208067
208093
|
// src/commands/backup.ts
|
|
208068
208094
|
init_fs();
|
|
@@ -208476,7 +208502,7 @@ function renderInstanceEnv(env) {
|
|
|
208476
208502
|
`SECONDLAYER_API_URL=${env.SECONDLAYER_API_URL}`
|
|
208477
208503
|
];
|
|
208478
208504
|
if (env.SECONDLAYER_API_KEY) {
|
|
208479
|
-
lines.push("# Hosted account key for archive
|
|
208505
|
+
lines.push("# Hosted account key for credits and archive fetches (sk-sl_*).", `SECONDLAYER_API_KEY=${env.SECONDLAYER_API_KEY}`);
|
|
208480
208506
|
}
|
|
208481
208507
|
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)}`, "");
|
|
208482
208508
|
return lines.join(`
|
|
@@ -208664,5 +208690,5 @@ registerCreditsCommand(program);
|
|
|
208664
208690
|
registerArchiveCommand(program);
|
|
208665
208691
|
program.parse();
|
|
208666
208692
|
|
|
208667
|
-
//# debugId=
|
|
208693
|
+
//# debugId=77FBD5CCBD0906B064756E2164756E21
|
|
208668
208694
|
//# sourceMappingURL=cli.js.map
|