@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 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. Hosted API and archive credits use
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 archive credits." : "";
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" }, `Archive credits need an account key. ${ARCHIVE_AUTH_HINT}${ignoredEnvKeyNote(ignoredEnvKey)}`);
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, `Archive credits rejected the credential (${err.message}). ${ARCHIVE_AUTH_HINT}${ignoredEnvKeyNote(ignoredEnvKey)}`);
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 --credits`.", ARCHIVE_CREDENTIAL_RE;
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
- if (err instanceof CliHttpError && err.code === "SESSION_EXPIRED" || status === 401) {
2573
- printError("Authentication required.", {
2574
- hint: isOssMode() ? "Set INSTANCE_TOKEN from `secondlayer init`." : "Run `secondlayer login` to re-authenticate."
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: "21.3.0",
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 archive credits: quote $${quote.usd}, balance $${balanceUsd}, short $${shortfallUsd}. Buy more with \`${BUY_CREDITS_COMMAND}\`.`;
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 --credits`), then run `secondlayer credits balance`"));
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 archive credits");
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));
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(opts) {
207840
- return opts.credits ? resolveArchiveOpsUrl() : resolveApiUrl();
207858
+ function loginTarget() {
207859
+ return resolveArchiveOpsUrl();
207841
207860
  }
207842
- async function runTokenLogin(opts) {
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(opts);
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
- async function shouldContinueLogin(target, options) {
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 { kind: "approved", key: res.key, account: res.account };
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(options);
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(`First copy your one-time code: ${start.userCode}`);
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 secondlayer.tools in your browser...");
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.key,
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("This machine has its own key. Revoke it under Keys."));
208006
+ info(dim("Run 'secondlayer logout' to sign this machine out."));
207980
208007
  }
207981
208008
  async function runLoginFlow(options = {}) {
207982
- const target = loginTarget(options);
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(options.credits ? "Run 'secondlayer credits balance' to see your archive credits." : "Run 'secondlayer whoami' to see your account status."));
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)").option("--credits", "Log in to the archive credits account used by bootstrap, repair, and credits").addHelpText("after", `
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 --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 }));
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(opts = {}) {
208045
- const target = loginTarget(opts);
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("Log out and revoke the local session").option("--credits", "Log out of the archive credits account used by bootstrap, repair, and credits").action((opts) => runLogout({ credits: opts.credits }));
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 credits (sk-sl_*).", `SECONDLAYER_API_KEY=${env.SECONDLAYER_API_KEY}`);
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=9552E9D4123BD8E564756E2164756E21
208693
+ //# debugId=77FBD5CCBD0906B064756E2164756E21
208668
208694
  //# sourceMappingURL=cli.js.map