@frumu/tandem-panel 0.4.6 → 0.4.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/.env.example +11 -0
- package/README.md +36 -2
- package/bin/setup.js +207 -3
- package/dist/assets/index-BIn54WIy.js +2460 -0
- package/dist/assets/index-CCT37xUj.css +1 -0
- package/dist/assets/{markdown-CscybTAD.js → markdown-DMcD1LHz.js} +13 -13
- package/dist/assets/{motion-1UTHZOUD.js → motion-BCvrfAt1.js} +2 -2
- package/dist/assets/preact-vendor-jo0muZ28.js +1 -0
- package/dist/assets/{react-query-tfsRN0su.js → react-query-CeeFMKtE.js} +1 -1
- package/dist/assets/vendor-UXzYZoAT.js +180 -0
- package/dist/index.html +7 -7
- package/package.json +5 -4
- package/dist/assets/index-BklSdD7w.js +0 -558
- package/dist/assets/index-CGq1XaYq.css +0 -1
- package/dist/assets/preact-vendor-CErzQRjo.js +0 -1
- package/dist/assets/vendor-B5SLbWPm.js +0 -42
package/.env.example
CHANGED
|
@@ -36,6 +36,17 @@ TANDEM_PROVIDER_STREAM_IDLE_TIMEOUT_MS=90000
|
|
|
36
36
|
TANDEM_PERMISSION_WAIT_TIMEOUT_MS=15000
|
|
37
37
|
TANDEM_TOOL_EXEC_TIMEOUT_MS=45000
|
|
38
38
|
TANDEM_BASH_TIMEOUT_MS=30000
|
|
39
|
+
|
|
40
|
+
# Built-in websearch defaults. `auto` can try configured backends in order
|
|
41
|
+
# (managed Tandem URL, SearxNG, Brave, Exa) before falling back.
|
|
42
|
+
TANDEM_SEARCH_BACKEND=auto
|
|
43
|
+
TANDEM_SEARCH_URL=https://search.tandem.frumu.ai
|
|
44
|
+
TANDEM_SEARCH_TIMEOUT_MS=10000
|
|
45
|
+
# Optional direct-provider overrides
|
|
46
|
+
# TANDEM_BRAVE_SEARCH_API_KEY=
|
|
47
|
+
# TANDEM_EXA_API_KEY=
|
|
48
|
+
# TANDEM_SEARXNG_URL=http://127.0.0.1:8080
|
|
49
|
+
|
|
39
50
|
# Bug Monitor (disabled by default)
|
|
40
51
|
# TANDEM_BUG_MONITOR_ENABLED=0
|
|
41
52
|
# TANDEM_BUG_MONITOR_REPO=owner/repo
|
package/README.md
CHANGED
|
@@ -8,6 +8,16 @@ Full web control center for Tandem Engine (non-desktop entry point).
|
|
|
8
8
|
npm i -g @frumu/tandem-panel
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
+
## Editable App Scaffold
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm create tandem-panel@latest my-panel
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Use the global install when you want the official ready-to-run panel.
|
|
18
|
+
|
|
19
|
+
Use the scaffold when you want the actual app source in your own folder so you can edit routes, pages, themes, styles, and runtime behavior without customizing files inside `node_modules`.
|
|
20
|
+
|
|
11
21
|
## Official Bootstrap
|
|
12
22
|
|
|
13
23
|
```bash
|
|
@@ -85,6 +95,15 @@ Variables:
|
|
|
85
95
|
- `TANDEM_CONTROL_PANEL_ENGINE_TOKEN` (token injected when panel auto-starts engine)
|
|
86
96
|
- `TANDEM_API_TOKEN` (backward-compatible alias for engine token)
|
|
87
97
|
- `TANDEM_CONTROL_PANEL_SESSION_TTL_MINUTES` (default `1440`)
|
|
98
|
+
- `TANDEM_SEARCH_BACKEND` (`auto`, `tandem`, `brave`, `exa`, `searxng`, or `none`; default official installs use `auto`)
|
|
99
|
+
- `TANDEM_SEARCH_URL` (hosted Tandem search endpoint or compatible router URL)
|
|
100
|
+
- `TANDEM_SEARCH_TIMEOUT_MS` (default `10000`)
|
|
101
|
+
- `TANDEM_BRAVE_SEARCH_API_KEY` (optional direct Brave override when `TANDEM_SEARCH_BACKEND=brave`)
|
|
102
|
+
- `TANDEM_EXA_API_KEY` (optional direct Exa override when `TANDEM_SEARCH_BACKEND=exa`)
|
|
103
|
+
- `TANDEM_SEARXNG_URL` (optional self-hosted override when `TANDEM_SEARCH_BACKEND=searxng`)
|
|
104
|
+
|
|
105
|
+
The desktop app now exposes these search settings directly in Settings, and the control panel exposes them under Settings -> Web Search when it is connected to a local engine host.
|
|
106
|
+
|
|
88
107
|
- `TANDEM_DISABLE_TOOL_GUARD_BUDGETS` (`1` disables per-run guard budgets; default in installer/service env is `1`)
|
|
89
108
|
- `TANDEM_PROMPT_CONTEXT_HOOK_TIMEOUT_MS` (default `5000`)
|
|
90
109
|
- `TANDEM_PROVIDER_STREAM_CONNECT_TIMEOUT_MS` (default `30000`)
|
|
@@ -144,17 +163,32 @@ npm run build
|
|
|
144
163
|
|
|
145
164
|
### Repo Source Workflow (No Global npm Install)
|
|
146
165
|
|
|
147
|
-
If you run from
|
|
166
|
+
If you run from the repo root, use:
|
|
148
167
|
|
|
149
168
|
```bash
|
|
150
169
|
node packages/tandem-control-panel/bin/cli.js init --no-service
|
|
151
170
|
node packages/tandem-control-panel/bin/cli.js run
|
|
152
171
|
```
|
|
153
172
|
|
|
154
|
-
|
|
173
|
+
If you are already inside `packages/tandem-control-panel`, use:
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
node bin/cli.js init --no-service
|
|
177
|
+
node bin/cli.js run
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Service install/ops from source from the repo root:
|
|
155
181
|
|
|
156
182
|
```bash
|
|
157
183
|
sudo node packages/tandem-control-panel/bin/cli.js service install
|
|
158
184
|
node packages/tandem-control-panel/bin/cli.js service status
|
|
159
185
|
sudo node packages/tandem-control-panel/bin/cli.js service restart
|
|
160
186
|
```
|
|
187
|
+
|
|
188
|
+
Service install/ops from inside `packages/tandem-control-panel`:
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
sudo node bin/cli.js service install
|
|
192
|
+
node bin/cli.js service status
|
|
193
|
+
sudo node bin/cli.js service restart
|
|
194
|
+
```
|
package/bin/setup.js
CHANGED
|
@@ -34,6 +34,10 @@ function parseDotEnv(content) {
|
|
|
34
34
|
return out;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
function serializeEnv(entries) {
|
|
38
|
+
return `${entries.map(([key, value]) => `${key}=${value}`).join("\n")}\n`;
|
|
39
|
+
}
|
|
40
|
+
|
|
37
41
|
function loadDotEnvFile(pathname) {
|
|
38
42
|
if (!existsSync(pathname)) return false;
|
|
39
43
|
const parsed = parseDotEnv(readFileSync(pathname, "utf8"));
|
|
@@ -43,6 +47,21 @@ function loadDotEnvFile(pathname) {
|
|
|
43
47
|
return true;
|
|
44
48
|
}
|
|
45
49
|
|
|
50
|
+
function posixHomeForUser(username) {
|
|
51
|
+
const name = String(username || "").trim();
|
|
52
|
+
if (!name) return homedir();
|
|
53
|
+
try {
|
|
54
|
+
const passwd = readFileSync("/etc/passwd", "utf8");
|
|
55
|
+
for (const line of passwd.split(/\r?\n/)) {
|
|
56
|
+
if (!line || line.startsWith("#")) continue;
|
|
57
|
+
const parts = line.split(":");
|
|
58
|
+
if (parts[0] === name && parts[5]) return parts[5];
|
|
59
|
+
}
|
|
60
|
+
} catch {}
|
|
61
|
+
if (process.env.USER === name || process.env.SUDO_USER === name) return homedir();
|
|
62
|
+
return resolve("/home", name);
|
|
63
|
+
}
|
|
64
|
+
|
|
46
65
|
function parseCliArgs(argv) {
|
|
47
66
|
const flags = new Set();
|
|
48
67
|
const values = new Map();
|
|
@@ -158,6 +177,9 @@ const ENGINE_PORT = Number.parseInt(process.env.TANDEM_ENGINE_PORT || "39731", 1
|
|
|
158
177
|
const ENGINE_URL = (
|
|
159
178
|
process.env.TANDEM_ENGINE_URL || `http://${ENGINE_HOST}:${ENGINE_PORT}`
|
|
160
179
|
).replace(/\/+$/, "");
|
|
180
|
+
const DEFAULT_TANDEM_SEARCH_URL = (
|
|
181
|
+
process.env.TANDEM_SEARCH_URL || "https://search.tandem.frumu.ai"
|
|
182
|
+
).replace(/\/+$/, "");
|
|
161
183
|
const SWARM_RUNS_PATH = resolve(homedir(), ".tandem", "control-panel", "swarm-runs.json");
|
|
162
184
|
const SWARM_HIDDEN_RUNS_PATH = resolve(
|
|
163
185
|
homedir(),
|
|
@@ -704,7 +726,8 @@ async function installServices() {
|
|
|
704
726
|
const serviceGroup = serviceUser;
|
|
705
727
|
const installEngine = serviceMode === "both" || serviceMode === "engine";
|
|
706
728
|
const installPanel = serviceMode === "both" || serviceMode === "panel";
|
|
707
|
-
const
|
|
729
|
+
const defaultStateDir = resolve(posixHomeForUser(serviceUser), ".local", "share", "tandem");
|
|
730
|
+
const stateDir = String(process.env.TANDEM_HOME || process.env.TANDEM_STATE_DIR || defaultStateDir).trim();
|
|
708
731
|
const engineEnvPath = "/etc/tandem/engine.env";
|
|
709
732
|
const panelEnvPath = "/etc/tandem/control-panel.env";
|
|
710
733
|
const engineServiceName = "tandem-engine";
|
|
@@ -728,11 +751,48 @@ async function installServices() {
|
|
|
728
751
|
const existingEngineEnv = existsSync(engineEnvPath)
|
|
729
752
|
? parseDotEnv(readFileSync(engineEnvPath, "utf8"))
|
|
730
753
|
: {};
|
|
754
|
+
const { TANDEM_MEMORY_DB_PATH: _legacyMemoryDbPath, ...engineEnvBase } = existingEngineEnv;
|
|
755
|
+
const searchEnv =
|
|
756
|
+
existingEngineEnv.TANDEM_SEARCH_BACKEND ||
|
|
757
|
+
existingEngineEnv.TANDEM_SEARCH_URL ||
|
|
758
|
+
existingEngineEnv.TANDEM_SEARCH_TIMEOUT_MS ||
|
|
759
|
+
existingEngineEnv.TANDEM_BRAVE_SEARCH_API_KEY ||
|
|
760
|
+
existingEngineEnv.BRAVE_SEARCH_API_KEY ||
|
|
761
|
+
existingEngineEnv.TANDEM_EXA_API_KEY ||
|
|
762
|
+
existingEngineEnv.EXA_API_KEY ||
|
|
763
|
+
existingEngineEnv.TANDEM_SEARXNG_URL
|
|
764
|
+
? {
|
|
765
|
+
...(existingEngineEnv.TANDEM_SEARCH_BACKEND
|
|
766
|
+
? { TANDEM_SEARCH_BACKEND: existingEngineEnv.TANDEM_SEARCH_BACKEND }
|
|
767
|
+
: {}),
|
|
768
|
+
...(existingEngineEnv.TANDEM_SEARCH_URL
|
|
769
|
+
? { TANDEM_SEARCH_URL: existingEngineEnv.TANDEM_SEARCH_URL }
|
|
770
|
+
: {}),
|
|
771
|
+
...(existingEngineEnv.TANDEM_SEARCH_TIMEOUT_MS
|
|
772
|
+
? { TANDEM_SEARCH_TIMEOUT_MS: existingEngineEnv.TANDEM_SEARCH_TIMEOUT_MS }
|
|
773
|
+
: {}),
|
|
774
|
+
...(existingEngineEnv.TANDEM_BRAVE_SEARCH_API_KEY
|
|
775
|
+
? { TANDEM_BRAVE_SEARCH_API_KEY: existingEngineEnv.TANDEM_BRAVE_SEARCH_API_KEY }
|
|
776
|
+
: {}),
|
|
777
|
+
...(existingEngineEnv.BRAVE_SEARCH_API_KEY
|
|
778
|
+
? { BRAVE_SEARCH_API_KEY: existingEngineEnv.BRAVE_SEARCH_API_KEY }
|
|
779
|
+
: {}),
|
|
780
|
+
...(existingEngineEnv.TANDEM_EXA_API_KEY
|
|
781
|
+
? { TANDEM_EXA_API_KEY: existingEngineEnv.TANDEM_EXA_API_KEY }
|
|
782
|
+
: {}),
|
|
783
|
+
...(existingEngineEnv.EXA_API_KEY
|
|
784
|
+
? { EXA_API_KEY: existingEngineEnv.EXA_API_KEY }
|
|
785
|
+
: {}),
|
|
786
|
+
...(existingEngineEnv.TANDEM_SEARXNG_URL
|
|
787
|
+
? { TANDEM_SEARXNG_URL: existingEngineEnv.TANDEM_SEARXNG_URL }
|
|
788
|
+
: {}),
|
|
789
|
+
}
|
|
790
|
+
: {};
|
|
731
791
|
const engineEnv = {
|
|
732
|
-
...
|
|
792
|
+
...engineEnvBase,
|
|
733
793
|
TANDEM_API_TOKEN: token,
|
|
734
794
|
TANDEM_STATE_DIR: stateDir,
|
|
735
|
-
|
|
795
|
+
...searchEnv,
|
|
736
796
|
TANDEM_ENABLE_GLOBAL_MEMORY: existingEngineEnv.TANDEM_ENABLE_GLOBAL_MEMORY || "1",
|
|
737
797
|
TANDEM_DISABLE_TOOL_GUARD_BUDGETS: existingEngineEnv.TANDEM_DISABLE_TOOL_GUARD_BUDGETS || "1",
|
|
738
798
|
TANDEM_TOOL_ROUTER_ENABLED: existingEngineEnv.TANDEM_TOOL_ROUTER_ENABLED || "0",
|
|
@@ -951,6 +1011,127 @@ async function readJsonBody(req) {
|
|
|
951
1011
|
return JSON.parse(raw);
|
|
952
1012
|
}
|
|
953
1013
|
|
|
1014
|
+
function normalizeSearchBackend(raw) {
|
|
1015
|
+
switch (String(raw || "").trim().toLowerCase()) {
|
|
1016
|
+
case "":
|
|
1017
|
+
case "auto":
|
|
1018
|
+
return "auto";
|
|
1019
|
+
case "tandem":
|
|
1020
|
+
case "brave":
|
|
1021
|
+
case "exa":
|
|
1022
|
+
case "searxng":
|
|
1023
|
+
return String(raw).trim().toLowerCase();
|
|
1024
|
+
case "none":
|
|
1025
|
+
case "disabled":
|
|
1026
|
+
return "none";
|
|
1027
|
+
default:
|
|
1028
|
+
return "auto";
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
function normalizeSearchUrl(raw) {
|
|
1033
|
+
const value = String(raw || "").trim().replace(/\/+$/, "");
|
|
1034
|
+
return value || "";
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
function getManagedEngineEnvPath() {
|
|
1038
|
+
return "/etc/tandem/engine.env";
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
function readManagedSearchSettings() {
|
|
1042
|
+
const envPath = getManagedEngineEnvPath();
|
|
1043
|
+
const localEngine = isLocalEngineUrl(ENGINE_URL);
|
|
1044
|
+
const env = existsSync(envPath) ? parseDotEnv(readFileSync(envPath, "utf8")) : {};
|
|
1045
|
+
const timeoutRaw = Number.parseInt(String(env.TANDEM_SEARCH_TIMEOUT_MS || "10000"), 10);
|
|
1046
|
+
const timeoutMs = Number.isFinite(timeoutRaw)
|
|
1047
|
+
? Math.min(Math.max(timeoutRaw, 1000), 120000)
|
|
1048
|
+
: 10000;
|
|
1049
|
+
return {
|
|
1050
|
+
available: localEngine,
|
|
1051
|
+
local_engine: localEngine,
|
|
1052
|
+
writable: localEngine,
|
|
1053
|
+
managed_env_path: envPath,
|
|
1054
|
+
restart_required: false,
|
|
1055
|
+
restart_hint: "Restart tandem-engine after saving search settings.",
|
|
1056
|
+
settings: {
|
|
1057
|
+
backend: normalizeSearchBackend(env.TANDEM_SEARCH_BACKEND || "auto"),
|
|
1058
|
+
tandem_url: normalizeSearchUrl(env.TANDEM_SEARCH_URL || ""),
|
|
1059
|
+
searxng_url: normalizeSearchUrl(env.TANDEM_SEARXNG_URL || ""),
|
|
1060
|
+
timeout_ms: timeoutMs,
|
|
1061
|
+
has_brave_key: !!String(
|
|
1062
|
+
env.TANDEM_BRAVE_SEARCH_API_KEY || env.BRAVE_SEARCH_API_KEY || ""
|
|
1063
|
+
).trim(),
|
|
1064
|
+
has_exa_key: !!String(env.TANDEM_EXA_API_KEY || env.EXA_API_KEY || "").trim(),
|
|
1065
|
+
},
|
|
1066
|
+
reason: localEngine
|
|
1067
|
+
? ""
|
|
1068
|
+
: "Search settings can only be edited here when the control panel points at a local engine host.",
|
|
1069
|
+
};
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
async function writeManagedSearchSettings(payload = {}) {
|
|
1073
|
+
const current = readManagedSearchSettings();
|
|
1074
|
+
if (!current.local_engine) {
|
|
1075
|
+
const error = new Error(current.reason || "Search settings are not editable for this engine.");
|
|
1076
|
+
error.statusCode = 400;
|
|
1077
|
+
throw error;
|
|
1078
|
+
}
|
|
1079
|
+
const envPath = current.managed_env_path;
|
|
1080
|
+
const existingEnv = existsSync(envPath) ? parseDotEnv(readFileSync(envPath, "utf8")) : {};
|
|
1081
|
+
const nextEnv = { ...existingEnv };
|
|
1082
|
+
|
|
1083
|
+
nextEnv.TANDEM_SEARCH_BACKEND = normalizeSearchBackend(payload.backend || "auto");
|
|
1084
|
+
const timeoutRaw = Number.parseInt(String(payload.timeout_ms || payload.timeoutMs || "10000"), 10);
|
|
1085
|
+
nextEnv.TANDEM_SEARCH_TIMEOUT_MS = String(
|
|
1086
|
+
Number.isFinite(timeoutRaw) ? Math.min(Math.max(timeoutRaw, 1000), 120000) : 10000
|
|
1087
|
+
);
|
|
1088
|
+
|
|
1089
|
+
const tandemUrl = normalizeSearchUrl(payload.tandem_url || payload.tandemUrl || "");
|
|
1090
|
+
if (tandemUrl) nextEnv.TANDEM_SEARCH_URL = tandemUrl;
|
|
1091
|
+
else delete nextEnv.TANDEM_SEARCH_URL;
|
|
1092
|
+
|
|
1093
|
+
const searxngUrl = normalizeSearchUrl(payload.searxng_url || payload.searxngUrl || "");
|
|
1094
|
+
if (searxngUrl) nextEnv.TANDEM_SEARXNG_URL = searxngUrl;
|
|
1095
|
+
else delete nextEnv.TANDEM_SEARXNG_URL;
|
|
1096
|
+
|
|
1097
|
+
const braveKey = String(payload.brave_api_key || payload.braveApiKey || "").trim();
|
|
1098
|
+
if (braveKey) nextEnv.TANDEM_BRAVE_SEARCH_API_KEY = braveKey;
|
|
1099
|
+
else if (payload.clear_brave_key || payload.clearBraveKey) {
|
|
1100
|
+
delete nextEnv.TANDEM_BRAVE_SEARCH_API_KEY;
|
|
1101
|
+
delete nextEnv.BRAVE_SEARCH_API_KEY;
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
const exaKey = String(payload.exa_api_key || payload.exaApiKey || "").trim();
|
|
1105
|
+
if (exaKey) nextEnv.TANDEM_EXA_API_KEY = exaKey;
|
|
1106
|
+
else if (payload.clear_exa_key || payload.clearExaKey) {
|
|
1107
|
+
delete nextEnv.TANDEM_EXA_API_KEY;
|
|
1108
|
+
delete nextEnv.EXA_API_KEY;
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
const preferredKeys = [
|
|
1112
|
+
"TANDEM_API_TOKEN",
|
|
1113
|
+
"TANDEM_STATE_DIR",
|
|
1114
|
+
"TANDEM_SEARCH_BACKEND",
|
|
1115
|
+
"TANDEM_SEARCH_URL",
|
|
1116
|
+
"TANDEM_SEARXNG_URL",
|
|
1117
|
+
"TANDEM_SEARCH_TIMEOUT_MS",
|
|
1118
|
+
"TANDEM_BRAVE_SEARCH_API_KEY",
|
|
1119
|
+
"TANDEM_EXA_API_KEY",
|
|
1120
|
+
];
|
|
1121
|
+
const ordered = [];
|
|
1122
|
+
for (const key of preferredKeys) {
|
|
1123
|
+
if (nextEnv[key] !== undefined) ordered.push([key, nextEnv[key]]);
|
|
1124
|
+
}
|
|
1125
|
+
for (const [key, value] of Object.entries(nextEnv)) {
|
|
1126
|
+
if (!preferredKeys.includes(key)) ordered.push([key, value]);
|
|
1127
|
+
}
|
|
1128
|
+
await writeFile(envPath, serializeEnv(ordered), "utf8");
|
|
1129
|
+
return {
|
|
1130
|
+
...readManagedSearchSettings(),
|
|
1131
|
+
restart_required: true,
|
|
1132
|
+
};
|
|
1133
|
+
}
|
|
1134
|
+
|
|
954
1135
|
function sendJson(res, code, payload) {
|
|
955
1136
|
if (res.headersSent || res.writableEnded || res.destroyed) return;
|
|
956
1137
|
const body = JSON.stringify(payload);
|
|
@@ -4353,6 +4534,29 @@ async function handleApi(req, res) {
|
|
|
4353
4534
|
return true;
|
|
4354
4535
|
}
|
|
4355
4536
|
|
|
4537
|
+
if (pathname === "/api/system/search-settings" && req.method === "GET") {
|
|
4538
|
+
const session = requireSession(req, res);
|
|
4539
|
+
if (!session) return true;
|
|
4540
|
+
sendJson(res, 200, readManagedSearchSettings());
|
|
4541
|
+
return true;
|
|
4542
|
+
}
|
|
4543
|
+
|
|
4544
|
+
if (pathname === "/api/system/search-settings" && req.method === "PATCH") {
|
|
4545
|
+
const session = requireSession(req, res);
|
|
4546
|
+
if (!session) return true;
|
|
4547
|
+
try {
|
|
4548
|
+
const payload = await readJsonBody(req);
|
|
4549
|
+
const saved = await writeManagedSearchSettings(payload);
|
|
4550
|
+
sendJson(res, 200, saved);
|
|
4551
|
+
} catch (error) {
|
|
4552
|
+
sendJson(res, Number(error?.statusCode || 500), {
|
|
4553
|
+
ok: false,
|
|
4554
|
+
error: error instanceof Error ? error.message : String(error),
|
|
4555
|
+
});
|
|
4556
|
+
}
|
|
4557
|
+
return true;
|
|
4558
|
+
}
|
|
4559
|
+
|
|
4356
4560
|
if (pathname === "/api/auth/login" && req.method === "POST") {
|
|
4357
4561
|
await handleAuthLogin(req, res);
|
|
4358
4562
|
return true;
|