@web-auto/webauto 0.1.8 → 0.1.11

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 (43) hide show
  1. package/apps/desktop-console/dist/main/index.mjs +909 -105
  2. package/apps/desktop-console/dist/main/preload.mjs +3 -0
  3. package/apps/desktop-console/dist/renderer/index.html +9 -1
  4. package/apps/desktop-console/dist/renderer/index.js +796 -331
  5. package/apps/desktop-console/entry/ui-cli.mjs +59 -9
  6. package/apps/desktop-console/entry/ui-console.mjs +8 -3
  7. package/apps/webauto/entry/account.mjs +70 -9
  8. package/apps/webauto/entry/lib/account-detect.mjs +106 -25
  9. package/apps/webauto/entry/lib/account-store.mjs +122 -35
  10. package/apps/webauto/entry/lib/profilepool.mjs +45 -13
  11. package/apps/webauto/entry/lib/schedule-store.mjs +1 -25
  12. package/apps/webauto/entry/profilepool.mjs +45 -3
  13. package/apps/webauto/entry/schedule.mjs +44 -2
  14. package/apps/webauto/entry/weibo-unified.mjs +2 -2
  15. package/apps/webauto/entry/xhs-install.mjs +248 -52
  16. package/apps/webauto/entry/xhs-unified.mjs +33 -6
  17. package/bin/webauto.mjs +137 -5
  18. package/dist/modules/camo-runtime/src/utils/browser-service.mjs +4 -0
  19. package/dist/services/unified-api/server.js +5 -0
  20. package/dist/services/unified-api/task-state.js +2 -0
  21. package/modules/camo-runtime/src/autoscript/action-providers/xhs/interaction.mjs +142 -14
  22. package/modules/camo-runtime/src/autoscript/action-providers/xhs/search.mjs +16 -1
  23. package/modules/camo-runtime/src/autoscript/action-providers/xhs.mjs +104 -0
  24. package/modules/camo-runtime/src/autoscript/runtime.mjs +14 -4
  25. package/modules/camo-runtime/src/autoscript/schema.mjs +9 -0
  26. package/modules/camo-runtime/src/autoscript/xhs-unified-template.mjs +9 -2
  27. package/modules/camo-runtime/src/container/runtime-core/checkpoint.mjs +107 -1
  28. package/modules/camo-runtime/src/container/runtime-core/subscription.mjs +24 -2
  29. package/modules/camo-runtime/src/utils/browser-service.mjs +4 -0
  30. package/package.json +7 -3
  31. package/runtime/infra/utils/README.md +13 -0
  32. package/runtime/infra/utils/scripts/README.md +0 -0
  33. package/runtime/infra/utils/scripts/development/eval-in-session.mjs +40 -0
  34. package/runtime/infra/utils/scripts/development/highlight-search-containers.mjs +35 -0
  35. package/runtime/infra/utils/scripts/service/kill-port.mjs +24 -0
  36. package/runtime/infra/utils/scripts/service/start-api.mjs +103 -0
  37. package/runtime/infra/utils/scripts/service/start-browser-service.mjs +173 -0
  38. package/runtime/infra/utils/scripts/service/stop-api.mjs +30 -0
  39. package/runtime/infra/utils/scripts/service/stop-browser-service.mjs +104 -0
  40. package/runtime/infra/utils/scripts/test-services.mjs +94 -0
  41. package/scripts/bump-version.mjs +120 -0
  42. package/services/unified-api/server.ts +4 -0
  43. package/services/unified-api/task-state.ts +5 -0
@@ -1,16 +1,17 @@
1
1
  import fs from 'node:fs';
2
- import os from 'node:os';
3
2
  import path from 'node:path';
4
3
  import {
5
4
  ensureProfile,
6
5
  resolveFingerprintsRoot,
7
6
  resolveNextProfileId,
8
7
  resolveProfilesRoot,
8
+ resolveWebautoRoot,
9
9
  } from './profilepool.mjs';
10
10
 
11
11
  const INDEX_FILE = 'index.json';
12
12
  const META_FILE = 'meta.json';
13
13
  const DEFAULT_PLATFORM = 'xiaohongshu';
14
+ const DEFAULT_PROFILE_PREFIX = 'profile';
14
15
  const STATUS_ACTIVE = 'active';
15
16
  const STATUS_VALID = 'valid';
16
17
  const STATUS_INVALID = 'invalid';
@@ -118,18 +119,6 @@ function ensureSafeName(name, field) {
118
119
  return value;
119
120
  }
120
121
 
121
- function resolvePortableRoot() {
122
- const root = String(process.env.WEBAUTO_PORTABLE_ROOT || process.env.WEBAUTO_ROOT || '').trim();
123
- return root ? path.join(root, '.webauto') : '';
124
- }
125
-
126
- export function resolveWebautoRoot() {
127
- const portableRoot = resolvePortableRoot();
128
- if (portableRoot) return portableRoot;
129
- const home = process.env.HOME || process.env.USERPROFILE || os.homedir();
130
- return path.join(home, '.webauto');
131
- }
132
-
133
122
  export function resolveAccountsRoot() {
134
123
  const envRoot = String(process.env.WEBAUTO_PATHS_ACCOUNTS || '').trim();
135
124
  if (envRoot) return envRoot;
@@ -213,21 +202,31 @@ function saveIndex(index) {
213
202
  return payload;
214
203
  }
215
204
 
216
- function resolveProfilePrefix(platform) {
205
+ function resolveLegacyProfilePrefix(platform) {
217
206
  if (platform === 'xiaohongshu') return 'xiaohongshu-batch';
218
207
  const slug = toSlug(platform) || 'account';
219
208
  return `${slug}-account`;
220
209
  }
221
210
 
211
+ function resolveProfilePrefix() {
212
+ return DEFAULT_PROFILE_PREFIX;
213
+ }
214
+
222
215
  function resolveProfileSeq(profileId, platform) {
223
216
  const value = String(profileId || '').trim();
224
217
  if (!value) return null;
225
- const prefix = resolveProfilePrefix(platform);
226
- const match = value.match(new RegExp(`^${prefix.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}-([0-9]+)$`));
227
- if (!match) return null;
228
- const seq = Number(match[1]);
229
- if (!Number.isFinite(seq) || seq < 0) return null;
230
- return seq;
218
+ const prefixes = [
219
+ resolveProfilePrefix(),
220
+ resolveLegacyProfilePrefix(platform),
221
+ ].filter(Boolean);
222
+ for (const prefix of prefixes) {
223
+ const match = value.match(new RegExp(`^${prefix.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}-([0-9]+)$`));
224
+ if (!match) continue;
225
+ const seq = Number(match[1]);
226
+ if (!Number.isFinite(seq) || seq < 0) return null;
227
+ return seq;
228
+ }
229
+ return null;
231
230
  }
232
231
 
233
232
  function resolveUsedAutoSeq(index, platform) {
@@ -280,23 +279,34 @@ function resolveAccountOrThrow(index, key) {
280
279
  throw new Error(`account not found: ${idOrAlias}`);
281
280
  }
282
281
 
283
- function resolveAccountByProfile(index, profileId) {
282
+ function resolveBindingKey(profileId, platform) {
283
+ const pid = String(profileId || '').trim();
284
+ const pf = normalizePlatform(platform);
285
+ if (!pid) return '';
286
+ return `${pid}::${pf}`;
287
+ }
288
+
289
+ function resolveAccountByProfile(index, profileId, platform = null) {
284
290
  const value = String(profileId || '').trim();
285
291
  if (!value) return null;
292
+ const wantedPlatform = platform ? normalizePlatform(platform) : null;
286
293
  let matched = null;
287
294
  for (const item of index.accounts) {
288
295
  if (String(item?.profileId || '').trim() !== value) continue;
296
+ if (wantedPlatform && normalizePlatform(item?.platform) !== wantedPlatform) continue;
289
297
  matched = pickNewerRecord(matched, item);
290
298
  }
291
299
  return matched;
292
300
  }
293
301
 
294
- function resolveAccountByAccountId(index, accountId) {
302
+ function resolveAccountByAccountId(index, accountId, platform = null) {
295
303
  const value = normalizeText(accountId);
296
304
  if (!value) return null;
305
+ const wantedPlatform = platform ? normalizePlatform(platform) : null;
297
306
  let matched = null;
298
307
  for (const item of index.accounts) {
299
308
  if (normalizeText(item?.accountId) !== value) continue;
309
+ if (wantedPlatform && normalizePlatform(item?.platform) !== wantedPlatform) continue;
300
310
  matched = pickNewerRecord(matched, item);
301
311
  }
302
312
  return matched;
@@ -342,39 +352,76 @@ function buildProfileAccountView(profileId, record = null) {
342
352
  };
343
353
  }
344
354
 
345
- export function listAccountProfiles() {
355
+ export function listAccountProfiles(options = {}) {
346
356
  const index = loadIndex();
357
+ const platformFilter = normalizeText(options?.platform) ? normalizePlatform(options.platform) : null;
347
358
  const byAccountId = new Map();
348
359
  for (const record of index.accounts) {
360
+ const recordPlatform = normalizePlatform(record?.platform);
361
+ if (platformFilter && recordPlatform !== platformFilter) continue;
349
362
  const accountId = normalizeText(record?.accountId);
350
363
  if (!accountId) continue;
351
- byAccountId.set(accountId, pickNewerRecord(byAccountId.get(accountId), record));
364
+ const key = `${recordPlatform}::${accountId}`;
365
+ byAccountId.set(key, pickNewerRecord(byAccountId.get(key), record));
352
366
  }
353
367
  const deduped = [];
354
368
  for (const record of index.accounts) {
369
+ const recordPlatform = normalizePlatform(record?.platform);
370
+ if (platformFilter && recordPlatform !== platformFilter) continue;
355
371
  const accountId = normalizeText(record?.accountId);
356
372
  if (accountId) {
357
- if (byAccountId.get(accountId) !== record) continue;
373
+ const key = `${recordPlatform}::${accountId}`;
374
+ if (byAccountId.get(key) !== record) continue;
358
375
  }
359
376
  deduped.push(record);
360
377
  }
361
- const byProfile = new Map();
378
+ const byBinding = new Map();
362
379
  for (const record of deduped) {
363
380
  const profileId = normalizeText(record?.profileId);
364
381
  if (!profileId) continue;
365
- byProfile.set(profileId, pickNewerRecord(byProfile.get(profileId), record));
382
+ const key = resolveBindingKey(profileId, record?.platform);
383
+ if (!key) continue;
384
+ byBinding.set(key, pickNewerRecord(byBinding.get(key), record));
385
+ }
386
+ const rows = Array.from(byBinding.values())
387
+ .sort((a, b) => {
388
+ const seqCmp = Number(a?.seq || 0) - Number(b?.seq || 0);
389
+ if (seqCmp !== 0) return seqCmp;
390
+ const pCmp = String(a?.profileId || '').localeCompare(String(b?.profileId || ''));
391
+ if (pCmp !== 0) return pCmp;
392
+ return normalizePlatform(a?.platform).localeCompare(normalizePlatform(b?.platform));
393
+ })
394
+ .map((record) => buildProfileAccountView(record?.profileId, record));
395
+ const validProfilesSet = new Set();
396
+ const invalidProfilesSet = new Set();
397
+ const validProfilesByPlatform = {};
398
+ const invalidProfilesByPlatform = {};
399
+ for (const row of rows) {
400
+ const platform = normalizePlatform(row.platform);
401
+ if (!validProfilesByPlatform[platform]) validProfilesByPlatform[platform] = [];
402
+ if (!invalidProfilesByPlatform[platform]) invalidProfilesByPlatform[platform] = [];
403
+ if (row.valid) {
404
+ validProfilesSet.add(row.profileId);
405
+ if (!validProfilesByPlatform[platform].includes(row.profileId)) {
406
+ validProfilesByPlatform[platform].push(row.profileId);
407
+ }
408
+ } else {
409
+ invalidProfilesSet.add(row.profileId);
410
+ if (!invalidProfilesByPlatform[platform].includes(row.profileId)) {
411
+ invalidProfilesByPlatform[platform].push(row.profileId);
412
+ }
413
+ }
366
414
  }
367
- const rows = Array.from(byProfile.entries())
368
- .sort((a, b) => (Number(a[1]?.seq || 0) - Number(b[1]?.seq || 0)))
369
- .map(([profileId, record]) => buildProfileAccountView(profileId, record));
370
- const validProfiles = rows.filter((item) => item.valid).map((item) => item.profileId);
371
- const invalidProfiles = rows.filter((item) => !item.valid).map((item) => item.profileId);
415
+ const validProfiles = Array.from(validProfilesSet);
416
+ const invalidProfiles = Array.from(invalidProfilesSet).filter((profileId) => !validProfilesSet.has(profileId));
372
417
  return {
373
418
  root: resolveAccountsRoot(),
374
419
  count: rows.length,
375
420
  profiles: rows,
376
421
  validProfiles,
377
422
  invalidProfiles,
423
+ validProfilesByPlatform,
424
+ invalidProfilesByPlatform,
378
425
  };
379
426
  }
380
427
 
@@ -426,6 +473,39 @@ export async function addAccount(input = {}) {
426
473
  status === STATUS_VALID
427
474
  ? null
428
475
  : (status === STATUS_PENDING ? 'waiting_login' : 'missing_account_id');
476
+
477
+ if (accountId) {
478
+ const existing = resolveAccountByAccountId(index, accountId, platform);
479
+ if (existing) {
480
+ const next = {
481
+ ...existing,
482
+ platform,
483
+ status,
484
+ valid: status === STATUS_VALID,
485
+ reason,
486
+ accountId,
487
+ name: accountId,
488
+ alias: alias || existing.alias || null,
489
+ username: normalizeText(input.username) || existing.username || null,
490
+ profileId,
491
+ fingerprintId,
492
+ updatedAt: createdAt,
493
+ aliasSource: alias ? (normalizeAlias(input.alias) ? 'manual' : 'username') : existing.aliasSource || null,
494
+ };
495
+ if (alias) ensureAliasUnique(index.accounts, alias, existing.id);
496
+ const rowIndex = index.accounts.findIndex((item) => item?.id === existing.id);
497
+ if (rowIndex < 0) throw new Error(`account not found: ${existing.id}`);
498
+ index.accounts[rowIndex] = next;
499
+ saveIndex(index);
500
+ persistAccountMeta(next);
501
+ return {
502
+ root: resolveAccountsRoot(),
503
+ account: next,
504
+ deduped: true,
505
+ };
506
+ }
507
+ }
508
+
429
509
  const account = {
430
510
  id,
431
511
  seq,
@@ -553,8 +633,8 @@ export function upsertProfileAccountState(input = {}) {
553
633
  const status = accountId ? STATUS_VALID : (pendingMode ? STATUS_PENDING : STATUS_INVALID);
554
634
 
555
635
  const index = loadIndex();
556
- const existingByProfile = resolveAccountByProfile(index, profileId);
557
- const existingByAccountId = accountId ? resolveAccountByAccountId(index, accountId) : null;
636
+ const existingByProfile = resolveAccountByProfile(index, profileId, platform);
637
+ const existingByAccountId = accountId ? resolveAccountByAccountId(index, accountId, platform) : null;
558
638
  let target = existingByAccountId || existingByProfile || null;
559
639
  const purgeIds = new Set();
560
640
 
@@ -611,7 +691,11 @@ export function upsertProfileAccountState(input = {}) {
611
691
  }
612
692
 
613
693
  const staleIds = index.accounts
614
- .filter((item) => String(item?.profileId || '').trim() === profileId && !hasPersistentAccountId(item))
694
+ .filter((item) => (
695
+ String(item?.profileId || '').trim() === profileId
696
+ && normalizePlatform(item?.platform) === platform
697
+ && !hasPersistentAccountId(item)
698
+ ))
615
699
  .map((item) => String(item?.id || '').trim())
616
700
  .filter(Boolean);
617
701
  if (staleIds.length > 0) {
@@ -677,6 +761,7 @@ export function upsertProfileAccountState(input = {}) {
677
761
  if (accountId) {
678
762
  for (const row of index.accounts) {
679
763
  if (!row || row.id === target.id) continue;
764
+ if (normalizePlatform(row?.platform) !== platform) continue;
680
765
  if (normalizeText(row.accountId) === accountId) {
681
766
  purgeIds.add(row.id);
682
767
  }
@@ -727,6 +812,7 @@ export function markProfileInvalid(profileId, reason = 'login_guard') {
727
812
  const id = ensureSafeName(normalizeText(profileId), 'profileId');
728
813
  return upsertProfileAccountState({
729
814
  profileId: id,
815
+ platform: DEFAULT_PLATFORM,
730
816
  accountId: null,
731
817
  reason,
732
818
  });
@@ -736,6 +822,7 @@ export function markProfilePending(profileId, reason = 'waiting_login') {
736
822
  const id = ensureSafeName(normalizeText(profileId), 'profileId');
737
823
  return upsertProfileAccountState({
738
824
  profileId: id,
825
+ platform: DEFAULT_PLATFORM,
739
826
  accountId: null,
740
827
  status: STATUS_PENDING,
741
828
  reason,
@@ -2,32 +2,64 @@ import os from 'node:os';
2
2
  import path from 'node:path';
3
3
  import fs from 'node:fs';
4
4
 
5
- function resolvePortableRoot() {
6
- const root = String(process.env.WEBAUTO_PORTABLE_ROOT || process.env.WEBAUTO_ROOT || '').trim();
7
- return root ? path.join(root, '.webauto') : '';
5
+ function hasDrive(letter) {
6
+ if (process.platform !== 'win32') return false;
7
+ try {
8
+ return fs.existsSync(`${String(letter || '').replace(/[^A-Za-z]/g, '').toUpperCase()}:\\`);
9
+ } catch {
10
+ return false;
11
+ }
8
12
  }
9
13
 
10
- function resolveHomeDir() {
11
- if (process.platform === 'win32') {
12
- return process.env.USERPROFILE || os.homedir();
13
- }
14
+ function normalizePathForPlatform(input, platform = process.platform) {
15
+ const raw = String(input || '').trim();
16
+ const isWinPath = platform === 'win32' || /^[A-Za-z]:[\\/]/.test(raw);
17
+ const pathApi = isWinPath ? path.win32 : path;
18
+ return isWinPath ? pathApi.normalize(raw) : path.resolve(raw);
19
+ }
20
+
21
+ function normalizeLegacyWebautoRoot(input, platform = process.platform) {
22
+ const pathApi = platform === 'win32' ? path.win32 : path;
23
+ const resolved = normalizePathForPlatform(input, platform);
24
+ const base = pathApi.basename(resolved).toLowerCase();
25
+ if (base === '.webauto' || base === 'webauto') return resolved;
26
+ return pathApi.join(resolved, '.webauto');
27
+ }
28
+
29
+ function resolveHomeDir(platform = process.platform) {
30
+ if (platform === 'win32') return process.env.USERPROFILE || os.homedir();
14
31
  return process.env.HOME || os.homedir();
15
32
  }
16
33
 
34
+ export function resolveWebautoRoot(options = {}) {
35
+ const env = options.env || process.env;
36
+ const platform = String(options.platform || process.platform);
37
+ const homeDir = String(options.homeDir || resolveHomeDir(platform));
38
+ const pathApi = platform === 'win32' ? path.win32 : path;
39
+
40
+ const explicitHome = String(env.WEBAUTO_HOME || '').trim();
41
+ if (explicitHome) return normalizePathForPlatform(explicitHome, platform);
42
+
43
+ const legacyRoot = String(env.WEBAUTO_ROOT || env.WEBAUTO_PORTABLE_ROOT || '').trim();
44
+ if (legacyRoot) return normalizeLegacyWebautoRoot(legacyRoot, platform);
45
+
46
+ if (platform === 'win32') {
47
+ const dDriveExists = typeof options.hasDDrive === 'boolean' ? options.hasDDrive : hasDrive('D');
48
+ return dDriveExists ? 'D:\\webauto' : pathApi.join(homeDir, '.webauto');
49
+ }
50
+ return pathApi.join(homeDir, '.webauto');
51
+ }
52
+
17
53
  export function resolveProfilesRoot() {
18
54
  const envProfiles = String(process.env.WEBAUTO_PATHS_PROFILES || '').trim();
19
55
  if (envProfiles) return envProfiles;
20
- const portableRoot = resolvePortableRoot();
21
- if (portableRoot) return path.join(portableRoot, 'profiles');
22
- return path.join(resolveHomeDir(), '.webauto', 'profiles');
56
+ return path.join(resolveWebautoRoot(), 'profiles');
23
57
  }
24
58
 
25
59
  export function resolveFingerprintsRoot() {
26
60
  const envFps = String(process.env.WEBAUTO_PATHS_FINGERPRINTS || '').trim();
27
61
  if (envFps) return envFps;
28
- const portableRoot = resolvePortableRoot();
29
- if (portableRoot) return path.join(portableRoot, 'fingerprints');
30
- return path.join(resolveHomeDir(), '.webauto', 'fingerprints');
62
+ return path.join(resolveWebautoRoot(), 'fingerprints');
31
63
  }
32
64
 
33
65
  export function listProfiles() {
@@ -1,6 +1,6 @@
1
1
  import fs from 'node:fs';
2
- import os from 'node:os';
3
2
  import path from 'node:path';
3
+ import { resolveWebautoRoot } from './profilepool.mjs';
4
4
 
5
5
  const INDEX_FILE = 'index.json';
6
6
  const DEFAULT_COMMAND_TYPE = 'xhs-unified';
@@ -78,18 +78,6 @@ function normalizeMaxRuns(value, fallback = null) {
78
78
  return Math.max(1, Math.floor(n));
79
79
  }
80
80
 
81
- function resolvePortableRoot() {
82
- const root = String(process.env.WEBAUTO_PORTABLE_ROOT || process.env.WEBAUTO_ROOT || '').trim();
83
- return root ? path.join(root, '.webauto') : '';
84
- }
85
-
86
- function resolveWebautoRoot() {
87
- const portableRoot = resolvePortableRoot();
88
- if (portableRoot) return portableRoot;
89
- const home = process.env.HOME || process.env.USERPROFILE || os.homedir();
90
- return path.join(home, '.webauto');
91
- }
92
-
93
81
  export function resolveSchedulesRoot() {
94
82
  const explicit = String(process.env.WEBAUTO_PATHS_SCHEDULES || '').trim();
95
83
  if (explicit) return explicit;
@@ -464,20 +452,11 @@ function validateCommand(task) {
464
452
 
465
453
  function validateXhsCommand(argv) {
466
454
  const keyword = normalizeText(argv.keyword || argv.k);
467
- const profile = normalizeText(argv.profile);
468
- const profiles = normalizeText(argv.profiles);
469
- const profilepool = normalizeText(argv.profilepool);
470
455
  if (!keyword) throw new Error('task command argv missing keyword');
471
- if (!profile && !profiles && !profilepool) {
472
- throw new Error('task command argv missing profile/profiles/profilepool');
473
- }
474
456
  }
475
457
 
476
458
  function validateGenericCommand(argv, platform, commandType = '') {
477
459
  const keyword = normalizeText(argv.keyword || argv.k);
478
- const profile = normalizeText(argv.profile);
479
- const profiles = normalizeText(argv.profiles);
480
- const profilepool = normalizeText(argv.profilepool);
481
460
  let weiboTaskType = '';
482
461
  if (platform === 'weibo') {
483
462
  weiboTaskType = String(argv['task-type'] || argv.taskType || '').trim();
@@ -495,9 +474,6 @@ function validateGenericCommand(argv, platform, commandType = '') {
495
474
  if (!keyword && (platform !== 'weibo' || weiboTaskType === 'search')) {
496
475
  throw new Error('task command argv missing keyword');
497
476
  }
498
- if (!profile && !profiles && !profilepool) {
499
- throw new Error('task command argv missing profile/profiles/profilepool');
500
- }
501
477
  }
502
478
 
503
479
  function normalizeScheduleFields(input = {}, fallback = {}) {
@@ -173,6 +173,46 @@ async function cmdMigrateFingerprints(jsonMode) {
173
173
  output({ ok: true, checked: profiles.length, ensured: created.length }, jsonMode);
174
174
  }
175
175
 
176
+ async function cmdGotoProfile(profileId, argv, jsonMode) {
177
+ const id = String(profileId || '').trim();
178
+ if (!id) throw new Error('profileId is required');
179
+ const url = String(argv.url || argv._?.[2] || '').trim();
180
+ if (!url) throw new Error('url is required');
181
+ await ensureProfile(id);
182
+
183
+ const gotoRet = runCamo(['goto', id, url], { rootDir: ROOT, timeoutMs: 30000 });
184
+ if (gotoRet.ok) {
185
+ output({
186
+ ok: true,
187
+ profileId: id,
188
+ url,
189
+ mode: 'goto',
190
+ result: gotoRet.json || null,
191
+ }, jsonMode);
192
+ return;
193
+ }
194
+
195
+ const idleTimeout = String(argv['idle-timeout'] || process.env.WEBAUTO_LOGIN_IDLE_TIMEOUT || '30m').trim() || '30m';
196
+ const startRet = runCamo(['start', id, '--url', url, '--idle-timeout', idleTimeout], { rootDir: ROOT });
197
+ if (!startRet.ok) {
198
+ output({
199
+ ok: false,
200
+ profileId: id,
201
+ url,
202
+ mode: 'start',
203
+ error: startRet.stderr || startRet.stdout || gotoRet.stderr || gotoRet.stdout || 'goto/start failed',
204
+ }, jsonMode);
205
+ process.exit(1);
206
+ }
207
+ output({
208
+ ok: true,
209
+ profileId: id,
210
+ url,
211
+ mode: 'start',
212
+ session: startRet.json || null,
213
+ }, jsonMode);
214
+ }
215
+
176
216
  async function main() {
177
217
  const argv = minimist(process.argv.slice(2));
178
218
  const cmd = String(argv._[0] || '').trim();
@@ -180,14 +220,16 @@ async function main() {
180
220
  const jsonMode = argv.json === true;
181
221
 
182
222
  if (!cmd || cmd === 'help' || cmd === '--help' || cmd === '-h') {
183
- console.log('Usage: node apps/webauto/entry/profilepool.mjs <list|add|login|login-profile|migrate-fingerprints> ... [--json]');
223
+ console.log('Usage: node apps/webauto/entry/profilepool.mjs <list|add|login|login-profile|goto-profile|migrate-fingerprints> ... [--json]');
224
+ console.log('Default profile prefix: profile (e.g. profile-0, profile-1)');
184
225
  return;
185
226
  }
186
227
 
187
228
  if (cmd === 'list') return cmdList(arg1, jsonMode);
188
- if (cmd === 'add') return cmdAdd(arg1 || 'xiaohongshu-batch', jsonMode);
229
+ if (cmd === 'add') return cmdAdd(arg1 || 'profile', jsonMode);
189
230
  if (cmd === 'login-profile') return cmdLoginProfile(arg1, argv, jsonMode);
190
- if (cmd === 'login') return cmdLogin(arg1 || 'xiaohongshu-batch', argv, jsonMode);
231
+ if (cmd === 'goto-profile') return cmdGotoProfile(arg1, argv, jsonMode);
232
+ if (cmd === 'login') return cmdLogin(arg1 || 'profile', argv, jsonMode);
191
233
  if (cmd === 'migrate-fingerprints') return cmdMigrateFingerprints(jsonMode);
192
234
 
193
235
  throw new Error(`unknown command: ${cmd}`);
@@ -22,6 +22,7 @@ import {
22
22
  resolveSchedulesRoot,
23
23
  updateScheduleTask,
24
24
  } from './lib/schedule-store.mjs';
25
+ import { listAccountProfiles } from './lib/account-store.mjs';
25
26
 
26
27
  let xhsRunnerPromise = null;
27
28
  let weiboRunnerPromise = null;
@@ -70,6 +71,46 @@ function parseJson(text, fallback = {}) {
70
71
  return JSON.parse(raw.replace(/^\uFEFF/, ''));
71
72
  }
72
73
 
74
+ function normalizePlatformByCommandType(commandType) {
75
+ const value = String(commandType || '').trim().toLowerCase();
76
+ if (value.startsWith('weibo')) return 'weibo';
77
+ if (value.startsWith('1688')) return '1688';
78
+ return 'xiaohongshu';
79
+ }
80
+
81
+ function hasProfileArg(argv = {}) {
82
+ return Boolean(
83
+ String(argv?.profile || '').trim()
84
+ || String(argv?.profiles || '').trim()
85
+ || String(argv?.profilepool || '').trim(),
86
+ );
87
+ }
88
+
89
+ function pickAutoProfile(platform) {
90
+ const rows = listAccountProfiles({ platform }).profiles || [];
91
+ const validRows = rows
92
+ .filter((row) => row?.valid === true && String(row?.accountId || '').trim())
93
+ .sort((a, b) => {
94
+ const ta = Date.parse(String(a?.updatedAt || '')) || 0;
95
+ const tb = Date.parse(String(b?.updatedAt || '')) || 0;
96
+ if (tb !== ta) return tb - ta;
97
+ return String(a?.profileId || '').localeCompare(String(b?.profileId || ''));
98
+ });
99
+ return String(validRows[0]?.profileId || '').trim();
100
+ }
101
+
102
+ function ensureProfileArgForTask(commandType, commandArgv = {}) {
103
+ const argv = commandArgv && typeof commandArgv === 'object' ? { ...commandArgv } : {};
104
+ if (hasProfileArg(argv)) return argv;
105
+ const platform = normalizePlatformByCommandType(commandType);
106
+ const profile = pickAutoProfile(platform);
107
+ if (!profile) {
108
+ throw new Error(`missing profile/profiles/profilepool and no valid account for platform=${platform}`);
109
+ }
110
+ argv.profile = profile;
111
+ return argv;
112
+ }
113
+
73
114
  function safeReadJsonFile(filePath) {
74
115
  const raw = fs.readFileSync(filePath, 'utf8');
75
116
  return parseJson(raw, {});
@@ -265,14 +306,15 @@ async function executeTask(task, options = {}) {
265
306
  const quietExecutors = options.quietExecutors === true;
266
307
  try {
267
308
  const commandType = String(task?.commandType || 'xhs-unified').trim();
309
+ const commandArgv = ensureProfileArgForTask(commandType, task?.commandArgv || {});
268
310
  const result = await withConsoleSilenced(quietExecutors, async () => {
269
311
  if (commandType === 'xhs-unified') {
270
312
  const runUnified = await getXhsRunner();
271
- return runUnified(task.commandArgv || {});
313
+ return runUnified(commandArgv);
272
314
  }
273
315
  if (commandType.startsWith('weibo-')) {
274
316
  const runWeiboUnified = await getWeiboRunner();
275
- return runWeiboUnified(task.commandArgv || {});
317
+ return runWeiboUnified(commandArgv);
276
318
  }
277
319
  if (commandType === '1688-search') {
278
320
  throw new Error(`executor_not_implemented: ${commandType}`);
@@ -4,7 +4,7 @@ import { runWorkflowById } from '../../../dist/modules/workflow/src/runner.js';
4
4
  import { pathToFileURL } from 'node:url';
5
5
 
6
6
  const WEIBO_HOME_URL = 'https://www.weibo.com';
7
- const DEFAULT_PROFILE = 'xiaohongshu-batch-1'; // Use shared profile for now
7
+ const DEFAULT_PROFILE = 'profile-0';
8
8
 
9
9
  async function runCommand(argv) {
10
10
  const profile = String(argv.profile || DEFAULT_PROFILE).trim();
@@ -106,7 +106,7 @@ if (isDirectExec) {
106
106
  export async function runWeiboUnified(argv) {
107
107
  const workflowId = String(argv.workflow || 'weibo-search-v1').trim();
108
108
  const keyword = String(argv.keyword || argv.k || '').trim();
109
- const profile = String(argv.profile || 'xiaohongshu-batch-1').trim();
109
+ const profile = String(argv.profile || DEFAULT_PROFILE).trim();
110
110
  const targetCount = Number(argv['max-notes'] || argv.target || argv['max-notes'] || 50);
111
111
  const maxComments = Number(argv['max-comments'] || 0);
112
112
  const env = String(argv.env || 'debug').trim();