@secondlayer/cli 21.1.1 → 21.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +216 -27
- package/dist/cli.js.map +6 -5
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -202692,7 +202692,7 @@ var {
|
|
|
202692
202692
|
// package.json
|
|
202693
202693
|
var package_default = {
|
|
202694
202694
|
name: "@secondlayer/cli",
|
|
202695
|
-
version: "21.
|
|
202695
|
+
version: "21.3.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",
|
|
@@ -204926,6 +204926,35 @@ function registerArchiveCommand(program) {
|
|
|
204926
204926
|
init_http();
|
|
204927
204927
|
init_output();
|
|
204928
204928
|
var PACKS = [10, 25, 50, 100];
|
|
204929
|
+
var DAY_MS = 24 * 60 * 60 * 1000;
|
|
204930
|
+
function rowsUsageDays(daily, days, now) {
|
|
204931
|
+
const byDate = new Map;
|
|
204932
|
+
for (const d of daily) {
|
|
204933
|
+
const day = byDate.get(d.date) ?? {
|
|
204934
|
+
date: d.date,
|
|
204935
|
+
rows: 0,
|
|
204936
|
+
free: 0,
|
|
204937
|
+
paid: 0
|
|
204938
|
+
};
|
|
204939
|
+
day.rows += d.rows;
|
|
204940
|
+
day.free += d.freeRows;
|
|
204941
|
+
day.paid += d.paidRows;
|
|
204942
|
+
byDate.set(d.date, day);
|
|
204943
|
+
}
|
|
204944
|
+
const today = Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate());
|
|
204945
|
+
return Array.from({ length: days }, (_, i) => {
|
|
204946
|
+
const date = new Date(today - (days - 1 - i) * DAY_MS).toISOString().slice(0, 10);
|
|
204947
|
+
return byDate.get(date) ?? { date, rows: 0, free: 0, paid: 0 };
|
|
204948
|
+
});
|
|
204949
|
+
}
|
|
204950
|
+
function parseDays(raw) {
|
|
204951
|
+
const n = Number(raw);
|
|
204952
|
+
if (!Number.isInteger(n) || n < 1 || n > 30) {
|
|
204953
|
+
error("--days must be a whole number from 1 to 30");
|
|
204954
|
+
process.exit(1);
|
|
204955
|
+
}
|
|
204956
|
+
return n;
|
|
204957
|
+
}
|
|
204929
204958
|
function parsePack(raw) {
|
|
204930
204959
|
const n = Number(raw);
|
|
204931
204960
|
if (!PACKS.includes(n)) {
|
|
@@ -204993,10 +205022,49 @@ async function runBalance(opts) {
|
|
|
204993
205022
|
}
|
|
204994
205023
|
});
|
|
204995
205024
|
}
|
|
205025
|
+
async function runUsage(opts) {
|
|
205026
|
+
const days = parseDays(opts.days);
|
|
205027
|
+
let res;
|
|
205028
|
+
try {
|
|
205029
|
+
res = await httpArchiveOps("/api/billing/rows");
|
|
205030
|
+
} catch (err) {
|
|
205031
|
+
if (err instanceof CliHttpError) {
|
|
205032
|
+
error(err.message);
|
|
205033
|
+
process.exit(1);
|
|
205034
|
+
}
|
|
205035
|
+
throw err;
|
|
205036
|
+
}
|
|
205037
|
+
output({
|
|
205038
|
+
json: opts.json,
|
|
205039
|
+
data: res,
|
|
205040
|
+
human: () => {
|
|
205041
|
+
const n = (v) => v.toLocaleString("en-US");
|
|
205042
|
+
const lines = rowsUsageDays(res.daily, days, new Date);
|
|
205043
|
+
console.log(formatTable(["Date", "Rows", "Free", "Paid"], lines.map((d) => [d.date, n(d.rows), n(d.free), n(d.paid)])));
|
|
205044
|
+
console.log();
|
|
205045
|
+
const { allowance } = res;
|
|
205046
|
+
const pairs = [
|
|
205047
|
+
[
|
|
205048
|
+
"This month",
|
|
205049
|
+
`${n(allowance.usedThisMonth)} rows, ${n(allowance.monthly)} free${allowance.exhaustedOn ? ` (used up ${allowance.exhaustedOn})` : ""}`
|
|
205050
|
+
]
|
|
205051
|
+
];
|
|
205052
|
+
for (const source of res.bySource) {
|
|
205053
|
+
const name = source.source === "streams" ? "Streams" : "Index";
|
|
205054
|
+
pairs.push([
|
|
205055
|
+
name,
|
|
205056
|
+
`${n(source.rows)} rows, ${n(source.calls)} calls, 30 days`
|
|
205057
|
+
]);
|
|
205058
|
+
}
|
|
205059
|
+
console.log(formatKeyValue(pairs));
|
|
205060
|
+
}
|
|
205061
|
+
});
|
|
205062
|
+
}
|
|
204996
205063
|
function registerCreditsCommand(program) {
|
|
204997
205064
|
const credits = program.command("credits").description("Buy and check archive credits");
|
|
204998
205065
|
credits.command("buy").description("Open a one-time Stripe checkout for archive 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));
|
|
204999
205066
|
credits.command("balance").description("Show prepaid archive credit balance").option("--json", "Output as JSON").action((opts) => runBalance(opts));
|
|
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));
|
|
205000
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));
|
|
205001
205069
|
}
|
|
205002
205070
|
async function runRefill(opts) {
|
|
@@ -207733,9 +207801,32 @@ function registerWhoamiCommand(program) {
|
|
|
207733
207801
|
// src/commands/login.ts
|
|
207734
207802
|
init_api_url();
|
|
207735
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
|
|
207736
207828
|
init_output();
|
|
207737
207829
|
init_session();
|
|
207738
|
-
import { confirm as confirm3, input } from "@inquirer/prompts";
|
|
207739
207830
|
function sessionExpiry() {
|
|
207740
207831
|
return new Date(Date.now() + 90 * 24 * 60 * 60 * 1000).toISOString();
|
|
207741
207832
|
}
|
|
@@ -207770,31 +207861,127 @@ async function runTokenLogin(opts) {
|
|
|
207770
207861
|
}, target);
|
|
207771
207862
|
success(`Logged in as ${account.email} (${target})`);
|
|
207772
207863
|
}
|
|
207773
|
-
async function
|
|
207774
|
-
|
|
207775
|
-
|
|
207776
|
-
|
|
207777
|
-
|
|
207778
|
-
|
|
207779
|
-
|
|
207780
|
-
|
|
207781
|
-
|
|
207782
|
-
|
|
207783
|
-
|
|
207784
|
-
|
|
207785
|
-
|
|
207786
|
-
|
|
207787
|
-
|
|
207788
|
-
|
|
207789
|
-
|
|
207790
|
-
|
|
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 { kind: "approved", key: res.key, account: res.account };
|
|
207899
|
+
} catch (err) {
|
|
207900
|
+
if (err instanceof CliHttpError) {
|
|
207901
|
+
if (err.status === 428) {
|
|
207902
|
+
failures = 0;
|
|
207903
|
+
continue;
|
|
207791
207904
|
}
|
|
207792
|
-
|
|
207793
|
-
|
|
207794
|
-
|
|
207905
|
+
if (err.status === 403 && err.body.error === "access_denied") {
|
|
207906
|
+
return { kind: "denied" };
|
|
207907
|
+
}
|
|
207908
|
+
if (err.status === 409 && err.body.error === "key_limit") {
|
|
207909
|
+
return { kind: "key_limit" };
|
|
207910
|
+
}
|
|
207911
|
+
if (err.status === 410)
|
|
207912
|
+
return { kind: "expired" };
|
|
207913
|
+
throw err;
|
|
207795
207914
|
}
|
|
207915
|
+
if (++failures >= 3)
|
|
207916
|
+
throw err;
|
|
207796
207917
|
}
|
|
207797
207918
|
}
|
|
207919
|
+
return { kind: "expired" };
|
|
207920
|
+
}
|
|
207921
|
+
var KEY_LIMIT_MESSAGE = "You have 50 keys. Revoke one under Keys, then run secondlayer login again.";
|
|
207922
|
+
function clientName() {
|
|
207923
|
+
return hostname().trim().slice(0, 64) || "this machine";
|
|
207924
|
+
}
|
|
207925
|
+
async function runDeviceLogin(options = {}) {
|
|
207926
|
+
const target = loginTarget(options);
|
|
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 === "key_limit") {
|
|
207965
|
+
error(KEY_LIMIT_MESSAGE);
|
|
207966
|
+
process.exit(1);
|
|
207967
|
+
}
|
|
207968
|
+
if (outcome.kind === "expired") {
|
|
207969
|
+
error("The code expired. Run secondlayer login again.");
|
|
207970
|
+
process.exit(1);
|
|
207971
|
+
}
|
|
207972
|
+
await writeSession({
|
|
207973
|
+
token: outcome.key,
|
|
207974
|
+
email: outcome.account.email,
|
|
207975
|
+
accountId: outcome.account.id,
|
|
207976
|
+
expiresAt: sessionExpiry()
|
|
207977
|
+
}, target);
|
|
207978
|
+
success(`Logged in as ${outcome.account.email} on ${clientName()}`);
|
|
207979
|
+
info(dim("This machine has its own key. Revoke it under Keys."));
|
|
207980
|
+
}
|
|
207981
|
+
async function runLoginFlow(options = {}) {
|
|
207982
|
+
const target = loginTarget(options);
|
|
207983
|
+
if (!await shouldContinueLogin(target, options))
|
|
207984
|
+
return;
|
|
207798
207985
|
const email = await input({
|
|
207799
207986
|
message: "Email",
|
|
207800
207987
|
validate: (v) => /^.+@.+\..+$/.test(v) ? true : "Invalid email"
|
|
@@ -207843,10 +208030,12 @@ async function runLoginFlow(options = {}) {
|
|
|
207843
208030
|
}
|
|
207844
208031
|
}
|
|
207845
208032
|
function registerLoginCommand(program) {
|
|
207846
|
-
program.command("login").description("Log in to Secondlayer
|
|
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)").option("--credits", "Log in to the archive credits account used by bootstrap, repair, and credits").addHelpText("after", `
|
|
207847
208034
|
Examples:
|
|
208035
|
+
$ secondlayer login
|
|
208036
|
+
$ secondlayer login --email
|
|
207848
208037
|
$ secondlayer login --credits
|
|
207849
|
-
$ echo "$SECONDLAYER_API_KEY" | secondlayer login --with-token`).action((opts) => opts.withToken ? runTokenLogin({ credits: opts.credits }) : runLoginFlow({ force: opts.force, credits: opts.credits }));
|
|
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 }));
|
|
207850
208039
|
}
|
|
207851
208040
|
// src/commands/logout.ts
|
|
207852
208041
|
init_http();
|
|
@@ -208475,5 +208664,5 @@ registerCreditsCommand(program);
|
|
|
208475
208664
|
registerArchiveCommand(program);
|
|
208476
208665
|
program.parse();
|
|
208477
208666
|
|
|
208478
|
-
//# debugId=
|
|
208667
|
+
//# debugId=9552E9D4123BD8E564756E2164756E21
|
|
208479
208668
|
//# sourceMappingURL=cli.js.map
|