@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.
Files changed (174) hide show
  1. package/apps/desktop-console/default-settings.json +2 -2
  2. package/apps/desktop-console/dist/main/index.mjs +983 -128
  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 +2423 -469
  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 +256 -31
  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 +13 -4
  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,36 +3,161 @@ 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';
10
58
  }
11
59
 
12
- function resolveNpxBin() {
13
- return process.platform === 'win32' ? 'npx.cmd' : 'npx';
60
+ function resolveNpmBin(platform = process.platform, pathEnv = process.env.PATH || process.env.Path || '') {
61
+ if (platform !== 'win32') return 'npm';
62
+ const resolved = resolveOnPath(
63
+ ['npm.cmd', 'npm.exe', 'npm.bat', 'npm.ps1'],
64
+ pathEnv,
65
+ ';',
66
+ );
67
+ return resolved || 'npm.cmd';
68
+ }
69
+
70
+ function runPackageCommand(packageName, commandArgs) {
71
+ const viaNpx = run(resolveNpxBin(), ['--yes', `--package=${packageName}`, ...commandArgs]);
72
+ if (viaNpx.status === 0) return viaNpx;
73
+ return run(resolveNpmBin(), ['exec', '--yes', `--package=${packageName}`, '--', ...commandArgs]);
74
+ }
75
+
76
+ function resolveWebautoRoot() {
77
+ const portableRoot = String(process.env.WEBAUTO_PORTABLE_ROOT || process.env.WEBAUTO_ROOT || '').trim();
78
+ if (portableRoot) return path.join(portableRoot, '.webauto');
79
+ return path.join(os.homedir(), '.webauto');
80
+ }
81
+
82
+ function resolveGeoIPPath() {
83
+ return path.join(resolveWebautoRoot(), 'geoip', 'GeoLite2-City.mmdb');
14
84
  }
15
85
 
16
86
  function checkCamoufoxInstalled() {
17
- const cmd = process.platform === 'win32' ? 'python' : 'python3';
18
- const ret = run(cmd, ['-m', 'camoufox', 'path']);
19
- return ret.status === 0;
87
+ const candidates =
88
+ process.platform === 'win32'
89
+ ? [
90
+ { cmd: 'python', args: ['-m', 'camoufox', 'path'] },
91
+ { cmd: 'py', args: ['-3', '-m', 'camoufox', 'path'] },
92
+ ]
93
+ : [{ cmd: 'python3', args: ['-m', 'camoufox', 'path'] }];
94
+ for (const candidate of candidates) {
95
+ const ret = run(candidate.cmd, candidate.args);
96
+ if (ret.status === 0) return true;
97
+ }
98
+ const npxRet = runPackageCommand('camoufox', ['camoufox', 'path']);
99
+ if (npxRet.status === 0) return true;
100
+ return false;
20
101
  }
21
102
 
22
103
  function installCamoufox() {
23
- const ret = run(resolveNpxBin(), ['--yes', '--package=camoufox', 'camoufox', 'fetch']);
104
+ const ret = runPackageCommand('camoufox', ['camoufox', 'fetch']);
24
105
  return ret.status === 0;
25
106
  }
26
107
 
27
108
  function checkGeoIPInstalled() {
28
- return existsSync(path.join(os.homedir(), '.webauto', 'geoip', 'GeoLite2-City.mmdb'));
109
+ return existsSync(resolveGeoIPPath());
29
110
  }
30
111
 
31
112
  function installGeoIP() {
32
- const ret = run(resolveNpxBin(), ['--yes', '--package=@web-auto/camo', 'camo', 'init', 'geoip']);
113
+ const ret = runPackageCommand('@web-auto/camo', ['camo', 'init', 'geoip']);
33
114
  return ret.status === 0;
34
115
  }
35
116
 
117
+ function uninstallCamoufox() {
118
+ const ret = runPackageCommand('camoufox', ['camoufox', 'remove']);
119
+ return ret.status === 0;
120
+ }
121
+
122
+ function uninstallGeoIP() {
123
+ const geoipDir = path.join(resolveWebautoRoot(), 'geoip');
124
+ try {
125
+ rmSync(geoipDir, { recursive: true, force: true });
126
+ } catch {
127
+ // ignore
128
+ }
129
+ return !checkGeoIPInstalled();
130
+ }
131
+
132
+ function resolveModeAndSelection(argv = {}) {
133
+ const legacyDownloadBrowser = argv['download-browser'] === true;
134
+ const legacyDownloadGeoip = argv['download-geoip'] === true;
135
+ const checkBrowserOnly = argv['check-browser-only'] === true;
136
+
137
+ let mode = 'check';
138
+ if (argv.auto === true) mode = 'auto';
139
+ else if (argv.reinstall === true) mode = 'reinstall';
140
+ else if (argv.uninstall === true || argv.remove === true) mode = 'uninstall';
141
+ else if (argv.install === true || legacyDownloadBrowser || legacyDownloadGeoip) mode = 'install';
142
+
143
+ const explicitBrowser = argv.browser === true || legacyDownloadBrowser || checkBrowserOnly;
144
+ const explicitGeoip = argv.geoip === true || legacyDownloadGeoip;
145
+ const explicitAll = argv.all === true;
146
+ const explicitAny = explicitBrowser || explicitGeoip || explicitAll;
147
+
148
+ let browser = false;
149
+ let geoip = false;
150
+ if (mode === 'check') {
151
+ browser = explicitBrowser || explicitAll || !explicitAny;
152
+ geoip = explicitGeoip || explicitAll;
153
+ } else {
154
+ browser = explicitBrowser || explicitAll || !explicitAny;
155
+ geoip = explicitGeoip || explicitAll || !explicitAny;
156
+ }
157
+
158
+ return { mode, browser, geoip };
159
+ }
160
+
36
161
  async function checkBackendHealth() {
37
162
  try {
38
163
  const res = await fetch('http://127.0.0.1:7704/health', { signal: AbortSignal.timeout(3000) });
@@ -43,19 +168,79 @@ async function checkBackendHealth() {
43
168
  }
44
169
 
45
170
  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;
171
+ const argv = minimist(process.argv.slice(2), {
172
+ boolean: [
173
+ 'auto',
174
+ 'install',
175
+ 'uninstall',
176
+ 'remove',
177
+ 'reinstall',
178
+ 'check-browser-only',
179
+ 'download-browser',
180
+ 'download-geoip',
181
+ 'browser',
182
+ 'geoip',
183
+ 'all',
184
+ 'ensure-backend',
185
+ 'json',
186
+ ],
187
+ });
188
+ const { mode, browser, geoip } = resolveModeAndSelection(argv);
49
189
  const ensureBackend = argv['ensure-backend'] === true;
50
190
  const provider = String(process.env.WEBAUTO_BROWSER_PROVIDER || 'camo').trim().toLowerCase();
51
- let camoufoxInstalled = checkCamoufoxInstalled();
52
- let geoipInstalled = checkGeoIPInstalled();
191
+ const before = {
192
+ camoufoxInstalled: checkCamoufoxInstalled(),
193
+ geoipInstalled: checkGeoIPInstalled(),
194
+ backendHealthy: await checkBackendHealth(),
195
+ };
196
+ const actions = {
197
+ browserInstalled: false,
198
+ browserUninstalled: false,
199
+ geoipInstalled: false,
200
+ geoipUninstalled: false,
201
+ };
202
+
203
+ let camoufoxInstalled = before.camoufoxInstalled;
204
+ let geoipInstalled = before.geoipInstalled;
205
+ let operationError = null;
206
+
207
+ if (mode === 'auto' || mode === 'install') {
208
+ if (browser && !camoufoxInstalled) {
209
+ actions.browserInstalled = installCamoufox();
210
+ camoufoxInstalled = checkCamoufoxInstalled();
211
+ if (!camoufoxInstalled) operationError = operationError || 'camoufox_install_failed';
212
+ }
213
+ if (geoip && !geoipInstalled) {
214
+ actions.geoipInstalled = installGeoIP();
215
+ geoipInstalled = checkGeoIPInstalled();
216
+ if (!geoipInstalled) operationError = operationError || 'geoip_install_failed';
217
+ }
218
+ }
53
219
 
54
- if (!camoufoxInstalled && download) {
55
- camoufoxInstalled = installCamoufox();
220
+ if (mode === 'uninstall' || mode === 'reinstall') {
221
+ if (browser) {
222
+ actions.browserUninstalled = uninstallCamoufox();
223
+ camoufoxInstalled = checkCamoufoxInstalled();
224
+ if (camoufoxInstalled) operationError = operationError || 'camoufox_uninstall_failed';
225
+ }
226
+ if (geoip) {
227
+ actions.geoipUninstalled = uninstallGeoIP();
228
+ geoipInstalled = checkGeoIPInstalled();
229
+ if (geoipInstalled) operationError = operationError || 'geoip_uninstall_failed';
230
+ }
56
231
  }
57
- if (!geoipInstalled && downloadGeoip) {
58
- geoipInstalled = installGeoIP();
232
+
233
+ if (mode === 'reinstall') {
234
+ if (browser) {
235
+ actions.browserInstalled = installCamoufox();
236
+ camoufoxInstalled = checkCamoufoxInstalled();
237
+ if (!camoufoxInstalled) operationError = operationError || 'camoufox_install_failed';
238
+ }
239
+ if (geoip) {
240
+ actions.geoipInstalled = installGeoIP();
241
+ geoipInstalled = checkGeoIPInstalled();
242
+ if (!geoipInstalled) operationError = operationError || 'geoip_install_failed';
243
+ }
59
244
  }
60
245
 
61
246
  let backendEnsured = false;
@@ -70,27 +255,67 @@ async function main() {
70
255
  }
71
256
  }
72
257
 
73
- const backendHealthy = await checkBackendHealth();
74
- const dependencyReady = camoufoxInstalled || backendHealthy;
75
- const geoipReady = downloadGeoip ? geoipInstalled : true;
76
- const ok = dependencyReady && geoipReady;
258
+ const after = {
259
+ camoufoxInstalled,
260
+ geoipInstalled,
261
+ backendHealthy: await checkBackendHealth(),
262
+ };
263
+ const browserReady =
264
+ mode === 'uninstall'
265
+ ? !browser || !after.camoufoxInstalled
266
+ : !browser || after.camoufoxInstalled || after.backendHealthy;
267
+ const geoipReady = mode === 'uninstall' ? !geoip || !after.geoipInstalled : !geoip || after.geoipInstalled;
268
+ const ok =
269
+ browserReady &&
270
+ geoipReady &&
271
+ operationError === null &&
272
+ (!ensureBackend || backendEnsured || after.backendHealthy);
77
273
 
78
274
  const result = {
79
275
  ok,
80
- camoufoxInstalled,
276
+ mode,
277
+ selection: {
278
+ browser,
279
+ geoip,
280
+ ensureBackend,
281
+ },
282
+ before,
283
+ actions,
284
+ after,
285
+ camoufoxInstalled: after.camoufoxInstalled,
81
286
  provider,
82
287
  backendEnsured,
83
288
  ensureBackendError,
84
- backendHealthy,
85
- geoipInstalled,
86
- message: ok ? 'Camo 后端就绪' : (downloadGeoip && !geoipInstalled ? 'GeoIP 未安装' : 'Camoufox 未安装'),
289
+ backendHealthy: after.backendHealthy,
290
+ geoipInstalled: after.geoipInstalled,
291
+ operationError,
292
+ message: ok
293
+ ? '资源状态就绪'
294
+ : operationError
295
+ ? `资源操作失败: ${operationError}`
296
+ : browser && !after.camoufoxInstalled
297
+ ? 'Camoufox 未安装'
298
+ : geoip && !after.geoipInstalled
299
+ ? 'GeoIP 未安装'
300
+ : '资源状态未就绪',
87
301
  };
88
302
 
89
303
  console.log(JSON.stringify(result));
90
304
  if (!ok) process.exit(1);
91
305
  }
92
306
 
93
- main().catch((err) => {
94
- console.error(JSON.stringify({ ok: false, error: err?.message || String(err) }));
95
- process.exit(1);
96
- });
307
+ const isEntrypoint = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
308
+ if (isEntrypoint) {
309
+ main().catch((err) => {
310
+ console.error(JSON.stringify({ ok: false, error: err?.message || String(err) }));
311
+ process.exit(1);
312
+ });
313
+ }
314
+
315
+ export const __internals = {
316
+ resolveOnPath,
317
+ resolveNpxBin,
318
+ resolveWebautoRoot,
319
+ resolveGeoIPPath,
320
+ resolveModeAndSelection,
321
+ };
@@ -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
  };