browser-use 0.6.1 → 0.7.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/README.md +24 -18
- package/dist/actor/element.js +24 -3
- package/dist/actor/mouse.js +21 -3
- package/dist/actor/page.js +33 -11
- package/dist/agent/gif.js +28 -3
- package/dist/agent/message-manager/service.js +2 -22
- package/dist/agent/message-manager/utils.js +15 -2
- package/dist/agent/message-manager/views.d.ts +7 -7
- package/dist/agent/message-manager/views.js +1 -0
- package/dist/agent/prompts.d.ts +3 -0
- package/dist/agent/prompts.js +22 -12
- package/dist/agent/service.d.ts +9 -1
- package/dist/agent/service.js +204 -79
- package/dist/agent/system_prompt.md +12 -11
- package/dist/agent/system_prompt_anthropic_flash.md +6 -5
- package/dist/agent/system_prompt_no_thinking.md +12 -11
- package/dist/agent/views.d.ts +2 -0
- package/dist/agent/views.js +48 -36
- package/dist/browser/extensions.js +20 -10
- package/dist/browser/profile.d.ts +4 -0
- package/dist/browser/profile.js +107 -4
- package/dist/browser/session.d.ts +28 -1
- package/dist/browser/session.js +1436 -528
- package/dist/browser/watchdogs/default-action-watchdog.js +32 -3
- package/dist/browser/watchdogs/downloads-watchdog.d.ts +4 -0
- package/dist/browser/watchdogs/downloads-watchdog.js +105 -9
- package/dist/browser/watchdogs/har-recording-watchdog.d.ts +1 -0
- package/dist/browser/watchdogs/har-recording-watchdog.js +54 -2
- package/dist/browser/watchdogs/permissions-watchdog.d.ts +5 -0
- package/dist/browser/watchdogs/permissions-watchdog.js +106 -3
- package/dist/browser/watchdogs/recording-watchdog.d.ts +2 -0
- package/dist/browser/watchdogs/recording-watchdog.js +54 -2
- package/dist/browser/watchdogs/security-watchdog.d.ts +1 -0
- package/dist/browser/watchdogs/security-watchdog.js +47 -7
- package/dist/browser/watchdogs/storage-state-watchdog.d.ts +6 -0
- package/dist/browser/watchdogs/storage-state-watchdog.js +206 -14
- package/dist/cli.d.ts +13 -2
- package/dist/cli.js +187 -7
- package/dist/code-use/namespace.js +52 -7
- package/dist/code-use/notebook-export.js +18 -2
- package/dist/code-use/service.js +1 -0
- package/dist/config.js +26 -4
- package/dist/controller/action-timeout.d.ts +9 -0
- package/dist/controller/action-timeout.js +95 -0
- package/dist/controller/registry/service.d.ts +1 -0
- package/dist/controller/registry/service.js +28 -1
- package/dist/controller/service.d.ts +2 -1
- package/dist/controller/service.js +494 -329
- package/dist/filesystem/file-system.js +38 -8
- package/dist/integrations/gmail/service.js +30 -6
- package/dist/llm/browser-use/chat.js +2 -2
- package/dist/llm/codex/auth.d.ts +118 -0
- package/dist/llm/codex/auth.js +599 -0
- package/dist/llm/codex/chat.d.ts +70 -0
- package/dist/llm/codex/chat.js +392 -0
- package/dist/llm/codex/index.d.ts +2 -0
- package/dist/llm/codex/index.js +2 -0
- package/dist/llm/google/chat.js +18 -1
- package/dist/logging-config.js +22 -11
- package/dist/mcp/client.d.ts +1 -0
- package/dist/mcp/client.js +12 -10
- package/dist/mcp/redaction.d.ts +3 -0
- package/dist/mcp/redaction.js +132 -0
- package/dist/mcp/server.d.ts +2 -0
- package/dist/mcp/server.js +64 -22
- package/dist/screenshots/service.js +25 -2
- package/dist/skill-cli/direct.d.ts +4 -1
- package/dist/skill-cli/direct.js +260 -64
- package/dist/skill-cli/server.d.ts +1 -0
- package/dist/skill-cli/server.js +115 -25
- package/dist/skill-cli/tunnel.d.ts +1 -0
- package/dist/skill-cli/tunnel.js +16 -4
- package/dist/sync/auth.js +22 -9
- package/dist/telemetry/service.js +21 -2
- package/dist/telemetry/views.js +31 -8
- package/dist/tokens/custom-pricing.js +2 -2
- package/dist/tokens/openrouter-pricing.d.ts +11 -0
- package/dist/tokens/openrouter-pricing.js +102 -0
- package/dist/tokens/service.js +20 -16
- package/dist/utils.d.ts +3 -1
- package/dist/utils.js +3 -1
- package/package.json +68 -27
|
@@ -6,6 +6,33 @@ import PDFDocument from 'pdfkit';
|
|
|
6
6
|
import { createRequire } from 'node:module';
|
|
7
7
|
import { spawnSync } from 'node:child_process';
|
|
8
8
|
const require = createRequire(import.meta.url);
|
|
9
|
+
const chmodPrivatePath = (targetPath, mode) => {
|
|
10
|
+
if (process.platform !== 'win32') {
|
|
11
|
+
fsSync.chmodSync(targetPath, mode);
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
const writePrivateTextFile = (filePath, content) => {
|
|
15
|
+
fsSync.writeFileSync(filePath, content, {
|
|
16
|
+
encoding: 'utf-8',
|
|
17
|
+
mode: 0o600,
|
|
18
|
+
});
|
|
19
|
+
chmodPrivatePath(filePath, 0o600);
|
|
20
|
+
};
|
|
21
|
+
const writePrivateBufferFile = (filePath, content) => {
|
|
22
|
+
fsSync.writeFileSync(filePath, content, { mode: 0o600 });
|
|
23
|
+
chmodPrivatePath(filePath, 0o600);
|
|
24
|
+
};
|
|
25
|
+
const writePrivateTextFileAsync = async (filePath, content) => {
|
|
26
|
+
await fsp.writeFile(filePath, content, {
|
|
27
|
+
encoding: 'utf-8',
|
|
28
|
+
mode: 0o600,
|
|
29
|
+
});
|
|
30
|
+
chmodPrivatePath(filePath, 0o600);
|
|
31
|
+
};
|
|
32
|
+
const writePrivateBufferFileAsync = async (filePath, content) => {
|
|
33
|
+
await fsp.writeFile(filePath, content, { mode: 0o600 });
|
|
34
|
+
chmodPrivatePath(filePath, 0o600);
|
|
35
|
+
};
|
|
9
36
|
export async function extractPdfText(buffer) {
|
|
10
37
|
const pdfParseModule = (await import('pdf-parse'));
|
|
11
38
|
if (typeof pdfParseModule.default === 'function') {
|
|
@@ -256,10 +283,10 @@ class BaseFile {
|
|
|
256
283
|
return this.content;
|
|
257
284
|
}
|
|
258
285
|
async syncToDisk(dir) {
|
|
259
|
-
await
|
|
286
|
+
await writePrivateTextFileAsync(path.join(dir, this.fullName), this.content);
|
|
260
287
|
}
|
|
261
288
|
syncToDiskSync(dir) {
|
|
262
|
-
|
|
289
|
+
writePrivateTextFile(path.join(dir, this.fullName), this.content);
|
|
263
290
|
}
|
|
264
291
|
async write(content, dir) {
|
|
265
292
|
this.writeFileContent(content);
|
|
@@ -314,13 +341,14 @@ class PdfFile extends BaseFile {
|
|
|
314
341
|
const filePath = path.join(dir, this.fullName);
|
|
315
342
|
await new Promise((resolve, reject) => {
|
|
316
343
|
const doc = new PDFDocument({ autoFirstPage: true });
|
|
317
|
-
const stream = fsSync.createWriteStream(filePath);
|
|
344
|
+
const stream = fsSync.createWriteStream(filePath, { mode: 0o600 });
|
|
318
345
|
doc.pipe(stream);
|
|
319
346
|
doc.fontSize(12).text(this.content || '', { width: 500, align: 'left' });
|
|
320
347
|
doc.end();
|
|
321
348
|
stream.on('finish', resolve);
|
|
322
349
|
stream.on('error', reject);
|
|
323
350
|
});
|
|
351
|
+
chmodPrivatePath(filePath, 0o600);
|
|
324
352
|
}
|
|
325
353
|
syncToDiskSync(dir) {
|
|
326
354
|
const filePath = path.join(dir, this.fullName);
|
|
@@ -330,7 +358,7 @@ const PDFDocument = require(${JSON.stringify(require.resolve('pdfkit'))});
|
|
|
330
358
|
const filePath = ${JSON.stringify(filePath)};
|
|
331
359
|
const content = ${JSON.stringify(this.content ?? '')};
|
|
332
360
|
const doc = new PDFDocument({ autoFirstPage: true });
|
|
333
|
-
const stream = createWriteStream(filePath);
|
|
361
|
+
const stream = createWriteStream(filePath, { mode: 0o600 });
|
|
334
362
|
doc.pipe(stream);
|
|
335
363
|
doc.fontSize(12).text(content || '', { width: 500, align: 'left' });
|
|
336
364
|
doc.end();
|
|
@@ -348,6 +376,7 @@ stream.on('error', (err) => {
|
|
|
348
376
|
`Could not write to file '${this.fullName}'.`;
|
|
349
377
|
throw new FileSystemError(`Error: ${errorMsg.trim()}`);
|
|
350
378
|
}
|
|
379
|
+
chmodPrivatePath(filePath, 0o600);
|
|
351
380
|
}
|
|
352
381
|
}
|
|
353
382
|
class DocxFile extends BaseFile {
|
|
@@ -357,12 +386,12 @@ class DocxFile extends BaseFile {
|
|
|
357
386
|
async syncToDisk(dir) {
|
|
358
387
|
const filePath = path.join(dir, this.fullName);
|
|
359
388
|
const docxBuffer = buildDocxBuffer(this.content || '');
|
|
360
|
-
await
|
|
389
|
+
await writePrivateBufferFileAsync(filePath, docxBuffer);
|
|
361
390
|
}
|
|
362
391
|
syncToDiskSync(dir) {
|
|
363
392
|
const filePath = path.join(dir, this.fullName);
|
|
364
393
|
const docxBuffer = buildDocxBuffer(this.content || '');
|
|
365
|
-
|
|
394
|
+
writePrivateBufferFile(filePath, docxBuffer);
|
|
366
395
|
}
|
|
367
396
|
}
|
|
368
397
|
class HtmlFile extends BaseFile {
|
|
@@ -410,7 +439,8 @@ export class FileSystem {
|
|
|
410
439
|
if (fsSync.existsSync(this.dataDir)) {
|
|
411
440
|
fsSync.rmSync(this.dataDir, { recursive: true, force: true });
|
|
412
441
|
}
|
|
413
|
-
fsSync.mkdirSync(this.dataDir, { recursive: true });
|
|
442
|
+
fsSync.mkdirSync(this.dataDir, { recursive: true, mode: 0o700 });
|
|
443
|
+
chmodPrivatePath(this.dataDir, 0o700);
|
|
414
444
|
if (createDefaultFiles) {
|
|
415
445
|
this.createDefaultFiles();
|
|
416
446
|
}
|
|
@@ -419,7 +449,7 @@ export class FileSystem {
|
|
|
419
449
|
for (const filename of this.defaultFiles) {
|
|
420
450
|
const file = this.instantiateFile(filename);
|
|
421
451
|
this.files.set(filename, file);
|
|
422
|
-
|
|
452
|
+
writePrivateTextFile(path.join(this.dataDir, filename), file.read());
|
|
423
453
|
}
|
|
424
454
|
}
|
|
425
455
|
isValidFilename(filename) {
|
|
@@ -9,6 +9,32 @@ import { google } from 'googleapis';
|
|
|
9
9
|
import { createLogger } from '../../logging-config.js';
|
|
10
10
|
import { CONFIG } from '../../config.js';
|
|
11
11
|
const logger = createLogger('browser_use.gmail');
|
|
12
|
+
const chmodPrivatePath = (targetPath, mode) => {
|
|
13
|
+
if (process.platform === 'win32') {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
try {
|
|
17
|
+
fs.chmodSync(targetPath, mode);
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
/* best effort */
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
const ensurePrivateDirectory = (dirPath) => {
|
|
24
|
+
fs.mkdirSync(dirPath, { recursive: true, mode: 0o700 });
|
|
25
|
+
chmodPrivatePath(dirPath, 0o700);
|
|
26
|
+
};
|
|
27
|
+
const readPrivateJsonFile = (filePath) => {
|
|
28
|
+
chmodPrivatePath(filePath, 0o600);
|
|
29
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
30
|
+
};
|
|
31
|
+
const writePrivateJsonFile = (filePath, value) => {
|
|
32
|
+
fs.writeFileSync(filePath, JSON.stringify(value), {
|
|
33
|
+
encoding: 'utf-8',
|
|
34
|
+
mode: 0o600,
|
|
35
|
+
});
|
|
36
|
+
chmodPrivatePath(filePath, 0o600);
|
|
37
|
+
};
|
|
12
38
|
export class GmailService {
|
|
13
39
|
static SCOPES = [
|
|
14
40
|
'https://www.googleapis.com/auth/gmail.readonly',
|
|
@@ -27,9 +53,7 @@ export class GmailService {
|
|
|
27
53
|
this.accessToken = options.access_token || null;
|
|
28
54
|
// Ensure config directory exists (only if not using direct token)
|
|
29
55
|
if (!this.accessToken) {
|
|
30
|
-
|
|
31
|
-
fs.mkdirSync(this.configDir, { recursive: true });
|
|
32
|
-
}
|
|
56
|
+
ensurePrivateDirectory(this.configDir);
|
|
33
57
|
}
|
|
34
58
|
// Set up credential paths
|
|
35
59
|
this.credentialsFile =
|
|
@@ -63,7 +87,7 @@ export class GmailService {
|
|
|
63
87
|
// Original file-based authentication flow
|
|
64
88
|
// Try to load existing tokens
|
|
65
89
|
if (fs.existsSync(this.tokenFile)) {
|
|
66
|
-
const tokenData =
|
|
90
|
+
const tokenData = readPrivateJsonFile(this.tokenFile);
|
|
67
91
|
const auth = new google.auth.OAuth2();
|
|
68
92
|
auth.setCredentials(tokenData);
|
|
69
93
|
this.creds = auth;
|
|
@@ -90,7 +114,7 @@ export class GmailService {
|
|
|
90
114
|
`4. Save as 'gmail_credentials.json' in ${this.configDir}/`);
|
|
91
115
|
return false;
|
|
92
116
|
}
|
|
93
|
-
const credentials =
|
|
117
|
+
const credentials = readPrivateJsonFile(this.credentialsFile);
|
|
94
118
|
const { client_secret, client_id, redirect_uris } = credentials.installed || credentials.web;
|
|
95
119
|
const oAuth2Client = new google.auth.OAuth2(client_id, client_secret, redirect_uris[0]);
|
|
96
120
|
const authUrl = oAuth2Client.generateAuthUrl({
|
|
@@ -108,7 +132,7 @@ export class GmailService {
|
|
|
108
132
|
'4. Implement token exchange logic');
|
|
109
133
|
}
|
|
110
134
|
// Save tokens for next time
|
|
111
|
-
|
|
135
|
+
writePrivateJsonFile(this.tokenFile, this.creds.credentials);
|
|
112
136
|
logger.info(`💾 Tokens saved to ${this.tokenFile}`);
|
|
113
137
|
}
|
|
114
138
|
// Build Gmail service
|
|
@@ -43,13 +43,13 @@ export class ChatBrowserUse {
|
|
|
43
43
|
fast;
|
|
44
44
|
fetchImplementation;
|
|
45
45
|
constructor(options = {}) {
|
|
46
|
-
const { model = 'bu-
|
|
46
|
+
const { model = 'bu-2-0', apiKey = process.env.BROWSER_USE_API_KEY, baseUrl = process.env.BROWSER_USE_LLM_URL ??
|
|
47
47
|
'https://llm.api.browser-use.com', timeout = 120, maxRetries = 5, retryBaseDelay = 1.0, retryMaxDelay = 60.0, fast = false, fetchImplementation = fetch, } = options;
|
|
48
48
|
const isValidModel = VALID_MODELS.has(model) || model.startsWith('browser-use/');
|
|
49
49
|
if (!isValidModel) {
|
|
50
50
|
throw new Error(`Invalid model: '${model}'. Must be one of bu-latest, bu-1-0, bu-2-0 or start with 'browser-use/'`);
|
|
51
51
|
}
|
|
52
|
-
this.model = model === 'bu-latest' ? 'bu-
|
|
52
|
+
this.model = model === 'bu-latest' ? 'bu-2-0' : model;
|
|
53
53
|
if (!apiKey) {
|
|
54
54
|
throw new Error('You need to set the BROWSER_USE_API_KEY environment variable. Get your key at https://cloud.browser-use.com/new-api-key');
|
|
55
55
|
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
export declare const CODEX_PROVIDER = "openai-codex";
|
|
2
|
+
export declare const DEFAULT_CODEX_BASE_URL = "https://chatgpt.com/backend-api/codex";
|
|
3
|
+
export declare const CODEX_OAUTH_CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
|
|
4
|
+
export declare const CODEX_OAUTH_TOKEN_URL = "https://auth.openai.com/oauth/token";
|
|
5
|
+
export declare const CODEX_DEVICE_AUTH_BASE_URL = "https://auth.openai.com";
|
|
6
|
+
export declare const CODEX_ACCESS_TOKEN_REFRESH_SKEW_SECONDS = 120;
|
|
7
|
+
export interface CodexTokens {
|
|
8
|
+
access_token: string;
|
|
9
|
+
refresh_token: string;
|
|
10
|
+
[key: string]: unknown;
|
|
11
|
+
}
|
|
12
|
+
export interface CodexTokenRecord {
|
|
13
|
+
tokens: CodexTokens;
|
|
14
|
+
last_refresh: string | null;
|
|
15
|
+
auth_mode: string | null;
|
|
16
|
+
source: string | null;
|
|
17
|
+
}
|
|
18
|
+
export interface CodexRuntimeCredentials {
|
|
19
|
+
provider: typeof CODEX_PROVIDER;
|
|
20
|
+
base_url: string;
|
|
21
|
+
api_key: string;
|
|
22
|
+
source: string;
|
|
23
|
+
last_refresh: string | null;
|
|
24
|
+
auth_mode: 'chatgpt';
|
|
25
|
+
}
|
|
26
|
+
export interface CodexAuthStatus {
|
|
27
|
+
authenticated: boolean;
|
|
28
|
+
auth_store_path: string;
|
|
29
|
+
provider: typeof CODEX_PROVIDER;
|
|
30
|
+
base_url: string;
|
|
31
|
+
source: string | null;
|
|
32
|
+
last_refresh: string | null;
|
|
33
|
+
access_token_expiring: boolean | null;
|
|
34
|
+
error?: {
|
|
35
|
+
code: string;
|
|
36
|
+
message: string;
|
|
37
|
+
relogin_required: boolean;
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
export declare class CodexAuthError extends Error {
|
|
41
|
+
provider: string;
|
|
42
|
+
code: string;
|
|
43
|
+
relogin_required: boolean;
|
|
44
|
+
constructor(message: string, code?: string, reloginRequired?: boolean);
|
|
45
|
+
}
|
|
46
|
+
interface AuthPathOptions {
|
|
47
|
+
configDir?: string | null;
|
|
48
|
+
authStorePath?: string | null;
|
|
49
|
+
}
|
|
50
|
+
interface FetchOptions {
|
|
51
|
+
fetchImplementation?: typeof fetch;
|
|
52
|
+
timeoutMs?: number;
|
|
53
|
+
}
|
|
54
|
+
export interface RefreshCodexOAuthOptions extends FetchOptions {
|
|
55
|
+
tokenUrl?: string;
|
|
56
|
+
clientId?: string;
|
|
57
|
+
}
|
|
58
|
+
export interface ResolveCodexRuntimeCredentialsOptions extends RefreshCodexOAuthOptions, AuthPathOptions {
|
|
59
|
+
forceRefresh?: boolean;
|
|
60
|
+
refreshIfExpiring?: boolean;
|
|
61
|
+
refreshSkewSeconds?: number;
|
|
62
|
+
lockTimeoutMs?: number;
|
|
63
|
+
baseURL?: string | null;
|
|
64
|
+
}
|
|
65
|
+
export interface DeviceCodeLoginOptions extends FetchOptions {
|
|
66
|
+
issuer?: string;
|
|
67
|
+
clientId?: string;
|
|
68
|
+
stdout?: Pick<NodeJS.WriteStream, 'write'>;
|
|
69
|
+
sleep?: (ms: number) => Promise<void>;
|
|
70
|
+
maxWaitMs?: number;
|
|
71
|
+
now?: () => number;
|
|
72
|
+
}
|
|
73
|
+
export declare const getCodexAuthStorePath: (options?: AuthPathOptions) => string;
|
|
74
|
+
export declare const readCodexTokens: (options?: AuthPathOptions) => Promise<CodexTokenRecord>;
|
|
75
|
+
export declare const saveCodexTokens: (tokens: CodexTokens, options?: AuthPathOptions & {
|
|
76
|
+
lastRefresh?: string | null;
|
|
77
|
+
source?: string | null;
|
|
78
|
+
lockTimeoutMs?: number;
|
|
79
|
+
}) => Promise<void>;
|
|
80
|
+
export declare const clearCodexTokens: (options?: AuthPathOptions & {
|
|
81
|
+
lockTimeoutMs?: number;
|
|
82
|
+
}) => Promise<void>;
|
|
83
|
+
export declare const codexAccessTokenIsExpiring: (accessToken: string, skewSeconds?: number, nowMs?: number) => boolean;
|
|
84
|
+
export declare const getCodexCloudflareHeaders: (accessToken: string) => Record<string, string>;
|
|
85
|
+
export declare const importCodexCliTokens: (options?: {
|
|
86
|
+
codexHome?: string | null;
|
|
87
|
+
authPath?: string | null;
|
|
88
|
+
nowMs?: number;
|
|
89
|
+
}) => Promise<CodexTokens | null>;
|
|
90
|
+
export declare const refreshCodexOAuth: (accessToken: string, refreshToken: string, options?: RefreshCodexOAuthOptions) => Promise<CodexTokens & {
|
|
91
|
+
last_refresh: string;
|
|
92
|
+
}>;
|
|
93
|
+
export declare const resolveCodexRuntimeCredentials: (options?: ResolveCodexRuntimeCredentialsOptions) => Promise<CodexRuntimeCredentials>;
|
|
94
|
+
export declare const getCodexAuthStatus: (options?: AuthPathOptions & {
|
|
95
|
+
baseURL?: string | null;
|
|
96
|
+
}) => Promise<CodexAuthStatus>;
|
|
97
|
+
export declare const saveImportedCodexCliTokens: (options?: AuthPathOptions & {
|
|
98
|
+
codexHome?: string | null;
|
|
99
|
+
codexAuthPath?: string | null;
|
|
100
|
+
lockTimeoutMs?: number;
|
|
101
|
+
}) => Promise<boolean>;
|
|
102
|
+
export declare const loginCodexDeviceCode: (options?: DeviceCodeLoginOptions) => Promise<{
|
|
103
|
+
tokens: CodexTokens;
|
|
104
|
+
base_url: string;
|
|
105
|
+
last_refresh: string;
|
|
106
|
+
auth_mode: "chatgpt";
|
|
107
|
+
source: "device-code";
|
|
108
|
+
}>;
|
|
109
|
+
export declare const loginAndSaveCodexDeviceCode: (options?: DeviceCodeLoginOptions & AuthPathOptions & {
|
|
110
|
+
lockTimeoutMs?: number;
|
|
111
|
+
}) => Promise<{
|
|
112
|
+
tokens: CodexTokens;
|
|
113
|
+
base_url: string;
|
|
114
|
+
last_refresh: string;
|
|
115
|
+
auth_mode: "chatgpt";
|
|
116
|
+
source: "device-code";
|
|
117
|
+
}>;
|
|
118
|
+
export {};
|