@mtreeai/msapling-cli 2.3.6-beta.7 → 2.3.6-beta.8

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.
Files changed (2) hide show
  1. package/dist/index.js +72 -72
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -284,8 +284,19 @@ var init_src = __esm({
284
284
  }
285
285
  /**
286
286
  * Login with email and password.
287
- * Returns access_token on success, or indicates TOTP is required.
288
- * On 401: throws MSaplingError with code 'invalid_credentials'
287
+ *
288
+ * The JWT `mfa` claim is the *session* MFA state ("has this session completed
289
+ * TOTP yet"), not "TOTP is required". Treating `mfa:false` as TOTP-required
290
+ * was wrong: it blocked every user without 2FA enabled, including all
291
+ * guest/free accounts and the live backend which doesn't enforce TOTP yet.
292
+ *
293
+ * New contract: accept the token as fully usable. If the backend enforces
294
+ * MFA on a subsequent protected endpoint, that endpoint will reply with
295
+ * `mfa_required` and the caller can step up via verifyLoginTotp(). Until
296
+ * the backend exposes an explicit `mfa_required` field on the login
297
+ * response, we don't pre-emptively branch.
298
+ *
299
+ * On 401: throws MSaplingError with code 'invalid_credentials'.
289
300
  */
290
301
  async loginEmailPassword(email, password) {
291
302
  const params = new URLSearchParams();
@@ -305,10 +316,6 @@ var init_src = __esm({
305
316
  clearTimeout(timeout);
306
317
  if (response.ok) {
307
318
  const data = await response.json();
308
- const payload = this.decodeJwtPayload(data.access_token);
309
- if (payload && payload.mfa === false) {
310
- return { kind: "totp_required", partialToken: data.access_token };
311
- }
312
319
  return { kind: "success", token: data.access_token };
313
320
  }
314
321
  if (response.status === 401) {
@@ -330,6 +337,41 @@ var init_src = __esm({
330
337
  clearTimeout(timeout);
331
338
  }
332
339
  }
340
+ /**
341
+ * Sign in as a guest. Backend issues a guest-scoped JWT with limited tier
342
+ * capabilities. No prior credentials required.
343
+ */
344
+ async loginGuest() {
345
+ const controller = new AbortController();
346
+ const timeout = setTimeout(() => controller.abort(), 3e4);
347
+ try {
348
+ const response = await fetch(`${this.apiUrl}/auth/guest`, {
349
+ method: "POST",
350
+ headers: { "Content-Type": "application/json" },
351
+ body: "{}",
352
+ signal: controller.signal
353
+ });
354
+ clearTimeout(timeout);
355
+ if (response.ok) {
356
+ const data = await response.json();
357
+ return { kind: "success", token: data.access_token };
358
+ }
359
+ let detail = "Guest login failed";
360
+ try {
361
+ const err = await response.json();
362
+ detail = err.detail || detail;
363
+ } catch (e) {
364
+ }
365
+ throw new MSaplingError(detail, response.status, "guest_login_failed");
366
+ } catch (e) {
367
+ if (e.name === "AbortError") {
368
+ throw new MSaplingError("Request timed out after 30s.", 408, "timeout");
369
+ }
370
+ throw e;
371
+ } finally {
372
+ clearTimeout(timeout);
373
+ }
374
+ }
333
375
  /**
334
376
  * Verify TOTP code during login.
335
377
  * Requires a partial token from a prior /auth/login call when MFA is enabled.
@@ -339,7 +381,7 @@ var init_src = __esm({
339
381
  const controller = new AbortController();
340
382
  const timeout = setTimeout(() => controller.abort(), 3e4);
341
383
  try {
342
- const response = await fetch(`${this.apiUrl}/auth/login-verify`, {
384
+ const response = await fetch(`${this.apiUrl}/api/mfa/login-verify`, {
343
385
  method: "POST",
344
386
  headers: {
345
387
  "Authorization": `Bearer ${this.token}`,
@@ -7964,37 +8006,6 @@ async function promptPassword(prompt) {
7964
8006
  stdin.on("data", onData);
7965
8007
  });
7966
8008
  }
7967
- async function promptTotp(prompt) {
7968
- return new Promise((resolve18) => {
7969
- const stdin = process.stdin;
7970
- const stdout = process.stdout;
7971
- stdout.write(prompt);
7972
- const wasRaw = stdin.isRaw ?? false;
7973
- setRawModeGuarded(stdin, true);
7974
- let code = "";
7975
- const onData = (chunk) => {
7976
- const char = chunk.toString();
7977
- if (char === "\n" || char === "\r") {
7978
- stdin.setRawMode(wasRaw);
7979
- stdin.removeListener("data", onData);
7980
- stdout.write("\n");
7981
- resolve18(code);
7982
- } else if (char === "") {
7983
- setRawModeGuarded(stdin, wasRaw);
7984
- stdin.removeListener("data", onData);
7985
- stdout.write("\n");
7986
- resolve18("");
7987
- } else if (char === "\x7F" || char === "\b") {
7988
- code = code.slice(0, -1);
7989
- } else if (/^\d$/.test(char)) {
7990
- if (code.length < 6) {
7991
- code += char;
7992
- }
7993
- }
7994
- };
7995
- stdin.on("data", onData);
7996
- });
7997
- }
7998
8009
  async function loginWithEmailPassword(email, context) {
7999
8010
  context.addMessage("system", `Logging in as ${email}...`);
8000
8011
  let password = "";
@@ -8011,40 +8022,26 @@ async function loginWithEmailPassword(email, context) {
8011
8022
  try {
8012
8023
  const result = await context.client.loginEmailPassword(email, password);
8013
8024
  password = "";
8014
- if (result.kind === "totp_required") {
8015
- context.addMessage("system", "Two-factor authentication required.");
8016
- let totpCode = "";
8017
- try {
8018
- totpCode = await promptTotp("Enter your 6-digit code: ");
8019
- } catch (err) {
8020
- context.addMessage("error", `TOTP prompt failed: ${err}`);
8021
- return;
8022
- }
8023
- if (!totpCode || totpCode.length !== 6) {
8024
- context.addMessage("error", "Invalid TOTP code (must be 6 digits).");
8025
- return;
8026
- }
8027
- context.client.setToken(result.partialToken);
8028
- try {
8029
- const totpResult = await context.client.verifyLoginTotp(totpCode);
8030
- await context.storage.saveToken(totpResult.token);
8031
- context.client.setToken(totpResult.token);
8032
- await context.refreshOverview();
8033
- context.addMessage("system", `Logged in as ${email} (2FA verified).`);
8034
- } catch (err) {
8035
- context.client.setToken(null);
8036
- context.addMessage("error", `TOTP verification failed: ${err}`);
8037
- }
8038
- } else if (result.kind === "success") {
8039
- await context.storage.saveToken(result.token);
8040
- context.client.setToken(result.token);
8041
- await context.refreshOverview();
8042
- context.addMessage("system", `Logged in as ${email}.`);
8043
- }
8025
+ await context.storage.saveToken(result.token);
8026
+ context.client.setToken(result.token);
8027
+ await context.refreshOverview();
8028
+ context.addMessage("system", `Logged in as ${email}.`);
8044
8029
  } catch (err) {
8045
8030
  context.addMessage("error", `Login failed: ${err}`);
8046
8031
  }
8047
8032
  }
8033
+ async function loginAsGuest(context) {
8034
+ context.addMessage("system", "Signing in as guest...");
8035
+ try {
8036
+ const result = await context.client.loginGuest();
8037
+ await context.storage.saveToken(result.token);
8038
+ context.client.setToken(result.token);
8039
+ await context.refreshOverview();
8040
+ context.addMessage("system", "Signed in as guest (limited tier).");
8041
+ } catch (err) {
8042
+ context.addMessage("error", `Guest login failed: ${err}`);
8043
+ }
8044
+ }
8048
8045
  async function loginWithGithubDevice(context) {
8049
8046
  context.addMessage("system", "Starting GitHub device flow...");
8050
8047
  let deviceResp;
@@ -8139,8 +8136,8 @@ var init_login = __esm({
8139
8136
  GITHUB_TOKEN_URL = "https://github.com/login/oauth/access_token";
8140
8137
  loginCommand = {
8141
8138
  name: "login",
8142
- args: "[email|github|token]",
8143
- description: "Authenticate: /login your@email.com | /login github | /login <token>",
8139
+ args: "[email|github|guest|token]",
8140
+ description: "Authenticate: /login your@email.com | /login github | /login guest | /login <token>",
8144
8141
  category: "auth",
8145
8142
  handler: async (args2, context) => {
8146
8143
  const arg = args2[0];
@@ -8150,12 +8147,15 @@ var init_login = __esm({
8150
8147
  `Usage:
8151
8148
  /login your@email.com Sign in with email + password
8152
8149
  /login github GitHub device flow
8150
+ /login guest Anonymous guest session (limited tier)
8153
8151
  /login <token> Paste an API token from msapling.com \u2192 Settings \u2192 API Keys`
8154
8152
  );
8155
8153
  return;
8156
8154
  }
8157
8155
  if (arg === "github") {
8158
8156
  await loginWithGithubDevice(context);
8157
+ } else if (arg === "guest") {
8158
+ await loginAsGuest(context);
8159
8159
  } else if (isEmailAddress(arg)) {
8160
8160
  await loginWithEmailPassword(arg, context);
8161
8161
  } else if (isTokenLike(arg)) {
@@ -8166,7 +8166,7 @@ var init_login = __esm({
8166
8166
  } else {
8167
8167
  context.addMessage(
8168
8168
  "error",
8169
- `Unrecognized argument: "${arg}". Expected email, 'github', or token string.`
8169
+ `Unrecognized argument: "${arg}". Expected email, 'github', 'guest', or token string.`
8170
8170
  );
8171
8171
  }
8172
8172
  }
@@ -13134,7 +13134,7 @@ import { jsx, jsxs } from "react/jsx-runtime";
13134
13134
  var Header = () => /* @__PURE__ */ jsxs(Box, { borderStyle: "single", borderColor: "cyan", paddingX: 1, marginBottom: 1, children: [
13135
13135
  /* @__PURE__ */ jsxs(Text, { bold: true, color: "cyan", children: [
13136
13136
  "\u25CF MSapling CLI v",
13137
- "2.3.6-beta.7"
13137
+ "2.3.6-beta.8"
13138
13138
  ] }),
13139
13139
  /* @__PURE__ */ jsx(Box, { marginLeft: 2, children: /* @__PURE__ */ jsx(Text, { color: "gray", children: "Platinum Tier Architecture" }) })
13140
13140
  ] });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mtreeai/msapling-cli",
3
- "version": "2.3.6-beta.7",
3
+ "version": "2.3.6-beta.8",
4
4
  "description": "MSapling CLI — React/Ink terminal client for the MSapling backend (chat, projects, MDrive, agent tools). Proprietary; redistribution prohibited.",
5
5
  "license": "UNLICENSED",
6
6
  "author": "MSapling Team",