@echomem/mcp 1.4.2 → 1.4.3
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/hud/cli.js +0 -0
- package/dist/hud/web.js +4 -2
- package/dist/index.js +0 -0
- package/dist/setup.js +307 -5
- package/package.json +1 -1
package/dist/hud/cli.js
CHANGED
|
File without changes
|
package/dist/hud/web.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { MCP_PACKAGE_VERSION } from "../package-metadata.js";
|
|
1
2
|
export const HUD_HTML = String.raw `<!doctype html>
|
|
2
3
|
<html lang="en">
|
|
3
4
|
<head>
|
|
@@ -193,6 +194,7 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
193
194
|
const history = [];
|
|
194
195
|
let lastSource = '';
|
|
195
196
|
let prevTurnState = null;
|
|
197
|
+
const hudVersion = 'v${MCP_PACKAGE_VERSION}';
|
|
196
198
|
updateBubbleSkins();
|
|
197
199
|
if ('ResizeObserver' in window) {
|
|
198
200
|
const observer = new ResizeObserver(updateBubbleSkins);
|
|
@@ -261,7 +263,7 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
261
263
|
if (deltaEl) deltaEl.style.display = 'none';
|
|
262
264
|
prevTurnState = null;
|
|
263
265
|
main.textContent = 'No active session yet';
|
|
264
|
-
client.textContent = '';
|
|
266
|
+
client.textContent = 'EchoMem HUD · ' + hudVersion;
|
|
265
267
|
if (summary) summary.textContent = 'Start working in Codex or Claude and I’ll check the context here.';
|
|
266
268
|
if (fullness) fullness.textContent = '';
|
|
267
269
|
if (meterfill) meterfill.style.width = '0';
|
|
@@ -285,7 +287,7 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
285
287
|
hud.classList.remove('s-green', 's-amber', 's-red');
|
|
286
288
|
hud.classList.add('s-' + (score.color || 'green'));
|
|
287
289
|
main.textContent = stateWord(score.color) + ' · ' + fmt(score.ctTokens) + ' ctx';
|
|
288
|
-
client.textContent = label(score.client);
|
|
290
|
+
client.textContent = label(score.client) + ' · ' + hudVersion;
|
|
289
291
|
if (summary) summary.textContent = summaryLine(score.color);
|
|
290
292
|
const sat = typeof score.saturationPct === 'number' ? score.saturationPct
|
|
291
293
|
: (score.modelContextWindow ? Math.round(100 * (score.ctTokens || 0) / score.modelContextWindow) : 0);
|
package/dist/index.js
CHANGED
|
File without changes
|
package/dist/setup.js
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import http from "node:http";
|
|
16
16
|
import { randomUUID } from "node:crypto";
|
|
17
|
-
import { spawn } from "node:child_process";
|
|
17
|
+
import { execFileSync, spawn } from "node:child_process";
|
|
18
18
|
import { Worker } from "node:worker_threads";
|
|
19
19
|
import fs from "node:fs";
|
|
20
20
|
import os from "node:os";
|
|
@@ -30,7 +30,7 @@ import { syncCodexUsage } from "./codex-sync.js";
|
|
|
30
30
|
import { renderSetupPage } from "./setup-page.js";
|
|
31
31
|
import { repoLabel } from "./forensics.js";
|
|
32
32
|
import { installHooks } from "./hud/hooks.js";
|
|
33
|
-
import { MCP_PACKAGE_LABEL, MCP_PACKAGE_VERSION, MCP_UPDATE_COMMAND } from "./package-metadata.js";
|
|
33
|
+
import { MCP_PACKAGE_LABEL, MCP_PACKAGE_NAME, MCP_PACKAGE_VERSION, MCP_UPDATE_COMMAND } from "./package-metadata.js";
|
|
34
34
|
// The hosted connect-device page is now only the account-auth/token courier. The dashboard itself is
|
|
35
35
|
// served by this localhost bridge, where local logs and processed/unprocessed counts never leave the
|
|
36
36
|
// device unless the user explicitly starts migration. Override the hosted auth origin with ECHO_WEB_URL.
|
|
@@ -142,6 +142,283 @@ export function writeJsonClientConfig(configPath, entry) {
|
|
|
142
142
|
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
143
143
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
144
144
|
}
|
|
145
|
+
export function writeClaudeCodeConfig(entry) {
|
|
146
|
+
try {
|
|
147
|
+
execFileSync("claude", ["mcp", "add-json", "echomem", JSON.stringify(entry)], {
|
|
148
|
+
encoding: "utf8",
|
|
149
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
150
|
+
timeout: 10000,
|
|
151
|
+
});
|
|
152
|
+
return "wrote";
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
return "unavailable";
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
function readJsonClientEntry(configPath) {
|
|
159
|
+
try {
|
|
160
|
+
const config = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
|
161
|
+
const servers = config.mcpServers;
|
|
162
|
+
if (!servers || typeof servers !== "object" || Array.isArray(servers))
|
|
163
|
+
return null;
|
|
164
|
+
const entry = servers.echomem;
|
|
165
|
+
return entry && typeof entry === "object" && !Array.isArray(entry) ? entry : null;
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
function readCodexEntry(configPath) {
|
|
172
|
+
let content = "";
|
|
173
|
+
try {
|
|
174
|
+
content = fs.readFileSync(configPath, "utf8");
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
const lines = content.split("\n");
|
|
180
|
+
const start = lines.findIndex((line) => /^\s*\[mcp_servers\.echomem\]\s*$/.test(line));
|
|
181
|
+
if (start < 0)
|
|
182
|
+
return null;
|
|
183
|
+
let end = start + 1;
|
|
184
|
+
while (end < lines.length && !/^\s*\[/.test(lines[end]))
|
|
185
|
+
end++;
|
|
186
|
+
const block = lines.slice(start, end);
|
|
187
|
+
const command = parseTomlString(block.find((line) => /^\s*command\s*=/.test(line)));
|
|
188
|
+
const args = parseTomlStringArray(block.find((line) => /^\s*args\s*=/.test(line)));
|
|
189
|
+
return command ? { command, args } : null;
|
|
190
|
+
}
|
|
191
|
+
function parseTomlString(line) {
|
|
192
|
+
if (!line)
|
|
193
|
+
return undefined;
|
|
194
|
+
const match = line.match(/=\s*(".*")\s*$/);
|
|
195
|
+
if (!match)
|
|
196
|
+
return undefined;
|
|
197
|
+
try {
|
|
198
|
+
const value = JSON.parse(match[1]);
|
|
199
|
+
return typeof value === "string" ? value : undefined;
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
return undefined;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
function parseTomlStringArray(line) {
|
|
206
|
+
if (!line)
|
|
207
|
+
return [];
|
|
208
|
+
const match = line.match(/=\s*(\[.*\])\s*$/);
|
|
209
|
+
if (!match)
|
|
210
|
+
return [];
|
|
211
|
+
try {
|
|
212
|
+
const value = JSON.parse(match[1]);
|
|
213
|
+
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
214
|
+
}
|
|
215
|
+
catch {
|
|
216
|
+
return [];
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
function entryArgs(entry) {
|
|
220
|
+
return Array.isArray(entry.args) ? entry.args.filter((arg) => typeof arg === "string") : [];
|
|
221
|
+
}
|
|
222
|
+
function entryCommand(entry) {
|
|
223
|
+
return typeof entry.command === "string" && entry.command.trim() ? entry.command.trim() : undefined;
|
|
224
|
+
}
|
|
225
|
+
function entryTarget(entry) {
|
|
226
|
+
const command = entryCommand(entry) ?? "(missing command)";
|
|
227
|
+
const args = entryArgs(entry);
|
|
228
|
+
return [command, ...args].join(" ");
|
|
229
|
+
}
|
|
230
|
+
function packageVersionFromPath(entryPath) {
|
|
231
|
+
let current = entryPath;
|
|
232
|
+
try {
|
|
233
|
+
current = fs.realpathSync(entryPath);
|
|
234
|
+
}
|
|
235
|
+
catch {
|
|
236
|
+
/* keep the original path; it may still be inside an existing package dir */
|
|
237
|
+
}
|
|
238
|
+
try {
|
|
239
|
+
if (fs.existsSync(current) && fs.statSync(current).isFile())
|
|
240
|
+
current = path.dirname(current);
|
|
241
|
+
}
|
|
242
|
+
catch {
|
|
243
|
+
current = path.dirname(current);
|
|
244
|
+
}
|
|
245
|
+
for (let i = 0; i < 12; i += 1) {
|
|
246
|
+
const packagePath = path.join(current, "package.json");
|
|
247
|
+
try {
|
|
248
|
+
const pkg = JSON.parse(fs.readFileSync(packagePath, "utf8"));
|
|
249
|
+
if (pkg.name === MCP_PACKAGE_NAME && typeof pkg.version === "string") {
|
|
250
|
+
return { version: pkg.version, packagePath };
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
catch {
|
|
254
|
+
/* keep walking */
|
|
255
|
+
}
|
|
256
|
+
const parent = path.dirname(current);
|
|
257
|
+
if (parent === current)
|
|
258
|
+
break;
|
|
259
|
+
current = parent;
|
|
260
|
+
}
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
263
|
+
export function resolveServerEntryVersion(entry) {
|
|
264
|
+
const command = entryCommand(entry);
|
|
265
|
+
const args = entryArgs(entry);
|
|
266
|
+
const npxPackageArg = args.find((arg) => arg === MCP_PACKAGE_NAME || arg.startsWith(`${MCP_PACKAGE_NAME}@`));
|
|
267
|
+
if (command && path.basename(command).replace(/\.(cmd|exe)$/i, "") === "npx" && npxPackageArg) {
|
|
268
|
+
const suffix = npxPackageArg.slice(MCP_PACKAGE_NAME.length);
|
|
269
|
+
if (suffix === "@latest")
|
|
270
|
+
return { runtime: "latest" };
|
|
271
|
+
if (suffix.startsWith("@") && /^\d+\.\d+\.\d+/.test(suffix.slice(1)))
|
|
272
|
+
return { version: suffix.slice(1) };
|
|
273
|
+
return { runtime: "dynamic" };
|
|
274
|
+
}
|
|
275
|
+
const candidates = [...args, command].filter((value) => typeof value === "string");
|
|
276
|
+
for (const candidate of candidates) {
|
|
277
|
+
if (!candidate.includes(MCP_PACKAGE_NAME) && !candidate.includes(`${path.sep}echomem-mcp`))
|
|
278
|
+
continue;
|
|
279
|
+
const resolved = packageVersionFromPath(candidate);
|
|
280
|
+
if (resolved)
|
|
281
|
+
return resolved;
|
|
282
|
+
}
|
|
283
|
+
return {};
|
|
284
|
+
}
|
|
285
|
+
function compareSemver(a, b) {
|
|
286
|
+
const av = a.split(/[.-]/).map((part) => Number(part));
|
|
287
|
+
const bv = b.split(/[.-]/).map((part) => Number(part));
|
|
288
|
+
for (let i = 0; i < Math.max(av.length, bv.length, 3); i += 1) {
|
|
289
|
+
const ai = Number.isFinite(av[i]) ? av[i] : 0;
|
|
290
|
+
const bi = Number.isFinite(bv[i]) ? bv[i] : 0;
|
|
291
|
+
if (ai !== bi)
|
|
292
|
+
return ai > bi ? 1 : -1;
|
|
293
|
+
}
|
|
294
|
+
return 0;
|
|
295
|
+
}
|
|
296
|
+
function versionState(version, desiredVersion) {
|
|
297
|
+
if (!version || !desiredVersion)
|
|
298
|
+
return "unknown";
|
|
299
|
+
const cmp = compareSemver(version, desiredVersion);
|
|
300
|
+
if (cmp < 0)
|
|
301
|
+
return "stale";
|
|
302
|
+
if (cmp > 0)
|
|
303
|
+
return "newer";
|
|
304
|
+
return "ok";
|
|
305
|
+
}
|
|
306
|
+
function readLatestPublishedVersion() {
|
|
307
|
+
try {
|
|
308
|
+
const raw = execFileSync("npm", ["view", MCP_PACKAGE_NAME, "version", "--silent"], {
|
|
309
|
+
encoding: "utf8",
|
|
310
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
311
|
+
timeout: 3500,
|
|
312
|
+
}).trim();
|
|
313
|
+
return /^\d+\.\d+\.\d+/.test(raw) ? raw : undefined;
|
|
314
|
+
}
|
|
315
|
+
catch {
|
|
316
|
+
return undefined;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
function inspectClientConfig(client, desiredVersion) {
|
|
320
|
+
if (client.kind === "snippet")
|
|
321
|
+
return inspectClaudeCodeConfig(client, desiredVersion);
|
|
322
|
+
if (client.kind === "json" && !fs.existsSync(path.dirname(client.configPath)))
|
|
323
|
+
return null;
|
|
324
|
+
if (client.kind === "command" && !fs.existsSync(client.detectDir))
|
|
325
|
+
return null;
|
|
326
|
+
const entry = client.kind === "json" ? readJsonClientEntry(client.configPath) : readCodexEntry(client.configPath);
|
|
327
|
+
if (!entry) {
|
|
328
|
+
return {
|
|
329
|
+
id: client.id,
|
|
330
|
+
label: client.label,
|
|
331
|
+
configured: false,
|
|
332
|
+
detail: `no EchoMem MCP entry in ${client.configPath}`,
|
|
333
|
+
state: "missing",
|
|
334
|
+
command: `${MCP_UPDATE_COMMAND} --client ${client.id}`,
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
const resolved = resolveServerEntryVersion(entry);
|
|
338
|
+
const state = resolved.runtime === "latest" ? "ok" : versionState(resolved.version, desiredVersion);
|
|
339
|
+
return {
|
|
340
|
+
id: client.id,
|
|
341
|
+
label: client.label,
|
|
342
|
+
configured: true,
|
|
343
|
+
detail: entryTarget(entry),
|
|
344
|
+
version: resolved.version,
|
|
345
|
+
packagePath: resolved.packagePath,
|
|
346
|
+
runtime: resolved.runtime,
|
|
347
|
+
state,
|
|
348
|
+
command: state === "stale" || state === "missing" ? `${MCP_UPDATE_COMMAND} --client ${client.id}` : undefined,
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
function inspectClaudeCodeConfig(client, desiredVersion) {
|
|
352
|
+
if (!fs.existsSync(home(".claude")))
|
|
353
|
+
return null;
|
|
354
|
+
try {
|
|
355
|
+
const output = execFileSync("claude", ["mcp", "list"], {
|
|
356
|
+
encoding: "utf8",
|
|
357
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
358
|
+
timeout: 3000,
|
|
359
|
+
});
|
|
360
|
+
const line = output.split("\n").find((item) => item.toLowerCase().includes("echomem"));
|
|
361
|
+
if (!line) {
|
|
362
|
+
return {
|
|
363
|
+
id: client.id,
|
|
364
|
+
label: client.label,
|
|
365
|
+
configured: false,
|
|
366
|
+
detail: "Claude Code reports no EchoMem MCP server",
|
|
367
|
+
state: "missing",
|
|
368
|
+
command: `${MCP_UPDATE_COMMAND} --client ${client.id}`,
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
const match = line.match(new RegExp(`${MCP_PACKAGE_NAME.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}@(\\d+\\.\\d+\\.\\d+)`));
|
|
372
|
+
const version = match?.[1];
|
|
373
|
+
return {
|
|
374
|
+
id: client.id,
|
|
375
|
+
label: client.label,
|
|
376
|
+
configured: true,
|
|
377
|
+
detail: line.trim(),
|
|
378
|
+
version,
|
|
379
|
+
state: versionState(version, desiredVersion),
|
|
380
|
+
command: version && versionState(version, desiredVersion) === "stale" ? `${MCP_UPDATE_COMMAND} --client ${client.id}` : undefined,
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
catch {
|
|
384
|
+
return {
|
|
385
|
+
id: client.id,
|
|
386
|
+
label: client.label,
|
|
387
|
+
configured: false,
|
|
388
|
+
detail: "Claude Code CLI not available for `claude mcp list`; add EchoMem from Claude Code manually",
|
|
389
|
+
state: "unknown",
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
function inspectClientConfigs(desiredVersion) {
|
|
394
|
+
return knownClients()
|
|
395
|
+
.map((client) => inspectClientConfig(client, desiredVersion))
|
|
396
|
+
.filter((report) => report !== null);
|
|
397
|
+
}
|
|
398
|
+
function formatClientConfigReport(report) {
|
|
399
|
+
const lines = [];
|
|
400
|
+
const status = report.state === "ok"
|
|
401
|
+
? "ok"
|
|
402
|
+
: report.state === "stale"
|
|
403
|
+
? "stale"
|
|
404
|
+
: report.state === "newer"
|
|
405
|
+
? "newer than this status command"
|
|
406
|
+
: report.state === "missing"
|
|
407
|
+
? "missing"
|
|
408
|
+
: "unknown";
|
|
409
|
+
const version = report.runtime === "latest"
|
|
410
|
+
? "latest at launch"
|
|
411
|
+
: report.runtime === "dynamic"
|
|
412
|
+
? "dynamic npm resolution"
|
|
413
|
+
: report.version
|
|
414
|
+
? `${MCP_PACKAGE_NAME}@${report.version}`
|
|
415
|
+
: "version unknown";
|
|
416
|
+
lines.push(` ${report.label}: ${report.configured ? "configured" : "not configured"} (${status}; ${version})`);
|
|
417
|
+
lines.push(` ${report.detail}`);
|
|
418
|
+
if (report.command)
|
|
419
|
+
lines.push(` Update: ${report.command}`);
|
|
420
|
+
return lines;
|
|
421
|
+
}
|
|
145
422
|
// ---------------------------------------------------------------------------
|
|
146
423
|
// Browser + localhost callback
|
|
147
424
|
// ---------------------------------------------------------------------------
|
|
@@ -788,7 +1065,13 @@ async function cmdSetup(flags) {
|
|
|
788
1065
|
console.log(`✅ ${c.label} already has the EchoMem MCP entry: ${c.configPath}`);
|
|
789
1066
|
}
|
|
790
1067
|
else {
|
|
791
|
-
|
|
1068
|
+
const result = c.id === "claude-code" ? writeClaudeCodeConfig(entry) : "unavailable";
|
|
1069
|
+
if (result === "wrote") {
|
|
1070
|
+
console.log(`✅ Wrote EchoMem MCP entry to ${c.label} via \`claude mcp add-json\` — start a new Claude Code session to load it.`);
|
|
1071
|
+
}
|
|
1072
|
+
else {
|
|
1073
|
+
console.log(`ℹ️ ${c.label}: ${c.note}\n entry: ${JSON.stringify(entry)}`);
|
|
1074
|
+
}
|
|
792
1075
|
}
|
|
793
1076
|
}
|
|
794
1077
|
}
|
|
@@ -1381,10 +1664,17 @@ async function cmdUnlock(flags) {
|
|
|
1381
1664
|
store.saveKey(keyB64);
|
|
1382
1665
|
console.log("✅ Vault unlocked. Reload your MCP client (or start a new session).");
|
|
1383
1666
|
}
|
|
1384
|
-
async function cmdStatus() {
|
|
1667
|
+
async function cmdStatus(flags = {}) {
|
|
1385
1668
|
const store = new KeyStore();
|
|
1386
1669
|
const token = store.getToken();
|
|
1387
1670
|
console.log(`EchoMem MCP: ${MCP_PACKAGE_LABEL}`);
|
|
1671
|
+
const latest = flags["no-network"] ? undefined : readLatestPublishedVersion();
|
|
1672
|
+
if (latest) {
|
|
1673
|
+
console.log(`Published latest: ${MCP_PACKAGE_NAME}@${latest}`);
|
|
1674
|
+
if (compareSemver(MCP_PACKAGE_VERSION, latest) < 0) {
|
|
1675
|
+
console.log(`This status command is older than latest. Update client configs with: ${MCP_UPDATE_COMMAND}`);
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1388
1678
|
console.log(`Credentials file: ${store.path()}`);
|
|
1389
1679
|
console.log(`API token: ${token ? "present" : "MISSING — run `echomem-mcp login`"}`);
|
|
1390
1680
|
if (token) {
|
|
@@ -1408,6 +1698,14 @@ async function cmdStatus() {
|
|
|
1408
1698
|
console.log(`Encryption key: ${store.getKey() ? "present" : store.isKeyExpired() ? "EXPIRED — run `echomem-mcp unlock`" : "not set"}`);
|
|
1409
1699
|
const detected = detectClients();
|
|
1410
1700
|
console.log(`Detected clients: ${detected.length ? detected.map((c) => c.label).join(", ") : "none auto-detected"}`);
|
|
1701
|
+
const reports = inspectClientConfigs(latest ?? MCP_PACKAGE_VERSION);
|
|
1702
|
+
if (reports.length) {
|
|
1703
|
+
console.log("Client MCP configs:");
|
|
1704
|
+
for (const report of reports) {
|
|
1705
|
+
for (const line of formatClientConfigReport(report))
|
|
1706
|
+
console.log(line);
|
|
1707
|
+
}
|
|
1708
|
+
}
|
|
1411
1709
|
}
|
|
1412
1710
|
function cmdLogout() {
|
|
1413
1711
|
const store = new KeyStore();
|
|
@@ -1430,6 +1728,7 @@ Usage:
|
|
|
1430
1728
|
echomem-mcp login Approve this device in the browser (or --token/--passphrase)
|
|
1431
1729
|
echomem-mcp unlock Re-derive the encryption key after its TTL (or --passphrase)
|
|
1432
1730
|
echomem-mcp status Show token/key/clients
|
|
1731
|
+
echomem-mcp doctor [--no-network] Diagnose configured client bridge versions
|
|
1433
1732
|
echomem-mcp logout Remove stored credentials
|
|
1434
1733
|
echomem-mcp report [--json] Your AI coding memory audit (local, no login, $0)
|
|
1435
1734
|
echomem-mcp migrate [--since D] Import your existing Codex/Claude history into your memory
|
|
@@ -1468,7 +1767,10 @@ export async function runCli(argv) {
|
|
|
1468
1767
|
await cmdUnlock(flags);
|
|
1469
1768
|
return true;
|
|
1470
1769
|
case "status":
|
|
1471
|
-
await cmdStatus();
|
|
1770
|
+
await cmdStatus(flags);
|
|
1771
|
+
return true;
|
|
1772
|
+
case "doctor":
|
|
1773
|
+
await cmdStatus(flags);
|
|
1472
1774
|
return true;
|
|
1473
1775
|
case "logout":
|
|
1474
1776
|
cmdLogout();
|