@web-auto/webauto 0.1.4 → 0.1.7
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 +983 -128
- 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 +2423 -469
- 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 +256 -31
- 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() {
|
|
@@ -230,6 +270,7 @@ async function importConfigFromFile(filePath) {
|
|
|
230
270
|
// src/main/core-daemon-manager.mts
|
|
231
271
|
import { spawn } from "child_process";
|
|
232
272
|
import path2 from "path";
|
|
273
|
+
import { existsSync as existsSync2 } from "fs";
|
|
233
274
|
import { fileURLToPath } from "url";
|
|
234
275
|
var REPO_ROOT = path2.resolve(path2.dirname(fileURLToPath(import.meta.url)), "../../../..");
|
|
235
276
|
var CORE_HEALTH_URLS = ["http://127.0.0.1:7701/health", "http://127.0.0.1:7704/health"];
|
|
@@ -243,11 +284,33 @@ function resolveNodeBin() {
|
|
|
243
284
|
if (explicit) return explicit;
|
|
244
285
|
const npmNode = String(process.env.npm_node_execpath || "").trim();
|
|
245
286
|
if (npmNode) return npmNode;
|
|
246
|
-
|
|
287
|
+
const fromPath = resolveOnPath(process.platform === "win32" ? ["node.exe", "node.cmd", "node"] : ["node"]);
|
|
288
|
+
if (fromPath) return fromPath;
|
|
289
|
+
return process.execPath;
|
|
247
290
|
}
|
|
248
291
|
function resolveNpxBin() {
|
|
292
|
+
const fromPath = resolveOnPath(
|
|
293
|
+
process.platform === "win32" ? ["npx.cmd", "npx.exe", "npx.bat", "npx.ps1"] : ["npx"]
|
|
294
|
+
);
|
|
295
|
+
if (fromPath) return fromPath;
|
|
249
296
|
return process.platform === "win32" ? "npx.cmd" : "npx";
|
|
250
297
|
}
|
|
298
|
+
function resolveOnPath(candidates) {
|
|
299
|
+
const pathEnv = process.env.PATH || process.env.Path || "";
|
|
300
|
+
const dirs = pathEnv.split(path2.delimiter).filter(Boolean);
|
|
301
|
+
for (const dir of dirs) {
|
|
302
|
+
for (const name of candidates) {
|
|
303
|
+
const full = path2.join(dir, name);
|
|
304
|
+
if (existsSync2(full)) return full;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
return null;
|
|
308
|
+
}
|
|
309
|
+
function quoteCmdArg(value) {
|
|
310
|
+
if (!value) return '""';
|
|
311
|
+
if (!/[\s"]/u.test(value)) return value;
|
|
312
|
+
return `"${value.replace(/"/g, '""')}"`;
|
|
313
|
+
}
|
|
251
314
|
async function checkHttpHealth(url) {
|
|
252
315
|
try {
|
|
253
316
|
const res = await fetch(url, { signal: AbortSignal.timeout(1e3) });
|
|
@@ -292,7 +355,18 @@ async function runNodeScript(scriptPath, timeoutMs) {
|
|
|
292
355
|
}
|
|
293
356
|
async function runCommand(command, args, timeoutMs) {
|
|
294
357
|
return new Promise((resolve) => {
|
|
295
|
-
const
|
|
358
|
+
const lower = String(command || "").toLowerCase();
|
|
359
|
+
let spawnCommand2 = command;
|
|
360
|
+
let spawnArgs = args;
|
|
361
|
+
if (process.platform === "win32" && (lower.endsWith(".cmd") || lower.endsWith(".bat"))) {
|
|
362
|
+
spawnCommand2 = "cmd.exe";
|
|
363
|
+
const cmdLine = [quoteCmdArg(command), ...args.map(quoteCmdArg)].join(" ");
|
|
364
|
+
spawnArgs = ["/d", "/s", "/c", cmdLine];
|
|
365
|
+
} else if (process.platform === "win32" && lower.endsWith(".ps1")) {
|
|
366
|
+
spawnCommand2 = "powershell.exe";
|
|
367
|
+
spawnArgs = ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", command, ...args];
|
|
368
|
+
}
|
|
369
|
+
const child = spawn(spawnCommand2, spawnArgs, {
|
|
296
370
|
cwd: REPO_ROOT,
|
|
297
371
|
stdio: "ignore",
|
|
298
372
|
windowsHide: true,
|
|
@@ -325,7 +399,11 @@ async function startCoreDaemon() {
|
|
|
325
399
|
console.error("[CoreDaemonManager] Failed to start unified API service");
|
|
326
400
|
return false;
|
|
327
401
|
}
|
|
328
|
-
const startedBrowser = await runCommand(
|
|
402
|
+
const startedBrowser = await runCommand(
|
|
403
|
+
resolveNpxBin(),
|
|
404
|
+
["--yes", "--package=@web-auto/camo", "camo", "init"],
|
|
405
|
+
4e4
|
|
406
|
+
);
|
|
329
407
|
if (!startedBrowser) {
|
|
330
408
|
console.error("[CoreDaemonManager] Failed to start camo browser backend");
|
|
331
409
|
return false;
|
|
@@ -546,6 +624,10 @@ var StateBridge = class {
|
|
|
546
624
|
win = null;
|
|
547
625
|
tasks = /* @__PURE__ */ new Map();
|
|
548
626
|
handlersRegistered = false;
|
|
627
|
+
emitBusEvent(payload) {
|
|
628
|
+
if (!this.win) return;
|
|
629
|
+
this.win.webContents.send("bus:event", payload);
|
|
630
|
+
}
|
|
549
631
|
start(win2) {
|
|
550
632
|
this.win = win2;
|
|
551
633
|
this.connect();
|
|
@@ -561,12 +643,26 @@ var StateBridge = class {
|
|
|
561
643
|
this.ws.on("open", () => {
|
|
562
644
|
console.log("[StateBridge] connected to", UNIFIED_API_WS);
|
|
563
645
|
this.ws?.send(JSON.stringify({ type: "subscribe", topic: "task:*" }));
|
|
646
|
+
this.emitBusEvent({ type: "env:unified", ok: true, ts: Date.now() });
|
|
564
647
|
});
|
|
565
648
|
this.ws.on("message", (data) => {
|
|
566
649
|
try {
|
|
567
650
|
const msg = JSON.parse(data.toString());
|
|
568
651
|
if (msg.type === "task:update" && msg.data) {
|
|
569
652
|
this.handleTaskUpdate(msg.data);
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
655
|
+
if (msg.type === "event" && msg.topic === "bus.message") {
|
|
656
|
+
const raw = msg?.payload?.data;
|
|
657
|
+
if (typeof raw === "string" && raw.trim()) {
|
|
658
|
+
try {
|
|
659
|
+
const parsed = JSON.parse(raw);
|
|
660
|
+
if (parsed && typeof parsed === "object") {
|
|
661
|
+
this.emitBusEvent(parsed);
|
|
662
|
+
}
|
|
663
|
+
} catch {
|
|
664
|
+
}
|
|
665
|
+
}
|
|
570
666
|
}
|
|
571
667
|
} catch (err) {
|
|
572
668
|
console.warn("[StateBridge] parse error:", err);
|
|
@@ -574,6 +670,7 @@ var StateBridge = class {
|
|
|
574
670
|
});
|
|
575
671
|
this.ws.on("close", () => {
|
|
576
672
|
console.log("[StateBridge] disconnected, reconnecting...");
|
|
673
|
+
this.emitBusEvent({ type: "env:unified", ok: false, ts: Date.now() });
|
|
577
674
|
this.scheduleReconnect();
|
|
578
675
|
});
|
|
579
676
|
this.ws.on("error", (err) => {
|
|
@@ -632,14 +729,29 @@ var StateBridge = class {
|
|
|
632
729
|
var stateBridge = new StateBridge();
|
|
633
730
|
|
|
634
731
|
// src/main/env-check.mts
|
|
635
|
-
import {
|
|
636
|
-
import {
|
|
637
|
-
import { existsSync } from "node:fs";
|
|
732
|
+
import { spawnSync } from "node:child_process";
|
|
733
|
+
import { existsSync as existsSync3 } from "node:fs";
|
|
638
734
|
import path4 from "node:path";
|
|
639
735
|
import os3 from "node:os";
|
|
640
|
-
|
|
736
|
+
function resolveWebautoRoot() {
|
|
737
|
+
const portableRoot = String(process.env.WEBAUTO_PORTABLE_ROOT || process.env.WEBAUTO_ROOT || "").trim();
|
|
738
|
+
return portableRoot ? path4.join(portableRoot, ".webauto") : path4.join(os3.homedir(), ".webauto");
|
|
739
|
+
}
|
|
641
740
|
function resolveNpxBin2() {
|
|
642
|
-
|
|
741
|
+
if (process.platform !== "win32") return "npx";
|
|
742
|
+
const resolved = resolveOnPath2(["npx.cmd", "npx.exe", "npx.bat", "npx.ps1"]);
|
|
743
|
+
return resolved || "npx.cmd";
|
|
744
|
+
}
|
|
745
|
+
function resolveOnPath2(candidates) {
|
|
746
|
+
const pathEnv = process.env.PATH || process.env.Path || "";
|
|
747
|
+
const dirs = pathEnv.split(path4.delimiter).filter(Boolean);
|
|
748
|
+
for (const dir of dirs) {
|
|
749
|
+
for (const name of candidates) {
|
|
750
|
+
const full = path4.join(dir, name);
|
|
751
|
+
if (existsSync3(full)) return full;
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
return null;
|
|
643
755
|
}
|
|
644
756
|
function resolveCamoVersionFromText(stdout, stderr) {
|
|
645
757
|
const merged = `${String(stdout || "")}
|
|
@@ -653,13 +765,35 @@ ${String(stderr || "")}`.trim();
|
|
|
653
765
|
}
|
|
654
766
|
return "unknown";
|
|
655
767
|
}
|
|
768
|
+
function quoteCmdArg2(value) {
|
|
769
|
+
if (!value) return '""';
|
|
770
|
+
if (!/[\s"]/u.test(value)) return value;
|
|
771
|
+
return `"${value.replace(/"/g, '""')}"`;
|
|
772
|
+
}
|
|
656
773
|
function runVersionCheck(command, args, explicitPath) {
|
|
657
774
|
try {
|
|
658
|
-
const
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
775
|
+
const lower = String(command || "").toLowerCase();
|
|
776
|
+
let ret;
|
|
777
|
+
if (process.platform === "win32" && (lower.endsWith(".cmd") || lower.endsWith(".bat"))) {
|
|
778
|
+
const cmdLine = [quoteCmdArg2(command), ...args.map(quoteCmdArg2)].join(" ");
|
|
779
|
+
ret = spawnSync("cmd.exe", ["/d", "/s", "/c", cmdLine], {
|
|
780
|
+
encoding: "utf8",
|
|
781
|
+
timeout: 8e3,
|
|
782
|
+
windowsHide: true
|
|
783
|
+
});
|
|
784
|
+
} else if (process.platform === "win32" && lower.endsWith(".ps1")) {
|
|
785
|
+
ret = spawnSync("powershell.exe", ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", command, ...args], {
|
|
786
|
+
encoding: "utf8",
|
|
787
|
+
timeout: 8e3,
|
|
788
|
+
windowsHide: true
|
|
789
|
+
});
|
|
790
|
+
} else {
|
|
791
|
+
ret = spawnSync(command, args, {
|
|
792
|
+
encoding: "utf8",
|
|
793
|
+
timeout: 8e3,
|
|
794
|
+
windowsHide: true
|
|
795
|
+
});
|
|
796
|
+
}
|
|
663
797
|
if (ret.status !== 0) {
|
|
664
798
|
return {
|
|
665
799
|
installed: false,
|
|
@@ -675,20 +809,33 @@ function runVersionCheck(command, args, explicitPath) {
|
|
|
675
809
|
return { installed: false, error: String(err) };
|
|
676
810
|
}
|
|
677
811
|
}
|
|
812
|
+
function resolvePathFromOutput(stdout) {
|
|
813
|
+
const lines = String(stdout || "").split(/\r?\n/).map((x) => x.trim()).filter(Boolean);
|
|
814
|
+
for (let i = lines.length - 1; i >= 0; i -= 1) {
|
|
815
|
+
const line = lines[i];
|
|
816
|
+
if (line.startsWith("/") || /^[A-Z]:\\/i.test(line)) return line;
|
|
817
|
+
}
|
|
818
|
+
return "";
|
|
819
|
+
}
|
|
678
820
|
async function checkCamoCli() {
|
|
679
|
-
const
|
|
680
|
-
|
|
821
|
+
const camoCandidates = process.platform === "win32" ? ["camo.cmd", "camo.exe", "camo.bat", "camo.ps1"] : ["camo"];
|
|
822
|
+
for (const candidate of camoCandidates) {
|
|
823
|
+
const pathCheck = runVersionCheck(candidate, ["help"], `PATH:${candidate}`);
|
|
824
|
+
if (pathCheck.installed) return pathCheck;
|
|
825
|
+
}
|
|
681
826
|
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)
|
|
827
|
+
const localRoots = [
|
|
828
|
+
path4.resolve(cwd, "node_modules", ".bin"),
|
|
829
|
+
path4.resolve(cwd, "..", "node_modules", ".bin"),
|
|
830
|
+
path4.resolve(cwd, "..", "..", "node_modules", ".bin")
|
|
687
831
|
];
|
|
688
|
-
for (const
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
832
|
+
for (const localRoot of localRoots) {
|
|
833
|
+
for (const suffix of camoCandidates) {
|
|
834
|
+
const candidate = path4.resolve(localRoot, suffix);
|
|
835
|
+
if (!existsSync3(candidate)) continue;
|
|
836
|
+
const ret = runVersionCheck(candidate, ["help"], candidate);
|
|
837
|
+
if (ret.installed) return ret;
|
|
838
|
+
}
|
|
692
839
|
}
|
|
693
840
|
const npxCheck = runVersionCheck(
|
|
694
841
|
resolveNpxBin2(),
|
|
@@ -702,59 +849,40 @@ async function checkCamoCli() {
|
|
|
702
849
|
};
|
|
703
850
|
}
|
|
704
851
|
async function checkServices() {
|
|
705
|
-
const [unifiedApi,
|
|
852
|
+
const [unifiedApi, camoRuntime, searchGate] = await Promise.all([
|
|
706
853
|
fetch("http://127.0.0.1:7701/health", { signal: AbortSignal.timeout(3e3) }).then((r) => r.ok).catch(() => false),
|
|
707
854
|
fetch("http://127.0.0.1:7704/health", { signal: AbortSignal.timeout(3e3) }).then((r) => r.ok).catch(() => false),
|
|
708
855
|
fetch("http://127.0.0.1:7790/health", { signal: AbortSignal.timeout(3e3) }).then((r) => r.ok).catch(() => false)
|
|
709
856
|
]);
|
|
710
|
-
return { unifiedApi,
|
|
857
|
+
return { unifiedApi, camoRuntime, searchGate };
|
|
711
858
|
}
|
|
712
859
|
async function checkFirefox() {
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
}
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
if (platform === "win32") {
|
|
733
|
-
const programFiles = process.env.PROGRAMFILES || "C:\\Program Files";
|
|
734
|
-
const programFilesX86 = process.env["PROGRAMFILES(X86)"] || "C:\\Program Files (x86)";
|
|
735
|
-
const localAppData = process.env.LOCALAPPDATA || path4.join(os3.homedir(), "AppData", "Local");
|
|
736
|
-
const possiblePaths = [
|
|
737
|
-
path4.join(programFiles, "Mozilla Firefox", "firefox.exe"),
|
|
738
|
-
path4.join(programFilesX86, "Mozilla Firefox", "firefox.exe"),
|
|
739
|
-
path4.join(localAppData, "Mozilla Firefox", "firefox.exe")
|
|
740
|
-
];
|
|
741
|
-
for (const firefoxPath2 of possiblePaths) {
|
|
742
|
-
if (existsSync(firefoxPath2)) return { installed: true, path: firefoxPath2 };
|
|
743
|
-
}
|
|
744
|
-
return { installed: false };
|
|
860
|
+
const candidates = process.platform === "win32" ? [
|
|
861
|
+
{ command: "python", args: ["-m", "camoufox", "path"] },
|
|
862
|
+
{ command: "py", args: ["-3", "-m", "camoufox", "path"] },
|
|
863
|
+
{ command: resolveNpxBin2(), args: ["--yes", "--package=camoufox", "camoufox", "path"] }
|
|
864
|
+
] : [
|
|
865
|
+
{ command: "python3", args: ["-m", "camoufox", "path"] },
|
|
866
|
+
{ command: resolveNpxBin2(), args: ["--yes", "--package=camoufox", "camoufox", "path"] }
|
|
867
|
+
];
|
|
868
|
+
for (const candidate of candidates) {
|
|
869
|
+
try {
|
|
870
|
+
const ret = spawnSync(candidate.command, candidate.args, {
|
|
871
|
+
encoding: "utf8",
|
|
872
|
+
timeout: 8e3,
|
|
873
|
+
windowsHide: true
|
|
874
|
+
});
|
|
875
|
+
if (ret.status !== 0) continue;
|
|
876
|
+
const resolvedPath = resolvePathFromOutput(String(ret.stdout || ""));
|
|
877
|
+
return resolvedPath ? { installed: true, path: resolvedPath } : { installed: true };
|
|
878
|
+
} catch {
|
|
745
879
|
}
|
|
746
|
-
const macBundle = "/Applications/Firefox.app/Contents/MacOS/firefox";
|
|
747
|
-
if (platform === "darwin" && existsSync(macBundle)) return { installed: true, path: macBundle };
|
|
748
|
-
const { stdout } = await execAsync("which firefox", { timeout: 3e3 });
|
|
749
|
-
const firefoxPath = String(stdout || "").trim();
|
|
750
|
-
return firefoxPath ? { installed: true, path: firefoxPath } : { installed: false };
|
|
751
|
-
} catch {
|
|
752
|
-
return { installed: false };
|
|
753
880
|
}
|
|
881
|
+
return { installed: false };
|
|
754
882
|
}
|
|
755
883
|
async function checkGeoIP() {
|
|
756
|
-
const geoIpPath = path4.join(
|
|
757
|
-
if (
|
|
884
|
+
const geoIpPath = path4.join(resolveWebautoRoot(), "geoip", "GeoLite2-City.mmdb");
|
|
885
|
+
if (existsSync3(geoIpPath)) {
|
|
758
886
|
return { installed: true, path: geoIpPath };
|
|
759
887
|
}
|
|
760
888
|
return { installed: false };
|
|
@@ -766,32 +894,585 @@ async function checkEnvironment() {
|
|
|
766
894
|
checkFirefox(),
|
|
767
895
|
checkGeoIP()
|
|
768
896
|
]);
|
|
769
|
-
const allReady = camo.installed && services.unifiedApi &&
|
|
897
|
+
const allReady = camo.installed && services.unifiedApi && firefox.installed;
|
|
770
898
|
return { camo, services, firefox, geoip, allReady };
|
|
771
899
|
}
|
|
772
900
|
|
|
901
|
+
// src/main/ui-cli-bridge.mts
|
|
902
|
+
import { createServer } from "node:http";
|
|
903
|
+
import os4 from "node:os";
|
|
904
|
+
import path5 from "node:path";
|
|
905
|
+
import { promises as fs3 } from "node:fs";
|
|
906
|
+
var DEFAULT_HOST = "127.0.0.1";
|
|
907
|
+
var DEFAULT_PORT = 7716;
|
|
908
|
+
var CONTROL_FILE = path5.join(os4.homedir(), ".webauto", "run", "ui-cli.json");
|
|
909
|
+
function readInt(input, fallback) {
|
|
910
|
+
const n = Number(input);
|
|
911
|
+
return Number.isFinite(n) && n > 0 ? Math.floor(n) : fallback;
|
|
912
|
+
}
|
|
913
|
+
function sendJson(res, code, payload) {
|
|
914
|
+
const body = JSON.stringify(payload);
|
|
915
|
+
res.statusCode = code;
|
|
916
|
+
res.setHeader("Content-Type", "application/json; charset=utf-8");
|
|
917
|
+
res.setHeader("Content-Length", Buffer.byteLength(body));
|
|
918
|
+
res.end(body);
|
|
919
|
+
}
|
|
920
|
+
function parseBody(req) {
|
|
921
|
+
return new Promise((resolve) => {
|
|
922
|
+
const chunks = [];
|
|
923
|
+
req.on("data", (c) => chunks.push(c));
|
|
924
|
+
req.on("end", () => {
|
|
925
|
+
const raw = Buffer.concat(chunks).toString("utf8").trim();
|
|
926
|
+
if (!raw) return resolve({});
|
|
927
|
+
try {
|
|
928
|
+
resolve(JSON.parse(raw));
|
|
929
|
+
} catch {
|
|
930
|
+
resolve({});
|
|
931
|
+
}
|
|
932
|
+
});
|
|
933
|
+
req.on("error", () => resolve({}));
|
|
934
|
+
});
|
|
935
|
+
}
|
|
936
|
+
function isUiReady(win2) {
|
|
937
|
+
if (!win2 || win2.isDestroyed()) return false;
|
|
938
|
+
const wc = win2.webContents;
|
|
939
|
+
if (!wc || wc.isDestroyed()) return false;
|
|
940
|
+
if (typeof wc.isCrashed === "function" && wc.isCrashed()) return false;
|
|
941
|
+
return true;
|
|
942
|
+
}
|
|
943
|
+
function toActionError(input, error, extra = {}) {
|
|
944
|
+
const action = String(input?.action || "").trim();
|
|
945
|
+
const selector = String(input?.selector || "").trim();
|
|
946
|
+
const state = String(input?.state || "").trim();
|
|
947
|
+
const payload = {
|
|
948
|
+
ok: false,
|
|
949
|
+
error: String(error || "unknown_error"),
|
|
950
|
+
action: action || null,
|
|
951
|
+
selector: selector || null,
|
|
952
|
+
state: state || null,
|
|
953
|
+
...extra
|
|
954
|
+
};
|
|
955
|
+
return payload;
|
|
956
|
+
}
|
|
957
|
+
async function writeControlFile(host, port) {
|
|
958
|
+
const payload = {
|
|
959
|
+
pid: process.pid,
|
|
960
|
+
host,
|
|
961
|
+
port,
|
|
962
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
963
|
+
};
|
|
964
|
+
try {
|
|
965
|
+
await fs3.mkdir(path5.dirname(CONTROL_FILE), { recursive: true });
|
|
966
|
+
await fs3.writeFile(CONTROL_FILE, JSON.stringify(payload, null, 2), "utf8");
|
|
967
|
+
} catch {
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
async function removeControlFile() {
|
|
971
|
+
try {
|
|
972
|
+
await fs3.unlink(CONTROL_FILE);
|
|
973
|
+
} catch {
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
function buildSnapshotScript() {
|
|
977
|
+
return `(() => {
|
|
978
|
+
const text = (sel) => {
|
|
979
|
+
const el = document.querySelector(sel);
|
|
980
|
+
return el ? String(el.textContent || '').trim() : '';
|
|
981
|
+
};
|
|
982
|
+
const value = (sel) => {
|
|
983
|
+
const el = document.querySelector(sel);
|
|
984
|
+
if (!el) return '';
|
|
985
|
+
if ('value' in el) return String(el.value ?? '');
|
|
986
|
+
return String(el.textContent || '').trim();
|
|
987
|
+
};
|
|
988
|
+
const activeTab = document.querySelector('.tab.active');
|
|
989
|
+
const errors = Array.from(document.querySelectorAll('#recent-errors-list li'))
|
|
990
|
+
.map((el) => String(el.textContent || '').trim())
|
|
991
|
+
.filter(Boolean)
|
|
992
|
+
.slice(0, 20);
|
|
993
|
+
return {
|
|
994
|
+
ready: true,
|
|
995
|
+
activeTabId: String(activeTab?.dataset?.tabId || '').trim(),
|
|
996
|
+
activeTabLabel: String(activeTab?.textContent || '').trim(),
|
|
997
|
+
status: text('#status'),
|
|
998
|
+
runId: text('#run-id-text'),
|
|
999
|
+
errorCount: text('#error-count-text'),
|
|
1000
|
+
currentPhase: text('#current-phase'),
|
|
1001
|
+
currentAction: text('#current-action'),
|
|
1002
|
+
progressPercent: text('#progress-percent'),
|
|
1003
|
+
keyword: value('#keyword-input'),
|
|
1004
|
+
target: value('#target-input'),
|
|
1005
|
+
account: value('#account-select'),
|
|
1006
|
+
env: value('#env-select'),
|
|
1007
|
+
recentErrors: errors,
|
|
1008
|
+
ts: new Date().toISOString(),
|
|
1009
|
+
};
|
|
1010
|
+
})()`;
|
|
1011
|
+
}
|
|
1012
|
+
function buildActionScript(action) {
|
|
1013
|
+
const payloadJson = JSON.stringify(action);
|
|
1014
|
+
const snapshotScript = buildSnapshotScript();
|
|
1015
|
+
return `(() => {
|
|
1016
|
+
const payload = ${payloadJson};
|
|
1017
|
+
const normalize = (v) => String(v || '').trim();
|
|
1018
|
+
const isVisible = (el) => {
|
|
1019
|
+
if (!el) return false;
|
|
1020
|
+
const rect = el.getBoundingClientRect();
|
|
1021
|
+
if (!rect || rect.width <= 0 || rect.height <= 0) return false;
|
|
1022
|
+
const style = window.getComputedStyle(el);
|
|
1023
|
+
return style.display !== 'none' && style.visibility !== 'hidden' && style.opacity !== '0';
|
|
1024
|
+
};
|
|
1025
|
+
const query = (selector) => {
|
|
1026
|
+
const s = normalize(selector);
|
|
1027
|
+
if (!s) return null;
|
|
1028
|
+
return document.querySelector(s);
|
|
1029
|
+
};
|
|
1030
|
+
const queryAll = (selector) => {
|
|
1031
|
+
const s = normalize(selector) || 'body';
|
|
1032
|
+
return Array.from(document.querySelectorAll(s));
|
|
1033
|
+
};
|
|
1034
|
+
const findByText = ({ selector, text, exact, nth }) => {
|
|
1035
|
+
const q = normalize(selector) || 'button';
|
|
1036
|
+
const target = normalize(text);
|
|
1037
|
+
const lower = target.toLowerCase();
|
|
1038
|
+
if (!target) return null;
|
|
1039
|
+
const nodes = Array.from(document.querySelectorAll(q));
|
|
1040
|
+
const matched = nodes.filter((el) => {
|
|
1041
|
+
const t = normalize(el.textContent);
|
|
1042
|
+
if (!t) return false;
|
|
1043
|
+
if (exact === true) return t === target;
|
|
1044
|
+
return t.toLowerCase().includes(lower);
|
|
1045
|
+
});
|
|
1046
|
+
const index = Number.isFinite(Number(nth)) ? Math.max(0, Math.floor(Number(nth))) : 0;
|
|
1047
|
+
return matched[index] || null;
|
|
1048
|
+
};
|
|
1049
|
+
const getElementDetails = (el) => {
|
|
1050
|
+
if (!el) return null;
|
|
1051
|
+
const rect = el.getBoundingClientRect();
|
|
1052
|
+
const style = window.getComputedStyle(el);
|
|
1053
|
+
const attrs = {};
|
|
1054
|
+
for (const attr of el.attributes) {
|
|
1055
|
+
attrs[attr.name] = attr.value;
|
|
1056
|
+
}
|
|
1057
|
+
return {
|
|
1058
|
+
rect: {
|
|
1059
|
+
x: Math.round(rect.x),
|
|
1060
|
+
y: Math.round(rect.y),
|
|
1061
|
+
width: Math.round(rect.width),
|
|
1062
|
+
height: Math.round(rect.height),
|
|
1063
|
+
top: Math.round(rect.top),
|
|
1064
|
+
left: Math.round(rect.left),
|
|
1065
|
+
right: Math.round(rect.right),
|
|
1066
|
+
bottom: Math.round(rect.bottom),
|
|
1067
|
+
},
|
|
1068
|
+
computedStyle: {
|
|
1069
|
+
display: style.display,
|
|
1070
|
+
visibility: style.visibility,
|
|
1071
|
+
opacity: style.opacity,
|
|
1072
|
+
backgroundColor: style.backgroundColor,
|
|
1073
|
+
color: style.color,
|
|
1074
|
+
fontSize: style.fontSize,
|
|
1075
|
+
fontFamily: style.fontFamily,
|
|
1076
|
+
position: style.position,
|
|
1077
|
+
zIndex: style.zIndex,
|
|
1078
|
+
},
|
|
1079
|
+
attributes: attrs,
|
|
1080
|
+
innerText: el.innerText,
|
|
1081
|
+
outerHTML: el.outerHTML?.slice(0, 2000),
|
|
1082
|
+
tagName: el.tagName,
|
|
1083
|
+
className: el.className,
|
|
1084
|
+
id: el.id,
|
|
1085
|
+
};
|
|
1086
|
+
};
|
|
1087
|
+
const focusEl = (el) => {
|
|
1088
|
+
if (!el || typeof el.focus !== 'function') return false;
|
|
1089
|
+
el.focus();
|
|
1090
|
+
return document.activeElement === el;
|
|
1091
|
+
};
|
|
1092
|
+
const clickEl = (el) => {
|
|
1093
|
+
if (!el || typeof el.click !== 'function') return false;
|
|
1094
|
+
if (typeof el.scrollIntoView === 'function') {
|
|
1095
|
+
try { el.scrollIntoView({ block: 'center', inline: 'nearest' }); } catch {}
|
|
1096
|
+
}
|
|
1097
|
+
focusEl(el);
|
|
1098
|
+
el.click();
|
|
1099
|
+
return true;
|
|
1100
|
+
};
|
|
1101
|
+
const setInputValue = (el, value) => {
|
|
1102
|
+
if (!el) return false;
|
|
1103
|
+
const text = String(value ?? '');
|
|
1104
|
+
if ('value' in el) {
|
|
1105
|
+
el.value = text;
|
|
1106
|
+
} else {
|
|
1107
|
+
el.textContent = text;
|
|
1108
|
+
}
|
|
1109
|
+
el.dispatchEvent(new Event('input', { bubbles: true }));
|
|
1110
|
+
el.dispatchEvent(new Event('change', { bubbles: true }));
|
|
1111
|
+
return true;
|
|
1112
|
+
};
|
|
1113
|
+
const pressKey = (el, key) => {
|
|
1114
|
+
const k = normalize(key) || 'Enter';
|
|
1115
|
+
const target = el || document.activeElement || document.body;
|
|
1116
|
+
const code = k === 'Escape' ? 'Escape' : k === 'Enter' ? 'Enter' : k;
|
|
1117
|
+
const init = { key: k, code, bubbles: true, cancelable: true };
|
|
1118
|
+
target.dispatchEvent(new KeyboardEvent('keydown', init));
|
|
1119
|
+
target.dispatchEvent(new KeyboardEvent('keyup', init));
|
|
1120
|
+
return true;
|
|
1121
|
+
};
|
|
1122
|
+
const findTab = () => {
|
|
1123
|
+
const tabId = normalize(payload.tabId || payload.value);
|
|
1124
|
+
const tabLabel = normalize(payload.tabLabel || payload.selector);
|
|
1125
|
+
const tabs = Array.from(document.querySelectorAll('.tab'));
|
|
1126
|
+
if (tabId) {
|
|
1127
|
+
const byId = tabs.find((el) => normalize(el?.dataset?.tabId) === tabId);
|
|
1128
|
+
if (byId) return byId;
|
|
1129
|
+
}
|
|
1130
|
+
if (tabLabel) {
|
|
1131
|
+
const lower = tabLabel.toLowerCase();
|
|
1132
|
+
return tabs.find((el) => normalize(el.textContent).toLowerCase().includes(lower)) || null;
|
|
1133
|
+
}
|
|
1134
|
+
return null;
|
|
1135
|
+
};
|
|
1136
|
+
|
|
1137
|
+
if (payload.action === 'snapshot') {
|
|
1138
|
+
return { ok: true, snapshot: ${snapshotScript} };
|
|
1139
|
+
}
|
|
1140
|
+
if (payload.action === 'dialogs') {
|
|
1141
|
+
const mode = normalize(payload.value).toLowerCase();
|
|
1142
|
+
const w = window;
|
|
1143
|
+
const key = '__webauto_ui_cli_dialogs__';
|
|
1144
|
+
if (mode === 'silent') {
|
|
1145
|
+
if (!w[key]) {
|
|
1146
|
+
w[key] = {
|
|
1147
|
+
alert: w.alert,
|
|
1148
|
+
confirm: w.confirm,
|
|
1149
|
+
prompt: w.prompt,
|
|
1150
|
+
};
|
|
1151
|
+
}
|
|
1152
|
+
w.alert = () => {};
|
|
1153
|
+
w.confirm = () => true;
|
|
1154
|
+
w.prompt = () => '';
|
|
1155
|
+
return { ok: true, mode: 'silent' };
|
|
1156
|
+
}
|
|
1157
|
+
if (mode === 'restore') {
|
|
1158
|
+
if (w[key]) {
|
|
1159
|
+
w.alert = w[key].alert;
|
|
1160
|
+
w.confirm = w[key].confirm;
|
|
1161
|
+
w.prompt = w[key].prompt;
|
|
1162
|
+
delete w[key];
|
|
1163
|
+
}
|
|
1164
|
+
return { ok: true, mode: 'restore' };
|
|
1165
|
+
}
|
|
1166
|
+
return { ok: false, error: 'unsupported_dialog_mode' };
|
|
1167
|
+
}
|
|
1168
|
+
if (payload.action === 'tab') {
|
|
1169
|
+
const tab = findTab();
|
|
1170
|
+
if (!tab) return { ok: false, error: 'tab_not_found' };
|
|
1171
|
+
clickEl(tab);
|
|
1172
|
+
return { ok: true, tab: normalize(tab.textContent), tabId: normalize(tab?.dataset?.tabId) };
|
|
1173
|
+
}
|
|
1174
|
+
if (payload.action === 'click') {
|
|
1175
|
+
const el = query(payload.selector);
|
|
1176
|
+
if (!el) return { ok: false, error: 'selector_not_found', selector: normalize(payload.selector) };
|
|
1177
|
+
clickEl(el);
|
|
1178
|
+
return { ok: true };
|
|
1179
|
+
}
|
|
1180
|
+
if (payload.action === 'focus') {
|
|
1181
|
+
const el = query(payload.selector);
|
|
1182
|
+
if (!el) return { ok: false, error: 'selector_not_found', selector: normalize(payload.selector) };
|
|
1183
|
+
const focused = focusEl(el);
|
|
1184
|
+
return { ok: focused, focused };
|
|
1185
|
+
}
|
|
1186
|
+
if (payload.action === 'input') {
|
|
1187
|
+
const el = query(payload.selector);
|
|
1188
|
+
if (!el) return { ok: false, error: 'selector_not_found', selector: normalize(payload.selector) };
|
|
1189
|
+
focusEl(el);
|
|
1190
|
+
const written = setInputValue(el, payload.value || '');
|
|
1191
|
+
return { ok: written, value: String(payload.value || '') };
|
|
1192
|
+
}
|
|
1193
|
+
if (payload.action === 'select') {
|
|
1194
|
+
const el = query(payload.selector);
|
|
1195
|
+
if (!el || el.tagName !== 'SELECT') return { ok: false, error: 'select_not_found', selector: normalize(payload.selector) };
|
|
1196
|
+
el.value = String(payload.value || '');
|
|
1197
|
+
el.dispatchEvent(new Event('input', { bubbles: true }));
|
|
1198
|
+
el.dispatchEvent(new Event('change', { bubbles: true }));
|
|
1199
|
+
return { ok: true, value: el.value };
|
|
1200
|
+
}
|
|
1201
|
+
if (payload.action === 'press') {
|
|
1202
|
+
const el = query(payload.selector);
|
|
1203
|
+
const ok = pressKey(el, payload.key);
|
|
1204
|
+
return { ok, key: normalize(payload.key) || 'Enter' };
|
|
1205
|
+
}
|
|
1206
|
+
if (payload.action === 'click_text') {
|
|
1207
|
+
const el = findByText({
|
|
1208
|
+
selector: payload.selector,
|
|
1209
|
+
text: payload.text || payload.value,
|
|
1210
|
+
exact: payload.exact === true,
|
|
1211
|
+
nth: payload.nth,
|
|
1212
|
+
});
|
|
1213
|
+
if (!el) return { ok: false, error: 'text_not_found', text: normalize(payload.text || payload.value), selector: normalize(payload.selector) };
|
|
1214
|
+
clickEl(el);
|
|
1215
|
+
return { ok: true, text: normalize(el.textContent) };
|
|
1216
|
+
}
|
|
1217
|
+
if (payload.action === 'probe') {
|
|
1218
|
+
const selector = normalize(payload.selector) || 'body';
|
|
1219
|
+
const nodes = queryAll(selector);
|
|
1220
|
+
const first = nodes[0] || null;
|
|
1221
|
+
const firstVisible = isVisible(first);
|
|
1222
|
+
const text = normalize(first?.textContent);
|
|
1223
|
+
const value = first && 'value' in first ? String(first.value ?? '') : text;
|
|
1224
|
+
const checked = Boolean(first && 'checked' in first && first.checked === true);
|
|
1225
|
+
const disabled = Boolean(first && 'disabled' in first && first.disabled === true);
|
|
1226
|
+
const probeText = normalize(payload.text || payload.value);
|
|
1227
|
+
let details = null;
|
|
1228
|
+
if (first && payload.detailed === true) {
|
|
1229
|
+
details = getElementDetails(first);
|
|
1230
|
+
}
|
|
1231
|
+
let textMatchedCount = 0;
|
|
1232
|
+
if (probeText) {
|
|
1233
|
+
const target = payload.exact === true ? probeText : probeText.toLowerCase();
|
|
1234
|
+
textMatchedCount = nodes.filter((el) => {
|
|
1235
|
+
const current = normalize(el.textContent);
|
|
1236
|
+
if (!current) return false;
|
|
1237
|
+
if (payload.exact === true) return current === target;
|
|
1238
|
+
return current.toLowerCase().includes(target);
|
|
1239
|
+
}).length;
|
|
1240
|
+
}
|
|
1241
|
+
return {
|
|
1242
|
+
ok: true,
|
|
1243
|
+
selector,
|
|
1244
|
+
exists: Boolean(first),
|
|
1245
|
+
count: nodes.length,
|
|
1246
|
+
visible: firstVisible,
|
|
1247
|
+
text,
|
|
1248
|
+
value,
|
|
1249
|
+
checked,
|
|
1250
|
+
disabled,
|
|
1251
|
+
tagName: first?.tagName || '',
|
|
1252
|
+
className: first?.className || '',
|
|
1253
|
+
details,
|
|
1254
|
+
textMatchedCount,
|
|
1255
|
+
};
|
|
1256
|
+
}
|
|
1257
|
+
if (payload.action === 'close_window') {
|
|
1258
|
+
window.close();
|
|
1259
|
+
return { ok: true };
|
|
1260
|
+
}
|
|
1261
|
+
return { ok: false, error: 'unsupported_action', action: normalize(payload.action) };
|
|
1262
|
+
})()`;
|
|
1263
|
+
}
|
|
1264
|
+
var UiCliBridge = class {
|
|
1265
|
+
server = null;
|
|
1266
|
+
options;
|
|
1267
|
+
host;
|
|
1268
|
+
port;
|
|
1269
|
+
constructor(options) {
|
|
1270
|
+
this.options = options;
|
|
1271
|
+
this.host = String(options.host || process.env.WEBAUTO_UI_CLI_HOST || DEFAULT_HOST);
|
|
1272
|
+
this.port = readInt(options.port || process.env.WEBAUTO_UI_CLI_PORT, DEFAULT_PORT);
|
|
1273
|
+
}
|
|
1274
|
+
getAddress() {
|
|
1275
|
+
return { host: this.host, port: this.port };
|
|
1276
|
+
}
|
|
1277
|
+
async start() {
|
|
1278
|
+
if (this.server) return this.getAddress();
|
|
1279
|
+
await new Promise((resolve, reject) => {
|
|
1280
|
+
const server = createServer((req, res) => {
|
|
1281
|
+
void this.handleRequest(req, res);
|
|
1282
|
+
});
|
|
1283
|
+
server.on("error", (err) => reject(err));
|
|
1284
|
+
server.listen(this.port, this.host, () => {
|
|
1285
|
+
this.server = server;
|
|
1286
|
+
resolve();
|
|
1287
|
+
});
|
|
1288
|
+
});
|
|
1289
|
+
await writeControlFile(this.host, this.port);
|
|
1290
|
+
return this.getAddress();
|
|
1291
|
+
}
|
|
1292
|
+
async stop() {
|
|
1293
|
+
if (!this.server) {
|
|
1294
|
+
await removeControlFile();
|
|
1295
|
+
return;
|
|
1296
|
+
}
|
|
1297
|
+
const srv = this.server;
|
|
1298
|
+
this.server = null;
|
|
1299
|
+
await new Promise((resolve) => srv.close(() => resolve()));
|
|
1300
|
+
await removeControlFile();
|
|
1301
|
+
}
|
|
1302
|
+
async handleRequest(req, res) {
|
|
1303
|
+
const method = String(req.method || "GET").toUpperCase();
|
|
1304
|
+
const url = new URL(req.url || "/", `http://${this.host}:${this.port}`);
|
|
1305
|
+
if (method === "GET" && url.pathname === "/health") {
|
|
1306
|
+
return sendJson(res, 200, await this.status());
|
|
1307
|
+
}
|
|
1308
|
+
if (method === "GET" && (url.pathname === "/status" || url.pathname === "/snapshot")) {
|
|
1309
|
+
return sendJson(res, 200, await this.status(true));
|
|
1310
|
+
}
|
|
1311
|
+
if (method === "POST" && url.pathname === "/action") {
|
|
1312
|
+
const body = await parseBody(req);
|
|
1313
|
+
const result = await this.handleAction(body || {});
|
|
1314
|
+
return sendJson(res, result.ok ? 200 : 400, result);
|
|
1315
|
+
}
|
|
1316
|
+
return sendJson(res, 404, { ok: false, error: "not_found" });
|
|
1317
|
+
}
|
|
1318
|
+
async status(includeSnapshot = false) {
|
|
1319
|
+
const win2 = this.options.getWindow();
|
|
1320
|
+
const ready = isUiReady(win2);
|
|
1321
|
+
if (!ready) {
|
|
1322
|
+
return {
|
|
1323
|
+
ok: false,
|
|
1324
|
+
pid: process.pid,
|
|
1325
|
+
ready: false,
|
|
1326
|
+
host: this.host,
|
|
1327
|
+
port: this.port,
|
|
1328
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1329
|
+
error: "window_not_ready"
|
|
1330
|
+
};
|
|
1331
|
+
}
|
|
1332
|
+
let snapshot;
|
|
1333
|
+
if (includeSnapshot) {
|
|
1334
|
+
try {
|
|
1335
|
+
snapshot = await win2.webContents.executeJavaScript(buildSnapshotScript(), true);
|
|
1336
|
+
} catch (err) {
|
|
1337
|
+
return {
|
|
1338
|
+
ok: false,
|
|
1339
|
+
pid: process.pid,
|
|
1340
|
+
ready,
|
|
1341
|
+
host: this.host,
|
|
1342
|
+
port: this.port,
|
|
1343
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1344
|
+
error: err?.message || String(err)
|
|
1345
|
+
};
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
return {
|
|
1349
|
+
ok: true,
|
|
1350
|
+
pid: process.pid,
|
|
1351
|
+
ready,
|
|
1352
|
+
host: this.host,
|
|
1353
|
+
port: this.port,
|
|
1354
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1355
|
+
snapshot
|
|
1356
|
+
};
|
|
1357
|
+
}
|
|
1358
|
+
async handleAction(input) {
|
|
1359
|
+
const action = String(input?.action || "").trim();
|
|
1360
|
+
if (!action) return toActionError(input, "missing_action");
|
|
1361
|
+
if (action === "wait") {
|
|
1362
|
+
return this.waitForSelector(input);
|
|
1363
|
+
}
|
|
1364
|
+
const win2 = this.options.getWindow();
|
|
1365
|
+
if (!isUiReady(win2)) return toActionError(input, "window_not_ready");
|
|
1366
|
+
try {
|
|
1367
|
+
const out = await win2.webContents.executeJavaScript(buildActionScript(input), true);
|
|
1368
|
+
return out && typeof out === "object" ? out : toActionError(input, "empty_result");
|
|
1369
|
+
} catch (err) {
|
|
1370
|
+
return toActionError(input, err?.message || String(err), { details: err?.stack || null });
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
async waitForSelector(input) {
|
|
1374
|
+
const selector = String(input.selector || "").trim();
|
|
1375
|
+
if (!selector) return toActionError(input, "missing_selector");
|
|
1376
|
+
const expected = input.state || "visible";
|
|
1377
|
+
const timeoutMs = readInt(input.timeoutMs, 15e3);
|
|
1378
|
+
const intervalMs = readInt(input.intervalMs, 250);
|
|
1379
|
+
const startedAt = Date.now();
|
|
1380
|
+
while (Date.now() - startedAt <= timeoutMs) {
|
|
1381
|
+
const win2 = this.options.getWindow();
|
|
1382
|
+
if (!isUiReady(win2)) return toActionError(input, "window_not_ready");
|
|
1383
|
+
try {
|
|
1384
|
+
const checkScript = `(() => {
|
|
1385
|
+
const el = document.querySelector(${JSON.stringify(selector)});
|
|
1386
|
+
const visible = (() => {
|
|
1387
|
+
if (!el) return false;
|
|
1388
|
+
const rect = el.getBoundingClientRect();
|
|
1389
|
+
if (!rect || rect.width <= 0 || rect.height <= 0) return false;
|
|
1390
|
+
const style = window.getComputedStyle(el);
|
|
1391
|
+
return style.display !== 'none' && style.visibility !== 'hidden' && style.opacity !== '0';
|
|
1392
|
+
})();
|
|
1393
|
+
const text = String(el?.textContent || '').trim();
|
|
1394
|
+
const value = el && 'value' in el ? String(el.value ?? '') : '';
|
|
1395
|
+
const disabled = Boolean(el && 'disabled' in el && el.disabled === true);
|
|
1396
|
+
return { exists: Boolean(el), visible, text, value, disabled };
|
|
1397
|
+
})()`;
|
|
1398
|
+
const state = await win2.webContents.executeJavaScript(checkScript, true);
|
|
1399
|
+
const exists = Boolean(state?.exists);
|
|
1400
|
+
const visible = Boolean(state?.visible);
|
|
1401
|
+
const text = String(state?.text || "");
|
|
1402
|
+
const value = String(state?.value || "");
|
|
1403
|
+
const disabled = Boolean(state?.disabled);
|
|
1404
|
+
let matched = false;
|
|
1405
|
+
let reason = "";
|
|
1406
|
+
switch (expected) {
|
|
1407
|
+
case "exists":
|
|
1408
|
+
matched = exists;
|
|
1409
|
+
reason = exists ? "element exists" : "element not found";
|
|
1410
|
+
break;
|
|
1411
|
+
case "visible":
|
|
1412
|
+
matched = visible;
|
|
1413
|
+
reason = visible ? "element visible" : "element not visible";
|
|
1414
|
+
break;
|
|
1415
|
+
case "hidden":
|
|
1416
|
+
matched = !exists || !visible;
|
|
1417
|
+
reason = matched ? "element hidden or absent" : "element is visible";
|
|
1418
|
+
break;
|
|
1419
|
+
case "text_contains":
|
|
1420
|
+
matched = exists && text.includes(String(input.value || ""));
|
|
1421
|
+
reason = matched ? "text matched" : `text '${text}' does not contain '${input.value || ""}'`;
|
|
1422
|
+
break;
|
|
1423
|
+
case "text_equals":
|
|
1424
|
+
matched = exists && text === String(input.value || "");
|
|
1425
|
+
reason = matched ? "text matched" : `text '${text}' !== '${input.value || ""}'`;
|
|
1426
|
+
break;
|
|
1427
|
+
case "value_equals":
|
|
1428
|
+
matched = exists && value === String(input.value || "");
|
|
1429
|
+
reason = matched ? "value matched" : `value '${value}' !== '${input.value || ""}'`;
|
|
1430
|
+
break;
|
|
1431
|
+
case "not_disabled":
|
|
1432
|
+
matched = exists && !disabled;
|
|
1433
|
+
reason = matched ? "element enabled" : "element disabled";
|
|
1434
|
+
break;
|
|
1435
|
+
default:
|
|
1436
|
+
return toActionError(input, "unsupported_state", { expected });
|
|
1437
|
+
}
|
|
1438
|
+
if (matched) {
|
|
1439
|
+
return { ok: true, selector, expected, exists, visible, text, value, disabled, elapsedMs: Date.now() - startedAt, reason };
|
|
1440
|
+
}
|
|
1441
|
+
} catch (err) {
|
|
1442
|
+
return toActionError(input, err?.message || String(err), { details: err?.stack || null });
|
|
1443
|
+
}
|
|
1444
|
+
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
1445
|
+
}
|
|
1446
|
+
return toActionError(input, "wait_timeout", {
|
|
1447
|
+
expected,
|
|
1448
|
+
timeoutMs,
|
|
1449
|
+
elapsedMs: Date.now() - startedAt
|
|
1450
|
+
});
|
|
1451
|
+
}
|
|
1452
|
+
};
|
|
1453
|
+
|
|
773
1454
|
// src/main/index.mts
|
|
774
1455
|
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
|
-
|
|
1456
|
+
var __dirname = path6.dirname(fileURLToPath2(import.meta.url));
|
|
1457
|
+
var APP_ROOT = path6.resolve(__dirname, "../..");
|
|
1458
|
+
var REPO_ROOT2 = path6.resolve(APP_ROOT, "../..");
|
|
1459
|
+
var DESKTOP_HEARTBEAT_FILE = path6.join(
|
|
1460
|
+
os5.homedir(),
|
|
780
1461
|
".webauto",
|
|
781
1462
|
"run",
|
|
782
1463
|
"desktop-console-heartbeat.json"
|
|
783
1464
|
);
|
|
784
1465
|
var profileStore = createProfileStore({ repoRoot: REPO_ROOT2 });
|
|
785
|
-
var XHS_SCRIPTS_ROOT =
|
|
1466
|
+
var XHS_SCRIPTS_ROOT = path6.join(REPO_ROOT2, "scripts", "xiaohongshu");
|
|
786
1467
|
var XHS_FULL_COLLECT_RE = /collect-content\.mjs$/;
|
|
787
1468
|
function configureElectronPaths() {
|
|
788
1469
|
try {
|
|
789
1470
|
const downloadRoot = resolveDefaultDownloadRoot();
|
|
790
|
-
const normalized =
|
|
791
|
-
const baseDir =
|
|
792
|
-
const userDataRoot =
|
|
793
|
-
const cacheRoot =
|
|
794
|
-
const gpuCacheRoot =
|
|
1471
|
+
const normalized = path6.normalize(downloadRoot);
|
|
1472
|
+
const baseDir = path6.basename(normalized).toLowerCase() === "download" ? path6.dirname(normalized) : normalized;
|
|
1473
|
+
const userDataRoot = path6.join(baseDir, "desktop-console");
|
|
1474
|
+
const cacheRoot = path6.join(userDataRoot, "cache");
|
|
1475
|
+
const gpuCacheRoot = path6.join(cacheRoot, "gpu");
|
|
795
1476
|
try {
|
|
796
1477
|
mkdirSync(cacheRoot, { recursive: true });
|
|
797
1478
|
} catch {
|
|
@@ -833,6 +1514,8 @@ var GroupQueue = class {
|
|
|
833
1514
|
};
|
|
834
1515
|
var groupQueues = /* @__PURE__ */ new Map();
|
|
835
1516
|
var runs = /* @__PURE__ */ new Map();
|
|
1517
|
+
var trackedRunPids = /* @__PURE__ */ new Set();
|
|
1518
|
+
var appExitCleanupPromise = null;
|
|
836
1519
|
var UI_HEARTBEAT_TIMEOUT_MS = resolveUiHeartbeatTimeoutMs(process.env);
|
|
837
1520
|
var lastUiHeartbeatAt = Date.now();
|
|
838
1521
|
var heartbeatWatchdog = null;
|
|
@@ -849,8 +1532,8 @@ async function writeCoreServiceHeartbeat(status) {
|
|
|
849
1532
|
source: "desktop-console"
|
|
850
1533
|
};
|
|
851
1534
|
try {
|
|
852
|
-
await
|
|
853
|
-
await
|
|
1535
|
+
await fs4.mkdir(path6.dirname(filePath), { recursive: true });
|
|
1536
|
+
await fs4.writeFile(filePath, JSON.stringify(payload), "utf8");
|
|
854
1537
|
} catch {
|
|
855
1538
|
}
|
|
856
1539
|
}
|
|
@@ -887,11 +1570,31 @@ function ensureStateBridge() {
|
|
|
887
1570
|
}
|
|
888
1571
|
}
|
|
889
1572
|
var win = null;
|
|
1573
|
+
var uiCliBridge = new UiCliBridge({ getWindow: getWin });
|
|
890
1574
|
configureElectronPaths();
|
|
1575
|
+
var singleInstanceLock = app.requestSingleInstanceLock();
|
|
1576
|
+
if (!singleInstanceLock) {
|
|
1577
|
+
app.quit();
|
|
1578
|
+
}
|
|
891
1579
|
function getWin() {
|
|
892
1580
|
if (!win || win.isDestroyed()) return null;
|
|
893
1581
|
return win;
|
|
894
1582
|
}
|
|
1583
|
+
if (singleInstanceLock) {
|
|
1584
|
+
app.on("second-instance", () => {
|
|
1585
|
+
const w = getWin();
|
|
1586
|
+
if (w) {
|
|
1587
|
+
if (w.isMinimized()) w.restore();
|
|
1588
|
+
w.focus();
|
|
1589
|
+
return;
|
|
1590
|
+
}
|
|
1591
|
+
if (app.isReady()) {
|
|
1592
|
+
createWindow();
|
|
1593
|
+
} else {
|
|
1594
|
+
app.whenReady().then(() => createWindow());
|
|
1595
|
+
}
|
|
1596
|
+
});
|
|
1597
|
+
}
|
|
895
1598
|
function isUiOperational() {
|
|
896
1599
|
const w = getWin();
|
|
897
1600
|
if (!w) return false;
|
|
@@ -951,6 +1654,85 @@ function killAllRuns(reason = "ui_heartbeat_timeout") {
|
|
|
951
1654
|
void stopCoreServicesBestEffort(reason);
|
|
952
1655
|
}
|
|
953
1656
|
}
|
|
1657
|
+
function trackRunPid(child) {
|
|
1658
|
+
const pid = Number(child?.pid || 0);
|
|
1659
|
+
if (pid > 0) trackedRunPids.add(pid);
|
|
1660
|
+
}
|
|
1661
|
+
function untrackRunPid(child) {
|
|
1662
|
+
const pid = Number(child?.pid || 0);
|
|
1663
|
+
if (pid > 0) trackedRunPids.delete(pid);
|
|
1664
|
+
}
|
|
1665
|
+
async function cleanupTrackedRunPidsBestEffort(reason) {
|
|
1666
|
+
for (const pid of Array.from(trackedRunPids.values())) {
|
|
1667
|
+
try {
|
|
1668
|
+
if (process.platform === "win32") {
|
|
1669
|
+
spawn2("taskkill", ["/PID", String(pid), "/T", "/F"], { stdio: "ignore", windowsHide: true });
|
|
1670
|
+
} else {
|
|
1671
|
+
spawn2("pkill", ["-TERM", "-P", String(pid)], { stdio: "ignore" }).on("error", () => {
|
|
1672
|
+
});
|
|
1673
|
+
process.kill(pid, "SIGTERM");
|
|
1674
|
+
}
|
|
1675
|
+
} catch {
|
|
1676
|
+
}
|
|
1677
|
+
trackedRunPids.delete(pid);
|
|
1678
|
+
}
|
|
1679
|
+
if (trackedRunPids.size > 0) {
|
|
1680
|
+
console.warn(`[desktop-console] residual run pids after cleanup (${reason}): ${trackedRunPids.size}`);
|
|
1681
|
+
trackedRunPids.clear();
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
async function cleanupCamoSessionsBestEffort(reason, includeLocks) {
|
|
1685
|
+
const camoCli = path6.join(REPO_ROOT2, "bin", "camoufox-cli.mjs");
|
|
1686
|
+
const invoke = async (args, timeoutMs = 6e4) => runJson({
|
|
1687
|
+
title: `camo ${args.join(" ")}`,
|
|
1688
|
+
cwd: REPO_ROOT2,
|
|
1689
|
+
args: [camoCli, ...args, "--json"],
|
|
1690
|
+
timeoutMs
|
|
1691
|
+
}).catch((err) => ({ ok: false, error: err?.message || String(err) }));
|
|
1692
|
+
const stopAll = await invoke(["stop", "all"]);
|
|
1693
|
+
if (!stopAll?.ok) {
|
|
1694
|
+
console.warn(`[desktop-console] camo stop all failed (${reason})`, stopAll?.error || stopAll?.stderr || stopAll?.stdout || stopAll);
|
|
1695
|
+
}
|
|
1696
|
+
if (!includeLocks) return;
|
|
1697
|
+
const cleanupLocks = await invoke(["cleanup", "locks"]);
|
|
1698
|
+
if (!cleanupLocks?.ok) {
|
|
1699
|
+
console.warn(`[desktop-console] camo cleanup locks failed (${reason})`, cleanupLocks?.error || cleanupLocks?.stderr || cleanupLocks?.stdout || cleanupLocks);
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
async function cleanupRuntimeEnvironment(reason, options = {}) {
|
|
1703
|
+
killAllRuns(reason);
|
|
1704
|
+
await cleanupTrackedRunPidsBestEffort(reason);
|
|
1705
|
+
await cleanupCamoSessionsBestEffort(reason, options.includeLockCleanup !== false);
|
|
1706
|
+
if (options.stopUiBridge) {
|
|
1707
|
+
await uiCliBridge.stop().catch(() => null);
|
|
1708
|
+
}
|
|
1709
|
+
if (options.stopHeartbeat) {
|
|
1710
|
+
stopCoreServiceHeartbeat();
|
|
1711
|
+
}
|
|
1712
|
+
if (heartbeatWatchdog) {
|
|
1713
|
+
clearInterval(heartbeatWatchdog);
|
|
1714
|
+
heartbeatWatchdog = null;
|
|
1715
|
+
}
|
|
1716
|
+
if (options.stopCoreServices) {
|
|
1717
|
+
await stopCoreServicesBestEffort(reason);
|
|
1718
|
+
}
|
|
1719
|
+
if (options.stopStateBridge) {
|
|
1720
|
+
stateBridge.stop();
|
|
1721
|
+
}
|
|
1722
|
+
}
|
|
1723
|
+
function ensureAppExitCleanup(reason, options = {}) {
|
|
1724
|
+
if (appExitCleanupPromise) return appExitCleanupPromise;
|
|
1725
|
+
appExitCleanupPromise = cleanupRuntimeEnvironment(reason, {
|
|
1726
|
+
stopUiBridge: true,
|
|
1727
|
+
stopHeartbeat: true,
|
|
1728
|
+
stopCoreServices: true,
|
|
1729
|
+
stopStateBridge: options.stopStateBridge === true,
|
|
1730
|
+
includeLockCleanup: true
|
|
1731
|
+
}).finally(() => {
|
|
1732
|
+
appExitCleanupPromise = null;
|
|
1733
|
+
});
|
|
1734
|
+
return appExitCleanupPromise;
|
|
1735
|
+
}
|
|
954
1736
|
function ensureHeartbeatWatchdog() {
|
|
955
1737
|
if (heartbeatWatchdog) return;
|
|
956
1738
|
heartbeatWatchdog = setInterval(() => {
|
|
@@ -1028,13 +1810,13 @@ function resolveNodeBin2() {
|
|
|
1028
1810
|
function resolveCwd(input) {
|
|
1029
1811
|
const raw = String(input || "").trim();
|
|
1030
1812
|
if (!raw) return REPO_ROOT2;
|
|
1031
|
-
return
|
|
1813
|
+
return path6.isAbsolute(raw) ? raw : path6.resolve(REPO_ROOT2, raw);
|
|
1032
1814
|
}
|
|
1033
1815
|
var cachedStateMod = null;
|
|
1034
1816
|
async function getStateModule() {
|
|
1035
1817
|
if (cachedStateMod) return cachedStateMod;
|
|
1036
1818
|
try {
|
|
1037
|
-
const p =
|
|
1819
|
+
const p = path6.join(REPO_ROOT2, "dist", "modules", "state", "src", "xiaohongshu-collect-state.js");
|
|
1038
1820
|
cachedStateMod = await import(pathToFileURL3(p).href);
|
|
1039
1821
|
return cachedStateMod;
|
|
1040
1822
|
} catch {
|
|
@@ -1047,6 +1829,34 @@ async function spawnCommand(spec) {
|
|
|
1047
1829
|
const groupKey = spec.groupKey || "xiaohongshu";
|
|
1048
1830
|
const q = getQueue(groupKey);
|
|
1049
1831
|
const cwd = resolveCwd(spec.cwd);
|
|
1832
|
+
const args = Array.isArray(spec.args) ? spec.args : [];
|
|
1833
|
+
const isXhsRunCommand = args.some((item) => /xhs-(orchestrate|unified)\.mjs$/i.test(String(item || "").replace(/\\/g, "/")));
|
|
1834
|
+
const extractProfilesFromArgs = (argv) => {
|
|
1835
|
+
const out = [];
|
|
1836
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
1837
|
+
const flag = String(argv[i] || "").trim();
|
|
1838
|
+
if (flag === "--profile" || flag === "--profile-id") {
|
|
1839
|
+
const value = String(argv[i + 1] || "").trim();
|
|
1840
|
+
if (value) out.push(value);
|
|
1841
|
+
} else if (flag === "--profiles") {
|
|
1842
|
+
const value = String(argv[i + 1] || "").trim();
|
|
1843
|
+
if (value) {
|
|
1844
|
+
value.split(",").map((v) => v.trim()).filter(Boolean).forEach((v) => out.push(v));
|
|
1845
|
+
}
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1848
|
+
return Array.from(new Set(out));
|
|
1849
|
+
};
|
|
1850
|
+
const requestedProfiles = isXhsRunCommand ? extractProfilesFromArgs(args) : [];
|
|
1851
|
+
if (requestedProfiles.length > 0) {
|
|
1852
|
+
for (const run of runs.values()) {
|
|
1853
|
+
const activeProfiles = Array.isArray(run.profiles) ? run.profiles : [];
|
|
1854
|
+
const conflict = requestedProfiles.find((p) => activeProfiles.includes(p));
|
|
1855
|
+
if (conflict) {
|
|
1856
|
+
throw new Error(`profile already running: ${conflict}`);
|
|
1857
|
+
}
|
|
1858
|
+
}
|
|
1859
|
+
}
|
|
1050
1860
|
q.enqueue(
|
|
1051
1861
|
() => new Promise((resolve) => {
|
|
1052
1862
|
let finished = false;
|
|
@@ -1059,7 +1869,7 @@ async function spawnCommand(spec) {
|
|
|
1059
1869
|
runs.delete(runId);
|
|
1060
1870
|
resolve();
|
|
1061
1871
|
};
|
|
1062
|
-
const child = spawn2(resolveNodeBin2(),
|
|
1872
|
+
const child = spawn2(resolveNodeBin2(), args, {
|
|
1063
1873
|
cwd,
|
|
1064
1874
|
env: {
|
|
1065
1875
|
...process.env,
|
|
@@ -1070,7 +1880,8 @@ async function spawnCommand(spec) {
|
|
|
1070
1880
|
stdio: ["ignore", "pipe", "pipe"],
|
|
1071
1881
|
windowsHide: true
|
|
1072
1882
|
});
|
|
1073
|
-
|
|
1883
|
+
trackRunPid(child);
|
|
1884
|
+
runs.set(runId, { child, title: spec.title, startedAt: now(), profiles: requestedProfiles });
|
|
1074
1885
|
sendEvent({ type: "started", runId, title: spec.title, pid: child.pid ?? -1, ts: now() });
|
|
1075
1886
|
const stdoutLines = createLineEmitter(runId, "stdout");
|
|
1076
1887
|
const stderrLines = createLineEmitter(runId, "stderr");
|
|
@@ -1089,6 +1900,7 @@ async function spawnCommand(spec) {
|
|
|
1089
1900
|
exitSignal = signal;
|
|
1090
1901
|
});
|
|
1091
1902
|
child.on("close", (code, signal) => {
|
|
1903
|
+
untrackRunPid(child);
|
|
1092
1904
|
stdoutLines.flush();
|
|
1093
1905
|
stderrLines.flush();
|
|
1094
1906
|
finalize(exitCode ?? code ?? null, exitSignal ?? signal ?? null);
|
|
@@ -1134,21 +1946,21 @@ async function runJson(spec) {
|
|
|
1134
1946
|
}
|
|
1135
1947
|
async function scanResults(input) {
|
|
1136
1948
|
const downloadRoot = String(input.downloadRoot || resolveDefaultDownloadRoot());
|
|
1137
|
-
const root =
|
|
1949
|
+
const root = path6.join(downloadRoot, "xiaohongshu");
|
|
1138
1950
|
const result = { ok: true, root, entries: [] };
|
|
1139
1951
|
try {
|
|
1140
1952
|
const stateMod = await getStateModule();
|
|
1141
|
-
const envDirs = await
|
|
1953
|
+
const envDirs = await fs4.readdir(root, { withFileTypes: true });
|
|
1142
1954
|
for (const envEnt of envDirs) {
|
|
1143
1955
|
if (!envEnt.isDirectory()) continue;
|
|
1144
1956
|
const env = envEnt.name;
|
|
1145
|
-
const envPath =
|
|
1146
|
-
const keywordDirs = await
|
|
1957
|
+
const envPath = path6.join(root, env);
|
|
1958
|
+
const keywordDirs = await fs4.readdir(envPath, { withFileTypes: true });
|
|
1147
1959
|
for (const kwEnt of keywordDirs) {
|
|
1148
1960
|
if (!kwEnt.isDirectory()) continue;
|
|
1149
1961
|
const keyword = kwEnt.name;
|
|
1150
|
-
const kwPath =
|
|
1151
|
-
const stat = await
|
|
1962
|
+
const kwPath = path6.join(envPath, keyword);
|
|
1963
|
+
const stat = await fs4.stat(kwPath).catch(() => null);
|
|
1152
1964
|
let stateSummary = null;
|
|
1153
1965
|
if (stateMod?.loadXhsCollectState) {
|
|
1154
1966
|
try {
|
|
@@ -1176,13 +1988,13 @@ async function scanResults(input) {
|
|
|
1176
1988
|
}
|
|
1177
1989
|
async function listXhsFullCollectScripts() {
|
|
1178
1990
|
try {
|
|
1179
|
-
const entries = await
|
|
1991
|
+
const entries = await fs4.readdir(XHS_SCRIPTS_ROOT, { withFileTypes: true });
|
|
1180
1992
|
const scripts = entries.filter((ent) => ent.isFile() && XHS_FULL_COLLECT_RE.test(ent.name)).map((ent) => {
|
|
1181
1993
|
const name = ent.name;
|
|
1182
1994
|
return {
|
|
1183
1995
|
id: `xhs:${name}`,
|
|
1184
1996
|
label: `Full Collect (${name})`,
|
|
1185
|
-
path:
|
|
1997
|
+
path: path6.join(XHS_SCRIPTS_ROOT, name)
|
|
1186
1998
|
};
|
|
1187
1999
|
});
|
|
1188
2000
|
return { ok: true, scripts };
|
|
@@ -1195,7 +2007,7 @@ async function readTextPreview(input) {
|
|
|
1195
2007
|
const maxBytes = typeof input.maxBytes === "number" ? input.maxBytes : 8e4;
|
|
1196
2008
|
const maxLines = typeof input.maxLines === "number" ? input.maxLines : 200;
|
|
1197
2009
|
try {
|
|
1198
|
-
const raw = await
|
|
2010
|
+
const raw = await fs4.readFile(filePath, "utf8");
|
|
1199
2011
|
const clipped = raw.slice(0, maxBytes);
|
|
1200
2012
|
const lines = clipped.split(/\r?\n/g).slice(0, maxLines);
|
|
1201
2013
|
return { ok: true, path: filePath, text: lines.join("\n") };
|
|
@@ -1208,14 +2020,14 @@ async function readTextTail(input) {
|
|
|
1208
2020
|
const filePath = String(input?.path || "");
|
|
1209
2021
|
const requestedOffset = typeof input?.fromOffset === "number" ? Math.max(0, Math.floor(input.fromOffset)) : 0;
|
|
1210
2022
|
const maxBytes = typeof input?.maxBytes === "number" ? Math.max(1024, Math.floor(input.maxBytes)) : 256e3;
|
|
1211
|
-
const st = await
|
|
2023
|
+
const st = await fs4.stat(filePath);
|
|
1212
2024
|
const size = Number(st?.size || 0);
|
|
1213
2025
|
const fromOffset = requestedOffset > size ? 0 : requestedOffset;
|
|
1214
2026
|
const toRead = Math.max(0, Math.min(maxBytes, size - fromOffset));
|
|
1215
2027
|
if (toRead <= 0) {
|
|
1216
2028
|
return { ok: true, path: filePath, text: "", fromOffset, nextOffset: fromOffset, fileSize: size };
|
|
1217
2029
|
}
|
|
1218
|
-
const fh = await
|
|
2030
|
+
const fh = await fs4.open(filePath, "r");
|
|
1219
2031
|
try {
|
|
1220
2032
|
const buf = Buffer.allocUnsafe(toRead);
|
|
1221
2033
|
const { bytesRead } = await fh.read(buf, 0, toRead, fromOffset);
|
|
@@ -1235,7 +2047,7 @@ async function readTextTail(input) {
|
|
|
1235
2047
|
async function readFileBase64(input) {
|
|
1236
2048
|
const filePath = String(input.path || "");
|
|
1237
2049
|
const maxBytes = typeof input.maxBytes === "number" ? input.maxBytes : 8e6;
|
|
1238
|
-
const buf = await
|
|
2050
|
+
const buf = await fs4.readFile(filePath);
|
|
1239
2051
|
if (buf.byteLength > maxBytes) {
|
|
1240
2052
|
return { ok: false, error: `file too large: ${buf.byteLength}` };
|
|
1241
2053
|
}
|
|
@@ -1249,14 +2061,14 @@ async function listDir(input) {
|
|
|
1249
2061
|
const stack = [root];
|
|
1250
2062
|
while (stack.length > 0 && entries.length < maxEntries) {
|
|
1251
2063
|
const dir = stack.pop();
|
|
1252
|
-
const items = await
|
|
2064
|
+
const items = await fs4.readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
1253
2065
|
for (const ent of items) {
|
|
1254
2066
|
if (entries.length >= maxEntries) break;
|
|
1255
|
-
const full =
|
|
1256
|
-
const st = await
|
|
2067
|
+
const full = path6.join(dir, ent.name);
|
|
2068
|
+
const st = await fs4.stat(full).catch(() => null);
|
|
1257
2069
|
entries.push({
|
|
1258
2070
|
path: full,
|
|
1259
|
-
rel:
|
|
2071
|
+
rel: path6.relative(root, full),
|
|
1260
2072
|
name: ent.name,
|
|
1261
2073
|
isDir: ent.isDirectory(),
|
|
1262
2074
|
size: st?.size || 0,
|
|
@@ -1279,7 +2091,7 @@ function createWindow() {
|
|
|
1279
2091
|
minWidth: 920,
|
|
1280
2092
|
minHeight: 800,
|
|
1281
2093
|
webPreferences: {
|
|
1282
|
-
preload:
|
|
2094
|
+
preload: path6.join(APP_ROOT, "dist", "main", "preload.mjs"),
|
|
1283
2095
|
contextIsolation: true,
|
|
1284
2096
|
nodeIntegration: false,
|
|
1285
2097
|
sandbox: false,
|
|
@@ -1287,28 +2099,19 @@ function createWindow() {
|
|
|
1287
2099
|
backgroundThrottling: false
|
|
1288
2100
|
}
|
|
1289
2101
|
});
|
|
1290
|
-
const htmlPath =
|
|
2102
|
+
const htmlPath = path6.join(APP_ROOT, "dist", "renderer", "index.html");
|
|
1291
2103
|
void win.loadFile(htmlPath);
|
|
1292
2104
|
ensureStateBridge();
|
|
1293
2105
|
}
|
|
1294
2106
|
app.on("window-all-closed", () => {
|
|
1295
|
-
|
|
2107
|
+
void ensureAppExitCleanup("window_closed");
|
|
1296
2108
|
app.quit();
|
|
1297
2109
|
});
|
|
1298
2110
|
app.on("before-quit", () => {
|
|
1299
|
-
|
|
1300
|
-
stopCoreServiceHeartbeat();
|
|
1301
|
-
void stopCoreServicesBestEffort("before_quit");
|
|
1302
|
-
if (heartbeatWatchdog) {
|
|
1303
|
-
clearInterval(heartbeatWatchdog);
|
|
1304
|
-
heartbeatWatchdog = null;
|
|
1305
|
-
}
|
|
2111
|
+
void ensureAppExitCleanup("before_quit");
|
|
1306
2112
|
});
|
|
1307
2113
|
app.on("will-quit", () => {
|
|
1308
|
-
|
|
1309
|
-
stopCoreServiceHeartbeat();
|
|
1310
|
-
void stopCoreServicesBestEffort("will_quit");
|
|
1311
|
-
stateBridge.stop();
|
|
2114
|
+
void ensureAppExitCleanup("will_quit", { stopStateBridge: true });
|
|
1312
2115
|
});
|
|
1313
2116
|
app.whenReady().then(async () => {
|
|
1314
2117
|
startCoreServiceHeartbeat();
|
|
@@ -1319,6 +2122,9 @@ app.whenReady().then(async () => {
|
|
|
1319
2122
|
markUiHeartbeat("main_ready");
|
|
1320
2123
|
ensureHeartbeatWatchdog();
|
|
1321
2124
|
createWindow();
|
|
2125
|
+
await uiCliBridge.start().catch((err) => {
|
|
2126
|
+
console.warn("[desktop-console] ui-cli bridge start failed", err);
|
|
2127
|
+
});
|
|
1322
2128
|
});
|
|
1323
2129
|
ipcMain2.on("preload:test", () => {
|
|
1324
2130
|
console.log("[preload-test] window.api OK");
|
|
@@ -1468,9 +2274,58 @@ ipcMain2.handle("env:checkGeoIP", async () => checkGeoIP());
|
|
|
1468
2274
|
ipcMain2.handle("env:checkAll", async () => checkEnvironment());
|
|
1469
2275
|
ipcMain2.handle("env:repairCore", async () => {
|
|
1470
2276
|
const ok = await startCoreDaemon().catch(() => false);
|
|
1471
|
-
const services = await checkServices().catch(() => ({ unifiedApi: false,
|
|
2277
|
+
const services = await checkServices().catch(() => ({ unifiedApi: false, camoRuntime: false }));
|
|
1472
2278
|
return { ok, services };
|
|
1473
2279
|
});
|
|
2280
|
+
ipcMain2.handle("env:repairDeps", async (_evt, input) => {
|
|
2281
|
+
const wantCore = Boolean(input?.core);
|
|
2282
|
+
const wantBrowser = Boolean(input?.browser);
|
|
2283
|
+
const wantGeoip = Boolean(input?.geoip);
|
|
2284
|
+
const wantReinstall = Boolean(input?.reinstall);
|
|
2285
|
+
const wantUninstall = Boolean(input?.uninstall);
|
|
2286
|
+
const result = { ok: true, core: null, install: null, env: null };
|
|
2287
|
+
if (wantCore) {
|
|
2288
|
+
const coreOk = await startCoreDaemon().catch(() => false);
|
|
2289
|
+
result.core = {
|
|
2290
|
+
ok: coreOk,
|
|
2291
|
+
services: await checkServices().catch(() => ({ unifiedApi: false, camoRuntime: false }))
|
|
2292
|
+
};
|
|
2293
|
+
if (!coreOk) result.ok = false;
|
|
2294
|
+
}
|
|
2295
|
+
if (wantBrowser || wantGeoip) {
|
|
2296
|
+
const args = [path6.join("apps", "webauto", "entry", "xhs-install.mjs")];
|
|
2297
|
+
if (wantReinstall) args.push("--reinstall");
|
|
2298
|
+
else if (wantUninstall) args.push("--uninstall");
|
|
2299
|
+
else args.push("--install");
|
|
2300
|
+
if (wantBrowser) args.push("--download-browser");
|
|
2301
|
+
if (wantGeoip) args.push("--download-geoip");
|
|
2302
|
+
if (!wantUninstall) args.push("--ensure-backend");
|
|
2303
|
+
const installRes = await runJson({
|
|
2304
|
+
title: "env repair deps",
|
|
2305
|
+
cwd: REPO_ROOT2,
|
|
2306
|
+
args,
|
|
2307
|
+
timeoutMs: 3e5
|
|
2308
|
+
}).catch((err) => ({ ok: false, error: err?.message || String(err) }));
|
|
2309
|
+
result.install = installRes;
|
|
2310
|
+
if (!installRes?.ok) result.ok = false;
|
|
2311
|
+
}
|
|
2312
|
+
result.env = await checkEnvironment().catch(() => null);
|
|
2313
|
+
return result;
|
|
2314
|
+
});
|
|
2315
|
+
ipcMain2.handle("env:cleanup", async () => {
|
|
2316
|
+
markUiHeartbeat("env_cleanup");
|
|
2317
|
+
await cleanupRuntimeEnvironment("env_cleanup", {
|
|
2318
|
+
stopUiBridge: false,
|
|
2319
|
+
stopHeartbeat: false,
|
|
2320
|
+
stopCoreServices: false,
|
|
2321
|
+
stopStateBridge: false,
|
|
2322
|
+
includeLockCleanup: true
|
|
2323
|
+
});
|
|
2324
|
+
return {
|
|
2325
|
+
ok: true,
|
|
2326
|
+
services: await checkServices().catch(() => ({ unifiedApi: false, camoRuntime: false }))
|
|
2327
|
+
};
|
|
2328
|
+
});
|
|
1474
2329
|
ipcMain2.handle("config:saveLast", async (_evt, config) => {
|
|
1475
2330
|
await saveCrawlConfig({ appRoot: APP_ROOT, repoRoot: REPO_ROOT2 }, config);
|
|
1476
2331
|
return { ok: true };
|
|
@@ -1561,7 +2416,7 @@ ipcMain2.handle("runtime:kill", async (_evt, input) => {
|
|
|
1561
2416
|
ipcMain2.handle("runtime:restartPhase1", async (_evt, input) => {
|
|
1562
2417
|
const profileId = String(input?.profileId || "").trim();
|
|
1563
2418
|
if (!profileId) return { ok: false, error: "missing profileId" };
|
|
1564
|
-
const args = [
|
|
2419
|
+
const args = [path6.join(REPO_ROOT2, "scripts", "xiaohongshu", "phase1-boot.mjs"), "--profile", profileId, "--headless", "false"];
|
|
1565
2420
|
return spawnCommand({ title: `Phase1 restart ${profileId}`, cwd: REPO_ROOT2, args, groupKey: "phase1" });
|
|
1566
2421
|
});
|
|
1567
2422
|
ipcMain2.handle("runtime:setBrowserTitle", async (_evt, input) => {
|