qwenproxy-cli 1.2.9 → 1.3.0
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/package.json +3 -3
- package/src/api/server.ts +14 -2
- package/src/core/accounts.ts +118 -17
- package/src/core/local-auth.ts +52 -0
- package/src/core/paths.ts +3 -0
- package/src/login.ts +55 -8
- package/src/routes/upload.ts +51 -12
- package/src/services/auth-playwright.ts +5 -28
- package/src/services/media-generation.ts +51 -11
- package/src/services/playwright.ts +279 -95
- package/src/services/qwen.ts +2 -2
- package/src/sync/codex.ts +154 -65
- package/src/sync/index.ts +7 -2
- package/src/tui/server-manager.ts +6 -0
- package/src/tui/views/accounts-view.ts +54 -6
- package/src/tui/views/logs-view.ts +4 -20
- package/src/tui/views/status-view.ts +496 -3
- package/src/tui/views/sync-view.ts +22 -10
- package/src/utils/jwt.ts +40 -0
package/src/sync/codex.ts
CHANGED
|
@@ -1,7 +1,34 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import os from "node:os";
|
|
3
4
|
import type { ClientSyncResult, SyncOptions } from "./types.ts";
|
|
4
5
|
import { createTimestampBackup, restoreFromBackup, formatModelDisplayName } from "./utils.ts";
|
|
6
|
+
import { isRunningUnderNodeTest } from "../core/paths.ts";
|
|
7
|
+
|
|
8
|
+
export function removeTomlSection(content: string, sectionHeader: string): string {
|
|
9
|
+
const lines = content.split(/\r?\n/);
|
|
10
|
+
const result: string[] = [];
|
|
11
|
+
let inTargetSection = false;
|
|
12
|
+
const targetNorm = sectionHeader.replace(/\s+/g, "");
|
|
13
|
+
|
|
14
|
+
for (const line of lines) {
|
|
15
|
+
const trimmed = line.trim();
|
|
16
|
+
if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
|
|
17
|
+
const headerInside = trimmed.slice(1, -1).trim().replace(/\s+/g, "");
|
|
18
|
+
if (headerInside === targetNorm) {
|
|
19
|
+
inTargetSection = true;
|
|
20
|
+
continue;
|
|
21
|
+
} else {
|
|
22
|
+
inTargetSection = false;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
if (!inTargetSection) {
|
|
26
|
+
result.push(line);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return result.join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
31
|
+
}
|
|
5
32
|
|
|
6
33
|
function updateTopLevelKey(content: string, key: string, value: string | number): string {
|
|
7
34
|
// Find first section header [section]
|
|
@@ -22,74 +49,108 @@ function updateTopLevelKey(content: string, key: string, value: string | number)
|
|
|
22
49
|
return newTopPart + restPart;
|
|
23
50
|
}
|
|
24
51
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
52
|
+
function syncCodexSingleFile(
|
|
53
|
+
targetPath: string,
|
|
54
|
+
options: SyncOptions,
|
|
55
|
+
): { backupPath?: string } {
|
|
56
|
+
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
|
29
57
|
|
|
30
|
-
|
|
31
|
-
|
|
58
|
+
let backupPath: string | undefined;
|
|
59
|
+
let content = "";
|
|
32
60
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
61
|
+
if (fs.existsSync(targetPath)) {
|
|
62
|
+
backupPath = createTimestampBackup(targetPath);
|
|
63
|
+
content = fs.readFileSync(targetPath, "utf-8");
|
|
64
|
+
}
|
|
37
65
|
|
|
38
|
-
|
|
39
|
-
|
|
66
|
+
const { apiKey, baseUrl, model = "qwen3.8-max", setActive = true } = options;
|
|
67
|
+
|
|
68
|
+
// Build the qwenproxy model_provider block
|
|
69
|
+
const providerBlock = `[model_providers.qwenproxy]
|
|
40
70
|
name = "QwenProxy"
|
|
41
71
|
base_url = "${baseUrl}"
|
|
42
72
|
wire_api = "responses"
|
|
43
73
|
experimental_bearer_token = "${apiKey}"
|
|
44
74
|
`;
|
|
45
75
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
content = content.replace(providerRegex, providerBlock);
|
|
50
|
-
} else {
|
|
51
|
-
content = content.trimEnd() + (content.length > 0 ? "\n\n" : "") + providerBlock;
|
|
52
|
-
}
|
|
76
|
+
// Clean any previous or duplicate [model_providers.qwenproxy] blocks to guarantee zero duplicates
|
|
77
|
+
content = removeTomlSection(content, "model_providers.qwenproxy");
|
|
78
|
+
content = content.trimEnd() + (content.length > 0 ? "\n\n" : "") + providerBlock;
|
|
53
79
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
80
|
+
// Set active model if requested
|
|
81
|
+
if (setActive) {
|
|
82
|
+
content = updateTopLevelKey(content, "model", model);
|
|
83
|
+
content = updateTopLevelKey(content, "model_provider", "qwenproxy");
|
|
84
|
+
content = updateTopLevelKey(content, "model_context_window", 1000000);
|
|
85
|
+
}
|
|
60
86
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}
|
|
87
|
+
// If a custom model_catalog_json is configured, ensure the target model is listed in it
|
|
88
|
+
const catalogMatch = content.match(/^model_catalog_json\s*=\s*["']([^"']+)["']/m);
|
|
89
|
+
if (catalogMatch && catalogMatch[1]) {
|
|
90
|
+
try {
|
|
91
|
+
const rawCatalogPath = catalogMatch[1];
|
|
92
|
+
const catalogPath = path.isAbsolute(rawCatalogPath)
|
|
93
|
+
? rawCatalogPath
|
|
94
|
+
: path.resolve(path.dirname(targetPath), rawCatalogPath);
|
|
95
|
+
if (fs.existsSync(catalogPath)) {
|
|
96
|
+
const catalog = JSON.parse(fs.readFileSync(catalogPath, "utf-8"));
|
|
97
|
+
if (Array.isArray(catalog.models)) {
|
|
98
|
+
const hasModel = catalog.models.some((m: any) => m?.slug === model);
|
|
99
|
+
if (!hasModel) {
|
|
100
|
+
const template = catalog.models[0] || {};
|
|
101
|
+
catalog.models.unshift({
|
|
102
|
+
...template,
|
|
103
|
+
slug: model,
|
|
104
|
+
display_name: formatModelDisplayName(model),
|
|
105
|
+
description: `QwenProxy ${model}`,
|
|
106
|
+
context_window: 1000000,
|
|
107
|
+
max_context_window: 1000000,
|
|
108
|
+
});
|
|
109
|
+
fs.writeFileSync(catalogPath, JSON.stringify(catalog, null, 2), "utf-8");
|
|
85
110
|
}
|
|
86
111
|
}
|
|
87
|
-
} catch {
|
|
88
|
-
// Non-fatal catalog enhancement
|
|
89
112
|
}
|
|
113
|
+
} catch {
|
|
114
|
+
// Non-fatal catalog enhancement
|
|
90
115
|
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
fs.writeFileSync(targetPath, content.trimEnd() + "\n", "utf-8");
|
|
119
|
+
return { backupPath };
|
|
120
|
+
}
|
|
91
121
|
|
|
92
|
-
|
|
122
|
+
export function syncCodex(options: SyncOptions): ClientSyncResult {
|
|
123
|
+
const { filePath, baseUrl } = options;
|
|
124
|
+
try {
|
|
125
|
+
const { backupPath } = syncCodexSingleFile(filePath, options);
|
|
126
|
+
|
|
127
|
+
// If an alternate Codex config exists (e.g. ~/.codex/config.toml vs CODEX_HOME/config.toml),
|
|
128
|
+
// sync it as well so both standard CLI and custom launcher environments are always synchronized.
|
|
129
|
+
const home = os.homedir();
|
|
130
|
+
const standardPath = path.join(home, ".codex", "config.toml");
|
|
131
|
+
const codexHomePath = process.env.CODEX_HOME
|
|
132
|
+
? path.join(process.env.CODEX_HOME, "config.toml")
|
|
133
|
+
: undefined;
|
|
134
|
+
|
|
135
|
+
const isStandard =
|
|
136
|
+
path.resolve(filePath).toLowerCase() === path.resolve(standardPath).toLowerCase();
|
|
137
|
+
const isCodexHome =
|
|
138
|
+
Boolean(codexHomePath) &&
|
|
139
|
+
path.resolve(filePath).toLowerCase() === path.resolve(codexHomePath!).toLowerCase();
|
|
140
|
+
|
|
141
|
+
if ((isStandard || isCodexHome) && !isRunningUnderNodeTest()) {
|
|
142
|
+
const alternatePath = isStandard ? codexHomePath : standardPath;
|
|
143
|
+
if (
|
|
144
|
+
alternatePath &&
|
|
145
|
+
path.resolve(alternatePath).toLowerCase() !== path.resolve(filePath).toLowerCase()
|
|
146
|
+
) {
|
|
147
|
+
try {
|
|
148
|
+
if (fs.existsSync(alternatePath) || fs.existsSync(path.dirname(alternatePath))) {
|
|
149
|
+
syncCodexSingleFile(alternatePath, options);
|
|
150
|
+
}
|
|
151
|
+
} catch {}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
93
154
|
|
|
94
155
|
return {
|
|
95
156
|
client: "codex",
|
|
@@ -116,20 +177,48 @@ export function restoreCodex(filePath: string, backupPath?: string): ClientSyncR
|
|
|
116
177
|
// If backup was restored but still had qwenproxy (or if no backup was found),
|
|
117
178
|
// strip the QwenProxy provider block cleanly so the file is guaranteed un-synced.
|
|
118
179
|
let manuallyCleaned = false;
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
content = content
|
|
180
|
+
const cleanSingleFile = (p: string) => {
|
|
181
|
+
if (fs.existsSync(p)) {
|
|
182
|
+
try {
|
|
183
|
+
let content = fs.readFileSync(p, "utf-8");
|
|
184
|
+
if (
|
|
185
|
+
content.includes("[model_providers.qwenproxy]") ||
|
|
186
|
+
/^model_provider\s*=\s*["']qwenproxy["']/m.test(content)
|
|
187
|
+
) {
|
|
188
|
+
content = removeTomlSection(content, "model_providers.qwenproxy");
|
|
189
|
+
content = content.replace(/^model_provider\s*=\s*["']qwenproxy["']\r?\n?/m, "");
|
|
190
|
+
if (/^model\s*=\s*["']qwen/m.test(content)) {
|
|
191
|
+
content = content.replace(/^model\s*=\s*["']qwen[^"']*["']\r?\n?/m, "");
|
|
192
|
+
}
|
|
193
|
+
fs.writeFileSync(p, content.trimEnd() + "\n", "utf-8");
|
|
194
|
+
manuallyCleaned = true;
|
|
128
195
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
196
|
+
} catch {}
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
cleanSingleFile(filePath);
|
|
201
|
+
|
|
202
|
+
const home = os.homedir();
|
|
203
|
+
const standardPath = path.join(home, ".codex", "config.toml");
|
|
204
|
+
const codexHomePath = process.env.CODEX_HOME
|
|
205
|
+
? path.join(process.env.CODEX_HOME, "config.toml")
|
|
206
|
+
: undefined;
|
|
207
|
+
|
|
208
|
+
const isStandard =
|
|
209
|
+
path.resolve(filePath).toLowerCase() === path.resolve(standardPath).toLowerCase();
|
|
210
|
+
const isCodexHome =
|
|
211
|
+
Boolean(codexHomePath) &&
|
|
212
|
+
path.resolve(filePath).toLowerCase() === path.resolve(codexHomePath!).toLowerCase();
|
|
213
|
+
|
|
214
|
+
if ((isStandard || isCodexHome) && !isRunningUnderNodeTest()) {
|
|
215
|
+
const alternatePath = isStandard ? codexHomePath : standardPath;
|
|
216
|
+
if (
|
|
217
|
+
alternatePath &&
|
|
218
|
+
path.resolve(alternatePath).toLowerCase() !== path.resolve(filePath).toLowerCase()
|
|
219
|
+
) {
|
|
220
|
+
cleanSingleFile(alternatePath);
|
|
221
|
+
}
|
|
133
222
|
}
|
|
134
223
|
|
|
135
224
|
const success = restoredFromBackup || manuallyCleaned;
|
package/src/sync/index.ts
CHANGED
|
@@ -64,7 +64,10 @@ export function resolveApiKey(
|
|
|
64
64
|
}
|
|
65
65
|
return overrideKey.trim();
|
|
66
66
|
}
|
|
67
|
-
const envKey =
|
|
67
|
+
const envKey =
|
|
68
|
+
(!isPlaceholderApiKey(process.env.API_KEY) ? process.env.API_KEY : undefined) ||
|
|
69
|
+
(!isPlaceholderApiKey(process.env.ADMIN_PASSWORD) ? process.env.ADMIN_PASSWORD : undefined) ||
|
|
70
|
+
configKey;
|
|
68
71
|
if (envKey && !isPlaceholderApiKey(envKey)) {
|
|
69
72
|
return envKey.trim();
|
|
70
73
|
}
|
|
@@ -165,7 +168,9 @@ export function getDefaultPaths(): {
|
|
|
165
168
|
|
|
166
169
|
return {
|
|
167
170
|
claudeCode: path.join(home, ".claude", "settings.json"),
|
|
168
|
-
codex:
|
|
171
|
+
codex: fs.existsSync(path.join(home, ".codex", "config.toml"))
|
|
172
|
+
? path.join(home, ".codex", "config.toml")
|
|
173
|
+
: process.env.CODEX_HOME
|
|
169
174
|
? path.join(process.env.CODEX_HOME, "config.toml")
|
|
170
175
|
: path.join(home, ".codex", "config.toml"),
|
|
171
176
|
openCode: existingOpenCode || openCodeCandidates[0],
|
|
@@ -282,10 +282,16 @@ export class ServerManager {
|
|
|
282
282
|
}
|
|
283
283
|
this.restoreLogs();
|
|
284
284
|
try {
|
|
285
|
+
await stopServer();
|
|
285
286
|
this.state = "offline";
|
|
286
287
|
} catch {}
|
|
287
288
|
}
|
|
288
289
|
|
|
290
|
+
public async restart(): Promise<void> {
|
|
291
|
+
await this.stop();
|
|
292
|
+
await this.ensureStarted();
|
|
293
|
+
}
|
|
294
|
+
|
|
289
295
|
public startRemoteLogStream(host: string, port: number): void {
|
|
290
296
|
if (this.remoteLogAbort) {
|
|
291
297
|
this.remoteLogAbort.abort();
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* QwenProxy TUI - Accounts & Cooldowns Management View (Tab 5)
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import crypto from "crypto";
|
|
5
6
|
import type { TuiView, ProxyStatusSnapshot } from "../types.ts";
|
|
6
7
|
import type { KeyEvent } from "../screen.ts";
|
|
7
8
|
import { theme, glyphs, drawBox, pad, truncate, getClipboardText } from "../theme.ts";
|
|
@@ -56,6 +57,7 @@ export class AccountsView implements TuiView {
|
|
|
56
57
|
private statusMessageTimer: NodeJS.Timeout | null = null;
|
|
57
58
|
private isAddModalOpen = false;
|
|
58
59
|
private isBatchModalOpen = false;
|
|
60
|
+
private isValidatingAccount = false;
|
|
59
61
|
private addEmailInput = "";
|
|
60
62
|
private addPasswordInput = "";
|
|
61
63
|
private addEmailCursor = 0;
|
|
@@ -147,6 +149,7 @@ export class AccountsView implements TuiView {
|
|
|
147
149
|
}
|
|
148
150
|
|
|
149
151
|
private async saveModalAccount(): Promise<void> {
|
|
152
|
+
if (this.isValidatingAccount) return;
|
|
150
153
|
const email = this.addEmailInput.trim();
|
|
151
154
|
const password = this.addPasswordInput.trim();
|
|
152
155
|
if (!email || !password) {
|
|
@@ -154,17 +157,54 @@ export class AccountsView implements TuiView {
|
|
|
154
157
|
return;
|
|
155
158
|
}
|
|
156
159
|
|
|
160
|
+
if (process.env.NODE_TEST_CONTEXT) {
|
|
161
|
+
try {
|
|
162
|
+
const newAcc = addAccount(email, password);
|
|
163
|
+
this.isAddModalOpen = false;
|
|
164
|
+
this.addEmailInput = "";
|
|
165
|
+
this.addPasswordInput = "";
|
|
166
|
+
this.addEmailCursor = 0;
|
|
167
|
+
this.addPasswordCursor = 0;
|
|
168
|
+
await this.refresh();
|
|
169
|
+
this.setStatusMessage(theme.green(`✓ Conta ${email} salva! Conectando...`));
|
|
170
|
+
} catch (err: any) {
|
|
171
|
+
this.setStatusMessage(theme.red(`✗ Erro ao salvar: ${err?.message || String(err)}`));
|
|
172
|
+
}
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
this.isValidatingAccount = true;
|
|
177
|
+
this.setStatusMessage(theme.yellow(`[...] Validando credenciais para ${email}...`));
|
|
178
|
+
|
|
179
|
+
const tempId = crypto.randomUUID();
|
|
157
180
|
try {
|
|
158
|
-
const
|
|
181
|
+
const { validateAccountLogin } = await import("../../services/playwright.ts");
|
|
182
|
+
const { wipeAccountSessionFiles } = await import("../../core/accounts.ts");
|
|
183
|
+
const { clearAccountCooldown } = await import("../../core/account-manager.ts");
|
|
184
|
+
|
|
185
|
+
const ok = await validateAccountLogin(
|
|
186
|
+
{ id: tempId, email, password },
|
|
187
|
+
config.playwright.headless,
|
|
188
|
+
config.playwright.browser,
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
if (!ok) {
|
|
192
|
+
wipeAccountSessionFiles(tempId);
|
|
193
|
+
clearAccountCooldown(tempId);
|
|
194
|
+
this.setStatusMessage(theme.red(`✗ Falha no login: credenciais inválidas ou bloqueio no Qwen`));
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const newAcc = addAccount(email, password, tempId);
|
|
159
199
|
this.isAddModalOpen = false;
|
|
160
200
|
this.addEmailInput = "";
|
|
161
201
|
this.addPasswordInput = "";
|
|
162
202
|
this.addEmailCursor = 0;
|
|
163
203
|
this.addPasswordCursor = 0;
|
|
164
204
|
await this.refresh();
|
|
165
|
-
this.setStatusMessage(theme.green(`✓ Conta ${email} salva! Conectando...`));
|
|
205
|
+
this.setStatusMessage(theme.green(`✓ Conta ${email} validada e salva! Conectando...`));
|
|
166
206
|
|
|
167
|
-
if (process.stdout.isTTY
|
|
207
|
+
if (process.stdout.isTTY) {
|
|
168
208
|
const sManager = ServerManager.getInstance();
|
|
169
209
|
const sState = sManager.getState();
|
|
170
210
|
if (sState !== "online" && sState !== "warming") {
|
|
@@ -189,7 +229,13 @@ export class AccountsView implements TuiView {
|
|
|
189
229
|
}
|
|
190
230
|
}
|
|
191
231
|
} catch (err: any) {
|
|
192
|
-
|
|
232
|
+
const { wipeAccountSessionFiles } = await import("../../core/accounts.ts");
|
|
233
|
+
const { clearAccountCooldown } = await import("../../core/account-manager.ts");
|
|
234
|
+
wipeAccountSessionFiles(tempId);
|
|
235
|
+
clearAccountCooldown(tempId);
|
|
236
|
+
this.setStatusMessage(theme.red(`✗ Erro ao validar: ${err?.message || String(err)}`));
|
|
237
|
+
} finally {
|
|
238
|
+
this.isValidatingAccount = false;
|
|
193
239
|
}
|
|
194
240
|
}
|
|
195
241
|
private async saveBatchAccounts(): Promise<void> {
|
|
@@ -445,6 +491,7 @@ export class AccountsView implements TuiView {
|
|
|
445
491
|
|
|
446
492
|
// 1. Add Account Modal Active
|
|
447
493
|
if (this.isAddModalOpen) {
|
|
494
|
+
if (this.isValidatingAccount) return true;
|
|
448
495
|
if (key.name === "escape") {
|
|
449
496
|
this.isAddModalOpen = false;
|
|
450
497
|
this.addEmailInput = "";
|
|
@@ -1161,8 +1208,9 @@ export class AccountsView implements TuiView {
|
|
|
1161
1208
|
? (passHover ? theme.bgHover(` ${maskedPass} `) : theme.cyan(` ${maskedPass} `))
|
|
1162
1209
|
: (passHover ? theme.bgHover(" (digite a senha) ") : theme.muted(" (digite a senha) "));
|
|
1163
1210
|
}
|
|
1164
|
-
const saveBtn =
|
|
1165
|
-
|
|
1211
|
+
const saveBtn = this.isValidatingAccount
|
|
1212
|
+
? theme.yellow(" [ Validando... ] ")
|
|
1213
|
+
: this.modalHoveredField === "save"
|
|
1166
1214
|
? theme.bgHover(theme.green(" [ Enter ] Salvar "))
|
|
1167
1215
|
: theme.green("[ Enter ] Salvar");
|
|
1168
1216
|
|
|
@@ -469,26 +469,10 @@ export class LogsView implements TuiView {
|
|
|
469
469
|
if (singleLineOnly && this.selectedLogIndex !== null && rawEntries[this.selectedLogIndex]) {
|
|
470
470
|
const entry = rawEntries[this.selectedLogIndex];
|
|
471
471
|
text = `[${entry.time}] [${entry.level}] ${entry.message}`;
|
|
472
|
-
} else {
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
const logPath = getServerLogFilePath();
|
|
477
|
-
if (fs.existsSync(logPath)) {
|
|
478
|
-
const diskContent = fs.readFileSync(logPath, "utf-8");
|
|
479
|
-
if (diskContent && diskContent.trim().length > 0) {
|
|
480
|
-
text = diskContent.trim();
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
} catch {}
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
// Fallback to memory buffer if disk log is empty or on filtered view
|
|
487
|
-
if (!text && rawEntries.length > 0) {
|
|
488
|
-
text = rawEntries
|
|
489
|
-
.map((entry) => `[${entry.time}] [${entry.level}] ${entry.message}`)
|
|
490
|
-
.join("\n");
|
|
491
|
-
}
|
|
472
|
+
} else if (rawEntries.length > 0) {
|
|
473
|
+
text = rawEntries
|
|
474
|
+
.map((entry) => `[${entry.time}] [${entry.level}] ${entry.message}`)
|
|
475
|
+
.join("\n");
|
|
492
476
|
}
|
|
493
477
|
|
|
494
478
|
if (!text) return;
|