@aluvia/sdk 1.4.0 → 2.0.0
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/CHANGELOG.md +188 -0
- package/README.md +162 -477
- package/dist/cjs/api/apiUtils.js +4 -1
- package/dist/cjs/client/AluviaClient.js +30 -32
- package/dist/cjs/client/BlockDetection.js +69 -87
- package/dist/cjs/client/rules.js +12 -2
- package/dist/cjs/connect.js +2 -2
- package/dist/cjs/index.js +12 -1
- package/dist/cjs/session/lock.js +40 -4
- package/dist/esm/api/apiUtils.js +4 -1
- package/dist/esm/client/AluviaClient.js +38 -40
- package/dist/esm/client/BlockDetection.js +69 -87
- package/dist/esm/client/rules.js +12 -2
- package/dist/esm/connect.js +2 -2
- package/dist/esm/index.js +6 -4
- package/dist/esm/session/lock.js +40 -4
- package/dist/types/client/AluviaClient.d.ts +2 -2
- package/dist/types/client/BlockDetection.d.ts +4 -4
- package/dist/types/client/types.d.ts +11 -11
- package/dist/types/index.d.ts +9 -7
- package/package.json +15 -23
- package/dist/cjs/bin/account.js +0 -31
- package/dist/cjs/bin/api-helpers.js +0 -58
- package/dist/cjs/bin/cli-adapter.js +0 -16
- package/dist/cjs/bin/cli.js +0 -245
- package/dist/cjs/bin/close.js +0 -120
- package/dist/cjs/bin/geos.js +0 -10
- package/dist/cjs/bin/mcp-helpers.js +0 -57
- package/dist/cjs/bin/mcp-server.js +0 -220
- package/dist/cjs/bin/mcp-tools.js +0 -90
- package/dist/cjs/bin/open.js +0 -293
- package/dist/cjs/bin/session.js +0 -259
- package/dist/cjs/client/PageLoadDetection.js +0 -175
- package/dist/esm/bin/account.js +0 -28
- package/dist/esm/bin/api-helpers.js +0 -53
- package/dist/esm/bin/cli-adapter.js +0 -8
- package/dist/esm/bin/cli.js +0 -242
- package/dist/esm/bin/close.js +0 -117
- package/dist/esm/bin/geos.js +0 -7
- package/dist/esm/bin/mcp-helpers.js +0 -51
- package/dist/esm/bin/mcp-server.js +0 -185
- package/dist/esm/bin/mcp-tools.js +0 -78
- package/dist/esm/bin/open.js +0 -256
- package/dist/esm/bin/session.js +0 -252
- package/dist/esm/client/PageLoadDetection.js +0 -171
- package/dist/types/bin/account.d.ts +0 -1
- package/dist/types/bin/api-helpers.d.ts +0 -20
- package/dist/types/bin/cli-adapter.d.ts +0 -8
- package/dist/types/bin/cli.d.ts +0 -2
- package/dist/types/bin/close.d.ts +0 -1
- package/dist/types/bin/geos.d.ts +0 -1
- package/dist/types/bin/mcp-helpers.d.ts +0 -28
- package/dist/types/bin/mcp-server.d.ts +0 -2
- package/dist/types/bin/mcp-tools.d.ts +0 -46
- package/dist/types/bin/open.d.ts +0 -21
- package/dist/types/bin/session.d.ts +0 -11
- package/dist/types/client/PageLoadDetection.d.ts +0 -93
|
@@ -1,175 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
// PageLoadDetection - Enhanced page load and blocking detection
|
|
3
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
-
exports.PageLoadDetection = void 0;
|
|
5
|
-
const DEFAULT_BLOCKING_KEYWORDS = [
|
|
6
|
-
"captcha",
|
|
7
|
-
"blocked",
|
|
8
|
-
"access denied",
|
|
9
|
-
"forbidden",
|
|
10
|
-
"cloudflare",
|
|
11
|
-
"please verify",
|
|
12
|
-
"recaptcha",
|
|
13
|
-
"hcaptcha",
|
|
14
|
-
"bot detection",
|
|
15
|
-
"automated access",
|
|
16
|
-
"unusual activity",
|
|
17
|
-
"verify you are human",
|
|
18
|
-
"security check",
|
|
19
|
-
"access restricted",
|
|
20
|
-
];
|
|
21
|
-
const DEFAULT_BLOCKING_STATUS_CODES = [403, 429, 503];
|
|
22
|
-
const DEFAULT_MIN_CONTENT_LENGTH = 100;
|
|
23
|
-
/**
|
|
24
|
-
* PageLoadDetection handles enhanced detection of page load failures and blocking
|
|
25
|
-
*/
|
|
26
|
-
class PageLoadDetection {
|
|
27
|
-
constructor(config, logger) {
|
|
28
|
-
this.blockedHostnames = new Set();
|
|
29
|
-
this.logger = logger;
|
|
30
|
-
this.config = {
|
|
31
|
-
enabled: config.enabled ?? true,
|
|
32
|
-
blockingKeywords: config.blockingKeywords ?? DEFAULT_BLOCKING_KEYWORDS,
|
|
33
|
-
blockingStatusCodes: config.blockingStatusCodes ?? DEFAULT_BLOCKING_STATUS_CODES,
|
|
34
|
-
minContentLength: config.minContentLength ?? DEFAULT_MIN_CONTENT_LENGTH,
|
|
35
|
-
autoAddRules: config.autoAddRules ?? false,
|
|
36
|
-
onBlockingDetected: config.onBlockingDetected,
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* Update detection configuration
|
|
41
|
-
*/
|
|
42
|
-
updateConfig(config) {
|
|
43
|
-
if (config.enabled !== undefined) {
|
|
44
|
-
this.config.enabled = config.enabled;
|
|
45
|
-
}
|
|
46
|
-
if (config.blockingKeywords !== undefined) {
|
|
47
|
-
this.config.blockingKeywords = config.blockingKeywords;
|
|
48
|
-
}
|
|
49
|
-
if (config.blockingStatusCodes !== undefined) {
|
|
50
|
-
this.config.blockingStatusCodes = config.blockingStatusCodes;
|
|
51
|
-
}
|
|
52
|
-
if (config.minContentLength !== undefined) {
|
|
53
|
-
this.config.minContentLength = config.minContentLength;
|
|
54
|
-
}
|
|
55
|
-
if (config.autoAddRules !== undefined) {
|
|
56
|
-
this.config.autoAddRules = config.autoAddRules;
|
|
57
|
-
}
|
|
58
|
-
if (config.onBlockingDetected !== undefined) {
|
|
59
|
-
this.config.onBlockingDetected = config.onBlockingDetected;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
/**
|
|
63
|
-
* Check if a hostname is already marked as blocked
|
|
64
|
-
*/
|
|
65
|
-
isHostnameBlocked(hostname) {
|
|
66
|
-
return this.blockedHostnames.has(hostname);
|
|
67
|
-
}
|
|
68
|
-
/**
|
|
69
|
-
* Get all blocked hostnames
|
|
70
|
-
*/
|
|
71
|
-
getBlockedHostnames() {
|
|
72
|
-
return Array.from(this.blockedHostnames);
|
|
73
|
-
}
|
|
74
|
-
/**
|
|
75
|
-
* Clear blocked hostnames cache
|
|
76
|
-
*/
|
|
77
|
-
clearBlockedHostnames() {
|
|
78
|
-
this.blockedHostnames.clear();
|
|
79
|
-
}
|
|
80
|
-
/**
|
|
81
|
-
* Analyze a page load and detect if it was blocked
|
|
82
|
-
*/
|
|
83
|
-
async analyzePage(page, response) {
|
|
84
|
-
if (!this.config.enabled) {
|
|
85
|
-
return {
|
|
86
|
-
url: page.url(),
|
|
87
|
-
hostname: this.extractHostname(page.url()),
|
|
88
|
-
success: true,
|
|
89
|
-
blocked: false,
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
this.logger.debug("Analyzing page load for URL: " + page.url());
|
|
93
|
-
const url = page.url();
|
|
94
|
-
const hostname = this.extractHostname(url);
|
|
95
|
-
try {
|
|
96
|
-
// Check HTTP status code
|
|
97
|
-
const statusCode = response?.status?.() ?? 0;
|
|
98
|
-
if (statusCode > 0 &&
|
|
99
|
-
this.config.blockingStatusCodes.includes(statusCode)) {
|
|
100
|
-
const reason = {
|
|
101
|
-
type: "status_code",
|
|
102
|
-
details: `HTTP status code ${statusCode} indicates blocking`,
|
|
103
|
-
statusCode,
|
|
104
|
-
};
|
|
105
|
-
await this.handleBlocking(hostname, reason, page);
|
|
106
|
-
return { url, hostname, success: false, blocked: true, reason };
|
|
107
|
-
}
|
|
108
|
-
// Get page content
|
|
109
|
-
const content = await page.content().catch(() => "");
|
|
110
|
-
// Check for blocking keywords first (more specific than content length)
|
|
111
|
-
const contentLower = content.toLowerCase();
|
|
112
|
-
const title = (await page.title().catch(() => "")).toLowerCase();
|
|
113
|
-
const combinedText = `${contentLower} ${title}`;
|
|
114
|
-
for (const keyword of this.config.blockingKeywords) {
|
|
115
|
-
if (combinedText.includes(keyword.toLowerCase())) {
|
|
116
|
-
const reason = {
|
|
117
|
-
type: "keyword",
|
|
118
|
-
details: `Blocking keyword detected: "${keyword}"`,
|
|
119
|
-
keyword,
|
|
120
|
-
};
|
|
121
|
-
await this.handleBlocking(hostname, reason, page);
|
|
122
|
-
return { url, hostname, success: false, blocked: true, reason };
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
// Check content length (less critical than keywords)
|
|
126
|
-
if (!content || content.length < this.config.minContentLength) {
|
|
127
|
-
const reason = {
|
|
128
|
-
type: "content_length",
|
|
129
|
-
details: `Page content too short (${content.length} < ${this.config.minContentLength})`,
|
|
130
|
-
};
|
|
131
|
-
this.logger.warn(`Page may have failed to load: ${url} (${reason.details})`);
|
|
132
|
-
return { url, hostname, success: false, blocked: false, reason };
|
|
133
|
-
}
|
|
134
|
-
// Page loaded successfully
|
|
135
|
-
return { url, hostname, success: true, blocked: false };
|
|
136
|
-
}
|
|
137
|
-
catch (error) {
|
|
138
|
-
const reason = {
|
|
139
|
-
type: "error",
|
|
140
|
-
details: `Error analyzing page: ${error.message}`,
|
|
141
|
-
};
|
|
142
|
-
this.logger.warn(`Error checking page load status for ${url}: ${error.message}`);
|
|
143
|
-
return { url, hostname, success: false, blocked: false, reason };
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
/**
|
|
147
|
-
* Handle detected blocking
|
|
148
|
-
*/
|
|
149
|
-
async handleBlocking(hostname, reason, page) {
|
|
150
|
-
this.blockedHostnames.add(hostname);
|
|
151
|
-
this.logger.warn(`Blocking detected for ${hostname}: ${reason.details}`);
|
|
152
|
-
// Trigger callback if provided
|
|
153
|
-
if (this.config.onBlockingDetected) {
|
|
154
|
-
try {
|
|
155
|
-
await this.config.onBlockingDetected(hostname, reason, page);
|
|
156
|
-
}
|
|
157
|
-
catch (error) {
|
|
158
|
-
this.logger.warn(`Error in onBlockingDetected callback: ${error.message}`);
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
/**
|
|
163
|
-
* Extract hostname from URL
|
|
164
|
-
*/
|
|
165
|
-
extractHostname(url) {
|
|
166
|
-
try {
|
|
167
|
-
const parsed = new URL(url);
|
|
168
|
-
return parsed.hostname;
|
|
169
|
-
}
|
|
170
|
-
catch {
|
|
171
|
-
return url;
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
exports.PageLoadDetection = PageLoadDetection;
|
package/dist/esm/bin/account.js
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { requireApi } from './api-helpers.js';
|
|
2
|
-
import { output } from './cli.js';
|
|
3
|
-
export async function handleAccount(args) {
|
|
4
|
-
const subcommand = args[0];
|
|
5
|
-
if (!subcommand) {
|
|
6
|
-
const api = requireApi();
|
|
7
|
-
const account = await api.account.get();
|
|
8
|
-
return output({ account });
|
|
9
|
-
}
|
|
10
|
-
if (subcommand === 'usage') {
|
|
11
|
-
let start;
|
|
12
|
-
let end;
|
|
13
|
-
for (let i = 1; i < args.length; i++) {
|
|
14
|
-
if (args[i] === '--start' && args[i + 1]) {
|
|
15
|
-
start = args[i + 1];
|
|
16
|
-
i++;
|
|
17
|
-
}
|
|
18
|
-
else if (args[i] === '--end' && args[i + 1]) {
|
|
19
|
-
end = args[i + 1];
|
|
20
|
-
i++;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
const api = requireApi();
|
|
24
|
-
const usage = await api.account.usage.get({ start, end });
|
|
25
|
-
return output({ usage });
|
|
26
|
-
}
|
|
27
|
-
return output({ error: `Unknown account subcommand: '${subcommand}'.` }, 1);
|
|
28
|
-
}
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { AluviaApi } from '../api/AluviaApi.js';
|
|
2
|
-
import { readLock, listSessions, isProcessAlive, removeLock, toLockData } from '../session/lock.js';
|
|
3
|
-
import { output } from './cli.js';
|
|
4
|
-
/**
|
|
5
|
-
* Create an AluviaApi instance from ALUVIA_API_KEY env var.
|
|
6
|
-
* Calls output() and exits if the key is missing.
|
|
7
|
-
*/
|
|
8
|
-
export function requireApi() {
|
|
9
|
-
const apiKey = (process.env.ALUVIA_API_KEY ?? '').trim();
|
|
10
|
-
if (!apiKey) {
|
|
11
|
-
return output({ error: 'ALUVIA_API_KEY environment variable is required.' }, 1);
|
|
12
|
-
}
|
|
13
|
-
return new AluviaApi({ apiKey });
|
|
14
|
-
}
|
|
15
|
-
/**
|
|
16
|
-
* Resolve a session by name or auto-select when only one is running.
|
|
17
|
-
* Calls output() and exits on error (no sessions, ambiguous sessions, stale lock).
|
|
18
|
-
*/
|
|
19
|
-
export function resolveSession(sessionName) {
|
|
20
|
-
if (sessionName) {
|
|
21
|
-
const lock = readLock(sessionName);
|
|
22
|
-
if (!lock) {
|
|
23
|
-
return output({ error: `No session found with name '${sessionName}'.` }, 1);
|
|
24
|
-
}
|
|
25
|
-
if (!isProcessAlive(lock.pid)) {
|
|
26
|
-
removeLock(sessionName);
|
|
27
|
-
return output({ error: `Session '${sessionName}' is no longer running (stale lock cleaned up).` }, 1);
|
|
28
|
-
}
|
|
29
|
-
return { session: sessionName, lock };
|
|
30
|
-
}
|
|
31
|
-
const sessions = listSessions();
|
|
32
|
-
if (sessions.length === 0) {
|
|
33
|
-
return output({ error: 'No running browser sessions found.' }, 1);
|
|
34
|
-
}
|
|
35
|
-
if (sessions.length > 1) {
|
|
36
|
-
return output({
|
|
37
|
-
error: 'Multiple sessions running. Specify --browser-session <name>.',
|
|
38
|
-
browserSessions: sessions.map((s) => s.session),
|
|
39
|
-
}, 1);
|
|
40
|
-
}
|
|
41
|
-
const s = sessions[0];
|
|
42
|
-
return { session: s.session, lock: toLockData(s) };
|
|
43
|
-
}
|
|
44
|
-
/**
|
|
45
|
-
* Require a connection ID from lock data.
|
|
46
|
-
* Calls output() and exits if connectionId is missing.
|
|
47
|
-
*/
|
|
48
|
-
export function requireConnectionId(lock, session) {
|
|
49
|
-
if (lock.connectionId == null) {
|
|
50
|
-
return output({ error: `Session '${session}' has no connection ID. It may have been started without API access.` }, 1);
|
|
51
|
-
}
|
|
52
|
-
return lock.connectionId;
|
|
53
|
-
}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Re-exports for @aluvia/mcp. MCP package imports from @aluvia/sdk/cli.
|
|
3
|
-
*/
|
|
4
|
-
export { handleSession } from "./session.js";
|
|
5
|
-
export { handleAccount } from "./account.js";
|
|
6
|
-
export { handleGeos } from "./geos.js";
|
|
7
|
-
export { handleOpen } from "./open.js";
|
|
8
|
-
export { captureOutput } from "./mcp-helpers.js";
|
package/dist/esm/bin/cli.js
DELETED
|
@@ -1,242 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { handleOpenDaemon } from "./open.js";
|
|
3
|
-
import { handleSession, parseSessionArgs } from "./session.js";
|
|
4
|
-
import { handleAccount } from "./account.js";
|
|
5
|
-
import { handleGeos } from "./geos.js";
|
|
6
|
-
import { validateSessionName } from "../session/lock.js";
|
|
7
|
-
import { isCapturing, MCPOutputCapture } from "./mcp-helpers.js";
|
|
8
|
-
export function output(data, exitCode = 0) {
|
|
9
|
-
if (isCapturing()) {
|
|
10
|
-
throw new MCPOutputCapture(data, exitCode);
|
|
11
|
-
}
|
|
12
|
-
console.log(JSON.stringify(data));
|
|
13
|
-
process.exit(exitCode);
|
|
14
|
-
}
|
|
15
|
-
function printHelp(toStderr = false) {
|
|
16
|
-
const log = toStderr ? console.error : console.log;
|
|
17
|
-
log("Aluvia CLI\n");
|
|
18
|
-
log("Usage:");
|
|
19
|
-
log(" aluvia session start <url> [options] Start a browser session");
|
|
20
|
-
log(" aluvia session close [options] Stop a browser session");
|
|
21
|
-
log(" aluvia session list List active browser sessions");
|
|
22
|
-
log(" aluvia session get [options] Get session details and proxy URLs");
|
|
23
|
-
log(" aluvia session rotate-ip [options] Rotate IP on a running session");
|
|
24
|
-
log(" aluvia session set-geo <geo> [options] Set target geo on a running session");
|
|
25
|
-
log(" aluvia session set-rules <rules> [options] Set routing rules on a running session\n");
|
|
26
|
-
log(" aluvia account Show account info");
|
|
27
|
-
log(" aluvia account usage [options] Show usage stats");
|
|
28
|
-
log(" aluvia geos List available geos");
|
|
29
|
-
log(" aluvia help [--json] Show this help\n");
|
|
30
|
-
log("Session start options:");
|
|
31
|
-
log(" --connection-id <id> Use a specific connection ID");
|
|
32
|
-
log(" --headful Run browser in headful mode");
|
|
33
|
-
log(" --browser-session <name> Name for this session (auto-generated if omitted)");
|
|
34
|
-
log(" --auto-unblock Auto-detect blocks and reload through Aluvia");
|
|
35
|
-
log(" --disable-block-detection Disable block detection entirely");
|
|
36
|
-
log(" --run <script> Run a script with page, browser, context injected\n");
|
|
37
|
-
log("Session close options:");
|
|
38
|
-
log(" --browser-session <name> Close a specific session");
|
|
39
|
-
log(" --all Close all sessions\n");
|
|
40
|
-
log("Session targeting (get, rotate-ip, set-geo, set-rules):");
|
|
41
|
-
log(" --browser-session <name> Target a specific session (auto-selects if only one)\n");
|
|
42
|
-
log("Session set-rules:");
|
|
43
|
-
log(' <rules> Comma-separated rules to append (e.g. "a.com,b.com")');
|
|
44
|
-
log(" --remove <rules> Remove specific rules instead of appending\n");
|
|
45
|
-
log("Session set-geo:");
|
|
46
|
-
log(' <geo> Geo code to set (e.g. "US")');
|
|
47
|
-
log(" --clear Clear target geo\n");
|
|
48
|
-
log("Account usage options:");
|
|
49
|
-
log(" --start <ISO8601> Start date filter");
|
|
50
|
-
log(" --end <ISO8601> End date filter\n");
|
|
51
|
-
log("Environment:");
|
|
52
|
-
log(" ALUVIA_API_KEY Required. Your Aluvia API key.\n");
|
|
53
|
-
log("Output:");
|
|
54
|
-
log(" All commands output JSON to stdout.");
|
|
55
|
-
}
|
|
56
|
-
function printHelpJson() {
|
|
57
|
-
return output({
|
|
58
|
-
commands: [
|
|
59
|
-
{
|
|
60
|
-
command: "session start <url>",
|
|
61
|
-
description: "Start a browser session",
|
|
62
|
-
options: [
|
|
63
|
-
{
|
|
64
|
-
flag: "--connection-id <id>",
|
|
65
|
-
description: "Use a specific connection ID",
|
|
66
|
-
},
|
|
67
|
-
{ flag: "--headful", description: "Run browser in headful mode" },
|
|
68
|
-
{
|
|
69
|
-
flag: "--browser-session <name>",
|
|
70
|
-
description: "Name for this session (auto-generated if omitted)",
|
|
71
|
-
},
|
|
72
|
-
{
|
|
73
|
-
flag: "--auto-unblock",
|
|
74
|
-
description: "Auto-detect blocks and reload through Aluvia",
|
|
75
|
-
},
|
|
76
|
-
{
|
|
77
|
-
flag: "--disable-block-detection",
|
|
78
|
-
description: "Disable block detection entirely",
|
|
79
|
-
},
|
|
80
|
-
{
|
|
81
|
-
flag: "--run <script>",
|
|
82
|
-
description: "Run a script with page, browser, context injected",
|
|
83
|
-
},
|
|
84
|
-
],
|
|
85
|
-
},
|
|
86
|
-
{
|
|
87
|
-
command: "session close",
|
|
88
|
-
description: "Stop a browser session",
|
|
89
|
-
options: [
|
|
90
|
-
{
|
|
91
|
-
flag: "--browser-session <name>",
|
|
92
|
-
description: "Close a specific session",
|
|
93
|
-
},
|
|
94
|
-
{ flag: "--all", description: "Close all sessions" },
|
|
95
|
-
],
|
|
96
|
-
},
|
|
97
|
-
{
|
|
98
|
-
command: "session list",
|
|
99
|
-
description: "List active browser sessions",
|
|
100
|
-
options: [],
|
|
101
|
-
},
|
|
102
|
-
{
|
|
103
|
-
command: "session get",
|
|
104
|
-
description: "Get session details and proxy URLs",
|
|
105
|
-
options: [
|
|
106
|
-
{
|
|
107
|
-
flag: "--browser-session <name>",
|
|
108
|
-
description: "Target a specific session (auto-selects if only one)",
|
|
109
|
-
},
|
|
110
|
-
],
|
|
111
|
-
},
|
|
112
|
-
{
|
|
113
|
-
command: "session rotate-ip",
|
|
114
|
-
description: "Rotate IP on a running session",
|
|
115
|
-
options: [
|
|
116
|
-
{
|
|
117
|
-
flag: "--browser-session <name>",
|
|
118
|
-
description: "Target a specific session (auto-selects if only one)",
|
|
119
|
-
},
|
|
120
|
-
],
|
|
121
|
-
},
|
|
122
|
-
{
|
|
123
|
-
command: "session set-geo <geo>",
|
|
124
|
-
description: "Set target geo on a running session",
|
|
125
|
-
options: [
|
|
126
|
-
{
|
|
127
|
-
flag: "--browser-session <name>",
|
|
128
|
-
description: "Target a specific session (auto-selects if only one)",
|
|
129
|
-
},
|
|
130
|
-
{ flag: "--clear", description: "Clear target geo" },
|
|
131
|
-
],
|
|
132
|
-
},
|
|
133
|
-
{
|
|
134
|
-
command: "session set-rules <rules>",
|
|
135
|
-
description: "Set routing rules on a running session",
|
|
136
|
-
options: [
|
|
137
|
-
{
|
|
138
|
-
flag: "--browser-session <name>",
|
|
139
|
-
description: "Target a specific session (auto-selects if only one)",
|
|
140
|
-
},
|
|
141
|
-
{
|
|
142
|
-
flag: "--remove <rules>",
|
|
143
|
-
description: "Remove specific rules instead of appending",
|
|
144
|
-
},
|
|
145
|
-
],
|
|
146
|
-
},
|
|
147
|
-
{
|
|
148
|
-
command: "account",
|
|
149
|
-
description: "Show account info",
|
|
150
|
-
options: [],
|
|
151
|
-
},
|
|
152
|
-
{
|
|
153
|
-
command: "account usage",
|
|
154
|
-
description: "Show usage stats",
|
|
155
|
-
options: [
|
|
156
|
-
{ flag: "--start <ISO8601>", description: "Start date filter" },
|
|
157
|
-
{ flag: "--end <ISO8601>", description: "End date filter" },
|
|
158
|
-
],
|
|
159
|
-
},
|
|
160
|
-
{
|
|
161
|
-
command: "geos",
|
|
162
|
-
description: "List available geos",
|
|
163
|
-
options: [],
|
|
164
|
-
},
|
|
165
|
-
{
|
|
166
|
-
command: "help",
|
|
167
|
-
description: "Show this help",
|
|
168
|
-
options: [{ flag: "--json", description: "Output help as JSON" }],
|
|
169
|
-
},
|
|
170
|
-
],
|
|
171
|
-
});
|
|
172
|
-
}
|
|
173
|
-
function printHelpAndExit(args) {
|
|
174
|
-
if (args.includes("--json")) {
|
|
175
|
-
return printHelpJson();
|
|
176
|
-
}
|
|
177
|
-
printHelp();
|
|
178
|
-
process.exit(0);
|
|
179
|
-
}
|
|
180
|
-
function parseDaemonArgs(args) {
|
|
181
|
-
return parseSessionArgs(args);
|
|
182
|
-
}
|
|
183
|
-
async function main() {
|
|
184
|
-
const args = process.argv.slice(2);
|
|
185
|
-
const command = args[0] ?? "";
|
|
186
|
-
// Internal: --daemon mode (spawned by `session start` in detached child)
|
|
187
|
-
if (command === "--daemon") {
|
|
188
|
-
const parsed = parseDaemonArgs(args.slice(1));
|
|
189
|
-
if (parsed.sessionName && !validateSessionName(parsed.sessionName)) {
|
|
190
|
-
output({
|
|
191
|
-
error: "Invalid session name. Use only letters, numbers, hyphens, and underscores.",
|
|
192
|
-
}, 1);
|
|
193
|
-
}
|
|
194
|
-
if (!parsed.url) {
|
|
195
|
-
return output({ error: "URL is required for daemon mode." }, 1);
|
|
196
|
-
}
|
|
197
|
-
await handleOpenDaemon({
|
|
198
|
-
url: parsed.url,
|
|
199
|
-
connectionId: parsed.connectionId,
|
|
200
|
-
headless: !parsed.headed,
|
|
201
|
-
sessionName: parsed.sessionName,
|
|
202
|
-
autoUnblock: parsed.autoUnblock,
|
|
203
|
-
disableBlockDetection: parsed.disableBlockDetection,
|
|
204
|
-
run: parsed.run,
|
|
205
|
-
});
|
|
206
|
-
return;
|
|
207
|
-
}
|
|
208
|
-
// Check for --help / -h anywhere in args (subcommand help)
|
|
209
|
-
const wantsHelp = args.includes("--help") || args.includes("-h");
|
|
210
|
-
if (command === "session") {
|
|
211
|
-
if (wantsHelp)
|
|
212
|
-
printHelpAndExit(args);
|
|
213
|
-
await handleSession(args.slice(1));
|
|
214
|
-
}
|
|
215
|
-
else if (command === "account") {
|
|
216
|
-
if (wantsHelp)
|
|
217
|
-
printHelpAndExit(args);
|
|
218
|
-
await handleAccount(args.slice(1));
|
|
219
|
-
}
|
|
220
|
-
else if (command === "geos") {
|
|
221
|
-
if (wantsHelp)
|
|
222
|
-
printHelpAndExit(args);
|
|
223
|
-
await handleGeos();
|
|
224
|
-
}
|
|
225
|
-
else if (command === "help" ||
|
|
226
|
-
command === "--help" ||
|
|
227
|
-
command === "-h" ||
|
|
228
|
-
command === "") {
|
|
229
|
-
printHelpAndExit(args);
|
|
230
|
-
}
|
|
231
|
-
else {
|
|
232
|
-
output({ error: `Unknown command: '${command}'. Run "aluvia help" for usage.` }, 1);
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
// Only run CLI when this file is the direct entry point (not when imported by MCP server).
|
|
236
|
-
// Check if process.argv[1] resolves to this CLI file rather than another entry point.
|
|
237
|
-
const isCli = process.argv[1]?.match(/(?:cli)\.[jt]s$/);
|
|
238
|
-
if (isCli) {
|
|
239
|
-
main().catch((err) => {
|
|
240
|
-
output({ error: err.message }, 1);
|
|
241
|
-
});
|
|
242
|
-
}
|
package/dist/esm/bin/close.js
DELETED
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
import { removeLock, isProcessAlive, listSessions, toLockData } from '../session/lock.js';
|
|
2
|
-
import { output } from './cli.js';
|
|
3
|
-
export async function handleClose(sessionName, closeAll) {
|
|
4
|
-
if (closeAll) {
|
|
5
|
-
const sessions = listSessions();
|
|
6
|
-
if (sessions.length === 0) {
|
|
7
|
-
return output({ error: 'No running browser sessions found.', closed: [], count: 0 }, 1);
|
|
8
|
-
}
|
|
9
|
-
// Send SIGTERM to all sessions
|
|
10
|
-
for (const s of sessions) {
|
|
11
|
-
try {
|
|
12
|
-
process.kill(s.pid, 'SIGTERM');
|
|
13
|
-
}
|
|
14
|
-
catch {
|
|
15
|
-
// ignore
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
// Wait up to 10 seconds for all processes to exit
|
|
19
|
-
const maxWait = 40;
|
|
20
|
-
const alive = new Set(sessions.map((s) => s.pid));
|
|
21
|
-
for (let i = 0; i < maxWait && alive.size > 0; i++) {
|
|
22
|
-
await new Promise((r) => setTimeout(r, 250));
|
|
23
|
-
for (const pid of alive) {
|
|
24
|
-
if (!isProcessAlive(pid)) {
|
|
25
|
-
alive.delete(pid);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
// Force-kill any survivors
|
|
30
|
-
for (const pid of alive) {
|
|
31
|
-
try {
|
|
32
|
-
process.kill(pid, 'SIGKILL');
|
|
33
|
-
}
|
|
34
|
-
catch {
|
|
35
|
-
// ignore
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
// Now remove all locks
|
|
39
|
-
const closed = [];
|
|
40
|
-
for (const s of sessions) {
|
|
41
|
-
removeLock(s.session);
|
|
42
|
-
closed.push(s.session);
|
|
43
|
-
}
|
|
44
|
-
return output({ message: 'All browser sessions closed.', closed, count: closed.length });
|
|
45
|
-
}
|
|
46
|
-
// If no session name specified, figure out what to close
|
|
47
|
-
if (!sessionName) {
|
|
48
|
-
const sessions = listSessions();
|
|
49
|
-
if (sessions.length === 0) {
|
|
50
|
-
return output({ error: 'No running browser sessions found.' }, 1);
|
|
51
|
-
}
|
|
52
|
-
if (sessions.length > 1) {
|
|
53
|
-
return output({
|
|
54
|
-
error: 'Multiple sessions running. Specify --browser-session <name> or --all.',
|
|
55
|
-
browserSessions: sessions.map((s) => s.session),
|
|
56
|
-
}, 1);
|
|
57
|
-
}
|
|
58
|
-
// Single session — use its data directly instead of re-reading the lock file
|
|
59
|
-
const session = sessions[0];
|
|
60
|
-
sessionName = session.session;
|
|
61
|
-
return closeSession(sessionName, toLockData(session));
|
|
62
|
-
}
|
|
63
|
-
// Session name provided — need to verify it's alive
|
|
64
|
-
const sessions = listSessions();
|
|
65
|
-
const match = sessions.find((s) => s.session === sessionName);
|
|
66
|
-
if (!match) {
|
|
67
|
-
return output({ browserSession: sessionName, error: 'No running browser session found.' }, 1);
|
|
68
|
-
}
|
|
69
|
-
return closeSession(sessionName, toLockData(match));
|
|
70
|
-
}
|
|
71
|
-
async function closeSession(sessionName, lock) {
|
|
72
|
-
if (!isProcessAlive(lock.pid)) {
|
|
73
|
-
removeLock(sessionName);
|
|
74
|
-
return output({
|
|
75
|
-
browserSession: sessionName,
|
|
76
|
-
message: 'Browser process was not running. Lock file cleaned up.',
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
try {
|
|
80
|
-
process.kill(lock.pid, 'SIGTERM');
|
|
81
|
-
}
|
|
82
|
-
catch (err) {
|
|
83
|
-
return output({ browserSession: sessionName, error: `Failed to stop process: ${err.message}` }, 1);
|
|
84
|
-
}
|
|
85
|
-
// Wait for the process to exit (up to 10 seconds)
|
|
86
|
-
const maxWait = 40;
|
|
87
|
-
for (let i = 0; i < maxWait; i++) {
|
|
88
|
-
await new Promise((r) => setTimeout(r, 250));
|
|
89
|
-
if (!isProcessAlive(lock.pid)) {
|
|
90
|
-
removeLock(sessionName);
|
|
91
|
-
return output({
|
|
92
|
-
browserSession: sessionName,
|
|
93
|
-
pid: lock.pid,
|
|
94
|
-
message: 'Browser session closed.',
|
|
95
|
-
startUrl: lock.url ?? null,
|
|
96
|
-
cdpUrl: lock.cdpUrl ?? null,
|
|
97
|
-
connectionId: lock.connectionId ?? null,
|
|
98
|
-
});
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
// Force kill if still alive
|
|
102
|
-
try {
|
|
103
|
-
process.kill(lock.pid, 'SIGKILL');
|
|
104
|
-
}
|
|
105
|
-
catch {
|
|
106
|
-
// ignore
|
|
107
|
-
}
|
|
108
|
-
removeLock(sessionName);
|
|
109
|
-
return output({
|
|
110
|
-
browserSession: sessionName,
|
|
111
|
-
pid: lock.pid,
|
|
112
|
-
message: 'Browser session force-killed.',
|
|
113
|
-
startUrl: lock.url ?? null,
|
|
114
|
-
cdpUrl: lock.cdpUrl ?? null,
|
|
115
|
-
connectionId: lock.connectionId ?? null,
|
|
116
|
-
});
|
|
117
|
-
}
|
package/dist/esm/bin/geos.js
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MCP output capture helpers.
|
|
3
|
-
*
|
|
4
|
-
* The CLI handlers call `output()` which normally does `console.log` + `process.exit()`.
|
|
5
|
-
* In MCP mode, we switch `output()` to throw an MCPOutputCapture instead,
|
|
6
|
-
* allowing us to catch and return the data without exiting the process.
|
|
7
|
-
*
|
|
8
|
-
* Uses AsyncLocalStorage so concurrent MCP tool calls don't interfere.
|
|
9
|
-
*/
|
|
10
|
-
import { AsyncLocalStorage } from "node:async_hooks";
|
|
11
|
-
/**
|
|
12
|
-
* Thrown by output() when in capture mode.
|
|
13
|
-
* Contains the JSON data and exit code that would have been written to stdout.
|
|
14
|
-
*/
|
|
15
|
-
export class MCPOutputCapture {
|
|
16
|
-
constructor(data, exitCode) {
|
|
17
|
-
this.data = data;
|
|
18
|
-
this.exitCode = exitCode;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
const captureContext = new AsyncLocalStorage();
|
|
22
|
-
export function isCapturing() {
|
|
23
|
-
return captureContext.getStore() ?? false;
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* Run a CLI handler function in capture mode.
|
|
27
|
-
* Returns the data that output() would have written to stdout.
|
|
28
|
-
* Safe for concurrent use — each call gets its own async context.
|
|
29
|
-
*/
|
|
30
|
-
export async function captureOutput(fn) {
|
|
31
|
-
return captureContext.run(true, async () => {
|
|
32
|
-
try {
|
|
33
|
-
await fn();
|
|
34
|
-
// Handler completed without calling output() — shouldn't happen for CLI handlers
|
|
35
|
-
return { data: { error: "Handler did not produce output" }, isError: true };
|
|
36
|
-
}
|
|
37
|
-
catch (err) {
|
|
38
|
-
if (err instanceof MCPOutputCapture) {
|
|
39
|
-
return {
|
|
40
|
-
data: err.data,
|
|
41
|
-
isError: err.exitCode !== 0,
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
// Unexpected error (not from output())
|
|
45
|
-
return {
|
|
46
|
-
data: { error: err instanceof Error ? err.message : String(err) },
|
|
47
|
-
isError: true,
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
});
|
|
51
|
-
}
|