@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.
- package/dist/index.js +72 -72
- 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
|
-
*
|
|
288
|
-
*
|
|
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}/
|
|
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
|
-
|
|
8015
|
-
|
|
8016
|
-
|
|
8017
|
-
|
|
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.
|
|
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.
|
|
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",
|