@web-auto/webauto 0.1.4 → 0.1.6
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/apps/desktop-console/default-settings.json +2 -2
- package/apps/desktop-console/dist/main/index.mjs +915 -85
- package/apps/desktop-console/dist/main/preload.mjs +7 -0
- package/apps/desktop-console/dist/renderer/index.html +622 -50
- package/apps/desktop-console/dist/renderer/index.js +2415 -470
- package/apps/desktop-console/dist/renderer/run.mts +6 -5
- package/apps/desktop-console/entry/ui-cli.mjs +672 -0
- package/apps/desktop-console/entry/ui-console.mjs +416 -29
- package/apps/webauto/entry/account.mjs +89 -53
- package/apps/webauto/entry/browser-status.mjs +7 -10
- package/apps/webauto/entry/lib/account-detect.mjs +254 -28
- package/apps/webauto/entry/lib/account-store.mjs +219 -30
- package/apps/webauto/entry/lib/bus-publish.mjs +63 -0
- package/apps/webauto/entry/lib/camo-cli.mjs +93 -0
- package/apps/webauto/entry/lib/profilepool.mjs +14 -5
- package/apps/webauto/entry/lib/quota-status.mjs +23 -0
- package/apps/webauto/entry/lib/schedule-store.mjs +1068 -0
- package/apps/webauto/entry/profilepool.mjs +106 -17
- package/apps/webauto/entry/schedule.mjs +612 -0
- package/apps/webauto/entry/weibo-unified.mjs +134 -0
- package/apps/webauto/entry/xhs-install.mjs +236 -29
- package/apps/webauto/entry/xhs-status.mjs +5 -2
- package/apps/webauto/entry/xhs-unified.mjs +631 -98
- package/apps/webauto/resources/container-library/weibo/weibo_detail_page/comment_item/container.json +40 -0
- package/apps/webauto/resources/container-library/weibo/weibo_detail_page/reply_expand_button/container.json +38 -0
- package/apps/webauto/resources/container-library/weibo/weibo_detail_page/reply_list/container.json +37 -0
- package/apps/webauto/resources/container-library/weibo/weibo_search_page/container.json +8 -3
- package/apps/webauto/resources/container-library/weibo/weibo_search_page/login_anchor/container.json +30 -0
- package/apps/webauto/resources/container-library/weibo/weibo_search_page/search_bar/container.json +47 -0
- package/apps/webauto/resources/container-library/weibo/weibo_search_page/search_button/container.json +39 -0
- package/bin/camoufox-cli.mjs +61 -0
- package/bin/webauto.mjs +301 -54
- package/dist/modules/camo-backend/src/index.js +49 -1
- package/dist/modules/camo-backend/src/internal/BrowserSession.js +572 -3
- package/dist/modules/camo-backend/src/internal/SessionManager.js +13 -1
- package/dist/modules/camo-backend/src/internal/storage-paths.js +6 -0
- package/dist/modules/collection-manager/bloom-filter.js +91 -0
- package/dist/modules/collection-manager/date-utils.js +275 -0
- package/dist/modules/collection-manager/index.js +258 -0
- package/dist/modules/collection-manager/storage.js +195 -0
- package/dist/modules/collection-manager/types.js +47 -0
- package/dist/modules/logging/src/index.js +1 -1
- package/dist/modules/process-registry/index.js +230 -0
- package/dist/modules/rate-limiter/index.js +242 -0
- package/dist/modules/workflow/blocks/ExecuteWeiboSearchBlock.js +128 -0
- package/dist/modules/workflow/blocks/PersistXhsNoteBlock.js +7 -3
- package/dist/modules/workflow/blocks/RenderMarkdown.js +4 -1
- package/dist/modules/workflow/blocks/WeiboCollectCommentsBlock.js +282 -0
- package/dist/modules/workflow/blocks/WeiboCollectFromLinksBlock.js +283 -0
- package/dist/modules/workflow/blocks/WeiboCollectSearchLinksBlock.js +208 -0
- package/dist/modules/workflow/blocks/WeiboCollectTimelineListBlock.js +128 -0
- package/dist/modules/workflow/blocks/WeiboCollectUserPostsListBlock.js +127 -0
- package/dist/modules/workflow/blocks/helpers/downloadPaths.js +21 -0
- package/dist/modules/workflow/config/workflowRegistry.js +2 -0
- package/dist/modules/workflow/definitions/weibo-search-workflow-v1.js +47 -0
- package/dist/modules/workflow/src/runner.js +6 -0
- package/dist/modules/xiaohongshu/app/src/blocks/Phase34PersistDetailBlock.js +4 -0
- package/dist/modules/xiaohongshu/app/src/blocks/Phase3InteractBlock.js +2 -2
- package/dist/modules/xiaohongshu/app/src/blocks/helpers/sharding.js +123 -0
- package/dist/modules/xiaohongshu/app/src/container-registry/src/index.d.ts +37 -0
- package/dist/modules/xiaohongshu/app/src/container-registry/src/index.js +184 -0
- package/dist/modules/xiaohongshu/app/src/workflow/blocks/AnchorVerificationBlock.d.ts +31 -0
- package/dist/modules/xiaohongshu/app/src/workflow/blocks/AnchorVerificationBlock.js +71 -0
- package/dist/modules/xiaohongshu/app/src/workflow/blocks/DetectPageStateBlock.d.ts +48 -0
- package/dist/modules/xiaohongshu/app/src/workflow/blocks/DetectPageStateBlock.js +259 -0
- package/dist/modules/xiaohongshu/app/src/workflow/blocks/ErrorRecoveryBlock.d.ts +28 -0
- package/dist/modules/xiaohongshu/app/src/workflow/blocks/ErrorRecoveryBlock.js +319 -0
- package/dist/modules/xiaohongshu/app/src/workflow/blocks/WaitSearchPermitBlock.d.ts +36 -0
- package/dist/modules/xiaohongshu/app/src/workflow/blocks/WaitSearchPermitBlock.js +162 -0
- package/dist/modules/xiaohongshu/app/src/workflow/blocks/helpers/containerAnchors.d.ts +36 -0
- package/dist/modules/xiaohongshu/app/src/workflow/blocks/helpers/containerAnchors.js +301 -0
- package/dist/modules/xiaohongshu/app/src/workflow/blocks/helpers/operationLogger.d.ts +29 -0
- package/dist/modules/xiaohongshu/app/src/workflow/blocks/helpers/operationLogger.js +195 -0
- package/dist/modules/xiaohongshu/app/src/workflow/blocks/helpers/searchPageState.d.ts +25 -0
- package/dist/modules/xiaohongshu/app/src/workflow/blocks/helpers/searchPageState.js +164 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/MatchCommentsBlock.d.ts +66 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/MatchCommentsBlock.js +139 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase1EnsureServicesBlock.d.ts +16 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase1EnsureServicesBlock.js +36 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase1MonitorCookieBlock.d.ts +27 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase1MonitorCookieBlock.js +213 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase1StartProfileBlock.d.ts +18 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase1StartProfileBlock.js +121 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase2CollectLinksBlock.d.ts +34 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase2CollectLinksBlock.js +1249 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase2SearchBlock.d.ts +17 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase2SearchBlock.js +703 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34CloseDetailBlock.d.ts +15 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34CloseDetailBlock.js +41 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34CloseTabsBlock.d.ts +26 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34CloseTabsBlock.js +44 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34CollectCommentsBlock.d.ts +29 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34CollectCommentsBlock.js +150 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34ExtractDetailBlock.d.ts +38 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34ExtractDetailBlock.js +117 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34OpenDetailBlock.d.ts +30 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34OpenDetailBlock.js +102 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34OpenTabsBlock.d.ts +23 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34OpenTabsBlock.js +109 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34PersistDetailBlock.d.ts +32 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34PersistDetailBlock.js +117 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34ProcessSingleNoteBlock.d.ts +35 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34ProcessSingleNoteBlock.js +114 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34ValidateLinksBlock.d.ts +34 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34ValidateLinksBlock.js +90 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase3InteractBlock.d.ts +111 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase3InteractBlock.js +1009 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase4MultiTabHarvestBlock.d.ts +20 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase4MultiTabHarvestBlock.js +233 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/ReplyInteractBlock.d.ts +48 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/ReplyInteractBlock.js +291 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/XhsDiscoverFallbackBlock.d.ts +23 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/XhsDiscoverFallbackBlock.js +240 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/commentMatchDsl.d.ts +55 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/commentMatchDsl.js +126 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/commentMatcher.d.ts +21 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/commentMatcher.js +99 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/evidence.d.ts +5 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/evidence.js +27 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/sharding.d.ts +37 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/sharding.js +165 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/xhsComments.d.ts +33 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/xhsComments.js +270 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/index.d.ts +9 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/index.js +9 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/utils/checkpoints.d.ts +50 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/utils/checkpoints.js +222 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/utils/controllerAction.d.ts +10 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/utils/controllerAction.js +43 -0
- package/dist/services/shared/serviceProcessLogger.js +1 -1
- package/dist/services/unified-api/server.js +105 -11
- package/modules/camo-backend/src/index.ts +46 -1
- package/modules/camo-backend/src/internal/BrowserSession.ts +619 -3
- package/modules/camo-backend/src/internal/SessionManager.ts +12 -1
- package/modules/camo-backend/src/internal/storage-paths.ts +5 -0
- package/modules/camo-runtime/src/autoscript/action-providers/xhs/comments.mjs +38 -2
- package/modules/camo-runtime/src/autoscript/action-providers/xhs/interaction.mjs +47 -2
- package/modules/camo-runtime/src/autoscript/action-providers/xhs/search.mjs +94 -11
- package/modules/camo-runtime/src/autoscript/action-providers/xhs.mjs +208 -2
- package/modules/camo-runtime/src/autoscript/runtime.mjs +7 -1
- package/modules/camo-runtime/src/autoscript/xhs-unified-template.mjs +76 -43
- package/modules/camo-runtime/src/container/runtime-core/operations/index.mjs +75 -1
- package/modules/camo-runtime/src/container/runtime-core/operations/selector-scripts.mjs +71 -4
- package/modules/camo-runtime/src/container/runtime-core/operations/tab-pool.mjs +183 -27
- package/modules/collection-manager/bloom-filter.ts +112 -0
- package/modules/collection-manager/date-utils.ts +316 -0
- package/modules/collection-manager/index.ts +309 -0
- package/modules/collection-manager/package.json +10 -0
- package/modules/collection-manager/storage.ts +174 -0
- package/modules/collection-manager/types.ts +156 -0
- package/modules/logging/src/index.ts +1 -1
- package/modules/process-registry/index.ts +284 -0
- package/modules/rate-limiter/index.ts +322 -0
- package/modules/state/src/paths.ts +9 -1
- package/modules/task-scheduler/index.ts +293 -0
- package/modules/workflow/blocks/ExecuteWeiboSearchBlock.ts +167 -0
- package/modules/workflow/blocks/PersistXhsNoteBlock.ts +7 -3
- package/modules/workflow/blocks/RenderMarkdown.ts +4 -1
- package/modules/workflow/blocks/WeiboCollectCommentsBlock.ts +339 -0
- package/modules/workflow/blocks/WeiboCollectFromLinksBlock.ts +338 -0
- package/modules/workflow/blocks/helpers/downloadPaths.ts +16 -0
- package/modules/workflow/config/workflowRegistry.ts +2 -0
- package/modules/workflow/definitions/weibo-search-workflow-v1.ts +47 -0
- package/modules/workflow/src/runner.ts +6 -0
- package/modules/xiaohongshu/app/src/blocks/Phase1StartProfileBlock.ts +1 -1
- package/modules/xiaohongshu/app/src/blocks/Phase34PersistDetailBlock.ts +4 -0
- package/modules/xiaohongshu/app/src/blocks/Phase3InteractBlock.ts +2 -3
- package/modules/xiaohongshu/app/src/blocks/helpers/sharding.ts +152 -0
- package/package.json +13 -4
- package/scripts/postinstall-resources.mjs +62 -0
- package/scripts/test/run-coverage.mjs +76 -0
- package/scripts/weibo/search.ts +49 -0
- package/services/shared/serviceProcessLogger.ts +1 -1
- package/services/unified-api/server.ts +98 -12
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
// src/main/index.mts
|
|
2
2
|
import electron from "electron";
|
|
3
3
|
import { spawn as spawn2 } from "node:child_process";
|
|
4
|
-
import
|
|
5
|
-
import
|
|
4
|
+
import os5 from "node:os";
|
|
5
|
+
import path6 from "node:path";
|
|
6
6
|
import { fileURLToPath as fileURLToPath2, pathToFileURL as pathToFileURL3 } from "node:url";
|
|
7
|
-
import { mkdirSync, promises as
|
|
7
|
+
import { mkdirSync, promises as fs4 } from "node:fs";
|
|
8
8
|
|
|
9
9
|
// src/main/desktop-settings.mts
|
|
10
|
-
import { promises as fs } from "node:fs";
|
|
10
|
+
import { existsSync, promises as fs } from "node:fs";
|
|
11
11
|
import os from "node:os";
|
|
12
12
|
import path from "node:path";
|
|
13
13
|
import { pathToFileURL } from "node:url";
|
|
@@ -19,9 +19,26 @@ function resolveHomeDir() {
|
|
|
19
19
|
function resolveLegacySettingsPath() {
|
|
20
20
|
return path.join(resolveHomeDir(), ".webauto", "ui-settings.console.json");
|
|
21
21
|
}
|
|
22
|
-
function
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
function hasWindowsDriveD() {
|
|
23
|
+
try {
|
|
24
|
+
return existsSync("D:\\");
|
|
25
|
+
} catch {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function normalizeWindowsDefaultRoot(input) {
|
|
30
|
+
const value = String(input || "").trim();
|
|
31
|
+
if (!value) return path.join(resolveHomeDir(), ".webauto");
|
|
32
|
+
if (!/^[dD]:[\\/]/.test(value)) return value;
|
|
33
|
+
if (hasWindowsDriveD()) return value;
|
|
34
|
+
return path.join(resolveHomeDir(), ".webauto");
|
|
35
|
+
}
|
|
36
|
+
function resolveDefaultDownloadRoot(options) {
|
|
37
|
+
const platform = String(options?.platform || process.platform).trim();
|
|
38
|
+
const homeDir = String(options?.homeDir || resolveHomeDir()).trim() || resolveHomeDir();
|
|
39
|
+
if (platform !== "win32") return path.join(homeDir, ".webauto", "download");
|
|
40
|
+
const driveExists = typeof options?.windowsDriveDExists === "boolean" ? options.windowsDriveDExists : hasWindowsDriveD();
|
|
41
|
+
return driveExists ? "D:\\webauto" : path.join(homeDir, ".webauto");
|
|
25
42
|
}
|
|
26
43
|
function normalizeAiReplyConfig(raw) {
|
|
27
44
|
if (!raw || typeof raw !== "object") {
|
|
@@ -67,10 +84,12 @@ function normalizeSettings(defaults, input) {
|
|
|
67
84
|
}
|
|
68
85
|
const merged = {
|
|
69
86
|
unifiedApiUrl: String(input.unifiedApiUrl || defaults.unifiedApiUrl || "http://127.0.0.1:7701"),
|
|
70
|
-
|
|
87
|
+
camoRuntimeUrl: String(
|
|
88
|
+
input.camoRuntimeUrl || input.browserServiceUrl || defaults.camoRuntimeUrl || defaults.browserServiceUrl || "http://127.0.0.1:7704"
|
|
89
|
+
),
|
|
71
90
|
searchGateUrl: String(input.searchGateUrl || defaults.searchGateUrl || "http://127.0.0.1:7790"),
|
|
72
91
|
downloadRoot: String(input.downloadRoot || defaults.downloadRoot || resolveDefaultDownloadRoot()),
|
|
73
|
-
defaultEnv: String(input.defaultEnv || defaults.defaultEnv || "
|
|
92
|
+
defaultEnv: String(input.defaultEnv || defaults.defaultEnv || "prod") === "prod" ? "prod" : "debug",
|
|
74
93
|
defaultKeyword: String(input.defaultKeyword ?? defaults.defaultKeyword ?? ""),
|
|
75
94
|
defaultTarget: Math.max(1, Math.floor(Number(input.defaultTarget ?? defaults.defaultTarget ?? 20) || 20)),
|
|
76
95
|
defaultDryRun: Boolean(input.defaultDryRun ?? defaults.defaultDryRun ?? false),
|
|
@@ -95,10 +114,30 @@ function normalizeSettings(defaults, input) {
|
|
|
95
114
|
profileAliases: aliases,
|
|
96
115
|
profileColors: normalizeColorMap(input.profileColors ?? defaults.profileColors ?? {}),
|
|
97
116
|
aiReply: normalizeAiReplyConfig(input.aiReply ?? defaults.aiReply ?? {}),
|
|
117
|
+
envRepairHistory: normalizeRepairHistory(
|
|
118
|
+
input.envRepairHistory ?? defaults.envRepairHistory ?? []
|
|
119
|
+
),
|
|
98
120
|
lastCrawlConfig: input.lastCrawlConfig ?? defaults.lastCrawlConfig ?? void 0
|
|
99
121
|
};
|
|
100
122
|
return merged;
|
|
101
123
|
}
|
|
124
|
+
function normalizeRepairHistory(raw) {
|
|
125
|
+
if (!Array.isArray(raw)) return [];
|
|
126
|
+
const out = [];
|
|
127
|
+
for (const item of raw) {
|
|
128
|
+
if (!item || typeof item !== "object") continue;
|
|
129
|
+
const ts = String(item.ts || "").trim();
|
|
130
|
+
const action = String(item.action || "").trim();
|
|
131
|
+
if (!ts || !action) continue;
|
|
132
|
+
out.push({
|
|
133
|
+
ts,
|
|
134
|
+
action,
|
|
135
|
+
ok: Boolean(item.ok),
|
|
136
|
+
detail: String(item.detail || "").trim() || void 0
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
return out.slice(-30);
|
|
140
|
+
}
|
|
102
141
|
function normalizeColorMap(raw) {
|
|
103
142
|
const out = {};
|
|
104
143
|
if (!raw || typeof raw !== "object") return out;
|
|
@@ -122,13 +161,14 @@ async function readDefaultSettingsFromAppRoot(appRoot) {
|
|
|
122
161
|
}
|
|
123
162
|
const base = {
|
|
124
163
|
unifiedApiUrl: raw.unifiedApiUrl,
|
|
125
|
-
|
|
164
|
+
camoRuntimeUrl: raw.camoRuntimeUrl || raw.browserServiceUrl,
|
|
126
165
|
searchGateUrl: raw.searchGateUrl,
|
|
127
166
|
defaultEnv: raw.defaultEnv,
|
|
128
167
|
defaultKeyword: raw.defaultKeyword,
|
|
129
168
|
timeouts: raw.timeouts
|
|
130
169
|
};
|
|
131
|
-
const
|
|
170
|
+
const downloadRootRaw = typeof raw.downloadRoot === "string" ? String(raw.downloadRoot) : process.platform === "win32" && typeof raw.downloadRootWindows === "string" ? String(raw.downloadRootWindows) : process.platform !== "win32" && typeof raw.downloadRootPosix === "string" ? String(raw.downloadRootPosix) : Array.isArray(raw.downloadRootParts) ? path.join(resolveHomeDir(), ...raw.downloadRootParts.map((x) => String(x))) : resolveDefaultDownloadRoot();
|
|
171
|
+
const downloadRoot = process.platform === "win32" ? normalizeWindowsDefaultRoot(downloadRootRaw) : downloadRootRaw;
|
|
132
172
|
return normalizeSettings({ ...base, downloadRoot }, {});
|
|
133
173
|
}
|
|
134
174
|
async function readLegacySettings() {
|
|
@@ -546,6 +586,10 @@ var StateBridge = class {
|
|
|
546
586
|
win = null;
|
|
547
587
|
tasks = /* @__PURE__ */ new Map();
|
|
548
588
|
handlersRegistered = false;
|
|
589
|
+
emitBusEvent(payload) {
|
|
590
|
+
if (!this.win) return;
|
|
591
|
+
this.win.webContents.send("bus:event", payload);
|
|
592
|
+
}
|
|
549
593
|
start(win2) {
|
|
550
594
|
this.win = win2;
|
|
551
595
|
this.connect();
|
|
@@ -561,12 +605,26 @@ var StateBridge = class {
|
|
|
561
605
|
this.ws.on("open", () => {
|
|
562
606
|
console.log("[StateBridge] connected to", UNIFIED_API_WS);
|
|
563
607
|
this.ws?.send(JSON.stringify({ type: "subscribe", topic: "task:*" }));
|
|
608
|
+
this.emitBusEvent({ type: "env:unified", ok: true, ts: Date.now() });
|
|
564
609
|
});
|
|
565
610
|
this.ws.on("message", (data) => {
|
|
566
611
|
try {
|
|
567
612
|
const msg = JSON.parse(data.toString());
|
|
568
613
|
if (msg.type === "task:update" && msg.data) {
|
|
569
614
|
this.handleTaskUpdate(msg.data);
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
if (msg.type === "event" && msg.topic === "bus.message") {
|
|
618
|
+
const raw = msg?.payload?.data;
|
|
619
|
+
if (typeof raw === "string" && raw.trim()) {
|
|
620
|
+
try {
|
|
621
|
+
const parsed = JSON.parse(raw);
|
|
622
|
+
if (parsed && typeof parsed === "object") {
|
|
623
|
+
this.emitBusEvent(parsed);
|
|
624
|
+
}
|
|
625
|
+
} catch {
|
|
626
|
+
}
|
|
627
|
+
}
|
|
570
628
|
}
|
|
571
629
|
} catch (err) {
|
|
572
630
|
console.warn("[StateBridge] parse error:", err);
|
|
@@ -574,6 +632,7 @@ var StateBridge = class {
|
|
|
574
632
|
});
|
|
575
633
|
this.ws.on("close", () => {
|
|
576
634
|
console.log("[StateBridge] disconnected, reconnecting...");
|
|
635
|
+
this.emitBusEvent({ type: "env:unified", ok: false, ts: Date.now() });
|
|
577
636
|
this.scheduleReconnect();
|
|
578
637
|
});
|
|
579
638
|
this.ws.on("error", (err) => {
|
|
@@ -634,12 +693,29 @@ var stateBridge = new StateBridge();
|
|
|
634
693
|
// src/main/env-check.mts
|
|
635
694
|
import { promisify } from "node:util";
|
|
636
695
|
import { exec, spawnSync } from "node:child_process";
|
|
637
|
-
import { existsSync } from "node:fs";
|
|
696
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
638
697
|
import path4 from "node:path";
|
|
639
698
|
import os3 from "node:os";
|
|
640
699
|
var execAsync = promisify(exec);
|
|
700
|
+
function resolveWebautoRoot() {
|
|
701
|
+
const portableRoot = String(process.env.WEBAUTO_PORTABLE_ROOT || process.env.WEBAUTO_ROOT || "").trim();
|
|
702
|
+
return portableRoot ? path4.join(portableRoot, ".webauto") : path4.join(os3.homedir(), ".webauto");
|
|
703
|
+
}
|
|
641
704
|
function resolveNpxBin2() {
|
|
642
|
-
|
|
705
|
+
if (process.platform !== "win32") return "npx";
|
|
706
|
+
const resolved = resolveOnPath(["npx.cmd", "npx.exe", "npx.bat", "npx.ps1"]);
|
|
707
|
+
return resolved || "npx.cmd";
|
|
708
|
+
}
|
|
709
|
+
function resolveOnPath(candidates) {
|
|
710
|
+
const pathEnv = process.env.PATH || process.env.Path || "";
|
|
711
|
+
const dirs = pathEnv.split(path4.delimiter).filter(Boolean);
|
|
712
|
+
for (const dir of dirs) {
|
|
713
|
+
for (const name of candidates) {
|
|
714
|
+
const full = path4.join(dir, name);
|
|
715
|
+
if (existsSync2(full)) return full;
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
return null;
|
|
643
719
|
}
|
|
644
720
|
function resolveCamoVersionFromText(stdout, stderr) {
|
|
645
721
|
const merged = `${String(stdout || "")}
|
|
@@ -653,13 +729,35 @@ ${String(stderr || "")}`.trim();
|
|
|
653
729
|
}
|
|
654
730
|
return "unknown";
|
|
655
731
|
}
|
|
732
|
+
function quoteCmdArg(value) {
|
|
733
|
+
if (!value) return '""';
|
|
734
|
+
if (!/[\s"]/u.test(value)) return value;
|
|
735
|
+
return `"${value.replace(/"/g, '""')}"`;
|
|
736
|
+
}
|
|
656
737
|
function runVersionCheck(command, args, explicitPath) {
|
|
657
738
|
try {
|
|
658
|
-
const
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
739
|
+
const lower = String(command || "").toLowerCase();
|
|
740
|
+
let ret;
|
|
741
|
+
if (process.platform === "win32" && (lower.endsWith(".cmd") || lower.endsWith(".bat"))) {
|
|
742
|
+
const cmdLine = [quoteCmdArg(command), ...args.map(quoteCmdArg)].join(" ");
|
|
743
|
+
ret = spawnSync("cmd.exe", ["/d", "/s", "/c", cmdLine], {
|
|
744
|
+
encoding: "utf8",
|
|
745
|
+
timeout: 8e3,
|
|
746
|
+
windowsHide: true
|
|
747
|
+
});
|
|
748
|
+
} else if (process.platform === "win32" && lower.endsWith(".ps1")) {
|
|
749
|
+
ret = spawnSync("powershell.exe", ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", command, ...args], {
|
|
750
|
+
encoding: "utf8",
|
|
751
|
+
timeout: 8e3,
|
|
752
|
+
windowsHide: true
|
|
753
|
+
});
|
|
754
|
+
} else {
|
|
755
|
+
ret = spawnSync(command, args, {
|
|
756
|
+
encoding: "utf8",
|
|
757
|
+
timeout: 8e3,
|
|
758
|
+
windowsHide: true
|
|
759
|
+
});
|
|
760
|
+
}
|
|
663
761
|
if (ret.status !== 0) {
|
|
664
762
|
return {
|
|
665
763
|
installed: false,
|
|
@@ -676,19 +774,24 @@ function runVersionCheck(command, args, explicitPath) {
|
|
|
676
774
|
}
|
|
677
775
|
}
|
|
678
776
|
async function checkCamoCli() {
|
|
679
|
-
const
|
|
680
|
-
|
|
777
|
+
const camoCandidates = process.platform === "win32" ? ["camo.cmd", "camo.exe", "camo.bat", "camo.ps1"] : ["camo"];
|
|
778
|
+
for (const candidate of camoCandidates) {
|
|
779
|
+
const pathCheck = runVersionCheck(candidate, ["help"], `PATH:${candidate}`);
|
|
780
|
+
if (pathCheck.installed) return pathCheck;
|
|
781
|
+
}
|
|
681
782
|
const cwd = process.cwd();
|
|
682
|
-
const
|
|
683
|
-
|
|
684
|
-
path4.resolve(cwd, "node_modules", ".bin"
|
|
685
|
-
path4.resolve(cwd, "..", "node_modules", ".bin"
|
|
686
|
-
path4.resolve(cwd, "..", "..", "node_modules", ".bin", suffix)
|
|
783
|
+
const localRoots = [
|
|
784
|
+
path4.resolve(cwd, "node_modules", ".bin"),
|
|
785
|
+
path4.resolve(cwd, "..", "node_modules", ".bin"),
|
|
786
|
+
path4.resolve(cwd, "..", "..", "node_modules", ".bin")
|
|
687
787
|
];
|
|
688
|
-
for (const
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
788
|
+
for (const localRoot of localRoots) {
|
|
789
|
+
for (const suffix of camoCandidates) {
|
|
790
|
+
const candidate = path4.resolve(localRoot, suffix);
|
|
791
|
+
if (!existsSync2(candidate)) continue;
|
|
792
|
+
const ret = runVersionCheck(candidate, ["help"], candidate);
|
|
793
|
+
if (ret.installed) return ret;
|
|
794
|
+
}
|
|
692
795
|
}
|
|
693
796
|
const npxCheck = runVersionCheck(
|
|
694
797
|
resolveNpxBin2(),
|
|
@@ -702,12 +805,12 @@ async function checkCamoCli() {
|
|
|
702
805
|
};
|
|
703
806
|
}
|
|
704
807
|
async function checkServices() {
|
|
705
|
-
const [unifiedApi,
|
|
808
|
+
const [unifiedApi, camoRuntime, searchGate] = await Promise.all([
|
|
706
809
|
fetch("http://127.0.0.1:7701/health", { signal: AbortSignal.timeout(3e3) }).then((r) => r.ok).catch(() => false),
|
|
707
810
|
fetch("http://127.0.0.1:7704/health", { signal: AbortSignal.timeout(3e3) }).then((r) => r.ok).catch(() => false),
|
|
708
811
|
fetch("http://127.0.0.1:7790/health", { signal: AbortSignal.timeout(3e3) }).then((r) => r.ok).catch(() => false)
|
|
709
812
|
]);
|
|
710
|
-
return { unifiedApi,
|
|
813
|
+
return { unifiedApi, camoRuntime, searchGate };
|
|
711
814
|
}
|
|
712
815
|
async function checkFirefox() {
|
|
713
816
|
try {
|
|
@@ -739,12 +842,12 @@ async function checkFirefox() {
|
|
|
739
842
|
path4.join(localAppData, "Mozilla Firefox", "firefox.exe")
|
|
740
843
|
];
|
|
741
844
|
for (const firefoxPath2 of possiblePaths) {
|
|
742
|
-
if (
|
|
845
|
+
if (existsSync2(firefoxPath2)) return { installed: true, path: firefoxPath2 };
|
|
743
846
|
}
|
|
744
847
|
return { installed: false };
|
|
745
848
|
}
|
|
746
849
|
const macBundle = "/Applications/Firefox.app/Contents/MacOS/firefox";
|
|
747
|
-
if (platform === "darwin" &&
|
|
850
|
+
if (platform === "darwin" && existsSync2(macBundle)) return { installed: true, path: macBundle };
|
|
748
851
|
const { stdout } = await execAsync("which firefox", { timeout: 3e3 });
|
|
749
852
|
const firefoxPath = String(stdout || "").trim();
|
|
750
853
|
return firefoxPath ? { installed: true, path: firefoxPath } : { installed: false };
|
|
@@ -753,8 +856,8 @@ async function checkFirefox() {
|
|
|
753
856
|
}
|
|
754
857
|
}
|
|
755
858
|
async function checkGeoIP() {
|
|
756
|
-
const geoIpPath = path4.join(
|
|
757
|
-
if (
|
|
859
|
+
const geoIpPath = path4.join(resolveWebautoRoot(), "geoip", "GeoLite2-City.mmdb");
|
|
860
|
+
if (existsSync2(geoIpPath)) {
|
|
758
861
|
return { installed: true, path: geoIpPath };
|
|
759
862
|
}
|
|
760
863
|
return { installed: false };
|
|
@@ -766,32 +869,585 @@ async function checkEnvironment() {
|
|
|
766
869
|
checkFirefox(),
|
|
767
870
|
checkGeoIP()
|
|
768
871
|
]);
|
|
769
|
-
const allReady = camo.installed && services.unifiedApi &&
|
|
872
|
+
const allReady = camo.installed && services.unifiedApi && firefox.installed;
|
|
770
873
|
return { camo, services, firefox, geoip, allReady };
|
|
771
874
|
}
|
|
772
875
|
|
|
876
|
+
// src/main/ui-cli-bridge.mts
|
|
877
|
+
import { createServer } from "node:http";
|
|
878
|
+
import os4 from "node:os";
|
|
879
|
+
import path5 from "node:path";
|
|
880
|
+
import { promises as fs3 } from "node:fs";
|
|
881
|
+
var DEFAULT_HOST = "127.0.0.1";
|
|
882
|
+
var DEFAULT_PORT = 7716;
|
|
883
|
+
var CONTROL_FILE = path5.join(os4.homedir(), ".webauto", "run", "ui-cli.json");
|
|
884
|
+
function readInt(input, fallback) {
|
|
885
|
+
const n = Number(input);
|
|
886
|
+
return Number.isFinite(n) && n > 0 ? Math.floor(n) : fallback;
|
|
887
|
+
}
|
|
888
|
+
function sendJson(res, code, payload) {
|
|
889
|
+
const body = JSON.stringify(payload);
|
|
890
|
+
res.statusCode = code;
|
|
891
|
+
res.setHeader("Content-Type", "application/json; charset=utf-8");
|
|
892
|
+
res.setHeader("Content-Length", Buffer.byteLength(body));
|
|
893
|
+
res.end(body);
|
|
894
|
+
}
|
|
895
|
+
function parseBody(req) {
|
|
896
|
+
return new Promise((resolve) => {
|
|
897
|
+
const chunks = [];
|
|
898
|
+
req.on("data", (c) => chunks.push(c));
|
|
899
|
+
req.on("end", () => {
|
|
900
|
+
const raw = Buffer.concat(chunks).toString("utf8").trim();
|
|
901
|
+
if (!raw) return resolve({});
|
|
902
|
+
try {
|
|
903
|
+
resolve(JSON.parse(raw));
|
|
904
|
+
} catch {
|
|
905
|
+
resolve({});
|
|
906
|
+
}
|
|
907
|
+
});
|
|
908
|
+
req.on("error", () => resolve({}));
|
|
909
|
+
});
|
|
910
|
+
}
|
|
911
|
+
function isUiReady(win2) {
|
|
912
|
+
if (!win2 || win2.isDestroyed()) return false;
|
|
913
|
+
const wc = win2.webContents;
|
|
914
|
+
if (!wc || wc.isDestroyed()) return false;
|
|
915
|
+
if (typeof wc.isCrashed === "function" && wc.isCrashed()) return false;
|
|
916
|
+
return true;
|
|
917
|
+
}
|
|
918
|
+
function toActionError(input, error, extra = {}) {
|
|
919
|
+
const action = String(input?.action || "").trim();
|
|
920
|
+
const selector = String(input?.selector || "").trim();
|
|
921
|
+
const state = String(input?.state || "").trim();
|
|
922
|
+
const payload = {
|
|
923
|
+
ok: false,
|
|
924
|
+
error: String(error || "unknown_error"),
|
|
925
|
+
action: action || null,
|
|
926
|
+
selector: selector || null,
|
|
927
|
+
state: state || null,
|
|
928
|
+
...extra
|
|
929
|
+
};
|
|
930
|
+
return payload;
|
|
931
|
+
}
|
|
932
|
+
async function writeControlFile(host, port) {
|
|
933
|
+
const payload = {
|
|
934
|
+
pid: process.pid,
|
|
935
|
+
host,
|
|
936
|
+
port,
|
|
937
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
938
|
+
};
|
|
939
|
+
try {
|
|
940
|
+
await fs3.mkdir(path5.dirname(CONTROL_FILE), { recursive: true });
|
|
941
|
+
await fs3.writeFile(CONTROL_FILE, JSON.stringify(payload, null, 2), "utf8");
|
|
942
|
+
} catch {
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
async function removeControlFile() {
|
|
946
|
+
try {
|
|
947
|
+
await fs3.unlink(CONTROL_FILE);
|
|
948
|
+
} catch {
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
function buildSnapshotScript() {
|
|
952
|
+
return `(() => {
|
|
953
|
+
const text = (sel) => {
|
|
954
|
+
const el = document.querySelector(sel);
|
|
955
|
+
return el ? String(el.textContent || '').trim() : '';
|
|
956
|
+
};
|
|
957
|
+
const value = (sel) => {
|
|
958
|
+
const el = document.querySelector(sel);
|
|
959
|
+
if (!el) return '';
|
|
960
|
+
if ('value' in el) return String(el.value ?? '');
|
|
961
|
+
return String(el.textContent || '').trim();
|
|
962
|
+
};
|
|
963
|
+
const activeTab = document.querySelector('.tab.active');
|
|
964
|
+
const errors = Array.from(document.querySelectorAll('#recent-errors-list li'))
|
|
965
|
+
.map((el) => String(el.textContent || '').trim())
|
|
966
|
+
.filter(Boolean)
|
|
967
|
+
.slice(0, 20);
|
|
968
|
+
return {
|
|
969
|
+
ready: true,
|
|
970
|
+
activeTabId: String(activeTab?.dataset?.tabId || '').trim(),
|
|
971
|
+
activeTabLabel: String(activeTab?.textContent || '').trim(),
|
|
972
|
+
status: text('#status'),
|
|
973
|
+
runId: text('#run-id-text'),
|
|
974
|
+
errorCount: text('#error-count-text'),
|
|
975
|
+
currentPhase: text('#current-phase'),
|
|
976
|
+
currentAction: text('#current-action'),
|
|
977
|
+
progressPercent: text('#progress-percent'),
|
|
978
|
+
keyword: value('#keyword-input'),
|
|
979
|
+
target: value('#target-input'),
|
|
980
|
+
account: value('#account-select'),
|
|
981
|
+
env: value('#env-select'),
|
|
982
|
+
recentErrors: errors,
|
|
983
|
+
ts: new Date().toISOString(),
|
|
984
|
+
};
|
|
985
|
+
})()`;
|
|
986
|
+
}
|
|
987
|
+
function buildActionScript(action) {
|
|
988
|
+
const payloadJson = JSON.stringify(action);
|
|
989
|
+
const snapshotScript = buildSnapshotScript();
|
|
990
|
+
return `(() => {
|
|
991
|
+
const payload = ${payloadJson};
|
|
992
|
+
const normalize = (v) => String(v || '').trim();
|
|
993
|
+
const isVisible = (el) => {
|
|
994
|
+
if (!el) return false;
|
|
995
|
+
const rect = el.getBoundingClientRect();
|
|
996
|
+
if (!rect || rect.width <= 0 || rect.height <= 0) return false;
|
|
997
|
+
const style = window.getComputedStyle(el);
|
|
998
|
+
return style.display !== 'none' && style.visibility !== 'hidden' && style.opacity !== '0';
|
|
999
|
+
};
|
|
1000
|
+
const query = (selector) => {
|
|
1001
|
+
const s = normalize(selector);
|
|
1002
|
+
if (!s) return null;
|
|
1003
|
+
return document.querySelector(s);
|
|
1004
|
+
};
|
|
1005
|
+
const queryAll = (selector) => {
|
|
1006
|
+
const s = normalize(selector) || 'body';
|
|
1007
|
+
return Array.from(document.querySelectorAll(s));
|
|
1008
|
+
};
|
|
1009
|
+
const findByText = ({ selector, text, exact, nth }) => {
|
|
1010
|
+
const q = normalize(selector) || 'button';
|
|
1011
|
+
const target = normalize(text);
|
|
1012
|
+
const lower = target.toLowerCase();
|
|
1013
|
+
if (!target) return null;
|
|
1014
|
+
const nodes = Array.from(document.querySelectorAll(q));
|
|
1015
|
+
const matched = nodes.filter((el) => {
|
|
1016
|
+
const t = normalize(el.textContent);
|
|
1017
|
+
if (!t) return false;
|
|
1018
|
+
if (exact === true) return t === target;
|
|
1019
|
+
return t.toLowerCase().includes(lower);
|
|
1020
|
+
});
|
|
1021
|
+
const index = Number.isFinite(Number(nth)) ? Math.max(0, Math.floor(Number(nth))) : 0;
|
|
1022
|
+
return matched[index] || null;
|
|
1023
|
+
};
|
|
1024
|
+
const getElementDetails = (el) => {
|
|
1025
|
+
if (!el) return null;
|
|
1026
|
+
const rect = el.getBoundingClientRect();
|
|
1027
|
+
const style = window.getComputedStyle(el);
|
|
1028
|
+
const attrs = {};
|
|
1029
|
+
for (const attr of el.attributes) {
|
|
1030
|
+
attrs[attr.name] = attr.value;
|
|
1031
|
+
}
|
|
1032
|
+
return {
|
|
1033
|
+
rect: {
|
|
1034
|
+
x: Math.round(rect.x),
|
|
1035
|
+
y: Math.round(rect.y),
|
|
1036
|
+
width: Math.round(rect.width),
|
|
1037
|
+
height: Math.round(rect.height),
|
|
1038
|
+
top: Math.round(rect.top),
|
|
1039
|
+
left: Math.round(rect.left),
|
|
1040
|
+
right: Math.round(rect.right),
|
|
1041
|
+
bottom: Math.round(rect.bottom),
|
|
1042
|
+
},
|
|
1043
|
+
computedStyle: {
|
|
1044
|
+
display: style.display,
|
|
1045
|
+
visibility: style.visibility,
|
|
1046
|
+
opacity: style.opacity,
|
|
1047
|
+
backgroundColor: style.backgroundColor,
|
|
1048
|
+
color: style.color,
|
|
1049
|
+
fontSize: style.fontSize,
|
|
1050
|
+
fontFamily: style.fontFamily,
|
|
1051
|
+
position: style.position,
|
|
1052
|
+
zIndex: style.zIndex,
|
|
1053
|
+
},
|
|
1054
|
+
attributes: attrs,
|
|
1055
|
+
innerText: el.innerText,
|
|
1056
|
+
outerHTML: el.outerHTML?.slice(0, 2000),
|
|
1057
|
+
tagName: el.tagName,
|
|
1058
|
+
className: el.className,
|
|
1059
|
+
id: el.id,
|
|
1060
|
+
};
|
|
1061
|
+
};
|
|
1062
|
+
const focusEl = (el) => {
|
|
1063
|
+
if (!el || typeof el.focus !== 'function') return false;
|
|
1064
|
+
el.focus();
|
|
1065
|
+
return document.activeElement === el;
|
|
1066
|
+
};
|
|
1067
|
+
const clickEl = (el) => {
|
|
1068
|
+
if (!el || typeof el.click !== 'function') return false;
|
|
1069
|
+
if (typeof el.scrollIntoView === 'function') {
|
|
1070
|
+
try { el.scrollIntoView({ block: 'center', inline: 'nearest' }); } catch {}
|
|
1071
|
+
}
|
|
1072
|
+
focusEl(el);
|
|
1073
|
+
el.click();
|
|
1074
|
+
return true;
|
|
1075
|
+
};
|
|
1076
|
+
const setInputValue = (el, value) => {
|
|
1077
|
+
if (!el) return false;
|
|
1078
|
+
const text = String(value ?? '');
|
|
1079
|
+
if ('value' in el) {
|
|
1080
|
+
el.value = text;
|
|
1081
|
+
} else {
|
|
1082
|
+
el.textContent = text;
|
|
1083
|
+
}
|
|
1084
|
+
el.dispatchEvent(new Event('input', { bubbles: true }));
|
|
1085
|
+
el.dispatchEvent(new Event('change', { bubbles: true }));
|
|
1086
|
+
return true;
|
|
1087
|
+
};
|
|
1088
|
+
const pressKey = (el, key) => {
|
|
1089
|
+
const k = normalize(key) || 'Enter';
|
|
1090
|
+
const target = el || document.activeElement || document.body;
|
|
1091
|
+
const code = k === 'Escape' ? 'Escape' : k === 'Enter' ? 'Enter' : k;
|
|
1092
|
+
const init = { key: k, code, bubbles: true, cancelable: true };
|
|
1093
|
+
target.dispatchEvent(new KeyboardEvent('keydown', init));
|
|
1094
|
+
target.dispatchEvent(new KeyboardEvent('keyup', init));
|
|
1095
|
+
return true;
|
|
1096
|
+
};
|
|
1097
|
+
const findTab = () => {
|
|
1098
|
+
const tabId = normalize(payload.tabId || payload.value);
|
|
1099
|
+
const tabLabel = normalize(payload.tabLabel || payload.selector);
|
|
1100
|
+
const tabs = Array.from(document.querySelectorAll('.tab'));
|
|
1101
|
+
if (tabId) {
|
|
1102
|
+
const byId = tabs.find((el) => normalize(el?.dataset?.tabId) === tabId);
|
|
1103
|
+
if (byId) return byId;
|
|
1104
|
+
}
|
|
1105
|
+
if (tabLabel) {
|
|
1106
|
+
const lower = tabLabel.toLowerCase();
|
|
1107
|
+
return tabs.find((el) => normalize(el.textContent).toLowerCase().includes(lower)) || null;
|
|
1108
|
+
}
|
|
1109
|
+
return null;
|
|
1110
|
+
};
|
|
1111
|
+
|
|
1112
|
+
if (payload.action === 'snapshot') {
|
|
1113
|
+
return { ok: true, snapshot: ${snapshotScript} };
|
|
1114
|
+
}
|
|
1115
|
+
if (payload.action === 'dialogs') {
|
|
1116
|
+
const mode = normalize(payload.value).toLowerCase();
|
|
1117
|
+
const w = window;
|
|
1118
|
+
const key = '__webauto_ui_cli_dialogs__';
|
|
1119
|
+
if (mode === 'silent') {
|
|
1120
|
+
if (!w[key]) {
|
|
1121
|
+
w[key] = {
|
|
1122
|
+
alert: w.alert,
|
|
1123
|
+
confirm: w.confirm,
|
|
1124
|
+
prompt: w.prompt,
|
|
1125
|
+
};
|
|
1126
|
+
}
|
|
1127
|
+
w.alert = () => {};
|
|
1128
|
+
w.confirm = () => true;
|
|
1129
|
+
w.prompt = () => '';
|
|
1130
|
+
return { ok: true, mode: 'silent' };
|
|
1131
|
+
}
|
|
1132
|
+
if (mode === 'restore') {
|
|
1133
|
+
if (w[key]) {
|
|
1134
|
+
w.alert = w[key].alert;
|
|
1135
|
+
w.confirm = w[key].confirm;
|
|
1136
|
+
w.prompt = w[key].prompt;
|
|
1137
|
+
delete w[key];
|
|
1138
|
+
}
|
|
1139
|
+
return { ok: true, mode: 'restore' };
|
|
1140
|
+
}
|
|
1141
|
+
return { ok: false, error: 'unsupported_dialog_mode' };
|
|
1142
|
+
}
|
|
1143
|
+
if (payload.action === 'tab') {
|
|
1144
|
+
const tab = findTab();
|
|
1145
|
+
if (!tab) return { ok: false, error: 'tab_not_found' };
|
|
1146
|
+
clickEl(tab);
|
|
1147
|
+
return { ok: true, tab: normalize(tab.textContent), tabId: normalize(tab?.dataset?.tabId) };
|
|
1148
|
+
}
|
|
1149
|
+
if (payload.action === 'click') {
|
|
1150
|
+
const el = query(payload.selector);
|
|
1151
|
+
if (!el) return { ok: false, error: 'selector_not_found', selector: normalize(payload.selector) };
|
|
1152
|
+
clickEl(el);
|
|
1153
|
+
return { ok: true };
|
|
1154
|
+
}
|
|
1155
|
+
if (payload.action === 'focus') {
|
|
1156
|
+
const el = query(payload.selector);
|
|
1157
|
+
if (!el) return { ok: false, error: 'selector_not_found', selector: normalize(payload.selector) };
|
|
1158
|
+
const focused = focusEl(el);
|
|
1159
|
+
return { ok: focused, focused };
|
|
1160
|
+
}
|
|
1161
|
+
if (payload.action === 'input') {
|
|
1162
|
+
const el = query(payload.selector);
|
|
1163
|
+
if (!el) return { ok: false, error: 'selector_not_found', selector: normalize(payload.selector) };
|
|
1164
|
+
focusEl(el);
|
|
1165
|
+
const written = setInputValue(el, payload.value || '');
|
|
1166
|
+
return { ok: written, value: String(payload.value || '') };
|
|
1167
|
+
}
|
|
1168
|
+
if (payload.action === 'select') {
|
|
1169
|
+
const el = query(payload.selector);
|
|
1170
|
+
if (!el || el.tagName !== 'SELECT') return { ok: false, error: 'select_not_found', selector: normalize(payload.selector) };
|
|
1171
|
+
el.value = String(payload.value || '');
|
|
1172
|
+
el.dispatchEvent(new Event('input', { bubbles: true }));
|
|
1173
|
+
el.dispatchEvent(new Event('change', { bubbles: true }));
|
|
1174
|
+
return { ok: true, value: el.value };
|
|
1175
|
+
}
|
|
1176
|
+
if (payload.action === 'press') {
|
|
1177
|
+
const el = query(payload.selector);
|
|
1178
|
+
const ok = pressKey(el, payload.key);
|
|
1179
|
+
return { ok, key: normalize(payload.key) || 'Enter' };
|
|
1180
|
+
}
|
|
1181
|
+
if (payload.action === 'click_text') {
|
|
1182
|
+
const el = findByText({
|
|
1183
|
+
selector: payload.selector,
|
|
1184
|
+
text: payload.text || payload.value,
|
|
1185
|
+
exact: payload.exact === true,
|
|
1186
|
+
nth: payload.nth,
|
|
1187
|
+
});
|
|
1188
|
+
if (!el) return { ok: false, error: 'text_not_found', text: normalize(payload.text || payload.value), selector: normalize(payload.selector) };
|
|
1189
|
+
clickEl(el);
|
|
1190
|
+
return { ok: true, text: normalize(el.textContent) };
|
|
1191
|
+
}
|
|
1192
|
+
if (payload.action === 'probe') {
|
|
1193
|
+
const selector = normalize(payload.selector) || 'body';
|
|
1194
|
+
const nodes = queryAll(selector);
|
|
1195
|
+
const first = nodes[0] || null;
|
|
1196
|
+
const firstVisible = isVisible(first);
|
|
1197
|
+
const text = normalize(first?.textContent);
|
|
1198
|
+
const value = first && 'value' in first ? String(first.value ?? '') : text;
|
|
1199
|
+
const checked = Boolean(first && 'checked' in first && first.checked === true);
|
|
1200
|
+
const disabled = Boolean(first && 'disabled' in first && first.disabled === true);
|
|
1201
|
+
const probeText = normalize(payload.text || payload.value);
|
|
1202
|
+
let details = null;
|
|
1203
|
+
if (first && payload.detailed === true) {
|
|
1204
|
+
details = getElementDetails(first);
|
|
1205
|
+
}
|
|
1206
|
+
let textMatchedCount = 0;
|
|
1207
|
+
if (probeText) {
|
|
1208
|
+
const target = payload.exact === true ? probeText : probeText.toLowerCase();
|
|
1209
|
+
textMatchedCount = nodes.filter((el) => {
|
|
1210
|
+
const current = normalize(el.textContent);
|
|
1211
|
+
if (!current) return false;
|
|
1212
|
+
if (payload.exact === true) return current === target;
|
|
1213
|
+
return current.toLowerCase().includes(target);
|
|
1214
|
+
}).length;
|
|
1215
|
+
}
|
|
1216
|
+
return {
|
|
1217
|
+
ok: true,
|
|
1218
|
+
selector,
|
|
1219
|
+
exists: Boolean(first),
|
|
1220
|
+
count: nodes.length,
|
|
1221
|
+
visible: firstVisible,
|
|
1222
|
+
text,
|
|
1223
|
+
value,
|
|
1224
|
+
checked,
|
|
1225
|
+
disabled,
|
|
1226
|
+
tagName: first?.tagName || '',
|
|
1227
|
+
className: first?.className || '',
|
|
1228
|
+
details,
|
|
1229
|
+
textMatchedCount,
|
|
1230
|
+
};
|
|
1231
|
+
}
|
|
1232
|
+
if (payload.action === 'close_window') {
|
|
1233
|
+
window.close();
|
|
1234
|
+
return { ok: true };
|
|
1235
|
+
}
|
|
1236
|
+
return { ok: false, error: 'unsupported_action', action: normalize(payload.action) };
|
|
1237
|
+
})()`;
|
|
1238
|
+
}
|
|
1239
|
+
var UiCliBridge = class {
|
|
1240
|
+
server = null;
|
|
1241
|
+
options;
|
|
1242
|
+
host;
|
|
1243
|
+
port;
|
|
1244
|
+
constructor(options) {
|
|
1245
|
+
this.options = options;
|
|
1246
|
+
this.host = String(options.host || process.env.WEBAUTO_UI_CLI_HOST || DEFAULT_HOST);
|
|
1247
|
+
this.port = readInt(options.port || process.env.WEBAUTO_UI_CLI_PORT, DEFAULT_PORT);
|
|
1248
|
+
}
|
|
1249
|
+
getAddress() {
|
|
1250
|
+
return { host: this.host, port: this.port };
|
|
1251
|
+
}
|
|
1252
|
+
async start() {
|
|
1253
|
+
if (this.server) return this.getAddress();
|
|
1254
|
+
await new Promise((resolve, reject) => {
|
|
1255
|
+
const server = createServer((req, res) => {
|
|
1256
|
+
void this.handleRequest(req, res);
|
|
1257
|
+
});
|
|
1258
|
+
server.on("error", (err) => reject(err));
|
|
1259
|
+
server.listen(this.port, this.host, () => {
|
|
1260
|
+
this.server = server;
|
|
1261
|
+
resolve();
|
|
1262
|
+
});
|
|
1263
|
+
});
|
|
1264
|
+
await writeControlFile(this.host, this.port);
|
|
1265
|
+
return this.getAddress();
|
|
1266
|
+
}
|
|
1267
|
+
async stop() {
|
|
1268
|
+
if (!this.server) {
|
|
1269
|
+
await removeControlFile();
|
|
1270
|
+
return;
|
|
1271
|
+
}
|
|
1272
|
+
const srv = this.server;
|
|
1273
|
+
this.server = null;
|
|
1274
|
+
await new Promise((resolve) => srv.close(() => resolve()));
|
|
1275
|
+
await removeControlFile();
|
|
1276
|
+
}
|
|
1277
|
+
async handleRequest(req, res) {
|
|
1278
|
+
const method = String(req.method || "GET").toUpperCase();
|
|
1279
|
+
const url = new URL(req.url || "/", `http://${this.host}:${this.port}`);
|
|
1280
|
+
if (method === "GET" && url.pathname === "/health") {
|
|
1281
|
+
return sendJson(res, 200, await this.status());
|
|
1282
|
+
}
|
|
1283
|
+
if (method === "GET" && (url.pathname === "/status" || url.pathname === "/snapshot")) {
|
|
1284
|
+
return sendJson(res, 200, await this.status(true));
|
|
1285
|
+
}
|
|
1286
|
+
if (method === "POST" && url.pathname === "/action") {
|
|
1287
|
+
const body = await parseBody(req);
|
|
1288
|
+
const result = await this.handleAction(body || {});
|
|
1289
|
+
return sendJson(res, result.ok ? 200 : 400, result);
|
|
1290
|
+
}
|
|
1291
|
+
return sendJson(res, 404, { ok: false, error: "not_found" });
|
|
1292
|
+
}
|
|
1293
|
+
async status(includeSnapshot = false) {
|
|
1294
|
+
const win2 = this.options.getWindow();
|
|
1295
|
+
const ready = isUiReady(win2);
|
|
1296
|
+
if (!ready) {
|
|
1297
|
+
return {
|
|
1298
|
+
ok: false,
|
|
1299
|
+
pid: process.pid,
|
|
1300
|
+
ready: false,
|
|
1301
|
+
host: this.host,
|
|
1302
|
+
port: this.port,
|
|
1303
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1304
|
+
error: "window_not_ready"
|
|
1305
|
+
};
|
|
1306
|
+
}
|
|
1307
|
+
let snapshot;
|
|
1308
|
+
if (includeSnapshot) {
|
|
1309
|
+
try {
|
|
1310
|
+
snapshot = await win2.webContents.executeJavaScript(buildSnapshotScript(), true);
|
|
1311
|
+
} catch (err) {
|
|
1312
|
+
return {
|
|
1313
|
+
ok: false,
|
|
1314
|
+
pid: process.pid,
|
|
1315
|
+
ready,
|
|
1316
|
+
host: this.host,
|
|
1317
|
+
port: this.port,
|
|
1318
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1319
|
+
error: err?.message || String(err)
|
|
1320
|
+
};
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
return {
|
|
1324
|
+
ok: true,
|
|
1325
|
+
pid: process.pid,
|
|
1326
|
+
ready,
|
|
1327
|
+
host: this.host,
|
|
1328
|
+
port: this.port,
|
|
1329
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1330
|
+
snapshot
|
|
1331
|
+
};
|
|
1332
|
+
}
|
|
1333
|
+
async handleAction(input) {
|
|
1334
|
+
const action = String(input?.action || "").trim();
|
|
1335
|
+
if (!action) return toActionError(input, "missing_action");
|
|
1336
|
+
if (action === "wait") {
|
|
1337
|
+
return this.waitForSelector(input);
|
|
1338
|
+
}
|
|
1339
|
+
const win2 = this.options.getWindow();
|
|
1340
|
+
if (!isUiReady(win2)) return toActionError(input, "window_not_ready");
|
|
1341
|
+
try {
|
|
1342
|
+
const out = await win2.webContents.executeJavaScript(buildActionScript(input), true);
|
|
1343
|
+
return out && typeof out === "object" ? out : toActionError(input, "empty_result");
|
|
1344
|
+
} catch (err) {
|
|
1345
|
+
return toActionError(input, err?.message || String(err), { details: err?.stack || null });
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
async waitForSelector(input) {
|
|
1349
|
+
const selector = String(input.selector || "").trim();
|
|
1350
|
+
if (!selector) return toActionError(input, "missing_selector");
|
|
1351
|
+
const expected = input.state || "visible";
|
|
1352
|
+
const timeoutMs = readInt(input.timeoutMs, 15e3);
|
|
1353
|
+
const intervalMs = readInt(input.intervalMs, 250);
|
|
1354
|
+
const startedAt = Date.now();
|
|
1355
|
+
while (Date.now() - startedAt <= timeoutMs) {
|
|
1356
|
+
const win2 = this.options.getWindow();
|
|
1357
|
+
if (!isUiReady(win2)) return toActionError(input, "window_not_ready");
|
|
1358
|
+
try {
|
|
1359
|
+
const checkScript = `(() => {
|
|
1360
|
+
const el = document.querySelector(${JSON.stringify(selector)});
|
|
1361
|
+
const visible = (() => {
|
|
1362
|
+
if (!el) return false;
|
|
1363
|
+
const rect = el.getBoundingClientRect();
|
|
1364
|
+
if (!rect || rect.width <= 0 || rect.height <= 0) return false;
|
|
1365
|
+
const style = window.getComputedStyle(el);
|
|
1366
|
+
return style.display !== 'none' && style.visibility !== 'hidden' && style.opacity !== '0';
|
|
1367
|
+
})();
|
|
1368
|
+
const text = String(el?.textContent || '').trim();
|
|
1369
|
+
const value = el && 'value' in el ? String(el.value ?? '') : '';
|
|
1370
|
+
const disabled = Boolean(el && 'disabled' in el && el.disabled === true);
|
|
1371
|
+
return { exists: Boolean(el), visible, text, value, disabled };
|
|
1372
|
+
})()`;
|
|
1373
|
+
const state = await win2.webContents.executeJavaScript(checkScript, true);
|
|
1374
|
+
const exists = Boolean(state?.exists);
|
|
1375
|
+
const visible = Boolean(state?.visible);
|
|
1376
|
+
const text = String(state?.text || "");
|
|
1377
|
+
const value = String(state?.value || "");
|
|
1378
|
+
const disabled = Boolean(state?.disabled);
|
|
1379
|
+
let matched = false;
|
|
1380
|
+
let reason = "";
|
|
1381
|
+
switch (expected) {
|
|
1382
|
+
case "exists":
|
|
1383
|
+
matched = exists;
|
|
1384
|
+
reason = exists ? "element exists" : "element not found";
|
|
1385
|
+
break;
|
|
1386
|
+
case "visible":
|
|
1387
|
+
matched = visible;
|
|
1388
|
+
reason = visible ? "element visible" : "element not visible";
|
|
1389
|
+
break;
|
|
1390
|
+
case "hidden":
|
|
1391
|
+
matched = !exists || !visible;
|
|
1392
|
+
reason = matched ? "element hidden or absent" : "element is visible";
|
|
1393
|
+
break;
|
|
1394
|
+
case "text_contains":
|
|
1395
|
+
matched = exists && text.includes(String(input.value || ""));
|
|
1396
|
+
reason = matched ? "text matched" : `text '${text}' does not contain '${input.value || ""}'`;
|
|
1397
|
+
break;
|
|
1398
|
+
case "text_equals":
|
|
1399
|
+
matched = exists && text === String(input.value || "");
|
|
1400
|
+
reason = matched ? "text matched" : `text '${text}' !== '${input.value || ""}'`;
|
|
1401
|
+
break;
|
|
1402
|
+
case "value_equals":
|
|
1403
|
+
matched = exists && value === String(input.value || "");
|
|
1404
|
+
reason = matched ? "value matched" : `value '${value}' !== '${input.value || ""}'`;
|
|
1405
|
+
break;
|
|
1406
|
+
case "not_disabled":
|
|
1407
|
+
matched = exists && !disabled;
|
|
1408
|
+
reason = matched ? "element enabled" : "element disabled";
|
|
1409
|
+
break;
|
|
1410
|
+
default:
|
|
1411
|
+
return toActionError(input, "unsupported_state", { expected });
|
|
1412
|
+
}
|
|
1413
|
+
if (matched) {
|
|
1414
|
+
return { ok: true, selector, expected, exists, visible, text, value, disabled, elapsedMs: Date.now() - startedAt, reason };
|
|
1415
|
+
}
|
|
1416
|
+
} catch (err) {
|
|
1417
|
+
return toActionError(input, err?.message || String(err), { details: err?.stack || null });
|
|
1418
|
+
}
|
|
1419
|
+
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
1420
|
+
}
|
|
1421
|
+
return toActionError(input, "wait_timeout", {
|
|
1422
|
+
expected,
|
|
1423
|
+
timeoutMs,
|
|
1424
|
+
elapsedMs: Date.now() - startedAt
|
|
1425
|
+
});
|
|
1426
|
+
}
|
|
1427
|
+
};
|
|
1428
|
+
|
|
773
1429
|
// src/main/index.mts
|
|
774
1430
|
var { app, BrowserWindow: BrowserWindow2, ipcMain: ipcMain2, shell, clipboard } = electron;
|
|
775
|
-
var __dirname =
|
|
776
|
-
var APP_ROOT =
|
|
777
|
-
var REPO_ROOT2 =
|
|
778
|
-
var DESKTOP_HEARTBEAT_FILE =
|
|
779
|
-
|
|
1431
|
+
var __dirname = path6.dirname(fileURLToPath2(import.meta.url));
|
|
1432
|
+
var APP_ROOT = path6.resolve(__dirname, "../..");
|
|
1433
|
+
var REPO_ROOT2 = path6.resolve(APP_ROOT, "../..");
|
|
1434
|
+
var DESKTOP_HEARTBEAT_FILE = path6.join(
|
|
1435
|
+
os5.homedir(),
|
|
780
1436
|
".webauto",
|
|
781
1437
|
"run",
|
|
782
1438
|
"desktop-console-heartbeat.json"
|
|
783
1439
|
);
|
|
784
1440
|
var profileStore = createProfileStore({ repoRoot: REPO_ROOT2 });
|
|
785
|
-
var XHS_SCRIPTS_ROOT =
|
|
1441
|
+
var XHS_SCRIPTS_ROOT = path6.join(REPO_ROOT2, "scripts", "xiaohongshu");
|
|
786
1442
|
var XHS_FULL_COLLECT_RE = /collect-content\.mjs$/;
|
|
787
1443
|
function configureElectronPaths() {
|
|
788
1444
|
try {
|
|
789
1445
|
const downloadRoot = resolveDefaultDownloadRoot();
|
|
790
|
-
const normalized =
|
|
791
|
-
const baseDir =
|
|
792
|
-
const userDataRoot =
|
|
793
|
-
const cacheRoot =
|
|
794
|
-
const gpuCacheRoot =
|
|
1446
|
+
const normalized = path6.normalize(downloadRoot);
|
|
1447
|
+
const baseDir = path6.basename(normalized).toLowerCase() === "download" ? path6.dirname(normalized) : normalized;
|
|
1448
|
+
const userDataRoot = path6.join(baseDir, "desktop-console");
|
|
1449
|
+
const cacheRoot = path6.join(userDataRoot, "cache");
|
|
1450
|
+
const gpuCacheRoot = path6.join(cacheRoot, "gpu");
|
|
795
1451
|
try {
|
|
796
1452
|
mkdirSync(cacheRoot, { recursive: true });
|
|
797
1453
|
} catch {
|
|
@@ -833,6 +1489,8 @@ var GroupQueue = class {
|
|
|
833
1489
|
};
|
|
834
1490
|
var groupQueues = /* @__PURE__ */ new Map();
|
|
835
1491
|
var runs = /* @__PURE__ */ new Map();
|
|
1492
|
+
var trackedRunPids = /* @__PURE__ */ new Set();
|
|
1493
|
+
var appExitCleanupPromise = null;
|
|
836
1494
|
var UI_HEARTBEAT_TIMEOUT_MS = resolveUiHeartbeatTimeoutMs(process.env);
|
|
837
1495
|
var lastUiHeartbeatAt = Date.now();
|
|
838
1496
|
var heartbeatWatchdog = null;
|
|
@@ -849,8 +1507,8 @@ async function writeCoreServiceHeartbeat(status) {
|
|
|
849
1507
|
source: "desktop-console"
|
|
850
1508
|
};
|
|
851
1509
|
try {
|
|
852
|
-
await
|
|
853
|
-
await
|
|
1510
|
+
await fs4.mkdir(path6.dirname(filePath), { recursive: true });
|
|
1511
|
+
await fs4.writeFile(filePath, JSON.stringify(payload), "utf8");
|
|
854
1512
|
} catch {
|
|
855
1513
|
}
|
|
856
1514
|
}
|
|
@@ -887,11 +1545,31 @@ function ensureStateBridge() {
|
|
|
887
1545
|
}
|
|
888
1546
|
}
|
|
889
1547
|
var win = null;
|
|
1548
|
+
var uiCliBridge = new UiCliBridge({ getWindow: getWin });
|
|
890
1549
|
configureElectronPaths();
|
|
1550
|
+
var singleInstanceLock = app.requestSingleInstanceLock();
|
|
1551
|
+
if (!singleInstanceLock) {
|
|
1552
|
+
app.quit();
|
|
1553
|
+
}
|
|
891
1554
|
function getWin() {
|
|
892
1555
|
if (!win || win.isDestroyed()) return null;
|
|
893
1556
|
return win;
|
|
894
1557
|
}
|
|
1558
|
+
if (singleInstanceLock) {
|
|
1559
|
+
app.on("second-instance", () => {
|
|
1560
|
+
const w = getWin();
|
|
1561
|
+
if (w) {
|
|
1562
|
+
if (w.isMinimized()) w.restore();
|
|
1563
|
+
w.focus();
|
|
1564
|
+
return;
|
|
1565
|
+
}
|
|
1566
|
+
if (app.isReady()) {
|
|
1567
|
+
createWindow();
|
|
1568
|
+
} else {
|
|
1569
|
+
app.whenReady().then(() => createWindow());
|
|
1570
|
+
}
|
|
1571
|
+
});
|
|
1572
|
+
}
|
|
895
1573
|
function isUiOperational() {
|
|
896
1574
|
const w = getWin();
|
|
897
1575
|
if (!w) return false;
|
|
@@ -951,6 +1629,85 @@ function killAllRuns(reason = "ui_heartbeat_timeout") {
|
|
|
951
1629
|
void stopCoreServicesBestEffort(reason);
|
|
952
1630
|
}
|
|
953
1631
|
}
|
|
1632
|
+
function trackRunPid(child) {
|
|
1633
|
+
const pid = Number(child?.pid || 0);
|
|
1634
|
+
if (pid > 0) trackedRunPids.add(pid);
|
|
1635
|
+
}
|
|
1636
|
+
function untrackRunPid(child) {
|
|
1637
|
+
const pid = Number(child?.pid || 0);
|
|
1638
|
+
if (pid > 0) trackedRunPids.delete(pid);
|
|
1639
|
+
}
|
|
1640
|
+
async function cleanupTrackedRunPidsBestEffort(reason) {
|
|
1641
|
+
for (const pid of Array.from(trackedRunPids.values())) {
|
|
1642
|
+
try {
|
|
1643
|
+
if (process.platform === "win32") {
|
|
1644
|
+
spawn2("taskkill", ["/PID", String(pid), "/T", "/F"], { stdio: "ignore", windowsHide: true });
|
|
1645
|
+
} else {
|
|
1646
|
+
spawn2("pkill", ["-TERM", "-P", String(pid)], { stdio: "ignore" }).on("error", () => {
|
|
1647
|
+
});
|
|
1648
|
+
process.kill(pid, "SIGTERM");
|
|
1649
|
+
}
|
|
1650
|
+
} catch {
|
|
1651
|
+
}
|
|
1652
|
+
trackedRunPids.delete(pid);
|
|
1653
|
+
}
|
|
1654
|
+
if (trackedRunPids.size > 0) {
|
|
1655
|
+
console.warn(`[desktop-console] residual run pids after cleanup (${reason}): ${trackedRunPids.size}`);
|
|
1656
|
+
trackedRunPids.clear();
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
async function cleanupCamoSessionsBestEffort(reason, includeLocks) {
|
|
1660
|
+
const camoCli = path6.join(REPO_ROOT2, "bin", "camoufox-cli.mjs");
|
|
1661
|
+
const invoke = async (args, timeoutMs = 6e4) => runJson({
|
|
1662
|
+
title: `camo ${args.join(" ")}`,
|
|
1663
|
+
cwd: REPO_ROOT2,
|
|
1664
|
+
args: [camoCli, ...args, "--json"],
|
|
1665
|
+
timeoutMs
|
|
1666
|
+
}).catch((err) => ({ ok: false, error: err?.message || String(err) }));
|
|
1667
|
+
const stopAll = await invoke(["stop", "all"]);
|
|
1668
|
+
if (!stopAll?.ok) {
|
|
1669
|
+
console.warn(`[desktop-console] camo stop all failed (${reason})`, stopAll?.error || stopAll?.stderr || stopAll?.stdout || stopAll);
|
|
1670
|
+
}
|
|
1671
|
+
if (!includeLocks) return;
|
|
1672
|
+
const cleanupLocks = await invoke(["cleanup", "locks"]);
|
|
1673
|
+
if (!cleanupLocks?.ok) {
|
|
1674
|
+
console.warn(`[desktop-console] camo cleanup locks failed (${reason})`, cleanupLocks?.error || cleanupLocks?.stderr || cleanupLocks?.stdout || cleanupLocks);
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
async function cleanupRuntimeEnvironment(reason, options = {}) {
|
|
1678
|
+
killAllRuns(reason);
|
|
1679
|
+
await cleanupTrackedRunPidsBestEffort(reason);
|
|
1680
|
+
await cleanupCamoSessionsBestEffort(reason, options.includeLockCleanup !== false);
|
|
1681
|
+
if (options.stopUiBridge) {
|
|
1682
|
+
await uiCliBridge.stop().catch(() => null);
|
|
1683
|
+
}
|
|
1684
|
+
if (options.stopHeartbeat) {
|
|
1685
|
+
stopCoreServiceHeartbeat();
|
|
1686
|
+
}
|
|
1687
|
+
if (heartbeatWatchdog) {
|
|
1688
|
+
clearInterval(heartbeatWatchdog);
|
|
1689
|
+
heartbeatWatchdog = null;
|
|
1690
|
+
}
|
|
1691
|
+
if (options.stopCoreServices) {
|
|
1692
|
+
await stopCoreServicesBestEffort(reason);
|
|
1693
|
+
}
|
|
1694
|
+
if (options.stopStateBridge) {
|
|
1695
|
+
stateBridge.stop();
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1698
|
+
function ensureAppExitCleanup(reason, options = {}) {
|
|
1699
|
+
if (appExitCleanupPromise) return appExitCleanupPromise;
|
|
1700
|
+
appExitCleanupPromise = cleanupRuntimeEnvironment(reason, {
|
|
1701
|
+
stopUiBridge: true,
|
|
1702
|
+
stopHeartbeat: true,
|
|
1703
|
+
stopCoreServices: true,
|
|
1704
|
+
stopStateBridge: options.stopStateBridge === true,
|
|
1705
|
+
includeLockCleanup: true
|
|
1706
|
+
}).finally(() => {
|
|
1707
|
+
appExitCleanupPromise = null;
|
|
1708
|
+
});
|
|
1709
|
+
return appExitCleanupPromise;
|
|
1710
|
+
}
|
|
954
1711
|
function ensureHeartbeatWatchdog() {
|
|
955
1712
|
if (heartbeatWatchdog) return;
|
|
956
1713
|
heartbeatWatchdog = setInterval(() => {
|
|
@@ -1028,13 +1785,13 @@ function resolveNodeBin2() {
|
|
|
1028
1785
|
function resolveCwd(input) {
|
|
1029
1786
|
const raw = String(input || "").trim();
|
|
1030
1787
|
if (!raw) return REPO_ROOT2;
|
|
1031
|
-
return
|
|
1788
|
+
return path6.isAbsolute(raw) ? raw : path6.resolve(REPO_ROOT2, raw);
|
|
1032
1789
|
}
|
|
1033
1790
|
var cachedStateMod = null;
|
|
1034
1791
|
async function getStateModule() {
|
|
1035
1792
|
if (cachedStateMod) return cachedStateMod;
|
|
1036
1793
|
try {
|
|
1037
|
-
const p =
|
|
1794
|
+
const p = path6.join(REPO_ROOT2, "dist", "modules", "state", "src", "xiaohongshu-collect-state.js");
|
|
1038
1795
|
cachedStateMod = await import(pathToFileURL3(p).href);
|
|
1039
1796
|
return cachedStateMod;
|
|
1040
1797
|
} catch {
|
|
@@ -1047,6 +1804,34 @@ async function spawnCommand(spec) {
|
|
|
1047
1804
|
const groupKey = spec.groupKey || "xiaohongshu";
|
|
1048
1805
|
const q = getQueue(groupKey);
|
|
1049
1806
|
const cwd = resolveCwd(spec.cwd);
|
|
1807
|
+
const args = Array.isArray(spec.args) ? spec.args : [];
|
|
1808
|
+
const isXhsRunCommand = args.some((item) => /xhs-(orchestrate|unified)\.mjs$/i.test(String(item || "").replace(/\\/g, "/")));
|
|
1809
|
+
const extractProfilesFromArgs = (argv) => {
|
|
1810
|
+
const out = [];
|
|
1811
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
1812
|
+
const flag = String(argv[i] || "").trim();
|
|
1813
|
+
if (flag === "--profile" || flag === "--profile-id") {
|
|
1814
|
+
const value = String(argv[i + 1] || "").trim();
|
|
1815
|
+
if (value) out.push(value);
|
|
1816
|
+
} else if (flag === "--profiles") {
|
|
1817
|
+
const value = String(argv[i + 1] || "").trim();
|
|
1818
|
+
if (value) {
|
|
1819
|
+
value.split(",").map((v) => v.trim()).filter(Boolean).forEach((v) => out.push(v));
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
return Array.from(new Set(out));
|
|
1824
|
+
};
|
|
1825
|
+
const requestedProfiles = isXhsRunCommand ? extractProfilesFromArgs(args) : [];
|
|
1826
|
+
if (requestedProfiles.length > 0) {
|
|
1827
|
+
for (const run of runs.values()) {
|
|
1828
|
+
const activeProfiles = Array.isArray(run.profiles) ? run.profiles : [];
|
|
1829
|
+
const conflict = requestedProfiles.find((p) => activeProfiles.includes(p));
|
|
1830
|
+
if (conflict) {
|
|
1831
|
+
throw new Error(`profile already running: ${conflict}`);
|
|
1832
|
+
}
|
|
1833
|
+
}
|
|
1834
|
+
}
|
|
1050
1835
|
q.enqueue(
|
|
1051
1836
|
() => new Promise((resolve) => {
|
|
1052
1837
|
let finished = false;
|
|
@@ -1059,7 +1844,7 @@ async function spawnCommand(spec) {
|
|
|
1059
1844
|
runs.delete(runId);
|
|
1060
1845
|
resolve();
|
|
1061
1846
|
};
|
|
1062
|
-
const child = spawn2(resolveNodeBin2(),
|
|
1847
|
+
const child = spawn2(resolveNodeBin2(), args, {
|
|
1063
1848
|
cwd,
|
|
1064
1849
|
env: {
|
|
1065
1850
|
...process.env,
|
|
@@ -1070,7 +1855,8 @@ async function spawnCommand(spec) {
|
|
|
1070
1855
|
stdio: ["ignore", "pipe", "pipe"],
|
|
1071
1856
|
windowsHide: true
|
|
1072
1857
|
});
|
|
1073
|
-
|
|
1858
|
+
trackRunPid(child);
|
|
1859
|
+
runs.set(runId, { child, title: spec.title, startedAt: now(), profiles: requestedProfiles });
|
|
1074
1860
|
sendEvent({ type: "started", runId, title: spec.title, pid: child.pid ?? -1, ts: now() });
|
|
1075
1861
|
const stdoutLines = createLineEmitter(runId, "stdout");
|
|
1076
1862
|
const stderrLines = createLineEmitter(runId, "stderr");
|
|
@@ -1089,6 +1875,7 @@ async function spawnCommand(spec) {
|
|
|
1089
1875
|
exitSignal = signal;
|
|
1090
1876
|
});
|
|
1091
1877
|
child.on("close", (code, signal) => {
|
|
1878
|
+
untrackRunPid(child);
|
|
1092
1879
|
stdoutLines.flush();
|
|
1093
1880
|
stderrLines.flush();
|
|
1094
1881
|
finalize(exitCode ?? code ?? null, exitSignal ?? signal ?? null);
|
|
@@ -1134,21 +1921,21 @@ async function runJson(spec) {
|
|
|
1134
1921
|
}
|
|
1135
1922
|
async function scanResults(input) {
|
|
1136
1923
|
const downloadRoot = String(input.downloadRoot || resolveDefaultDownloadRoot());
|
|
1137
|
-
const root =
|
|
1924
|
+
const root = path6.join(downloadRoot, "xiaohongshu");
|
|
1138
1925
|
const result = { ok: true, root, entries: [] };
|
|
1139
1926
|
try {
|
|
1140
1927
|
const stateMod = await getStateModule();
|
|
1141
|
-
const envDirs = await
|
|
1928
|
+
const envDirs = await fs4.readdir(root, { withFileTypes: true });
|
|
1142
1929
|
for (const envEnt of envDirs) {
|
|
1143
1930
|
if (!envEnt.isDirectory()) continue;
|
|
1144
1931
|
const env = envEnt.name;
|
|
1145
|
-
const envPath =
|
|
1146
|
-
const keywordDirs = await
|
|
1932
|
+
const envPath = path6.join(root, env);
|
|
1933
|
+
const keywordDirs = await fs4.readdir(envPath, { withFileTypes: true });
|
|
1147
1934
|
for (const kwEnt of keywordDirs) {
|
|
1148
1935
|
if (!kwEnt.isDirectory()) continue;
|
|
1149
1936
|
const keyword = kwEnt.name;
|
|
1150
|
-
const kwPath =
|
|
1151
|
-
const stat = await
|
|
1937
|
+
const kwPath = path6.join(envPath, keyword);
|
|
1938
|
+
const stat = await fs4.stat(kwPath).catch(() => null);
|
|
1152
1939
|
let stateSummary = null;
|
|
1153
1940
|
if (stateMod?.loadXhsCollectState) {
|
|
1154
1941
|
try {
|
|
@@ -1176,13 +1963,13 @@ async function scanResults(input) {
|
|
|
1176
1963
|
}
|
|
1177
1964
|
async function listXhsFullCollectScripts() {
|
|
1178
1965
|
try {
|
|
1179
|
-
const entries = await
|
|
1966
|
+
const entries = await fs4.readdir(XHS_SCRIPTS_ROOT, { withFileTypes: true });
|
|
1180
1967
|
const scripts = entries.filter((ent) => ent.isFile() && XHS_FULL_COLLECT_RE.test(ent.name)).map((ent) => {
|
|
1181
1968
|
const name = ent.name;
|
|
1182
1969
|
return {
|
|
1183
1970
|
id: `xhs:${name}`,
|
|
1184
1971
|
label: `Full Collect (${name})`,
|
|
1185
|
-
path:
|
|
1972
|
+
path: path6.join(XHS_SCRIPTS_ROOT, name)
|
|
1186
1973
|
};
|
|
1187
1974
|
});
|
|
1188
1975
|
return { ok: true, scripts };
|
|
@@ -1195,7 +1982,7 @@ async function readTextPreview(input) {
|
|
|
1195
1982
|
const maxBytes = typeof input.maxBytes === "number" ? input.maxBytes : 8e4;
|
|
1196
1983
|
const maxLines = typeof input.maxLines === "number" ? input.maxLines : 200;
|
|
1197
1984
|
try {
|
|
1198
|
-
const raw = await
|
|
1985
|
+
const raw = await fs4.readFile(filePath, "utf8");
|
|
1199
1986
|
const clipped = raw.slice(0, maxBytes);
|
|
1200
1987
|
const lines = clipped.split(/\r?\n/g).slice(0, maxLines);
|
|
1201
1988
|
return { ok: true, path: filePath, text: lines.join("\n") };
|
|
@@ -1208,14 +1995,14 @@ async function readTextTail(input) {
|
|
|
1208
1995
|
const filePath = String(input?.path || "");
|
|
1209
1996
|
const requestedOffset = typeof input?.fromOffset === "number" ? Math.max(0, Math.floor(input.fromOffset)) : 0;
|
|
1210
1997
|
const maxBytes = typeof input?.maxBytes === "number" ? Math.max(1024, Math.floor(input.maxBytes)) : 256e3;
|
|
1211
|
-
const st = await
|
|
1998
|
+
const st = await fs4.stat(filePath);
|
|
1212
1999
|
const size = Number(st?.size || 0);
|
|
1213
2000
|
const fromOffset = requestedOffset > size ? 0 : requestedOffset;
|
|
1214
2001
|
const toRead = Math.max(0, Math.min(maxBytes, size - fromOffset));
|
|
1215
2002
|
if (toRead <= 0) {
|
|
1216
2003
|
return { ok: true, path: filePath, text: "", fromOffset, nextOffset: fromOffset, fileSize: size };
|
|
1217
2004
|
}
|
|
1218
|
-
const fh = await
|
|
2005
|
+
const fh = await fs4.open(filePath, "r");
|
|
1219
2006
|
try {
|
|
1220
2007
|
const buf = Buffer.allocUnsafe(toRead);
|
|
1221
2008
|
const { bytesRead } = await fh.read(buf, 0, toRead, fromOffset);
|
|
@@ -1235,7 +2022,7 @@ async function readTextTail(input) {
|
|
|
1235
2022
|
async function readFileBase64(input) {
|
|
1236
2023
|
const filePath = String(input.path || "");
|
|
1237
2024
|
const maxBytes = typeof input.maxBytes === "number" ? input.maxBytes : 8e6;
|
|
1238
|
-
const buf = await
|
|
2025
|
+
const buf = await fs4.readFile(filePath);
|
|
1239
2026
|
if (buf.byteLength > maxBytes) {
|
|
1240
2027
|
return { ok: false, error: `file too large: ${buf.byteLength}` };
|
|
1241
2028
|
}
|
|
@@ -1249,14 +2036,14 @@ async function listDir(input) {
|
|
|
1249
2036
|
const stack = [root];
|
|
1250
2037
|
while (stack.length > 0 && entries.length < maxEntries) {
|
|
1251
2038
|
const dir = stack.pop();
|
|
1252
|
-
const items = await
|
|
2039
|
+
const items = await fs4.readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
1253
2040
|
for (const ent of items) {
|
|
1254
2041
|
if (entries.length >= maxEntries) break;
|
|
1255
|
-
const full =
|
|
1256
|
-
const st = await
|
|
2042
|
+
const full = path6.join(dir, ent.name);
|
|
2043
|
+
const st = await fs4.stat(full).catch(() => null);
|
|
1257
2044
|
entries.push({
|
|
1258
2045
|
path: full,
|
|
1259
|
-
rel:
|
|
2046
|
+
rel: path6.relative(root, full),
|
|
1260
2047
|
name: ent.name,
|
|
1261
2048
|
isDir: ent.isDirectory(),
|
|
1262
2049
|
size: st?.size || 0,
|
|
@@ -1279,7 +2066,7 @@ function createWindow() {
|
|
|
1279
2066
|
minWidth: 920,
|
|
1280
2067
|
minHeight: 800,
|
|
1281
2068
|
webPreferences: {
|
|
1282
|
-
preload:
|
|
2069
|
+
preload: path6.join(APP_ROOT, "dist", "main", "preload.mjs"),
|
|
1283
2070
|
contextIsolation: true,
|
|
1284
2071
|
nodeIntegration: false,
|
|
1285
2072
|
sandbox: false,
|
|
@@ -1287,28 +2074,19 @@ function createWindow() {
|
|
|
1287
2074
|
backgroundThrottling: false
|
|
1288
2075
|
}
|
|
1289
2076
|
});
|
|
1290
|
-
const htmlPath =
|
|
2077
|
+
const htmlPath = path6.join(APP_ROOT, "dist", "renderer", "index.html");
|
|
1291
2078
|
void win.loadFile(htmlPath);
|
|
1292
2079
|
ensureStateBridge();
|
|
1293
2080
|
}
|
|
1294
2081
|
app.on("window-all-closed", () => {
|
|
1295
|
-
|
|
2082
|
+
void ensureAppExitCleanup("window_closed");
|
|
1296
2083
|
app.quit();
|
|
1297
2084
|
});
|
|
1298
2085
|
app.on("before-quit", () => {
|
|
1299
|
-
|
|
1300
|
-
stopCoreServiceHeartbeat();
|
|
1301
|
-
void stopCoreServicesBestEffort("before_quit");
|
|
1302
|
-
if (heartbeatWatchdog) {
|
|
1303
|
-
clearInterval(heartbeatWatchdog);
|
|
1304
|
-
heartbeatWatchdog = null;
|
|
1305
|
-
}
|
|
2086
|
+
void ensureAppExitCleanup("before_quit");
|
|
1306
2087
|
});
|
|
1307
2088
|
app.on("will-quit", () => {
|
|
1308
|
-
|
|
1309
|
-
stopCoreServiceHeartbeat();
|
|
1310
|
-
void stopCoreServicesBestEffort("will_quit");
|
|
1311
|
-
stateBridge.stop();
|
|
2089
|
+
void ensureAppExitCleanup("will_quit", { stopStateBridge: true });
|
|
1312
2090
|
});
|
|
1313
2091
|
app.whenReady().then(async () => {
|
|
1314
2092
|
startCoreServiceHeartbeat();
|
|
@@ -1319,6 +2097,9 @@ app.whenReady().then(async () => {
|
|
|
1319
2097
|
markUiHeartbeat("main_ready");
|
|
1320
2098
|
ensureHeartbeatWatchdog();
|
|
1321
2099
|
createWindow();
|
|
2100
|
+
await uiCliBridge.start().catch((err) => {
|
|
2101
|
+
console.warn("[desktop-console] ui-cli bridge start failed", err);
|
|
2102
|
+
});
|
|
1322
2103
|
});
|
|
1323
2104
|
ipcMain2.on("preload:test", () => {
|
|
1324
2105
|
console.log("[preload-test] window.api OK");
|
|
@@ -1468,9 +2249,58 @@ ipcMain2.handle("env:checkGeoIP", async () => checkGeoIP());
|
|
|
1468
2249
|
ipcMain2.handle("env:checkAll", async () => checkEnvironment());
|
|
1469
2250
|
ipcMain2.handle("env:repairCore", async () => {
|
|
1470
2251
|
const ok = await startCoreDaemon().catch(() => false);
|
|
1471
|
-
const services = await checkServices().catch(() => ({ unifiedApi: false,
|
|
2252
|
+
const services = await checkServices().catch(() => ({ unifiedApi: false, camoRuntime: false }));
|
|
1472
2253
|
return { ok, services };
|
|
1473
2254
|
});
|
|
2255
|
+
ipcMain2.handle("env:repairDeps", async (_evt, input) => {
|
|
2256
|
+
const wantCore = Boolean(input?.core);
|
|
2257
|
+
const wantBrowser = Boolean(input?.browser);
|
|
2258
|
+
const wantGeoip = Boolean(input?.geoip);
|
|
2259
|
+
const wantReinstall = Boolean(input?.reinstall);
|
|
2260
|
+
const wantUninstall = Boolean(input?.uninstall);
|
|
2261
|
+
const result = { ok: true, core: null, install: null, env: null };
|
|
2262
|
+
if (wantCore) {
|
|
2263
|
+
const coreOk = await startCoreDaemon().catch(() => false);
|
|
2264
|
+
result.core = {
|
|
2265
|
+
ok: coreOk,
|
|
2266
|
+
services: await checkServices().catch(() => ({ unifiedApi: false, camoRuntime: false }))
|
|
2267
|
+
};
|
|
2268
|
+
if (!coreOk) result.ok = false;
|
|
2269
|
+
}
|
|
2270
|
+
if (wantBrowser || wantGeoip) {
|
|
2271
|
+
const args = [path6.join("apps", "webauto", "entry", "xhs-install.mjs")];
|
|
2272
|
+
if (wantReinstall) args.push("--reinstall");
|
|
2273
|
+
else if (wantUninstall) args.push("--uninstall");
|
|
2274
|
+
else args.push("--install");
|
|
2275
|
+
if (wantBrowser) args.push("--download-browser");
|
|
2276
|
+
if (wantGeoip) args.push("--download-geoip");
|
|
2277
|
+
if (!wantUninstall) args.push("--ensure-backend");
|
|
2278
|
+
const installRes = await runJson({
|
|
2279
|
+
title: "env repair deps",
|
|
2280
|
+
cwd: REPO_ROOT2,
|
|
2281
|
+
args,
|
|
2282
|
+
timeoutMs: 3e5
|
|
2283
|
+
}).catch((err) => ({ ok: false, error: err?.message || String(err) }));
|
|
2284
|
+
result.install = installRes;
|
|
2285
|
+
if (!installRes?.ok) result.ok = false;
|
|
2286
|
+
}
|
|
2287
|
+
result.env = await checkEnvironment().catch(() => null);
|
|
2288
|
+
return result;
|
|
2289
|
+
});
|
|
2290
|
+
ipcMain2.handle("env:cleanup", async () => {
|
|
2291
|
+
markUiHeartbeat("env_cleanup");
|
|
2292
|
+
await cleanupRuntimeEnvironment("env_cleanup", {
|
|
2293
|
+
stopUiBridge: false,
|
|
2294
|
+
stopHeartbeat: false,
|
|
2295
|
+
stopCoreServices: false,
|
|
2296
|
+
stopStateBridge: false,
|
|
2297
|
+
includeLockCleanup: true
|
|
2298
|
+
});
|
|
2299
|
+
return {
|
|
2300
|
+
ok: true,
|
|
2301
|
+
services: await checkServices().catch(() => ({ unifiedApi: false, camoRuntime: false }))
|
|
2302
|
+
};
|
|
2303
|
+
});
|
|
1474
2304
|
ipcMain2.handle("config:saveLast", async (_evt, config) => {
|
|
1475
2305
|
await saveCrawlConfig({ appRoot: APP_ROOT, repoRoot: REPO_ROOT2 }, config);
|
|
1476
2306
|
return { ok: true };
|
|
@@ -1561,7 +2391,7 @@ ipcMain2.handle("runtime:kill", async (_evt, input) => {
|
|
|
1561
2391
|
ipcMain2.handle("runtime:restartPhase1", async (_evt, input) => {
|
|
1562
2392
|
const profileId = String(input?.profileId || "").trim();
|
|
1563
2393
|
if (!profileId) return { ok: false, error: "missing profileId" };
|
|
1564
|
-
const args = [
|
|
2394
|
+
const args = [path6.join(REPO_ROOT2, "scripts", "xiaohongshu", "phase1-boot.mjs"), "--profile", profileId, "--headless", "false"];
|
|
1565
2395
|
return spawnCommand({ title: `Phase1 restart ${profileId}`, cwd: REPO_ROOT2, args, groupKey: "phase1" });
|
|
1566
2396
|
});
|
|
1567
2397
|
ipcMain2.handle("runtime:setBrowserTitle", async (_evt, input) => {
|