@web-auto/camo 0.1.12 → 0.1.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -459,7 +459,11 @@ Condition types:
459
459
  ### Environment Variables
460
460
 
461
461
  - `WEBAUTO_BROWSER_URL` - Browser service URL (default: `http://127.0.0.1:7704`)
462
- - `WEBAUTO_REPO_ROOT` - WebAuto repository root (optional)
462
+ - `WEBAUTO_INSTALL_DIR` - `@web-auto/webauto` 安装目录(可选,首次安装兜底)
463
+ - `WEBAUTO_REPO_ROOT` - WebAuto repository root (optional, dev mode)
464
+ - `WEBAUTO_DATA_ROOT` / `WEBAUTO_HOME` - 用户数据目录(Windows 默认 `D:/webauto`,无 D 盘回退 `~/.webauto`)
465
+ - `WEBAUTO_PROFILE_ROOT` - Profile 目录覆盖(默认 `<data-root>/profiles`)
466
+ - `WEBAUTO_ROOT` - 兼容旧变量(当值不是 `webauto/.webauto` 目录时会自动补 `.webauto`)
463
467
  - `WEBAUTO_CONTAINER_ROOT` - User container root override (default: `~/.webauto/container-lib`)
464
468
  - `CAMO_PROGRESS_EVENTS_FILE` - Optional progress event JSONL path override
465
469
  - `CAMO_PROGRESS_WS_HOST` / `CAMO_PROGRESS_WS_PORT` - Progress websocket daemon bind address (default: `127.0.0.1:7788`)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@web-auto/camo",
3
- "version": "0.1.12",
3
+ "version": "0.1.14",
4
4
  "description": "Camoufox Browser CLI - Cross-platform browser automation",
5
5
  "type": "module",
6
6
  "bin": {
@@ -31,12 +31,15 @@ function splitCsv(value) {
31
31
  .filter(Boolean);
32
32
  }
33
33
 
34
- function pickCloseDependency(options) {
35
- if (options.doReply || options.doLikes) return 'comment_match_gate';
36
- if (options.matchGateEnabled) return 'comment_match_gate';
37
- if (options.commentsHarvestEnabled) return 'comments_harvest';
38
- if (options.detailHarvestEnabled) return 'detail_harvest';
39
- return 'open_first_detail';
34
+ function pickCloseDependencies(options) {
35
+ const deps = [];
36
+ if (options.doLikes) deps.push('comment_like');
37
+ if (options.doReply) deps.push('comment_reply');
38
+ if (deps.length > 0) return deps;
39
+ if (options.matchGateEnabled) return ['comment_match_gate'];
40
+ if (options.commentsHarvestEnabled) return ['comments_harvest'];
41
+ if (options.detailHarvestEnabled) return ['detail_harvest'];
42
+ return ['open_first_detail'];
40
43
  }
41
44
 
42
45
  function buildOpenFirstDetailScript(maxNotes, keyword) {
@@ -486,7 +489,7 @@ export function buildXhsUnifiedAutoscript(rawOptions = {}) {
486
489
  const detailHarvestEnabled = doHomepage || doImages || doComments || doLikes || doReply || doOcr;
487
490
  const commentsHarvestEnabled = doComments || doLikes || doReply;
488
491
  const matchGateEnabled = doLikes || doReply;
489
- const closeDependsOn = pickCloseDependency({
492
+ const closeDependsOn = pickCloseDependencies({
490
493
  doReply,
491
494
  doLikes,
492
495
  matchGateEnabled,
@@ -793,7 +796,7 @@ export function buildXhsUnifiedAutoscript(rawOptions = {}) {
793
796
  action: 'xhs_close_detail',
794
797
  params: {},
795
798
  trigger: 'detail_modal.exist',
796
- dependsOn: [closeDependsOn],
799
+ dependsOn: closeDependsOn,
797
800
  once: false,
798
801
  oncePerAppear: true,
799
802
  pacing: { operationMinIntervalMs: 2500, eventCooldownMs: 1300, jitterMs: 180 },
@@ -1,12 +1,12 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
- import os from 'node:os';
4
3
  import { findRepoRootCandidate } from '../utils/browser-service.mjs';
4
+ import { CONFIG_DIR } from '../utils/config.mjs';
5
5
 
6
6
  const CONTAINER_ROOT_ENV = process.env.WEBAUTO_CONTAINER_ROOT || process.env.ROUTECODEX_CONTAINER_ROOT;
7
7
 
8
- export const USER_CONTAINER_ROOT = CONTAINER_ROOT_ENV || path.join(os.homedir(), '.webauto', 'container-lib');
9
- export const SUBSCRIPTION_ROOT = path.join(os.homedir(), '.webauto', 'container-subscriptions');
8
+ export const USER_CONTAINER_ROOT = CONTAINER_ROOT_ENV || path.join(CONFIG_DIR, 'container-lib');
9
+ export const SUBSCRIPTION_ROOT = path.join(CONFIG_DIR, 'container-subscriptions');
10
10
  export const SUBSCRIPTION_SETS_DIR = path.join(SUBSCRIPTION_ROOT, 'sets');
11
11
  export const SUBSCRIPTION_INDEX_FILE = path.join(SUBSCRIPTION_ROOT, 'index.json');
12
12
  export const SUBSCRIPTION_TARGETS_FILE = path.join(SUBSCRIPTION_ROOT, 'targets.json');
@@ -5,10 +5,7 @@
5
5
  import { spawn, execSync } from 'node:child_process';
6
6
  import fs from 'node:fs';
7
7
  import path from 'node:path';
8
- import os from 'node:os';
9
-
10
- const CONFIG_DIR = path.join(os.homedir(), '.webauto');
11
- const PROFILES_DIR = path.join(CONFIG_DIR, 'profiles');
8
+ import { PROFILES_DIR } from '../utils/config.mjs';
12
9
 
13
10
  // Active browser instances registry (in-memory)
14
11
  const activeBrowsers = new Map();
@@ -267,4 +264,3 @@ export async function getCurrentPage(profileId) {
267
264
  export function getActiveBrowser(profileId) {
268
265
  return activeBrowsers.get(profileId) || null;
269
266
  }
270
-
@@ -5,9 +5,9 @@
5
5
  */
6
6
  import fs from 'node:fs';
7
7
  import path from 'node:path';
8
- import os from 'node:os';
8
+ import { CONFIG_DIR } from '../utils/config.mjs';
9
9
 
10
- const LOCK_DIR = path.join(os.homedir(), '.webauto', 'locks');
10
+ const LOCK_DIR = path.join(CONFIG_DIR, 'locks');
11
11
 
12
12
  function ensureLockDir() {
13
13
  if (!fs.existsSync(LOCK_DIR)) {
@@ -5,10 +5,10 @@
5
5
  */
6
6
  import fs from 'node:fs';
7
7
  import path from 'node:path';
8
- import os from 'node:os';
9
8
  import crypto from 'node:crypto';
9
+ import { CONFIG_DIR } from '../utils/config.mjs';
10
10
 
11
- const SESSION_DIR = path.join(os.homedir(), '.webauto', 'sessions');
11
+ const SESSION_DIR = path.join(CONFIG_DIR, 'sessions');
12
12
 
13
13
  function ensureSessionDir() {
14
14
  if (!fs.existsSync(SESSION_DIR)) {
@@ -1,14 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  import fs from 'node:fs';
3
- import os from 'node:os';
4
3
  import path from 'node:path';
5
4
  import { spawn } from 'node:child_process';
6
5
  import { fileURLToPath } from 'node:url';
7
6
  import { callAPI } from '../utils/browser-service.mjs';
7
+ import { CONFIG_DIR } from '../utils/config.mjs';
8
8
  import { releaseLock } from './lock.mjs';
9
9
  import { getSessionInfo, markSessionClosed } from './session-registry.mjs';
10
10
 
11
- const WATCHDOG_DIR = path.join(os.homedir(), '.webauto', 'run', 'camo-watchdogs');
11
+ const WATCHDOG_DIR = path.join(CONFIG_DIR, 'run', 'camo-watchdogs');
12
12
  const DEFAULT_HEADLESS_IDLE_TIMEOUT_MS = 30 * 60 * 1000;
13
13
 
14
14
  function ensureWatchdogDir() {
@@ -1,11 +1,15 @@
1
1
  #!/usr/bin/env node
2
- import { execSync } from 'node:child_process';
2
+ import { execSync, spawn } from 'node:child_process';
3
3
  import fs from 'node:fs';
4
4
  import path from 'node:path';
5
5
  import os from 'node:os';
6
+ import { createRequire } from 'node:module';
7
+ import { fileURLToPath } from 'node:url';
6
8
  import { BROWSER_SERVICE_URL, loadConfig, setRepoRoot } from './config.mjs';
7
9
  import { touchSessionActivity } from '../lifecycle/session-registry.mjs';
8
10
 
11
+ const require = createRequire(import.meta.url);
12
+
9
13
  function shouldTrackSessionActivity(action, payload) {
10
14
  const profileId = String(payload?.profileId || '').trim();
11
15
  if (!profileId) return false;
@@ -304,12 +308,18 @@ export function ensureCamoufox() {
304
308
  }
305
309
 
306
310
  const START_SCRIPT_REL = path.join('runtime', 'infra', 'utils', 'scripts', 'service', 'start-browser-service.mjs');
311
+ const CONTROLLER_SERVER_REL = path.join('services', 'controller', 'src', 'server.mjs');
307
312
 
308
313
  function hasStartScript(root) {
309
314
  if (!root) return false;
310
315
  return fs.existsSync(path.join(root, START_SCRIPT_REL));
311
316
  }
312
317
 
318
+ function hasControllerServer(root) {
319
+ if (!root) return false;
320
+ return fs.existsSync(path.join(root, CONTROLLER_SERVER_REL));
321
+ }
322
+
313
323
  function walkUpForRepoRoot(startDir) {
314
324
  if (!startDir) return null;
315
325
  let cursor = path.resolve(startDir);
@@ -356,6 +366,27 @@ function scanCommonRepoRoots() {
356
366
  return null;
357
367
  }
358
368
 
369
+ function scanCommonInstallRoots() {
370
+ const home = os.homedir();
371
+ const appData = String(process.env.APPDATA || path.join(home, 'AppData', 'Roaming'));
372
+ const npmPrefix = String(process.env.npm_config_prefix || '').trim();
373
+ const nodeModuleRoots = [
374
+ path.join(appData, 'npm', 'node_modules'),
375
+ path.join(home, 'AppData', 'Roaming', 'npm', 'node_modules'),
376
+ npmPrefix ? path.join(npmPrefix, 'node_modules') : '',
377
+ npmPrefix ? path.join(npmPrefix, 'lib', 'node_modules') : '',
378
+ '/usr/local/lib/node_modules',
379
+ '/usr/lib/node_modules',
380
+ path.join(home, '.npm-global', 'lib', 'node_modules'),
381
+ ].filter(Boolean);
382
+
383
+ for (const root of nodeModuleRoots) {
384
+ const candidate = path.join(root, '@web-auto', 'webauto');
385
+ if (hasControllerServer(candidate)) return candidate;
386
+ }
387
+ return null;
388
+ }
389
+
359
390
  export function findRepoRootCandidate() {
360
391
  const cfg = loadConfig();
361
392
  const candidates = [
@@ -404,21 +435,69 @@ export function findRepoRootCandidate() {
404
435
  return null;
405
436
  }
406
437
 
438
+ export function findInstallRootCandidate() {
439
+ const cfg = loadConfig();
440
+ const currentDir = path.dirname(fileURLToPath(import.meta.url));
441
+ const siblingInScopedNodeModules = path.resolve(currentDir, '..', '..', '..', 'webauto');
442
+ const candidates = [
443
+ process.env.WEBAUTO_INSTALL_DIR,
444
+ process.env.WEBAUTO_PACKAGE_ROOT,
445
+ process.env.WEBAUTO_REPO_ROOT,
446
+ cfg.repoRoot,
447
+ siblingInScopedNodeModules,
448
+ process.cwd(),
449
+ ].filter(Boolean);
450
+
451
+ try {
452
+ const pkgPath = require.resolve('@web-auto/webauto/package.json');
453
+ candidates.push(path.dirname(pkgPath));
454
+ } catch {
455
+ // ignore resolution failures in npx-only environments
456
+ }
457
+
458
+ const seen = new Set();
459
+ for (const raw of candidates) {
460
+ const resolved = path.resolve(String(raw));
461
+ if (seen.has(resolved)) continue;
462
+ seen.add(resolved);
463
+ if (hasControllerServer(resolved)) return resolved;
464
+ }
465
+
466
+ return scanCommonInstallRoots();
467
+ }
468
+
407
469
  export async function ensureBrowserService() {
408
470
  if (await checkBrowserService()) return;
409
471
 
410
472
  const repoRoot = findRepoRootCandidate();
411
- if (!repoRoot) {
412
- throw new Error(
413
- `Cannot locate browser-service start script (${START_SCRIPT_REL}). ` +
414
- 'Run from webauto repo once or set WEBAUTO_REPO_ROOT=/path/to/webauto.',
415
- );
473
+ if (repoRoot) {
474
+ const scriptPath = path.join(repoRoot, START_SCRIPT_REL);
475
+ console.log('Starting browser-service daemon...');
476
+ execSync(`node "${scriptPath}"`, { stdio: 'inherit', cwd: repoRoot });
477
+ } else {
478
+ const installRoot = findInstallRootCandidate();
479
+ if (!installRoot) {
480
+ throw new Error(
481
+ `Cannot locate browser-service launcher (${START_SCRIPT_REL} or ${CONTROLLER_SERVER_REL}). ` +
482
+ 'Set WEBAUTO_INSTALL_DIR=<@web-auto/webauto install dir> or WEBAUTO_REPO_ROOT=<repo root>.',
483
+ );
484
+ }
485
+ const scriptPath = path.join(installRoot, CONTROLLER_SERVER_REL);
486
+ const env = {
487
+ ...process.env,
488
+ WEBAUTO_REPO_ROOT: String(process.env.WEBAUTO_REPO_ROOT || '').trim() || installRoot,
489
+ };
490
+ const child = spawn(process.execPath, [scriptPath], {
491
+ cwd: installRoot,
492
+ detached: true,
493
+ stdio: 'ignore',
494
+ windowsHide: true,
495
+ env,
496
+ });
497
+ child.unref();
498
+ console.log(`Starting browser-service daemon (packaged webauto, pid=${child.pid || 'unknown'})...`);
416
499
  }
417
500
 
418
- const scriptPath = path.join(repoRoot, START_SCRIPT_REL);
419
- console.log('Starting browser-service daemon...');
420
- execSync(`node "${scriptPath}"`, { stdio: 'inherit', cwd: repoRoot });
421
-
422
501
  for (let i = 0; i < 20; i += 1) {
423
502
  await new Promise((r) => setTimeout(r, 400));
424
503
  if (await checkBrowserService()) {
@@ -3,8 +3,63 @@ import fs from 'node:fs';
3
3
  import path from 'node:path';
4
4
  import os from 'node:os';
5
5
 
6
- export const CONFIG_DIR = path.join(os.homedir(), '.webauto');
7
- export const PROFILES_DIR = path.join(CONFIG_DIR, 'profiles');
6
+ function hasDrive(letter) {
7
+ if (process.platform !== 'win32') return false;
8
+ try {
9
+ return fs.existsSync(`${String(letter || '').replace(/[^A-Za-z]/g, '').toUpperCase()}:\\`);
10
+ } catch {
11
+ return false;
12
+ }
13
+ }
14
+
15
+ function normalizePathForPlatform(input, platform = process.platform) {
16
+ const raw = String(input || '').trim();
17
+ const isWinPath = platform === 'win32' || /^[A-Za-z]:[\\/]/.test(raw);
18
+ const pathApi = isWinPath ? path.win32 : path;
19
+ return isWinPath ? pathApi.normalize(raw) : path.resolve(raw);
20
+ }
21
+
22
+ function normalizeLegacyWebautoRoot(input, platform = process.platform) {
23
+ const pathApi = platform === 'win32' ? path.win32 : path;
24
+ const resolved = normalizePathForPlatform(input, platform);
25
+ const base = pathApi.basename(resolved).toLowerCase();
26
+ if (base === '.webauto' || base === 'webauto') return resolved;
27
+ return pathApi.join(resolved, '.webauto');
28
+ }
29
+
30
+ export function resolveWebautoRoot(options = {}) {
31
+ const env = options.env || process.env;
32
+ const platform = String(options.platform || process.platform);
33
+ const pathApi = platform === 'win32' ? path.win32 : path;
34
+ const homeDir = String(options.homeDir || os.homedir());
35
+ const explicitDataRoot = String(env.WEBAUTO_DATA_ROOT || env.WEBAUTO_HOME || '').trim();
36
+ if (explicitDataRoot) return normalizePathForPlatform(explicitDataRoot, platform);
37
+
38
+ const legacyRoot = String(env.WEBAUTO_ROOT || env.WEBAUTO_PORTABLE_ROOT || '').trim();
39
+ if (legacyRoot) return normalizeLegacyWebautoRoot(legacyRoot, platform);
40
+
41
+ const dDriveExists = typeof options.hasDDrive === 'boolean'
42
+ ? options.hasDDrive
43
+ : hasDrive('D');
44
+ if (platform === 'win32') {
45
+ return dDriveExists ? 'D:\\webauto' : pathApi.join(homeDir, '.webauto');
46
+ }
47
+ return pathApi.join(homeDir, '.webauto');
48
+ }
49
+
50
+ export function resolveProfilesDir(options = {}) {
51
+ const env = options.env || process.env;
52
+ const platform = String(options.platform || process.platform);
53
+ const explicitProfileRoot = String(env.WEBAUTO_PROFILE_ROOT || '').trim();
54
+ if (explicitProfileRoot) {
55
+ return normalizePathForPlatform(explicitProfileRoot, platform);
56
+ }
57
+ const pathApi = platform === 'win32' ? path.win32 : path;
58
+ return pathApi.join(resolveWebautoRoot(options), 'profiles');
59
+ }
60
+
61
+ export const CONFIG_DIR = resolveWebautoRoot();
62
+ export const PROFILES_DIR = resolveProfilesDir();
8
63
  export const CONFIG_FILE = path.join(CONFIG_DIR, 'camo-cli.json');
9
64
  export const PROFILE_META_FILE = 'camo-profile.json';
10
65
  export const BROWSER_SERVICE_URL = process.env.WEBAUTO_BROWSER_URL || 'http://127.0.0.1:7704';
@@ -173,7 +173,11 @@ PROGRESS EVENTS:
173
173
 
174
174
  ENV:
175
175
  WEBAUTO_BROWSER_URL Default: http://127.0.0.1:7704
176
- WEBAUTO_REPO_ROOT Optional explicit webauto repo root
176
+ WEBAUTO_INSTALL_DIR Optional @web-auto/webauto install dir
177
+ WEBAUTO_REPO_ROOT Optional webauto repo root (dev mode)
178
+ WEBAUTO_DATA_ROOT / WEBAUTO_HOME Optional data root (Windows default D:\\webauto)
179
+ WEBAUTO_PROFILE_ROOT Optional profile dir override
180
+ WEBAUTO_ROOT Legacy data root (auto-appends .webauto if needed)
177
181
  CAMO_PROGRESS_EVENTS_FILE Optional path override for progress jsonl
178
182
  CAMO_PROGRESS_WS_HOST / CAMO_PROGRESS_WS_PORT Progress daemon host/port (defaults: 127.0.0.1:7788)
179
183
  `);