@xmemo/client 0.4.128 → 0.4.131
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 +16 -7
- package/package.json +1 -1
- package/src/cli.js +158 -37
package/README.md
CHANGED
|
@@ -20,12 +20,10 @@ Upgrade an existing global install:
|
|
|
20
20
|
|
|
21
21
|
```bash
|
|
22
22
|
xmemo update
|
|
23
|
-
# or
|
|
24
|
-
xmemo --update
|
|
25
23
|
```
|
|
26
24
|
|
|
27
|
-
|
|
28
|
-
|
|
25
|
+
This runs `npm install -g @xmemo/client@latest`. Use `xmemo update --dry-run`
|
|
26
|
+
to print the exact command without changing anything.
|
|
29
27
|
|
|
30
28
|
## Commands
|
|
31
29
|
|
|
@@ -38,6 +36,7 @@ xmemo doctor
|
|
|
38
36
|
xmemo discovery show
|
|
39
37
|
xmemo setup
|
|
40
38
|
xmemo login
|
|
39
|
+
xmemo auth status
|
|
41
40
|
xmemo status
|
|
42
41
|
xmemo token status
|
|
43
42
|
xmemo token add --from-stdin
|
|
@@ -57,8 +56,11 @@ xmemo privacy
|
|
|
57
56
|
token values into project files.
|
|
58
57
|
- The CLI generates one stable non-secret `XMEMO_AGENT_INSTANCE_ID` per local
|
|
59
58
|
client profile and stores it in user-scoped config outside git.
|
|
60
|
-
- `xmemo login`
|
|
61
|
-
|
|
59
|
+
- `xmemo login` stores the issued credential in the user-scoped XMemo CLI
|
|
60
|
+
config directory, shows the approved account when the server provides it,
|
|
61
|
+
and does not require extra token configuration afterward.
|
|
62
|
+
- `xmemo token add` remains available for existing tokens and still avoids
|
|
63
|
+
project files, shell history, and printed token values.
|
|
62
64
|
- Legacy `xmemo token set` refuses plaintext credential storage unless
|
|
63
65
|
`--allow-plaintext` is explicitly provided.
|
|
64
66
|
- The npm package uses a `files` whitelist so only `bin`, `src`, `README.md`,
|
|
@@ -70,12 +72,19 @@ Recommended personal-user flow:
|
|
|
70
72
|
|
|
71
73
|
```bash
|
|
72
74
|
xmemo login
|
|
75
|
+
xmemo auth status
|
|
73
76
|
xmemo token status --verify
|
|
74
77
|
```
|
|
75
78
|
|
|
76
79
|
`xmemo login` uses the hosted device-login flow when the service advertises it:
|
|
77
80
|
the CLI shows a browser URL and one-time code, the user authorizes in XMemo, and
|
|
78
|
-
the CLI stores the issued MCP token in the user-scoped credential file.
|
|
81
|
+
the CLI stores the issued MCP token in the user-scoped credential file. When the
|
|
82
|
+
service returns approved account metadata, the CLI prints the account label so
|
|
83
|
+
users can confirm which XMemo account was connected. No manual token setup is
|
|
84
|
+
needed after a successful `xmemo login`; `xmemo token status --verify` is only
|
|
85
|
+
an optional connectivity check. The CLI waits for the full browser authorization
|
|
86
|
+
window by default; use `--timeout-ms` only to shorten or extend that approval
|
|
87
|
+
window, and `--http-timeout-ms` only for individual service requests.
|
|
79
88
|
|
|
80
89
|
Users who already have a token can configure it directly without shell profiles:
|
|
81
90
|
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -10,7 +10,7 @@ const PACKAGE_NAME = '@xmemo/client';
|
|
|
10
10
|
const FALLBACK_PACKAGE_NAME = '@yonro/xmemo-client';
|
|
11
11
|
const COMMAND_NAME = 'xmemo';
|
|
12
12
|
const LEGACY_COMMAND_NAME = 'memory-os';
|
|
13
|
-
const CLI_VERSION = '0.4.
|
|
13
|
+
const CLI_VERSION = '0.4.131';
|
|
14
14
|
const DEFAULT_SERVICE_URL = 'https://xmemo.dev';
|
|
15
15
|
const TOKEN_ENV_VAR = 'XMEMO_KEY';
|
|
16
16
|
const LEGACY_TOKEN_ENV_VAR = 'MEMORY_OS_MCP_TOKEN';
|
|
@@ -89,6 +89,10 @@ export async function run(args, io = defaultIo()) {
|
|
|
89
89
|
return await loginCommand(args.slice(1), io);
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
+
if (command === 'auth') {
|
|
93
|
+
return await authCommand(args.slice(1), io);
|
|
94
|
+
}
|
|
95
|
+
|
|
92
96
|
if (command === 'token') {
|
|
93
97
|
return await tokenCommand(args.slice(1), io);
|
|
94
98
|
}
|
|
@@ -144,11 +148,11 @@ function writeHelp(io) {
|
|
|
144
148
|
writeLine(io.stdout, '');
|
|
145
149
|
writeLine(io.stdout, 'Usage:');
|
|
146
150
|
writeLine(io.stdout, ` ${COMMAND_NAME} update [--dry-run] [--json]`);
|
|
147
|
-
writeLine(io.stdout, ` ${COMMAND_NAME} --update [--dry-run] [--json]`);
|
|
148
151
|
writeLine(io.stdout, ` ${COMMAND_NAME} doctor [--base-url <https://api.example.com>] [--json]`);
|
|
149
152
|
writeLine(io.stdout, ` ${COMMAND_NAME} discovery show [--base-url <https://api.example.com>] [--json]`);
|
|
150
153
|
writeLine(io.stdout, ` ${COMMAND_NAME} setup [codex|cursor] [--url <https://api.example.com>] [--write|--yes] [--json]`);
|
|
151
|
-
writeLine(io.stdout, ` ${COMMAND_NAME} login [--from-stdin] [--base-url <url>] [--json]`);
|
|
154
|
+
writeLine(io.stdout, ` ${COMMAND_NAME} login [--from-stdin] [--base-url <url>] [--timeout-ms <ms>] [--http-timeout-ms <ms>] [--json]`);
|
|
155
|
+
writeLine(io.stdout, ` ${COMMAND_NAME} auth status [--verify] [--base-url <url>] [--json]`);
|
|
152
156
|
writeLine(io.stdout, ` ${COMMAND_NAME} status [--url <https://api.example.com>] [--json]`);
|
|
153
157
|
writeLine(io.stdout, ` ${COMMAND_NAME} token status [--verify]`);
|
|
154
158
|
writeLine(io.stdout, ` ${COMMAND_NAME} token add --from-stdin`);
|
|
@@ -165,6 +169,7 @@ function writeHelp(io) {
|
|
|
165
169
|
writeLine(io.stdout, ` ${COMMAND_NAME} privacy`);
|
|
166
170
|
writeLine(io.stdout, '');
|
|
167
171
|
writeLine(io.stdout, `Default service URL: ${DEFAULT_SERVICE_URL}; use --url or XMEMO_URL for private deployments.`);
|
|
172
|
+
writeLine(io.stdout, '`login --timeout-ms` controls the full browser approval window; HTTP calls use `--http-timeout-ms`.');
|
|
168
173
|
writeLine(io.stdout, '');
|
|
169
174
|
writeLine(io.stdout, 'Privacy defaults: no telemetry, no token in project files, and no token is sent by `status`, `doctor`, or `discovery`.');
|
|
170
175
|
writeLine(io.stdout, '`login` and `token add` store credentials only in the user-scoped XMemo CLI config directory.');
|
|
@@ -453,7 +458,8 @@ async function loginCommand(args, io) {
|
|
|
453
458
|
const outputJson = hasFlag(args, '--json');
|
|
454
459
|
const fromStdin = hasFlag(args, '--from-stdin') || hasFlag(args, '--token-stdin');
|
|
455
460
|
const baseUrl = normalizeBaseUrl(baseUrlOption(args, io.env));
|
|
456
|
-
const
|
|
461
|
+
const httpTimeoutMs = parsePositiveInteger(optionValue(args, '--http-timeout-ms') ?? '30000', '--http-timeout-ms');
|
|
462
|
+
const loginTimeoutOption = optionValue(args, '--timeout-ms');
|
|
457
463
|
const pollOnce = hasFlag(args, '--poll-once');
|
|
458
464
|
|
|
459
465
|
if (fromStdin) {
|
|
@@ -468,7 +474,10 @@ async function loginCommand(args, io) {
|
|
|
468
474
|
return 0;
|
|
469
475
|
}
|
|
470
476
|
|
|
471
|
-
const start = await startDeviceLogin(baseUrl,
|
|
477
|
+
const start = await startDeviceLogin(baseUrl, httpTimeoutMs, io);
|
|
478
|
+
const loginTimeoutMs = loginTimeoutOption
|
|
479
|
+
? parsePositiveInteger(loginTimeoutOption, '--timeout-ms')
|
|
480
|
+
: Math.max(1000, start.expiresIn * 1000);
|
|
472
481
|
if (!outputJson) {
|
|
473
482
|
writeLine(io.stdout, `${PRODUCT_NAME} device login`);
|
|
474
483
|
writeLine(io.stdout, `Open: ${start.verificationUriComplete ?? start.verificationUri}`);
|
|
@@ -478,12 +487,13 @@ async function loginCommand(args, io) {
|
|
|
478
487
|
writeLine(io.stdout, 'Waiting for authorization...');
|
|
479
488
|
}
|
|
480
489
|
|
|
481
|
-
const token = await pollDeviceLogin(baseUrl, start,
|
|
482
|
-
const result = await storeTokenValue(token, { source: 'device-login' }, io.env);
|
|
490
|
+
const token = await pollDeviceLogin(baseUrl, start, loginTimeoutMs, httpTimeoutMs, io, { pollOnce });
|
|
491
|
+
const result = await storeTokenValue(token.accessToken, { source: 'device-login', account: token.account }, io.env);
|
|
483
492
|
const payload = {
|
|
484
493
|
...result,
|
|
485
494
|
baseUrl,
|
|
486
495
|
verificationUri: start.verificationUri,
|
|
496
|
+
account: token.account,
|
|
487
497
|
deviceLogin: true
|
|
488
498
|
};
|
|
489
499
|
|
|
@@ -491,12 +501,34 @@ async function loginCommand(args, io) {
|
|
|
491
501
|
writeLine(io.stdout, JSON.stringify(payload, null, 2));
|
|
492
502
|
} else {
|
|
493
503
|
writeLine(io.stdout, 'Login complete. Token stored securely in the user-scoped XMemo CLI config directory.');
|
|
504
|
+
if (token.account) {
|
|
505
|
+
writeLine(io.stdout, `Signed in as: ${formatAccount(token.account)}`);
|
|
506
|
+
}
|
|
494
507
|
writeLine(io.stdout, `Credential path: ${result.credentialPath}`);
|
|
495
|
-
writeLine(io.stdout,
|
|
508
|
+
writeLine(io.stdout, 'No extra token configuration is required.');
|
|
509
|
+
writeLine(io.stdout, `Optional check: ${COMMAND_NAME} token status --verify`);
|
|
496
510
|
}
|
|
497
511
|
return 0;
|
|
498
512
|
}
|
|
499
513
|
|
|
514
|
+
async function authCommand(args, io) {
|
|
515
|
+
const subcommand = args[0] ?? 'help';
|
|
516
|
+
|
|
517
|
+
if (subcommand === 'help' || subcommand === '--help' || subcommand === '-h') {
|
|
518
|
+
writeLine(io.stdout, 'Auth commands:');
|
|
519
|
+
writeLine(io.stdout, ` ${COMMAND_NAME} auth status [--verify] [--base-url <url>] [--json]`);
|
|
520
|
+
writeLine(io.stdout, '');
|
|
521
|
+
writeLine(io.stdout, `Use \`${COMMAND_NAME} login\` to sign in and \`${COMMAND_NAME} token add --from-stdin\` to store an existing token.`);
|
|
522
|
+
return 0;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
if (subcommand === 'status') {
|
|
526
|
+
return await credentialStatusCommand(args.slice(1), io, { mode: 'auth' });
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
throw new UsageError(`Unknown auth command: ${subcommand}`);
|
|
530
|
+
}
|
|
531
|
+
|
|
500
532
|
async function tokenCommand(args, io) {
|
|
501
533
|
const subcommand = args[0] ?? 'help';
|
|
502
534
|
|
|
@@ -512,25 +544,7 @@ async function tokenCommand(args, io) {
|
|
|
512
544
|
}
|
|
513
545
|
|
|
514
546
|
if (subcommand === 'status') {
|
|
515
|
-
|
|
516
|
-
const hasEnvironmentToken = Boolean(io.env[TOKEN_ENV_VAR] ?? io.env[LEGACY_TOKEN_ENV_VAR]);
|
|
517
|
-
const hasUserCredential = Boolean(credential.token);
|
|
518
|
-
writeLine(io.stdout, `Environment token: ${hasEnvironmentToken ? 'present' : 'missing'} (${TOKEN_ENV_VAR})`);
|
|
519
|
-
writeLine(io.stdout, `User credential file: ${hasUserCredential ? 'present' : 'missing'} (${credential.path})`);
|
|
520
|
-
writeLine(io.stdout, 'Token values are never printed.');
|
|
521
|
-
if (hasFlag(args, '--verify')) {
|
|
522
|
-
const token = await resolveCredentialToken(io.env);
|
|
523
|
-
if (!token) {
|
|
524
|
-
writeLine(io.stderr, `No token found. Run \`${COMMAND_NAME} login\` or \`${COMMAND_NAME} token add --from-stdin\`.`);
|
|
525
|
-
return 1;
|
|
526
|
-
}
|
|
527
|
-
const baseUrl = normalizeBaseUrl(baseUrlOption(args, io.env));
|
|
528
|
-
const timeoutMs = parsePositiveInteger(optionValue(args, '--timeout-ms') ?? '10000', '--timeout-ms');
|
|
529
|
-
const verification = await verifyTokenWithMcp(baseUrl, token, timeoutMs, io);
|
|
530
|
-
writeLine(io.stdout, `Remote token verification: ${verification.ok ? 'ok' : 'failed'} (${verification.detail})`);
|
|
531
|
-
return verification.ok ? 0 : 1;
|
|
532
|
-
}
|
|
533
|
-
return hasEnvironmentToken || hasUserCredential ? 0 : 1;
|
|
547
|
+
return await credentialStatusCommand(args.slice(1), io, { mode: 'token' });
|
|
534
548
|
}
|
|
535
549
|
|
|
536
550
|
if (subcommand === 'add') {
|
|
@@ -569,6 +583,79 @@ async function tokenCommand(args, io) {
|
|
|
569
583
|
throw new UsageError(`Unknown token command: ${subcommand}`);
|
|
570
584
|
}
|
|
571
585
|
|
|
586
|
+
async function credentialStatusCommand(args, io, { mode }) {
|
|
587
|
+
const outputJson = hasFlag(args, '--json');
|
|
588
|
+
const verify = hasFlag(args, '--verify');
|
|
589
|
+
const credential = await readStoredCredential(io.env);
|
|
590
|
+
const environmentToken = io.env[TOKEN_ENV_VAR] ?? io.env[LEGACY_TOKEN_ENV_VAR] ?? '';
|
|
591
|
+
const hasEnvironmentToken = Boolean(environmentToken);
|
|
592
|
+
const hasUserCredential = Boolean(credential.token);
|
|
593
|
+
const tokenSource = hasEnvironmentToken ? 'environment' : hasUserCredential ? 'user-credential-file' : 'missing';
|
|
594
|
+
const report = {
|
|
595
|
+
loggedIn: hasEnvironmentToken || hasUserCredential,
|
|
596
|
+
tokenSource,
|
|
597
|
+
environmentToken: {
|
|
598
|
+
present: hasEnvironmentToken,
|
|
599
|
+
variable: hasEnvironmentToken && io.env[TOKEN_ENV_VAR] ? TOKEN_ENV_VAR : hasEnvironmentToken ? LEGACY_TOKEN_ENV_VAR : TOKEN_ENV_VAR
|
|
600
|
+
},
|
|
601
|
+
userCredentialFile: {
|
|
602
|
+
present: hasUserCredential,
|
|
603
|
+
path: credential.path,
|
|
604
|
+
storage: credential.storage ?? null
|
|
605
|
+
},
|
|
606
|
+
account: credential.account ?? null,
|
|
607
|
+
privacy: {
|
|
608
|
+
tokenPrinted: false,
|
|
609
|
+
projectFilesModified: false
|
|
610
|
+
}
|
|
611
|
+
};
|
|
612
|
+
|
|
613
|
+
if (verify) {
|
|
614
|
+
const token = await resolveCredentialToken(io.env);
|
|
615
|
+
if (!token) {
|
|
616
|
+
if (outputJson) {
|
|
617
|
+
writeLine(io.stdout, JSON.stringify({ ...report, verification: { ok: false, detail: 'no token found' } }, null, 2));
|
|
618
|
+
} else {
|
|
619
|
+
writeCredentialStatus(report, io, { mode });
|
|
620
|
+
writeLine(io.stderr, `No token found. Run \`${COMMAND_NAME} login\` or \`${COMMAND_NAME} token add --from-stdin\`.`);
|
|
621
|
+
}
|
|
622
|
+
return 1;
|
|
623
|
+
}
|
|
624
|
+
const baseUrl = normalizeBaseUrl(baseUrlOption(args, io.env));
|
|
625
|
+
const timeoutMs = parsePositiveInteger(optionValue(args, '--timeout-ms') ?? '10000', '--timeout-ms');
|
|
626
|
+
const verification = await verifyTokenWithMcp(baseUrl, token, timeoutMs, io);
|
|
627
|
+
report.verification = verification;
|
|
628
|
+
if (outputJson) {
|
|
629
|
+
writeLine(io.stdout, JSON.stringify(report, null, 2));
|
|
630
|
+
return verification.ok ? 0 : 1;
|
|
631
|
+
}
|
|
632
|
+
writeCredentialStatus(report, io, { mode });
|
|
633
|
+
writeLine(io.stdout, `Remote token verification: ${verification.ok ? 'ok' : 'failed'} (${verification.detail})`);
|
|
634
|
+
return verification.ok ? 0 : 1;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
if (outputJson) {
|
|
638
|
+
writeLine(io.stdout, JSON.stringify(report, null, 2));
|
|
639
|
+
} else {
|
|
640
|
+
writeCredentialStatus(report, io, { mode });
|
|
641
|
+
}
|
|
642
|
+
return report.loggedIn ? 0 : 1;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
function writeCredentialStatus(report, io, { mode }) {
|
|
646
|
+
if (mode === 'auth') {
|
|
647
|
+
writeLine(io.stdout, `${PRODUCT_NAME} auth status`);
|
|
648
|
+
writeLine(io.stdout, `Logged in: ${report.loggedIn ? 'yes' : 'no'}`);
|
|
649
|
+
writeLine(io.stdout, `Credential source: ${report.tokenSource}`);
|
|
650
|
+
}
|
|
651
|
+
writeLine(io.stdout, `Environment token: ${report.environmentToken.present ? 'present' : 'missing'} (${report.environmentToken.variable})`);
|
|
652
|
+
writeLine(io.stdout, `User credential file: ${report.userCredentialFile.present ? 'present' : 'missing'} (${report.userCredentialFile.path})`);
|
|
653
|
+
if (report.account) {
|
|
654
|
+
writeLine(io.stdout, `Account: ${formatAccount(report.account)}`);
|
|
655
|
+
}
|
|
656
|
+
writeLine(io.stdout, report.loggedIn ? 'Credential is ready; token value remains hidden.' : `Run \`${COMMAND_NAME} login\` to sign in.`);
|
|
657
|
+
}
|
|
658
|
+
|
|
572
659
|
async function mcpCommand(args, io) {
|
|
573
660
|
const subcommand = args[0] ?? 'help';
|
|
574
661
|
|
|
@@ -893,18 +980,23 @@ async function startDeviceLogin(baseUrl, timeoutMs, io) {
|
|
|
893
980
|
};
|
|
894
981
|
}
|
|
895
982
|
|
|
896
|
-
async function pollDeviceLogin(baseUrl, start,
|
|
897
|
-
const deadline = Date.now() + Math.min(start.expiresIn * 1000,
|
|
983
|
+
async function pollDeviceLogin(baseUrl, start, loginTimeoutMs, httpTimeoutMs, io, options = {}) {
|
|
984
|
+
const deadline = Date.now() + Math.min(start.expiresIn * 1000, loginTimeoutMs);
|
|
985
|
+
const sleepFn = io.sleep ?? sleep;
|
|
986
|
+
let intervalSeconds = start.interval;
|
|
898
987
|
while (Date.now() <= deadline) {
|
|
899
988
|
const payload = await postJson(endpointUrl(baseUrl, DEVICE_LOGIN_TOKEN_PATH), {
|
|
900
989
|
device_code: start.deviceCode,
|
|
901
990
|
grant_type: 'urn:ietf:params:oauth:grant-type:device_code'
|
|
902
|
-
},
|
|
903
|
-
|
|
904
|
-
const
|
|
905
|
-
if (
|
|
906
|
-
validateToken(
|
|
907
|
-
return
|
|
991
|
+
}, httpTimeoutMs, io, { allowDevicePending: true });
|
|
992
|
+
|
|
993
|
+
const accessToken = stringValue(payload, ['access_token']) ?? stringValue(payload, ['token']);
|
|
994
|
+
if (accessToken) {
|
|
995
|
+
validateToken(accessToken);
|
|
996
|
+
return {
|
|
997
|
+
accessToken,
|
|
998
|
+
account: accountFromPayload(payload)
|
|
999
|
+
};
|
|
908
1000
|
}
|
|
909
1001
|
|
|
910
1002
|
const error = stringValue(payload, ['error']);
|
|
@@ -914,7 +1006,10 @@ async function pollDeviceLogin(baseUrl, start, timeoutMs, io, options = {}) {
|
|
|
914
1006
|
if (options.pollOnce) {
|
|
915
1007
|
throw new UsageError('Device login is still pending.');
|
|
916
1008
|
}
|
|
917
|
-
|
|
1009
|
+
if (error === 'slow_down') {
|
|
1010
|
+
intervalSeconds += 5;
|
|
1011
|
+
}
|
|
1012
|
+
await sleepFn(intervalSeconds * 1000);
|
|
918
1013
|
}
|
|
919
1014
|
|
|
920
1015
|
throw new UsageError('Device login expired before authorization completed.');
|
|
@@ -951,10 +1046,36 @@ async function readStoredCredential(env) {
|
|
|
951
1046
|
return {
|
|
952
1047
|
path: credentialPath,
|
|
953
1048
|
token: stringValue(parsed, ['token']),
|
|
954
|
-
storage: stringValue(parsed, ['storage'])
|
|
1049
|
+
storage: stringValue(parsed, ['storage']),
|
|
1050
|
+
account: accountFromPayload(parsed.metadata)
|
|
1051
|
+
};
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
function accountFromPayload(payload) {
|
|
1055
|
+
const account = payload && typeof payload === 'object'
|
|
1056
|
+
? (payload.user && typeof payload.user === 'object' ? payload.user : payload.account)
|
|
1057
|
+
: null;
|
|
1058
|
+
if (!account || typeof account !== 'object') {
|
|
1059
|
+
return null;
|
|
1060
|
+
}
|
|
1061
|
+
const userId = stringValue(account, ['user_id']) ?? stringValue(account, ['id']) ?? stringValue(account, ['userId']);
|
|
1062
|
+
const email = stringValue(account, ['email']);
|
|
1063
|
+
const displayName = stringValue(account, ['display_name']) ?? stringValue(account, ['name']) ?? stringValue(account, ['displayName']);
|
|
1064
|
+
if (!userId && !email && !displayName) {
|
|
1065
|
+
return null;
|
|
1066
|
+
}
|
|
1067
|
+
return {
|
|
1068
|
+
userId: userId ?? null,
|
|
1069
|
+
email: email ?? null,
|
|
1070
|
+
displayName: displayName ?? null
|
|
955
1071
|
};
|
|
956
1072
|
}
|
|
957
1073
|
|
|
1074
|
+
function formatAccount(account) {
|
|
1075
|
+
const label = account.displayName || account.email || account.userId || 'XMemo account';
|
|
1076
|
+
return account.email && account.displayName ? `${account.displayName} <${account.email}>` : label;
|
|
1077
|
+
}
|
|
1078
|
+
|
|
958
1079
|
async function resolveCredentialToken(env) {
|
|
959
1080
|
const environmentToken = env[TOKEN_ENV_VAR] ?? env[LEGACY_TOKEN_ENV_VAR];
|
|
960
1081
|
if (environmentToken) {
|