@secondlayer/cli 21.2.0 → 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 CHANGED
@@ -202692,7 +202692,7 @@ var {
202692
202692
  // package.json
202693
202693
  var package_default = {
202694
202694
  name: "@secondlayer/cli",
202695
- version: "21.2.0",
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.1.0",
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",
@@ -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
  }
@@ -207838,31 +207861,127 @@ async function runTokenLogin(opts) {
207838
207861
  }, target);
207839
207862
  success(`Logged in as ${account.email} (${target})`);
207840
207863
  }
207841
- async function runLoginFlow(options = {}) {
207842
- const target = loginTarget(options);
207843
- if (!options.force) {
207844
- const existing = await readSession(target);
207845
- if (existing) {
207846
- info(`Already logged in as ${existing.email} (${target}).`);
207847
- if (!process.stdin.isTTY) {
207848
- info(dim("Run 'secondlayer logout' first, or re-run with --force to switch accounts."));
207849
- return;
207850
- }
207851
- try {
207852
- const proceed = await confirm3({
207853
- message: "Log in as a different user?",
207854
- default: false
207855
- });
207856
- if (!proceed) {
207857
- info(dim("Run 'secondlayer logout' to sign out."));
207858
- return;
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;
207859
207904
  }
207860
- } catch {
207861
- info(dim("Run 'secondlayer logout' first, or re-run with --force to switch accounts."));
207862
- return;
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;
207863
207914
  }
207915
+ if (++failures >= 3)
207916
+ throw err;
207864
207917
  }
207865
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;
207866
207985
  const email = await input({
207867
207986
  message: "Email",
207868
207987
  validate: (v) => /^.+@.+\..+$/.test(v) ? true : "Invalid email"
@@ -207911,10 +208030,12 @@ async function runLoginFlow(options = {}) {
207911
208030
  }
207912
208031
  }
207913
208032
  function registerLoginCommand(program) {
207914
- program.command("login").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", `
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", `
207915
208034
  Examples:
208035
+ $ secondlayer login
208036
+ $ secondlayer login --email
207916
208037
  $ secondlayer login --credits
207917
- $ 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 }));
207918
208039
  }
207919
208040
  // src/commands/logout.ts
207920
208041
  init_http();
@@ -208543,5 +208664,5 @@ registerCreditsCommand(program);
208543
208664
  registerArchiveCommand(program);
208544
208665
  program.parse();
208545
208666
 
208546
- //# debugId=FA7A9048A9D4FA1264756E2164756E21
208667
+ //# debugId=9552E9D4123BD8E564756E2164756E21
208547
208668
  //# sourceMappingURL=cli.js.map