@web-auto/webauto 0.1.3 → 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.
Files changed (174) hide show
  1. package/apps/desktop-console/default-settings.json +2 -2
  2. package/apps/desktop-console/dist/main/index.mjs +915 -85
  3. package/apps/desktop-console/dist/main/preload.mjs +7 -0
  4. package/apps/desktop-console/dist/renderer/index.html +622 -50
  5. package/apps/desktop-console/dist/renderer/index.js +2415 -470
  6. package/apps/desktop-console/dist/renderer/run.mts +6 -5
  7. package/apps/desktop-console/entry/ui-cli.mjs +672 -0
  8. package/apps/desktop-console/entry/ui-console.mjs +416 -29
  9. package/apps/webauto/entry/account.mjs +89 -53
  10. package/apps/webauto/entry/browser-status.mjs +7 -10
  11. package/apps/webauto/entry/lib/account-detect.mjs +254 -28
  12. package/apps/webauto/entry/lib/account-store.mjs +219 -30
  13. package/apps/webauto/entry/lib/bus-publish.mjs +63 -0
  14. package/apps/webauto/entry/lib/camo-cli.mjs +93 -0
  15. package/apps/webauto/entry/lib/profilepool.mjs +14 -5
  16. package/apps/webauto/entry/lib/quota-status.mjs +23 -0
  17. package/apps/webauto/entry/lib/schedule-store.mjs +1068 -0
  18. package/apps/webauto/entry/profilepool.mjs +106 -17
  19. package/apps/webauto/entry/schedule.mjs +612 -0
  20. package/apps/webauto/entry/weibo-unified.mjs +134 -0
  21. package/apps/webauto/entry/xhs-install.mjs +236 -29
  22. package/apps/webauto/entry/xhs-status.mjs +5 -2
  23. package/apps/webauto/entry/xhs-unified.mjs +631 -98
  24. package/apps/webauto/resources/container-library/weibo/weibo_detail_page/comment_item/container.json +40 -0
  25. package/apps/webauto/resources/container-library/weibo/weibo_detail_page/reply_expand_button/container.json +38 -0
  26. package/apps/webauto/resources/container-library/weibo/weibo_detail_page/reply_list/container.json +37 -0
  27. package/apps/webauto/resources/container-library/weibo/weibo_search_page/container.json +8 -3
  28. package/apps/webauto/resources/container-library/weibo/weibo_search_page/login_anchor/container.json +30 -0
  29. package/apps/webauto/resources/container-library/weibo/weibo_search_page/search_bar/container.json +47 -0
  30. package/apps/webauto/resources/container-library/weibo/weibo_search_page/search_button/container.json +39 -0
  31. package/bin/camoufox-cli.mjs +61 -0
  32. package/bin/webauto.mjs +301 -54
  33. package/dist/modules/camo-backend/src/index.js +49 -1
  34. package/dist/modules/camo-backend/src/internal/BrowserSession.js +572 -3
  35. package/dist/modules/camo-backend/src/internal/SessionManager.js +13 -1
  36. package/dist/modules/camo-backend/src/internal/storage-paths.js +6 -0
  37. package/dist/modules/collection-manager/bloom-filter.js +91 -0
  38. package/dist/modules/collection-manager/date-utils.js +275 -0
  39. package/dist/modules/collection-manager/index.js +258 -0
  40. package/dist/modules/collection-manager/storage.js +195 -0
  41. package/dist/modules/collection-manager/types.js +47 -0
  42. package/dist/modules/logging/src/index.js +1 -1
  43. package/dist/modules/process-registry/index.js +230 -0
  44. package/dist/modules/rate-limiter/index.js +242 -0
  45. package/dist/modules/workflow/blocks/ExecuteWeiboSearchBlock.js +128 -0
  46. package/dist/modules/workflow/blocks/PersistXhsNoteBlock.js +7 -3
  47. package/dist/modules/workflow/blocks/RenderMarkdown.js +4 -1
  48. package/dist/modules/workflow/blocks/WeiboCollectCommentsBlock.js +282 -0
  49. package/dist/modules/workflow/blocks/WeiboCollectFromLinksBlock.js +283 -0
  50. package/dist/modules/workflow/blocks/WeiboCollectSearchLinksBlock.js +208 -0
  51. package/dist/modules/workflow/blocks/WeiboCollectTimelineListBlock.js +128 -0
  52. package/dist/modules/workflow/blocks/WeiboCollectUserPostsListBlock.js +127 -0
  53. package/dist/modules/workflow/blocks/helpers/downloadPaths.js +21 -0
  54. package/dist/modules/workflow/config/workflowRegistry.js +2 -0
  55. package/dist/modules/workflow/definitions/weibo-search-workflow-v1.js +47 -0
  56. package/dist/modules/workflow/src/runner.js +6 -0
  57. package/dist/modules/xiaohongshu/app/src/blocks/Phase34PersistDetailBlock.js +4 -0
  58. package/dist/modules/xiaohongshu/app/src/blocks/Phase3InteractBlock.js +2 -2
  59. package/dist/modules/xiaohongshu/app/src/blocks/helpers/sharding.js +123 -0
  60. package/dist/modules/xiaohongshu/app/src/container-registry/src/index.d.ts +37 -0
  61. package/dist/modules/xiaohongshu/app/src/container-registry/src/index.js +184 -0
  62. package/dist/modules/xiaohongshu/app/src/workflow/blocks/AnchorVerificationBlock.d.ts +31 -0
  63. package/dist/modules/xiaohongshu/app/src/workflow/blocks/AnchorVerificationBlock.js +71 -0
  64. package/dist/modules/xiaohongshu/app/src/workflow/blocks/DetectPageStateBlock.d.ts +48 -0
  65. package/dist/modules/xiaohongshu/app/src/workflow/blocks/DetectPageStateBlock.js +259 -0
  66. package/dist/modules/xiaohongshu/app/src/workflow/blocks/ErrorRecoveryBlock.d.ts +28 -0
  67. package/dist/modules/xiaohongshu/app/src/workflow/blocks/ErrorRecoveryBlock.js +319 -0
  68. package/dist/modules/xiaohongshu/app/src/workflow/blocks/WaitSearchPermitBlock.d.ts +36 -0
  69. package/dist/modules/xiaohongshu/app/src/workflow/blocks/WaitSearchPermitBlock.js +162 -0
  70. package/dist/modules/xiaohongshu/app/src/workflow/blocks/helpers/containerAnchors.d.ts +36 -0
  71. package/dist/modules/xiaohongshu/app/src/workflow/blocks/helpers/containerAnchors.js +301 -0
  72. package/dist/modules/xiaohongshu/app/src/workflow/blocks/helpers/operationLogger.d.ts +29 -0
  73. package/dist/modules/xiaohongshu/app/src/workflow/blocks/helpers/operationLogger.js +195 -0
  74. package/dist/modules/xiaohongshu/app/src/workflow/blocks/helpers/searchPageState.d.ts +25 -0
  75. package/dist/modules/xiaohongshu/app/src/workflow/blocks/helpers/searchPageState.js +164 -0
  76. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/MatchCommentsBlock.d.ts +66 -0
  77. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/MatchCommentsBlock.js +139 -0
  78. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase1EnsureServicesBlock.d.ts +16 -0
  79. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase1EnsureServicesBlock.js +36 -0
  80. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase1MonitorCookieBlock.d.ts +27 -0
  81. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase1MonitorCookieBlock.js +213 -0
  82. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase1StartProfileBlock.d.ts +18 -0
  83. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase1StartProfileBlock.js +121 -0
  84. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase2CollectLinksBlock.d.ts +34 -0
  85. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase2CollectLinksBlock.js +1249 -0
  86. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase2SearchBlock.d.ts +17 -0
  87. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase2SearchBlock.js +703 -0
  88. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34CloseDetailBlock.d.ts +15 -0
  89. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34CloseDetailBlock.js +41 -0
  90. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34CloseTabsBlock.d.ts +26 -0
  91. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34CloseTabsBlock.js +44 -0
  92. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34CollectCommentsBlock.d.ts +29 -0
  93. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34CollectCommentsBlock.js +150 -0
  94. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34ExtractDetailBlock.d.ts +38 -0
  95. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34ExtractDetailBlock.js +117 -0
  96. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34OpenDetailBlock.d.ts +30 -0
  97. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34OpenDetailBlock.js +102 -0
  98. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34OpenTabsBlock.d.ts +23 -0
  99. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34OpenTabsBlock.js +109 -0
  100. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34PersistDetailBlock.d.ts +32 -0
  101. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34PersistDetailBlock.js +117 -0
  102. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34ProcessSingleNoteBlock.d.ts +35 -0
  103. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34ProcessSingleNoteBlock.js +114 -0
  104. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34ValidateLinksBlock.d.ts +34 -0
  105. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34ValidateLinksBlock.js +90 -0
  106. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase3InteractBlock.d.ts +111 -0
  107. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase3InteractBlock.js +1009 -0
  108. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase4MultiTabHarvestBlock.d.ts +20 -0
  109. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase4MultiTabHarvestBlock.js +233 -0
  110. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/ReplyInteractBlock.d.ts +48 -0
  111. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/ReplyInteractBlock.js +291 -0
  112. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/XhsDiscoverFallbackBlock.d.ts +23 -0
  113. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/XhsDiscoverFallbackBlock.js +240 -0
  114. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/commentMatchDsl.d.ts +55 -0
  115. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/commentMatchDsl.js +126 -0
  116. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/commentMatcher.d.ts +21 -0
  117. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/commentMatcher.js +99 -0
  118. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/evidence.d.ts +5 -0
  119. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/evidence.js +27 -0
  120. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/sharding.d.ts +37 -0
  121. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/sharding.js +165 -0
  122. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/xhsComments.d.ts +33 -0
  123. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/xhsComments.js +270 -0
  124. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/index.d.ts +9 -0
  125. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/index.js +9 -0
  126. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/utils/checkpoints.d.ts +50 -0
  127. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/utils/checkpoints.js +222 -0
  128. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/utils/controllerAction.d.ts +10 -0
  129. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/utils/controllerAction.js +43 -0
  130. package/dist/services/shared/serviceProcessLogger.js +1 -1
  131. package/dist/services/unified-api/server.js +105 -11
  132. package/modules/camo-backend/src/index.ts +46 -1
  133. package/modules/camo-backend/src/internal/BrowserSession.ts +619 -3
  134. package/modules/camo-backend/src/internal/SessionManager.ts +12 -1
  135. package/modules/camo-backend/src/internal/storage-paths.ts +5 -0
  136. package/modules/camo-runtime/src/autoscript/action-providers/xhs/comments.mjs +38 -2
  137. package/modules/camo-runtime/src/autoscript/action-providers/xhs/interaction.mjs +47 -2
  138. package/modules/camo-runtime/src/autoscript/action-providers/xhs/search.mjs +94 -11
  139. package/modules/camo-runtime/src/autoscript/action-providers/xhs.mjs +208 -2
  140. package/modules/camo-runtime/src/autoscript/runtime.mjs +7 -1
  141. package/modules/camo-runtime/src/autoscript/xhs-unified-template.mjs +76 -43
  142. package/modules/camo-runtime/src/container/runtime-core/operations/index.mjs +75 -1
  143. package/modules/camo-runtime/src/container/runtime-core/operations/selector-scripts.mjs +71 -4
  144. package/modules/camo-runtime/src/container/runtime-core/operations/tab-pool.mjs +183 -27
  145. package/modules/collection-manager/bloom-filter.ts +112 -0
  146. package/modules/collection-manager/date-utils.ts +316 -0
  147. package/modules/collection-manager/index.ts +309 -0
  148. package/modules/collection-manager/package.json +10 -0
  149. package/modules/collection-manager/storage.ts +174 -0
  150. package/modules/collection-manager/types.ts +156 -0
  151. package/modules/logging/src/index.ts +1 -1
  152. package/modules/process-registry/index.ts +284 -0
  153. package/modules/rate-limiter/index.ts +322 -0
  154. package/modules/state/src/paths.ts +9 -1
  155. package/modules/task-scheduler/index.ts +293 -0
  156. package/modules/workflow/blocks/ExecuteWeiboSearchBlock.ts +167 -0
  157. package/modules/workflow/blocks/PersistXhsNoteBlock.ts +7 -3
  158. package/modules/workflow/blocks/RenderMarkdown.ts +4 -1
  159. package/modules/workflow/blocks/WeiboCollectCommentsBlock.ts +339 -0
  160. package/modules/workflow/blocks/WeiboCollectFromLinksBlock.ts +338 -0
  161. package/modules/workflow/blocks/helpers/downloadPaths.ts +16 -0
  162. package/modules/workflow/config/workflowRegistry.ts +2 -0
  163. package/modules/workflow/definitions/weibo-search-workflow-v1.ts +47 -0
  164. package/modules/workflow/src/runner.ts +6 -0
  165. package/modules/xiaohongshu/app/src/blocks/Phase1StartProfileBlock.ts +1 -1
  166. package/modules/xiaohongshu/app/src/blocks/Phase34PersistDetailBlock.ts +4 -0
  167. package/modules/xiaohongshu/app/src/blocks/Phase3InteractBlock.ts +2 -3
  168. package/modules/xiaohongshu/app/src/blocks/helpers/sharding.ts +152 -0
  169. package/package.json +14 -5
  170. package/scripts/postinstall-resources.mjs +62 -0
  171. package/scripts/test/run-coverage.mjs +76 -0
  172. package/scripts/weibo/search.ts +49 -0
  173. package/services/shared/serviceProcessLogger.ts +1 -1
  174. package/services/unified-api/server.ts +98 -12
@@ -0,0 +1,134 @@
1
+ #!/usr/bin/env node
2
+ import minimist from 'minimist';
3
+ import { runWorkflowById } from '../../../dist/modules/workflow/src/runner.js';
4
+ import { pathToFileURL } from 'node:url';
5
+
6
+ const WEIBO_HOME_URL = 'https://www.weibo.com';
7
+ const DEFAULT_PROFILE = 'xiaohongshu-batch-1'; // Use shared profile for now
8
+
9
+ async function runCommand(argv) {
10
+ const profile = String(argv.profile || DEFAULT_PROFILE).trim();
11
+ if (!profile) {
12
+ throw new Error('Profile ID is required. Use --profile <id>');
13
+ }
14
+
15
+ const workflowId = String(argv.workflow || '').trim();
16
+ const keyword = String(argv.keyword || '').trim();
17
+ const targetCount = Number(argv['max-notes'] || argv.target || 50);
18
+ const maxComments = Number(argv['max-comments'] || 0); // 0 means no limit
19
+
20
+ if (!keyword && workflowId === 'weibo-search-v1') {
21
+ throw new Error('Keyword is required for search tasks. Use --keyword <text>');
22
+ }
23
+
24
+ const env = String(argv.env || 'debug').trim();
25
+
26
+ const initialContext = {
27
+ sessionId: profile,
28
+ keyword,
29
+ env,
30
+ targetCount,
31
+ maxComments,
32
+ // Add other common parameters as needed
33
+ };
34
+
35
+ if (!workflowId) {
36
+ throw new Error('Workflow ID is required. e.g., --workflow weibo-search-v1');
37
+ }
38
+
39
+ console.log(`[Weibo Unified] Running workflow: ${workflowId} with profile: ${profile}`);
40
+ const result = await runWorkflowById(workflowId, initialContext);
41
+
42
+ if (result.success) {
43
+ console.log(`[Weibo Unified] Workflow ${workflowId} completed successfully.`);
44
+ } else {
45
+ console.error(`[Weibo Unified] Workflow ${workflowId} failed: ${result.error}`);
46
+ process.exit(1);
47
+ }
48
+ }
49
+
50
+ async function main() {
51
+ const argv = minimist(process.argv.slice(2), {
52
+ string: ['profile', 'keyword', 'env', 'workflow'],
53
+ boolean: ['help'],
54
+ alias: { k: 'keyword', t: 'target', h: 'help' },
55
+ default: {
56
+ profile: DEFAULT_PROFILE,
57
+ env: 'debug',
58
+ target: 50,
59
+ },
60
+ });
61
+
62
+ if (argv.help) {
63
+ console.log(`
64
+ Usage: webauto weibo <command> [options]
65
+
66
+ Commands:
67
+ search Perform a Weibo search task.
68
+
69
+ Options:
70
+ --profile <id> Camo profile ID to use (default: ${DEFAULT_PROFILE})
71
+ --keyword <text> Keyword to search for (required for search command)
72
+ --target <n> Target number of posts to collect (default: 50)
73
+ --max-comments <n> Maximum comments to collect per post (default: 0, no limit)
74
+ --env <debug|prod> Environment for data storage (default: debug)
75
+ --help Show this help message
76
+
77
+ Examples:
78
+ webauto weibo search --keyword "AI" --target 100 --profile my-weibo-profile
79
+ webauto weibo search --keyword "大模型" --env prod
80
+ `);
81
+ return;
82
+ }
83
+
84
+ const command = argv._[0];
85
+
86
+ switch (command) {
87
+ case 'search':
88
+ await runCommand({ ...argv, workflow: 'weibo-search-v1' });
89
+ break;
90
+ default:
91
+ console.error(`Unknown command: ${command}`);
92
+ process.exit(1);
93
+ }
94
+ }
95
+
96
+ const isDirectExec =
97
+ process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href;
98
+
99
+ if (isDirectExec) {
100
+ main().catch((error) => {
101
+ console.error('Error in Weibo Unified:', error.message);
102
+ process.exit(1);
103
+ });
104
+ }
105
+
106
+ export async function runWeiboUnified(argv) {
107
+ const workflowId = String(argv.workflow || 'weibo-search-v1').trim();
108
+ const keyword = String(argv.keyword || argv.k || '').trim();
109
+ const profile = String(argv.profile || 'xiaohongshu-batch-1').trim();
110
+ const targetCount = Number(argv['max-notes'] || argv.target || argv['max-notes'] || 50);
111
+ const maxComments = Number(argv['max-comments'] || 0);
112
+ const env = String(argv.env || 'debug').trim();
113
+
114
+ if (!keyword) {
115
+ throw new Error('Keyword is required for Weibo search tasks. Use --keyword <text>');
116
+ }
117
+
118
+ const initialContext = {
119
+ sessionId: profile,
120
+ keyword,
121
+ env,
122
+ targetCount,
123
+ maxComments,
124
+ };
125
+
126
+ const result = await runWorkflowById(workflowId, initialContext);
127
+
128
+ return {
129
+ ok: result.success,
130
+ runId: `weibo-${Date.now()}`,
131
+ summaryPath: null,
132
+ error: result.error,
133
+ };
134
+ }
@@ -3,20 +3,83 @@ import minimist from 'minimist';
3
3
  import { spawnSync } from 'node:child_process';
4
4
  import os from 'node:os';
5
5
  import path from 'node:path';
6
- import { existsSync } from 'node:fs';
6
+ import { existsSync, rmSync } from 'node:fs';
7
+ import { fileURLToPath } from 'node:url';
7
8
 
8
9
  function run(cmd, args) {
9
- return spawnSync(cmd, args, { encoding: 'utf8' });
10
+ const lower = String(cmd || '').toLowerCase();
11
+ const spawnOptions = {
12
+ encoding: 'utf8',
13
+ windowsHide: true,
14
+ timeout: 120000,
15
+ };
16
+ if (process.platform === 'win32' && (lower.endsWith('.cmd') || lower.endsWith('.bat'))) {
17
+ const cmdLine = [quoteCmdArg(cmd), ...args.map(quoteCmdArg)].join(' ');
18
+ return spawnSync('cmd.exe', ['/d', '/s', '/c', cmdLine], spawnOptions);
19
+ }
20
+ if (process.platform === 'win32' && lower.endsWith('.ps1')) {
21
+ return spawnSync(
22
+ 'powershell.exe',
23
+ ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', cmd, ...args],
24
+ spawnOptions,
25
+ );
26
+ }
27
+ return spawnSync(cmd, args, spawnOptions);
28
+ }
29
+
30
+ function quoteCmdArg(value) {
31
+ if (!value) return '""';
32
+ if (!/[\s"]/u.test(value)) return value;
33
+ return `"${String(value).replace(/"/g, '""')}"`;
34
+ }
35
+
36
+ function resolveOnPath(candidates, pathEnv = process.env.PATH || process.env.Path || '', delimiter = path.delimiter) {
37
+ const dirs = String(pathEnv)
38
+ .split(delimiter)
39
+ .map((x) => x.trim())
40
+ .filter(Boolean);
41
+ for (const dir of dirs) {
42
+ for (const name of candidates) {
43
+ const full = path.join(dir, name);
44
+ if (existsSync(full)) return full;
45
+ }
46
+ }
47
+ return null;
48
+ }
49
+
50
+ function resolveNpxBin(platform = process.platform, pathEnv = process.env.PATH || process.env.Path || '') {
51
+ if (platform !== 'win32') return 'npx';
52
+ const resolved = resolveOnPath(
53
+ ['npx.cmd', 'npx.exe', 'npx.bat', 'npx.ps1'],
54
+ pathEnv,
55
+ ';',
56
+ );
57
+ return resolved || 'npx.cmd';
58
+ }
59
+
60
+ function resolveWebautoRoot() {
61
+ const portableRoot = String(process.env.WEBAUTO_PORTABLE_ROOT || process.env.WEBAUTO_ROOT || '').trim();
62
+ if (portableRoot) return path.join(portableRoot, '.webauto');
63
+ return path.join(os.homedir(), '.webauto');
10
64
  }
11
65
 
12
- function resolveNpxBin() {
13
- return process.platform === 'win32' ? 'npx.cmd' : 'npx';
66
+ function resolveGeoIPPath() {
67
+ return path.join(resolveWebautoRoot(), 'geoip', 'GeoLite2-City.mmdb');
14
68
  }
15
69
 
16
70
  function checkCamoufoxInstalled() {
17
- const cmd = process.platform === 'win32' ? 'python' : 'python3';
18
- const ret = run(cmd, ['-m', 'camoufox', 'path']);
19
- return ret.status === 0;
71
+ const candidates =
72
+ process.platform === 'win32'
73
+ ? [
74
+ { cmd: 'python', args: ['-m', 'camoufox', 'path'] },
75
+ { cmd: 'py', args: ['-3', '-m', 'camoufox', 'path'] },
76
+ ]
77
+ : [{ cmd: 'python3', args: ['-m', 'camoufox', 'path'] }];
78
+ for (const candidate of candidates) {
79
+ const ret = run(candidate.cmd, candidate.args);
80
+ if (ret.status === 0) return true;
81
+ }
82
+ return false;
20
83
  }
21
84
 
22
85
  function installCamoufox() {
@@ -25,7 +88,7 @@ function installCamoufox() {
25
88
  }
26
89
 
27
90
  function checkGeoIPInstalled() {
28
- return existsSync(path.join(os.homedir(), '.webauto', 'geoip', 'GeoLite2-City.mmdb'));
91
+ return existsSync(resolveGeoIPPath());
29
92
  }
30
93
 
31
94
  function installGeoIP() {
@@ -33,6 +96,50 @@ function installGeoIP() {
33
96
  return ret.status === 0;
34
97
  }
35
98
 
99
+ function uninstallCamoufox() {
100
+ const ret = run(resolveNpxBin(), ['--yes', '--package=camoufox', 'camoufox', 'remove']);
101
+ return ret.status === 0;
102
+ }
103
+
104
+ function uninstallGeoIP() {
105
+ const geoipDir = path.join(resolveWebautoRoot(), 'geoip');
106
+ try {
107
+ rmSync(geoipDir, { recursive: true, force: true });
108
+ } catch {
109
+ // ignore
110
+ }
111
+ return !checkGeoIPInstalled();
112
+ }
113
+
114
+ function resolveModeAndSelection(argv = {}) {
115
+ const legacyDownloadBrowser = argv['download-browser'] === true;
116
+ const legacyDownloadGeoip = argv['download-geoip'] === true;
117
+ const checkBrowserOnly = argv['check-browser-only'] === true;
118
+
119
+ let mode = 'check';
120
+ if (argv.auto === true) mode = 'auto';
121
+ else if (argv.reinstall === true) mode = 'reinstall';
122
+ else if (argv.uninstall === true || argv.remove === true) mode = 'uninstall';
123
+ else if (argv.install === true || legacyDownloadBrowser || legacyDownloadGeoip) mode = 'install';
124
+
125
+ const explicitBrowser = argv.browser === true || legacyDownloadBrowser || checkBrowserOnly;
126
+ const explicitGeoip = argv.geoip === true || legacyDownloadGeoip;
127
+ const explicitAll = argv.all === true;
128
+ const explicitAny = explicitBrowser || explicitGeoip || explicitAll;
129
+
130
+ let browser = false;
131
+ let geoip = false;
132
+ if (mode === 'check') {
133
+ browser = explicitBrowser || explicitAll || !explicitAny;
134
+ geoip = explicitGeoip || explicitAll;
135
+ } else {
136
+ browser = explicitBrowser || explicitAll || !explicitAny;
137
+ geoip = explicitGeoip || explicitAll || !explicitAny;
138
+ }
139
+
140
+ return { mode, browser, geoip };
141
+ }
142
+
36
143
  async function checkBackendHealth() {
37
144
  try {
38
145
  const res = await fetch('http://127.0.0.1:7704/health', { signal: AbortSignal.timeout(3000) });
@@ -43,19 +150,79 @@ async function checkBackendHealth() {
43
150
  }
44
151
 
45
152
  async function main() {
46
- const argv = minimist(process.argv.slice(2));
47
- const download = argv['download-browser'] === true;
48
- const downloadGeoip = argv['download-geoip'] === true;
153
+ const argv = minimist(process.argv.slice(2), {
154
+ boolean: [
155
+ 'auto',
156
+ 'install',
157
+ 'uninstall',
158
+ 'remove',
159
+ 'reinstall',
160
+ 'check-browser-only',
161
+ 'download-browser',
162
+ 'download-geoip',
163
+ 'browser',
164
+ 'geoip',
165
+ 'all',
166
+ 'ensure-backend',
167
+ 'json',
168
+ ],
169
+ });
170
+ const { mode, browser, geoip } = resolveModeAndSelection(argv);
49
171
  const ensureBackend = argv['ensure-backend'] === true;
50
172
  const provider = String(process.env.WEBAUTO_BROWSER_PROVIDER || 'camo').trim().toLowerCase();
51
- let camoufoxInstalled = checkCamoufoxInstalled();
52
- let geoipInstalled = checkGeoIPInstalled();
173
+ const before = {
174
+ camoufoxInstalled: checkCamoufoxInstalled(),
175
+ geoipInstalled: checkGeoIPInstalled(),
176
+ backendHealthy: await checkBackendHealth(),
177
+ };
178
+ const actions = {
179
+ browserInstalled: false,
180
+ browserUninstalled: false,
181
+ geoipInstalled: false,
182
+ geoipUninstalled: false,
183
+ };
184
+
185
+ let camoufoxInstalled = before.camoufoxInstalled;
186
+ let geoipInstalled = before.geoipInstalled;
187
+ let operationError = null;
188
+
189
+ if (mode === 'auto' || mode === 'install') {
190
+ if (browser && !camoufoxInstalled) {
191
+ actions.browserInstalled = installCamoufox();
192
+ camoufoxInstalled = checkCamoufoxInstalled();
193
+ if (!camoufoxInstalled) operationError = operationError || 'camoufox_install_failed';
194
+ }
195
+ if (geoip && !geoipInstalled) {
196
+ actions.geoipInstalled = installGeoIP();
197
+ geoipInstalled = checkGeoIPInstalled();
198
+ if (!geoipInstalled) operationError = operationError || 'geoip_install_failed';
199
+ }
200
+ }
53
201
 
54
- if (!camoufoxInstalled && download) {
55
- camoufoxInstalled = installCamoufox();
202
+ if (mode === 'uninstall' || mode === 'reinstall') {
203
+ if (browser) {
204
+ actions.browserUninstalled = uninstallCamoufox();
205
+ camoufoxInstalled = checkCamoufoxInstalled();
206
+ if (camoufoxInstalled) operationError = operationError || 'camoufox_uninstall_failed';
207
+ }
208
+ if (geoip) {
209
+ actions.geoipUninstalled = uninstallGeoIP();
210
+ geoipInstalled = checkGeoIPInstalled();
211
+ if (geoipInstalled) operationError = operationError || 'geoip_uninstall_failed';
212
+ }
56
213
  }
57
- if (!geoipInstalled && downloadGeoip) {
58
- geoipInstalled = installGeoIP();
214
+
215
+ if (mode === 'reinstall') {
216
+ if (browser) {
217
+ actions.browserInstalled = installCamoufox();
218
+ camoufoxInstalled = checkCamoufoxInstalled();
219
+ if (!camoufoxInstalled) operationError = operationError || 'camoufox_install_failed';
220
+ }
221
+ if (geoip) {
222
+ actions.geoipInstalled = installGeoIP();
223
+ geoipInstalled = checkGeoIPInstalled();
224
+ if (!geoipInstalled) operationError = operationError || 'geoip_install_failed';
225
+ }
59
226
  }
60
227
 
61
228
  let backendEnsured = false;
@@ -70,27 +237,67 @@ async function main() {
70
237
  }
71
238
  }
72
239
 
73
- const backendHealthy = await checkBackendHealth();
74
- const dependencyReady = camoufoxInstalled || backendHealthy;
75
- const geoipReady = downloadGeoip ? geoipInstalled : true;
76
- const ok = dependencyReady && geoipReady;
240
+ const after = {
241
+ camoufoxInstalled,
242
+ geoipInstalled,
243
+ backendHealthy: await checkBackendHealth(),
244
+ };
245
+ const browserReady =
246
+ mode === 'uninstall'
247
+ ? !browser || !after.camoufoxInstalled
248
+ : !browser || after.camoufoxInstalled || after.backendHealthy;
249
+ const geoipReady = mode === 'uninstall' ? !geoip || !after.geoipInstalled : !geoip || after.geoipInstalled;
250
+ const ok =
251
+ browserReady &&
252
+ geoipReady &&
253
+ operationError === null &&
254
+ (!ensureBackend || backendEnsured || after.backendHealthy);
77
255
 
78
256
  const result = {
79
257
  ok,
80
- camoufoxInstalled,
258
+ mode,
259
+ selection: {
260
+ browser,
261
+ geoip,
262
+ ensureBackend,
263
+ },
264
+ before,
265
+ actions,
266
+ after,
267
+ camoufoxInstalled: after.camoufoxInstalled,
81
268
  provider,
82
269
  backendEnsured,
83
270
  ensureBackendError,
84
- backendHealthy,
85
- geoipInstalled,
86
- message: ok ? 'Camo 后端就绪' : (downloadGeoip && !geoipInstalled ? 'GeoIP 未安装' : 'Camoufox 未安装'),
271
+ backendHealthy: after.backendHealthy,
272
+ geoipInstalled: after.geoipInstalled,
273
+ operationError,
274
+ message: ok
275
+ ? '资源状态就绪'
276
+ : operationError
277
+ ? `资源操作失败: ${operationError}`
278
+ : browser && !after.camoufoxInstalled
279
+ ? 'Camoufox 未安装'
280
+ : geoip && !after.geoipInstalled
281
+ ? 'GeoIP 未安装'
282
+ : '资源状态未就绪',
87
283
  };
88
284
 
89
285
  console.log(JSON.stringify(result));
90
286
  if (!ok) process.exit(1);
91
287
  }
92
288
 
93
- main().catch((err) => {
94
- console.error(JSON.stringify({ ok: false, error: err?.message || String(err) }));
95
- process.exit(1);
96
- });
289
+ const isEntrypoint = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
290
+ if (isEntrypoint) {
291
+ main().catch((err) => {
292
+ console.error(JSON.stringify({ ok: false, error: err?.message || String(err) }));
293
+ process.exit(1);
294
+ });
295
+ }
296
+
297
+ export const __internals = {
298
+ resolveOnPath,
299
+ resolveNpxBin,
300
+ resolveWebautoRoot,
301
+ resolveGeoIPPath,
302
+ resolveModeAndSelection,
303
+ };
@@ -33,6 +33,9 @@ function summarizeTasks(tasks = []) {
33
33
  const rows = tasks.map((task) => {
34
34
  const runId = asText(task?.runId || task?.id || '');
35
35
  const status = asText(task?.status || 'unknown').toLowerCase();
36
+ const progressPayload = task?.progress && typeof task.progress === 'object' ? task.progress : null;
37
+ const processed = Number(progressPayload?.processed ?? task?.progress ?? task?.current ?? 0) || 0;
38
+ const total = Number(progressPayload?.total ?? task?.total ?? 0) || 0;
36
39
  if (status === 'running' || status === 'starting') totals.running += 1;
37
40
  else if (status === 'queued' || status === 'pending') totals.queued += 1;
38
41
  else if (status === 'completed' || status === 'success' || status === 'succeeded') totals.succeeded += 1;
@@ -43,8 +46,8 @@ function summarizeTasks(tasks = []) {
43
46
  runId,
44
47
  status,
45
48
  phase: asText(task?.phase || task?.lastPhase || ''),
46
- progress: Number(task?.progress || task?.current || 0) || 0,
47
- total: Number(task?.total || 0) || 0,
49
+ progress: processed,
50
+ total,
48
51
  updatedAt: asText(task?.updatedAt || task?.lastActiveAt || ''),
49
52
  error: asText(task?.error || ''),
50
53
  };