@aluvia/sdk 1.1.0 → 1.3.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.
Files changed (77) hide show
  1. package/README.md +409 -285
  2. package/dist/cjs/api/account.js +10 -74
  3. package/dist/cjs/api/apiUtils.js +80 -0
  4. package/dist/cjs/api/geos.js +2 -63
  5. package/dist/cjs/api/request.js +8 -2
  6. package/dist/cjs/bin/account.js +31 -0
  7. package/dist/cjs/bin/api-helpers.js +58 -0
  8. package/dist/cjs/bin/cli.js +245 -0
  9. package/dist/cjs/bin/close.js +120 -0
  10. package/dist/cjs/bin/geos.js +10 -0
  11. package/dist/cjs/bin/mcp-helpers.js +57 -0
  12. package/dist/cjs/bin/mcp-server.js +220 -0
  13. package/dist/cjs/bin/mcp-tools.js +90 -0
  14. package/dist/cjs/bin/open.js +293 -0
  15. package/dist/cjs/bin/session.js +259 -0
  16. package/dist/cjs/client/AluviaClient.js +365 -189
  17. package/dist/cjs/client/BlockDetection.js +486 -0
  18. package/dist/cjs/client/ConfigManager.js +26 -23
  19. package/dist/cjs/client/PageLoadDetection.js +175 -0
  20. package/dist/cjs/client/ProxyServer.js +4 -2
  21. package/dist/cjs/client/logger.js +4 -0
  22. package/dist/cjs/client/rules.js +38 -49
  23. package/dist/cjs/connect.js +117 -0
  24. package/dist/cjs/errors.js +12 -1
  25. package/dist/cjs/index.js +5 -1
  26. package/dist/cjs/session/lock.js +186 -0
  27. package/dist/esm/api/account.js +2 -66
  28. package/dist/esm/api/apiUtils.js +71 -0
  29. package/dist/esm/api/geos.js +2 -63
  30. package/dist/esm/api/request.js +8 -2
  31. package/dist/esm/bin/account.js +28 -0
  32. package/dist/esm/bin/api-helpers.js +53 -0
  33. package/dist/esm/bin/cli.js +242 -0
  34. package/dist/esm/bin/close.js +117 -0
  35. package/dist/esm/bin/geos.js +7 -0
  36. package/dist/esm/bin/mcp-helpers.js +51 -0
  37. package/dist/esm/bin/mcp-server.js +185 -0
  38. package/dist/esm/bin/mcp-tools.js +78 -0
  39. package/dist/esm/bin/open.js +256 -0
  40. package/dist/esm/bin/session.js +252 -0
  41. package/dist/esm/client/AluviaClient.js +371 -195
  42. package/dist/esm/client/BlockDetection.js +482 -0
  43. package/dist/esm/client/ConfigManager.js +21 -18
  44. package/dist/esm/client/PageLoadDetection.js +171 -0
  45. package/dist/esm/client/ProxyServer.js +5 -3
  46. package/dist/esm/client/logger.js +4 -0
  47. package/dist/esm/client/rules.js +36 -49
  48. package/dist/esm/connect.js +81 -0
  49. package/dist/esm/errors.js +10 -0
  50. package/dist/esm/index.js +5 -3
  51. package/dist/esm/session/lock.js +142 -0
  52. package/dist/types/api/AluviaApi.d.ts +2 -7
  53. package/dist/types/api/account.d.ts +1 -16
  54. package/dist/types/api/apiUtils.d.ts +28 -0
  55. package/dist/types/api/geos.d.ts +1 -1
  56. package/dist/types/bin/account.d.ts +1 -0
  57. package/dist/types/bin/api-helpers.d.ts +20 -0
  58. package/dist/types/bin/cli.d.ts +2 -0
  59. package/dist/types/bin/close.d.ts +1 -0
  60. package/dist/types/bin/geos.d.ts +1 -0
  61. package/dist/types/bin/mcp-helpers.d.ts +28 -0
  62. package/dist/types/bin/mcp-server.d.ts +2 -0
  63. package/dist/types/bin/mcp-tools.d.ts +46 -0
  64. package/dist/types/bin/open.d.ts +21 -0
  65. package/dist/types/bin/session.d.ts +11 -0
  66. package/dist/types/client/AluviaClient.d.ts +51 -4
  67. package/dist/types/client/BlockDetection.d.ts +96 -0
  68. package/dist/types/client/ConfigManager.d.ts +6 -1
  69. package/dist/types/client/PageLoadDetection.d.ts +93 -0
  70. package/dist/types/client/logger.d.ts +2 -0
  71. package/dist/types/client/rules.d.ts +18 -0
  72. package/dist/types/client/types.d.ts +48 -47
  73. package/dist/types/connect.d.ts +18 -0
  74. package/dist/types/errors.d.ts +6 -0
  75. package/dist/types/index.d.ts +7 -5
  76. package/dist/types/session/lock.d.ts +43 -0
  77. package/package.json +11 -10
@@ -2,75 +2,11 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createAccountApi = createAccountApi;
4
4
  const errors_js_1 = require("../errors.js");
5
- function isRecord(value) {
6
- return typeof value === 'object' && value !== null;
7
- }
8
- function asErrorEnvelope(value) {
9
- if (!isRecord(value))
10
- return null;
11
- if (value['success'] !== false)
12
- return null;
13
- const error = value['error'];
14
- if (!isRecord(error))
15
- return null;
16
- const code = error['code'];
17
- const message = error['message'];
18
- if (typeof code !== 'string' || typeof message !== 'string')
19
- return null;
20
- return { success: false, error: { code, message, details: error['details'] } };
21
- }
22
- function unwrapSuccess(value) {
23
- if (!isRecord(value))
24
- return null;
25
- if (value['success'] === true && 'data' in value) {
26
- return value.data;
27
- }
28
- if ('data' in value) {
29
- return value.data;
30
- }
31
- return null;
32
- }
33
- function formatErrorDetails(details) {
34
- if (details == null)
35
- return '';
36
- try {
37
- const json = JSON.stringify(details);
38
- if (!json)
39
- return '';
40
- return json.length > 500 ? `${json.slice(0, 500)}…` : json;
41
- }
42
- catch {
43
- return String(details);
44
- }
45
- }
46
- function throwForNon2xx(result) {
47
- const status = result.status;
48
- if (status === 401 || status === 403) {
49
- throw new errors_js_1.InvalidApiKeyError(`Authentication failed (HTTP ${status}). Check token validity and that you are using an account API token for account endpoints.`);
50
- }
51
- const maybeError = asErrorEnvelope(result.body);
52
- if (maybeError) {
53
- const details = formatErrorDetails(maybeError.error.details);
54
- const detailsSuffix = details ? ` details=${details}` : '';
55
- throw new errors_js_1.ApiError(`API request failed (HTTP ${status}) code=${maybeError.error.code} message=${maybeError.error.message}${detailsSuffix}`, status);
56
- }
57
- throw new errors_js_1.ApiError(`API request failed (HTTP ${status})`, status);
58
- }
59
- async function requestAndUnwrap(ctx, args) {
60
- const result = await ctx.request(args);
61
- if (result.status < 200 || result.status >= 300) {
62
- throwForNon2xx(result);
63
- }
64
- const data = unwrapSuccess(result.body);
65
- if (data == null) {
66
- throw new errors_js_1.ApiError('API response missing expected success envelope data', result.status);
67
- }
68
- return { data, etag: result.etag };
69
- }
5
+ const apiUtils_js_1 = require("./apiUtils.js");
70
6
  function createAccountApi(ctx) {
71
7
  return {
72
8
  get: async () => {
73
- const { data } = await requestAndUnwrap(ctx, {
9
+ const { data } = await (0, apiUtils_js_1.requestAndUnwrap)(ctx, {
74
10
  method: 'GET',
75
11
  path: '/account',
76
12
  });
@@ -78,7 +14,7 @@ function createAccountApi(ctx) {
78
14
  },
79
15
  usage: {
80
16
  get: async (params = {}) => {
81
- const { data } = await requestAndUnwrap(ctx, {
17
+ const { data } = await (0, apiUtils_js_1.requestAndUnwrap)(ctx, {
82
18
  method: 'GET',
83
19
  path: '/account/usage',
84
20
  query: {
@@ -91,7 +27,7 @@ function createAccountApi(ctx) {
91
27
  },
92
28
  payments: {
93
29
  list: async (params = {}) => {
94
- const { data } = await requestAndUnwrap(ctx, {
30
+ const { data } = await (0, apiUtils_js_1.requestAndUnwrap)(ctx, {
95
31
  method: 'GET',
96
32
  path: '/account/payments',
97
33
  query: {
@@ -104,14 +40,14 @@ function createAccountApi(ctx) {
104
40
  },
105
41
  connections: {
106
42
  list: async () => {
107
- const { data } = await requestAndUnwrap(ctx, {
43
+ const { data } = await (0, apiUtils_js_1.requestAndUnwrap)(ctx, {
108
44
  method: 'GET',
109
45
  path: '/account/connections',
110
46
  });
111
47
  return data;
112
48
  },
113
49
  create: async (body) => {
114
- const { data } = await requestAndUnwrap(ctx, {
50
+ const { data } = await (0, apiUtils_js_1.requestAndUnwrap)(ctx, {
115
51
  method: 'POST',
116
52
  path: '/account/connections',
117
53
  body,
@@ -127,16 +63,16 @@ function createAccountApi(ctx) {
127
63
  if (result.status === 304)
128
64
  return null;
129
65
  if (result.status < 200 || result.status >= 300) {
130
- throwForNon2xx(result);
66
+ (0, apiUtils_js_1.throwForNon2xx)(result);
131
67
  }
132
- const data = unwrapSuccess(result.body);
68
+ const data = (0, apiUtils_js_1.unwrapSuccess)(result.body);
133
69
  if (data == null) {
134
70
  throw new errors_js_1.ApiError('API response missing expected success envelope data', result.status);
135
71
  }
136
72
  return data;
137
73
  },
138
74
  patch: async (connectionId, body) => {
139
- const { data } = await requestAndUnwrap(ctx, {
75
+ const { data } = await (0, apiUtils_js_1.requestAndUnwrap)(ctx, {
140
76
  method: 'PATCH',
141
77
  path: `/account/connections/${String(connectionId)}`,
142
78
  body,
@@ -144,7 +80,7 @@ function createAccountApi(ctx) {
144
80
  return data;
145
81
  },
146
82
  delete: async (connectionId) => {
147
- const { data } = await requestAndUnwrap(ctx, {
83
+ const { data } = await (0, apiUtils_js_1.requestAndUnwrap)(ctx, {
148
84
  method: 'DELETE',
149
85
  path: `/account/connections/${String(connectionId)}`,
150
86
  });
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isRecord = isRecord;
4
+ exports.asErrorEnvelope = asErrorEnvelope;
5
+ exports.formatErrorDetails = formatErrorDetails;
6
+ exports.throwForNon2xx = throwForNon2xx;
7
+ exports.throwIfAuthError = throwIfAuthError;
8
+ exports.unwrapSuccess = unwrapSuccess;
9
+ exports.requestAndUnwrap = requestAndUnwrap;
10
+ const errors_js_1 = require("../errors.js");
11
+ function isRecord(value) {
12
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
13
+ }
14
+ function asErrorEnvelope(value) {
15
+ if (!isRecord(value))
16
+ return null;
17
+ if (value['success'] !== false)
18
+ return null;
19
+ const error = value['error'];
20
+ if (!isRecord(error))
21
+ return null;
22
+ const code = error['code'];
23
+ const message = error['message'];
24
+ if (typeof code !== 'string' || typeof message !== 'string')
25
+ return null;
26
+ return { success: false, error: { code, message, details: error['details'] } };
27
+ }
28
+ function formatErrorDetails(details) {
29
+ if (details == null)
30
+ return '';
31
+ try {
32
+ const json = JSON.stringify(details);
33
+ if (!json)
34
+ return '';
35
+ return json.length > 500 ? `${json.slice(0, 500)}…` : json;
36
+ }
37
+ catch {
38
+ return String(details);
39
+ }
40
+ }
41
+ function throwForNon2xx(result) {
42
+ const status = result.status;
43
+ if (status === 401 || status === 403) {
44
+ throw new errors_js_1.InvalidApiKeyError(`Authentication failed (HTTP ${status}). Check token validity and that you are using an account API token for account endpoints.`);
45
+ }
46
+ const maybeError = asErrorEnvelope(result.body);
47
+ if (maybeError) {
48
+ const details = formatErrorDetails(maybeError.error.details);
49
+ const detailsSuffix = details ? ` details=${details}` : '';
50
+ throw new errors_js_1.ApiError(`API request failed (HTTP ${status}) code=${maybeError.error.code} message=${maybeError.error.message}${detailsSuffix}`, status);
51
+ }
52
+ throw new errors_js_1.ApiError(`API request failed (HTTP ${status})`, status);
53
+ }
54
+ function throwIfAuthError(status) {
55
+ if (status === 401 || status === 403) {
56
+ throw new errors_js_1.InvalidApiKeyError(`Authentication failed with status ${status}`);
57
+ }
58
+ }
59
+ function unwrapSuccess(value) {
60
+ if (!isRecord(value))
61
+ return null;
62
+ if (value['success'] === true && 'data' in value) {
63
+ return value.data;
64
+ }
65
+ if ('data' in value) {
66
+ return value.data;
67
+ }
68
+ return null;
69
+ }
70
+ async function requestAndUnwrap(ctx, args) {
71
+ const result = await ctx.request(args);
72
+ if (result.status < 200 || result.status >= 300) {
73
+ throwForNon2xx(result);
74
+ }
75
+ const data = unwrapSuccess(result.body);
76
+ if (data == null) {
77
+ throw new errors_js_1.ApiError('API response missing expected success envelope data', result.status);
78
+ }
79
+ return { data, etag: result.etag };
80
+ }
@@ -1,75 +1,14 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createGeosApi = createGeosApi;
4
- const errors_js_1 = require("../errors.js");
5
- function isRecord(value) {
6
- return typeof value === 'object' && value !== null;
7
- }
8
- function asErrorEnvelope(value) {
9
- if (!isRecord(value))
10
- return null;
11
- if (value['success'] !== false)
12
- return null;
13
- const error = value['error'];
14
- if (!isRecord(error))
15
- return null;
16
- const code = error['code'];
17
- const message = error['message'];
18
- if (typeof code !== 'string' || typeof message !== 'string')
19
- return null;
20
- return { success: false, error: { code, message, details: error['details'] } };
21
- }
22
- function formatErrorDetails(details) {
23
- if (details == null)
24
- return '';
25
- try {
26
- const json = JSON.stringify(details);
27
- if (!json)
28
- return '';
29
- return json.length > 500 ? `${json.slice(0, 500)}…` : json;
30
- }
31
- catch {
32
- return String(details);
33
- }
34
- }
35
- function unwrapSuccessArray(value) {
36
- if (!isRecord(value))
37
- return null;
38
- if (value['success'] === true && Array.isArray(value.data)) {
39
- return value.data;
40
- }
41
- if (Array.isArray(value.data)) {
42
- return value.data;
43
- }
44
- return null;
45
- }
46
- function throwForNon2xx(result) {
47
- const status = result.status;
48
- if (status === 401 || status === 403) {
49
- throw new errors_js_1.InvalidApiKeyError(`Authentication failed (HTTP ${status}). Check token validity and that you are using an account API token for account endpoints.`);
50
- }
51
- const maybeError = asErrorEnvelope(result.body);
52
- if (maybeError) {
53
- const details = formatErrorDetails(maybeError.error.details);
54
- const detailsSuffix = details ? ` details=${details}` : '';
55
- throw new errors_js_1.ApiError(`API request failed (HTTP ${status}) code=${maybeError.error.code} message=${maybeError.error.message}${detailsSuffix}`, status);
56
- }
57
- throw new errors_js_1.ApiError(`API request failed (HTTP ${status})`, status);
58
- }
4
+ const apiUtils_js_1 = require("./apiUtils.js");
59
5
  function createGeosApi(ctx) {
60
6
  return {
61
7
  list: async () => {
62
- const result = await ctx.request({
8
+ const { data } = await (0, apiUtils_js_1.requestAndUnwrap)(ctx, {
63
9
  method: 'GET',
64
10
  path: '/geos',
65
11
  });
66
- if (result.status < 200 || result.status >= 300) {
67
- throwForNon2xx(result);
68
- }
69
- const data = unwrapSuccessArray(result.body);
70
- if (data == null) {
71
- throw new errors_js_1.ApiError('API response missing expected success envelope data', result.status);
72
- }
73
12
  return data;
74
13
  },
75
14
  };
@@ -63,7 +63,8 @@ async function requestCore(options) {
63
63
  if (controller.signal.aborted) {
64
64
  throw new errors_js_1.ApiError(`Request timed out after ${timeoutMs}ms`, 408);
65
65
  }
66
- throw err;
66
+ const message = err instanceof Error ? err.message : String(err);
67
+ throw new errors_js_1.ApiError(`Network request failed: ${message}`);
67
68
  }
68
69
  const etag = response.headers.get('etag');
69
70
  if (response.status === 204 || response.status === 304) {
@@ -76,7 +77,12 @@ async function requestCore(options) {
76
77
  const text = await response.text();
77
78
  if (!text)
78
79
  return { status: response.status, etag, body: null };
79
- return { status: response.status, etag, body: JSON.parse(text) };
80
+ try {
81
+ return { status: response.status, etag, body: JSON.parse(text) };
82
+ }
83
+ catch {
84
+ return { status: response.status, etag, body: null };
85
+ }
80
86
  }
81
87
  finally {
82
88
  clearTimeout(timeoutId);
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.handleAccount = handleAccount;
4
+ const api_helpers_js_1 = require("./api-helpers.js");
5
+ const cli_js_1 = require("./cli.js");
6
+ async function handleAccount(args) {
7
+ const subcommand = args[0];
8
+ if (!subcommand) {
9
+ const api = (0, api_helpers_js_1.requireApi)();
10
+ const account = await api.account.get();
11
+ return (0, cli_js_1.output)({ account });
12
+ }
13
+ if (subcommand === 'usage') {
14
+ let start;
15
+ let end;
16
+ for (let i = 1; i < args.length; i++) {
17
+ if (args[i] === '--start' && args[i + 1]) {
18
+ start = args[i + 1];
19
+ i++;
20
+ }
21
+ else if (args[i] === '--end' && args[i + 1]) {
22
+ end = args[i + 1];
23
+ i++;
24
+ }
25
+ }
26
+ const api = (0, api_helpers_js_1.requireApi)();
27
+ const usage = await api.account.usage.get({ start, end });
28
+ return (0, cli_js_1.output)({ usage });
29
+ }
30
+ return (0, cli_js_1.output)({ error: `Unknown account subcommand: '${subcommand}'.` }, 1);
31
+ }
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.requireApi = requireApi;
4
+ exports.resolveSession = resolveSession;
5
+ exports.requireConnectionId = requireConnectionId;
6
+ const AluviaApi_js_1 = require("../api/AluviaApi.js");
7
+ const lock_js_1 = require("../session/lock.js");
8
+ const cli_js_1 = require("./cli.js");
9
+ /**
10
+ * Create an AluviaApi instance from ALUVIA_API_KEY env var.
11
+ * Calls output() and exits if the key is missing.
12
+ */
13
+ function requireApi() {
14
+ const apiKey = (process.env.ALUVIA_API_KEY ?? '').trim();
15
+ if (!apiKey) {
16
+ return (0, cli_js_1.output)({ error: 'ALUVIA_API_KEY environment variable is required.' }, 1);
17
+ }
18
+ return new AluviaApi_js_1.AluviaApi({ apiKey });
19
+ }
20
+ /**
21
+ * Resolve a session by name or auto-select when only one is running.
22
+ * Calls output() and exits on error (no sessions, ambiguous sessions, stale lock).
23
+ */
24
+ function resolveSession(sessionName) {
25
+ if (sessionName) {
26
+ const lock = (0, lock_js_1.readLock)(sessionName);
27
+ if (!lock) {
28
+ return (0, cli_js_1.output)({ error: `No session found with name '${sessionName}'.` }, 1);
29
+ }
30
+ if (!(0, lock_js_1.isProcessAlive)(lock.pid)) {
31
+ (0, lock_js_1.removeLock)(sessionName);
32
+ return (0, cli_js_1.output)({ error: `Session '${sessionName}' is no longer running (stale lock cleaned up).` }, 1);
33
+ }
34
+ return { session: sessionName, lock };
35
+ }
36
+ const sessions = (0, lock_js_1.listSessions)();
37
+ if (sessions.length === 0) {
38
+ return (0, cli_js_1.output)({ error: 'No running browser sessions found.' }, 1);
39
+ }
40
+ if (sessions.length > 1) {
41
+ return (0, cli_js_1.output)({
42
+ error: 'Multiple sessions running. Specify --browser-session <name>.',
43
+ browserSessions: sessions.map((s) => s.session),
44
+ }, 1);
45
+ }
46
+ const s = sessions[0];
47
+ return { session: s.session, lock: (0, lock_js_1.toLockData)(s) };
48
+ }
49
+ /**
50
+ * Require a connection ID from lock data.
51
+ * Calls output() and exits if connectionId is missing.
52
+ */
53
+ function requireConnectionId(lock, session) {
54
+ if (lock.connectionId == null) {
55
+ return (0, cli_js_1.output)({ error: `Session '${session}' has no connection ID. It may have been started without API access.` }, 1);
56
+ }
57
+ return lock.connectionId;
58
+ }
@@ -0,0 +1,245 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.output = output;
5
+ const open_js_1 = require("./open.js");
6
+ const session_js_1 = require("./session.js");
7
+ const account_js_1 = require("./account.js");
8
+ const geos_js_1 = require("./geos.js");
9
+ const lock_js_1 = require("../session/lock.js");
10
+ const mcp_helpers_js_1 = require("./mcp-helpers.js");
11
+ function output(data, exitCode = 0) {
12
+ if ((0, mcp_helpers_js_1.isCapturing)()) {
13
+ throw new mcp_helpers_js_1.MCPOutputCapture(data, exitCode);
14
+ }
15
+ console.log(JSON.stringify(data));
16
+ process.exit(exitCode);
17
+ }
18
+ function printHelp(toStderr = false) {
19
+ const log = toStderr ? console.error : console.log;
20
+ log("Aluvia CLI\n");
21
+ log("Usage:");
22
+ log(" aluvia session start <url> [options] Start a browser session");
23
+ log(" aluvia session close [options] Stop a browser session");
24
+ log(" aluvia session list List active browser sessions");
25
+ log(" aluvia session get [options] Get session details and proxy URLs");
26
+ log(" aluvia session rotate-ip [options] Rotate IP on a running session");
27
+ log(" aluvia session set-geo <geo> [options] Set target geo on a running session");
28
+ log(" aluvia session set-rules <rules> [options] Set routing rules on a running session\n");
29
+ log(" aluvia account Show account info");
30
+ log(" aluvia account usage [options] Show usage stats");
31
+ log(" aluvia geos List available geos");
32
+ log(" aluvia help [--json] Show this help\n");
33
+ log("Session start options:");
34
+ log(" --connection-id <id> Use a specific connection ID");
35
+ log(" --headful Run browser in headful mode");
36
+ log(" --browser-session <name> Name for this session (auto-generated if omitted)");
37
+ log(" --auto-unblock Auto-detect blocks and reload through Aluvia");
38
+ log(" --disable-block-detection Disable block detection entirely");
39
+ log(" --run <script> Run a script with page, browser, context injected\n");
40
+ log("Session close options:");
41
+ log(" --browser-session <name> Close a specific session");
42
+ log(" --all Close all sessions\n");
43
+ log("Session targeting (get, rotate-ip, set-geo, set-rules):");
44
+ log(" --browser-session <name> Target a specific session (auto-selects if only one)\n");
45
+ log("Session set-rules:");
46
+ log(' <rules> Comma-separated rules to append (e.g. "a.com,b.com")');
47
+ log(" --remove <rules> Remove specific rules instead of appending\n");
48
+ log("Session set-geo:");
49
+ log(' <geo> Geo code to set (e.g. "US")');
50
+ log(" --clear Clear target geo\n");
51
+ log("Account usage options:");
52
+ log(" --start <ISO8601> Start date filter");
53
+ log(" --end <ISO8601> End date filter\n");
54
+ log("Environment:");
55
+ log(" ALUVIA_API_KEY Required. Your Aluvia API key.\n");
56
+ log("Output:");
57
+ log(" All commands output JSON to stdout.");
58
+ }
59
+ function printHelpJson() {
60
+ return output({
61
+ commands: [
62
+ {
63
+ command: "session start <url>",
64
+ description: "Start a browser session",
65
+ options: [
66
+ {
67
+ flag: "--connection-id <id>",
68
+ description: "Use a specific connection ID",
69
+ },
70
+ { flag: "--headful", description: "Run browser in headful mode" },
71
+ {
72
+ flag: "--browser-session <name>",
73
+ description: "Name for this session (auto-generated if omitted)",
74
+ },
75
+ {
76
+ flag: "--auto-unblock",
77
+ description: "Auto-detect blocks and reload through Aluvia",
78
+ },
79
+ {
80
+ flag: "--disable-block-detection",
81
+ description: "Disable block detection entirely",
82
+ },
83
+ {
84
+ flag: "--run <script>",
85
+ description: "Run a script with page, browser, context injected",
86
+ },
87
+ ],
88
+ },
89
+ {
90
+ command: "session close",
91
+ description: "Stop a browser session",
92
+ options: [
93
+ {
94
+ flag: "--browser-session <name>",
95
+ description: "Close a specific session",
96
+ },
97
+ { flag: "--all", description: "Close all sessions" },
98
+ ],
99
+ },
100
+ {
101
+ command: "session list",
102
+ description: "List active browser sessions",
103
+ options: [],
104
+ },
105
+ {
106
+ command: "session get",
107
+ description: "Get session details and proxy URLs",
108
+ options: [
109
+ {
110
+ flag: "--browser-session <name>",
111
+ description: "Target a specific session (auto-selects if only one)",
112
+ },
113
+ ],
114
+ },
115
+ {
116
+ command: "session rotate-ip",
117
+ description: "Rotate IP on a running session",
118
+ options: [
119
+ {
120
+ flag: "--browser-session <name>",
121
+ description: "Target a specific session (auto-selects if only one)",
122
+ },
123
+ ],
124
+ },
125
+ {
126
+ command: "session set-geo <geo>",
127
+ description: "Set target geo on a running session",
128
+ options: [
129
+ {
130
+ flag: "--browser-session <name>",
131
+ description: "Target a specific session (auto-selects if only one)",
132
+ },
133
+ { flag: "--clear", description: "Clear target geo" },
134
+ ],
135
+ },
136
+ {
137
+ command: "session set-rules <rules>",
138
+ description: "Set routing rules on a running session",
139
+ options: [
140
+ {
141
+ flag: "--browser-session <name>",
142
+ description: "Target a specific session (auto-selects if only one)",
143
+ },
144
+ {
145
+ flag: "--remove <rules>",
146
+ description: "Remove specific rules instead of appending",
147
+ },
148
+ ],
149
+ },
150
+ {
151
+ command: "account",
152
+ description: "Show account info",
153
+ options: [],
154
+ },
155
+ {
156
+ command: "account usage",
157
+ description: "Show usage stats",
158
+ options: [
159
+ { flag: "--start <ISO8601>", description: "Start date filter" },
160
+ { flag: "--end <ISO8601>", description: "End date filter" },
161
+ ],
162
+ },
163
+ {
164
+ command: "geos",
165
+ description: "List available geos",
166
+ options: [],
167
+ },
168
+ {
169
+ command: "help",
170
+ description: "Show this help",
171
+ options: [{ flag: "--json", description: "Output help as JSON" }],
172
+ },
173
+ ],
174
+ });
175
+ }
176
+ function printHelpAndExit(args) {
177
+ if (args.includes("--json")) {
178
+ return printHelpJson();
179
+ }
180
+ printHelp();
181
+ process.exit(0);
182
+ }
183
+ function parseDaemonArgs(args) {
184
+ return (0, session_js_1.parseSessionArgs)(args);
185
+ }
186
+ async function main() {
187
+ const args = process.argv.slice(2);
188
+ const command = args[0] ?? "";
189
+ // Internal: --daemon mode (spawned by `session start` in detached child)
190
+ if (command === "--daemon") {
191
+ const parsed = parseDaemonArgs(args.slice(1));
192
+ if (parsed.sessionName && !(0, lock_js_1.validateSessionName)(parsed.sessionName)) {
193
+ output({
194
+ error: "Invalid session name. Use only letters, numbers, hyphens, and underscores.",
195
+ }, 1);
196
+ }
197
+ if (!parsed.url) {
198
+ return output({ error: "URL is required for daemon mode." }, 1);
199
+ }
200
+ await (0, open_js_1.handleOpenDaemon)({
201
+ url: parsed.url,
202
+ connectionId: parsed.connectionId,
203
+ headless: !parsed.headed,
204
+ sessionName: parsed.sessionName,
205
+ autoUnblock: parsed.autoUnblock,
206
+ disableBlockDetection: parsed.disableBlockDetection,
207
+ run: parsed.run,
208
+ });
209
+ return;
210
+ }
211
+ // Check for --help / -h anywhere in args (subcommand help)
212
+ const wantsHelp = args.includes("--help") || args.includes("-h");
213
+ if (command === "session") {
214
+ if (wantsHelp)
215
+ printHelpAndExit(args);
216
+ await (0, session_js_1.handleSession)(args.slice(1));
217
+ }
218
+ else if (command === "account") {
219
+ if (wantsHelp)
220
+ printHelpAndExit(args);
221
+ await (0, account_js_1.handleAccount)(args.slice(1));
222
+ }
223
+ else if (command === "geos") {
224
+ if (wantsHelp)
225
+ printHelpAndExit(args);
226
+ await (0, geos_js_1.handleGeos)();
227
+ }
228
+ else if (command === "help" ||
229
+ command === "--help" ||
230
+ command === "-h" ||
231
+ command === "") {
232
+ printHelpAndExit(args);
233
+ }
234
+ else {
235
+ output({ error: `Unknown command: '${command}'. Run "aluvia help" for usage.` }, 1);
236
+ }
237
+ }
238
+ // Only run CLI when this file is the direct entry point (not when imported by MCP server).
239
+ // Check if process.argv[1] resolves to this CLI file rather than another entry point.
240
+ const isCli = process.argv[1]?.match(/(?:cli)\.[jt]s$/);
241
+ if (isCli) {
242
+ main().catch((err) => {
243
+ output({ error: err.message }, 1);
244
+ });
245
+ }