@phnx-labs/agents-cli 1.14.7 → 1.15.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 +68 -1
- package/dist/commands/beta.js +6 -1
- package/dist/commands/exec.js +9 -2
- package/dist/commands/init.js +10 -0
- package/dist/commands/mcp.js +4 -4
- package/dist/commands/prune.d.ts +0 -20
- package/dist/commands/prune.js +268 -15
- package/dist/commands/teams.js +2 -3
- package/dist/commands/usage.js +6 -0
- package/dist/commands/versions.js +8 -6
- package/dist/lib/browser/chrome.js +1 -1
- package/dist/lib/browser/drivers/ssh.d.ts +1 -0
- package/dist/lib/browser/drivers/ssh.js +23 -2
- package/dist/lib/browser/ipc.js +1 -0
- package/dist/lib/browser/service.d.ts +3 -0
- package/dist/lib/browser/service.js +84 -7
- package/dist/lib/daemon.js +4 -4
- package/dist/lib/events.d.ts +94 -1
- package/dist/lib/events.js +262 -4
- package/dist/lib/exec.js +16 -10
- package/dist/lib/permissions.d.ts +6 -3
- package/dist/lib/permissions.js +38 -34
- package/dist/lib/routines.d.ts +15 -0
- package/dist/lib/routines.js +68 -0
- package/dist/lib/runner.js +9 -5
- package/dist/lib/secrets/index.d.ts +14 -11
- package/dist/lib/secrets/index.js +49 -21
- package/dist/lib/secrets/linux.d.ts +27 -0
- package/dist/lib/secrets/linux.js +161 -0
- package/dist/lib/session/db.d.ts +4 -0
- package/dist/lib/session/db.js +26 -0
- package/dist/lib/usage.d.ts +1 -1
- package/dist/lib/usage.js +13 -46
- package/dist/lib/versions.js +11 -0
- package/package.json +1 -1
- package/scripts/postinstall.js +37 -9
|
@@ -9,7 +9,7 @@ export async function connectSSH(endpoint, profile) {
|
|
|
9
9
|
}
|
|
10
10
|
const user = url.username || process.env.USER || 'root';
|
|
11
11
|
const host = url.hostname;
|
|
12
|
-
const remotePort = parseInt(url.
|
|
12
|
+
const remotePort = url.port ? parseInt(url.port, 10) : 9222;
|
|
13
13
|
const localPort = allocatePort();
|
|
14
14
|
try {
|
|
15
15
|
await ensureRemoteBrowser(user, host, profile.browser, remotePort, profile.binary);
|
|
@@ -17,7 +17,7 @@ export async function connectSSH(endpoint, profile) {
|
|
|
17
17
|
catch {
|
|
18
18
|
// Browser may already be running, continue
|
|
19
19
|
}
|
|
20
|
-
|
|
20
|
+
let tunnel = await startSSHTunnel(user, host, localPort, remotePort);
|
|
21
21
|
try {
|
|
22
22
|
await waitForPort(localPort, 8000);
|
|
23
23
|
}
|
|
@@ -133,6 +133,27 @@ async function ensureRemoteBrowser(user, host, browserType, port, customBinary)
|
|
|
133
133
|
}, 2000);
|
|
134
134
|
});
|
|
135
135
|
}
|
|
136
|
+
export async function restartRemoteBrowser(user, host, browserType, port, customBinary) {
|
|
137
|
+
// Kill any process using the remote debugging port
|
|
138
|
+
const killCmd = `lsof -ti :${port} | xargs kill -9 2>/dev/null || true`;
|
|
139
|
+
await runSSHCommand(user, host, killCmd);
|
|
140
|
+
await sleep(500);
|
|
141
|
+
await ensureRemoteBrowser(user, host, browserType, port, customBinary);
|
|
142
|
+
await sleep(1500);
|
|
143
|
+
}
|
|
144
|
+
function runSSHCommand(user, host, cmd) {
|
|
145
|
+
return new Promise((resolve) => {
|
|
146
|
+
const child = spawn('ssh', [`${user}@${host}`, '-o', 'BatchMode=yes', cmd], {
|
|
147
|
+
stdio: 'ignore',
|
|
148
|
+
});
|
|
149
|
+
child.on('close', () => resolve());
|
|
150
|
+
child.on('error', () => resolve());
|
|
151
|
+
setTimeout(() => {
|
|
152
|
+
child.kill();
|
|
153
|
+
resolve();
|
|
154
|
+
}, 3000);
|
|
155
|
+
});
|
|
156
|
+
}
|
|
136
157
|
function sleep(ms) {
|
|
137
158
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
138
159
|
}
|
package/dist/lib/browser/ipc.js
CHANGED
|
@@ -195,6 +195,7 @@ export async function sendIPCRequest(request) {
|
|
|
195
195
|
if (!fs.existsSync(socketPath)) {
|
|
196
196
|
throw new Error('Failed to start browser daemon');
|
|
197
197
|
}
|
|
198
|
+
await new Promise((r) => setTimeout(r, 300));
|
|
198
199
|
}
|
|
199
200
|
return new Promise((resolve, reject) => {
|
|
200
201
|
const socket = net.createConnection(socketPath);
|
|
@@ -2,6 +2,7 @@ import { type TabInfo, type ProfileStatus } from './types.js';
|
|
|
2
2
|
import { type RefOpts, type RefNode } from './refs.js';
|
|
3
3
|
export declare class BrowserService {
|
|
4
4
|
private connections;
|
|
5
|
+
private forkingProfiles;
|
|
5
6
|
start(profileName: string, taskId?: string): Promise<{
|
|
6
7
|
task: string;
|
|
7
8
|
windowTargetId?: string;
|
|
@@ -30,6 +31,8 @@ export declare class BrowserService {
|
|
|
30
31
|
hover(taskId: string, tabId: string, ref: number): Promise<void>;
|
|
31
32
|
status(profileName?: string): Promise<ProfileStatus[]>;
|
|
32
33
|
shutdown(): Promise<void>;
|
|
34
|
+
private findAvailableFork;
|
|
35
|
+
private forkElectronProfile;
|
|
33
36
|
private connectProfile;
|
|
34
37
|
private connectEndpoint;
|
|
35
38
|
private enableDomains;
|
|
@@ -2,7 +2,7 @@ import * as fs from 'fs';
|
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import { CDPClient, discoverBrowserWsUrl } from './cdp.js';
|
|
4
4
|
import { getProfile, getProfileRuntimeDir, getBrowserRuntimeDir } from './profiles.js';
|
|
5
|
-
import { killChrome, getRunningChromeInfo } from './chrome.js';
|
|
5
|
+
import { killChrome, getRunningChromeInfo, launchBrowser, allocatePort } from './chrome.js';
|
|
6
6
|
import { connectLocal } from './drivers/local.js';
|
|
7
7
|
import { connectSSH } from './drivers/ssh.js';
|
|
8
8
|
import { generateTaskId, isValidTaskId, } from './types.js';
|
|
@@ -11,6 +11,7 @@ import { clickAtCoords, hoverAtCoords, typeText, pressKey, focusNode } from './i
|
|
|
11
11
|
import { emit } from '../events.js';
|
|
12
12
|
export class BrowserService {
|
|
13
13
|
connections = new Map();
|
|
14
|
+
forkingProfiles = new Set();
|
|
14
15
|
async start(profileName, taskId) {
|
|
15
16
|
const profile = await getProfile(profileName);
|
|
16
17
|
if (!profile) {
|
|
@@ -21,7 +22,34 @@ export class BrowserService {
|
|
|
21
22
|
throw new Error(`Invalid task ID "${finalTaskId}". Must be lowercase alphanumeric with hyphens.`);
|
|
22
23
|
}
|
|
23
24
|
let conn = this.connections.get(profileName);
|
|
24
|
-
|
|
25
|
+
let effectiveProfileName = profileName;
|
|
26
|
+
if (conn && conn.electron && conn.tasks.size > 0) {
|
|
27
|
+
if (this.forkingProfiles.has(profileName)) {
|
|
28
|
+
while (this.forkingProfiles.has(profileName)) {
|
|
29
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
30
|
+
}
|
|
31
|
+
const existingFork = this.findAvailableFork(profileName);
|
|
32
|
+
if (existingFork) {
|
|
33
|
+
conn = existingFork.conn;
|
|
34
|
+
effectiveProfileName = existingFork.name;
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
throw new Error(`Fork in progress but no available fork found for "${profileName}"`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
this.forkingProfiles.add(profileName);
|
|
42
|
+
try {
|
|
43
|
+
const { forkName, connection } = await this.forkElectronProfile(profile);
|
|
44
|
+
conn = connection;
|
|
45
|
+
effectiveProfileName = forkName;
|
|
46
|
+
}
|
|
47
|
+
finally {
|
|
48
|
+
this.forkingProfiles.delete(profileName);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
else if (!conn) {
|
|
25
53
|
conn = await this.connectProfile(profile);
|
|
26
54
|
this.connections.set(profileName, conn);
|
|
27
55
|
}
|
|
@@ -32,15 +60,15 @@ export class BrowserService {
|
|
|
32
60
|
const { windowTargetId } = await this.createTaskWindow(conn, finalTaskId);
|
|
33
61
|
const task = {
|
|
34
62
|
id: finalTaskId,
|
|
35
|
-
profile:
|
|
63
|
+
profile: effectiveProfileName,
|
|
36
64
|
windowTargetId,
|
|
37
|
-
tabIds: [],
|
|
65
|
+
tabIds: conn.electron && windowTargetId ? [windowTargetId] : [],
|
|
38
66
|
createdAt: Date.now(),
|
|
39
67
|
pid: conn.pid,
|
|
40
68
|
};
|
|
41
69
|
conn.tasks.set(finalTaskId, task);
|
|
42
|
-
await this.saveTaskState(
|
|
43
|
-
emit('browser.launch', { profile:
|
|
70
|
+
await this.saveTaskState(effectiveProfileName, conn.tasks);
|
|
71
|
+
emit('browser.launch', { profile: effectiveProfileName, task: finalTaskId, pid: conn.pid });
|
|
44
72
|
return { task: finalTaskId, windowTargetId };
|
|
45
73
|
}
|
|
46
74
|
async stop(taskId) {
|
|
@@ -65,6 +93,11 @@ export class BrowserService {
|
|
|
65
93
|
conn.tasks.delete(taskId);
|
|
66
94
|
await this.saveTaskState(profileName, conn.tasks);
|
|
67
95
|
emit('browser.close', { profile: profileName, task: taskId });
|
|
96
|
+
if (conn.forkedFrom && conn.tasks.size === 0) {
|
|
97
|
+
conn.cdp.close();
|
|
98
|
+
killChrome(conn.pid);
|
|
99
|
+
this.connections.delete(profileName);
|
|
100
|
+
}
|
|
68
101
|
return { ok: true, profile: profileName };
|
|
69
102
|
}
|
|
70
103
|
}
|
|
@@ -259,6 +292,37 @@ export class BrowserService {
|
|
|
259
292
|
}
|
|
260
293
|
this.connections.clear();
|
|
261
294
|
}
|
|
295
|
+
findAvailableFork(profileName) {
|
|
296
|
+
for (const [name, conn] of this.connections) {
|
|
297
|
+
if (conn.forkedFrom === profileName && conn.tasks.size === 0) {
|
|
298
|
+
return { name, conn };
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
return null;
|
|
302
|
+
}
|
|
303
|
+
async forkElectronProfile(profile) {
|
|
304
|
+
let forkNum = 2;
|
|
305
|
+
while (this.connections.has(`${profile.name}.${forkNum}`)) {
|
|
306
|
+
forkNum++;
|
|
307
|
+
}
|
|
308
|
+
const forkName = `${profile.name}.${forkNum}`;
|
|
309
|
+
const port = allocatePort();
|
|
310
|
+
const { pid, wsUrl } = await launchBrowser(forkName, profile.browser, port, profile.chrome, profile.secrets, profile.binary);
|
|
311
|
+
const cdp = new CDPClient();
|
|
312
|
+
await cdp.connect(wsUrl);
|
|
313
|
+
await this.enableDomains(cdp);
|
|
314
|
+
const connection = {
|
|
315
|
+
cdp,
|
|
316
|
+
port,
|
|
317
|
+
pid,
|
|
318
|
+
electron: true,
|
|
319
|
+
forkedFrom: profile.name,
|
|
320
|
+
tasks: new Map(),
|
|
321
|
+
sessionCache: new Map(),
|
|
322
|
+
};
|
|
323
|
+
this.connections.set(forkName, connection);
|
|
324
|
+
return { forkName, connection };
|
|
325
|
+
}
|
|
262
326
|
async connectProfile(profile) {
|
|
263
327
|
const existingInfo = getRunningChromeInfo(profile.name);
|
|
264
328
|
if (existingInfo) {
|
|
@@ -351,7 +415,20 @@ export class BrowserService {
|
|
|
351
415
|
if (conn.electron) {
|
|
352
416
|
const { targetInfos } = (await conn.cdp.send('Target.getTargets'));
|
|
353
417
|
const pageTarget = targetInfos.find((t) => t.type === 'page');
|
|
354
|
-
|
|
418
|
+
if (pageTarget) {
|
|
419
|
+
return { windowTargetId: pageTarget.targetId };
|
|
420
|
+
}
|
|
421
|
+
// No existing page - try to create one (works on some Electron apps)
|
|
422
|
+
try {
|
|
423
|
+
const result = (await conn.cdp.send('Target.createTarget', {
|
|
424
|
+
url: 'about:blank',
|
|
425
|
+
newWindow: true,
|
|
426
|
+
}));
|
|
427
|
+
return { windowTargetId: result.targetId };
|
|
428
|
+
}
|
|
429
|
+
catch {
|
|
430
|
+
throw new Error('No page targets found and unable to create new window');
|
|
431
|
+
}
|
|
355
432
|
}
|
|
356
433
|
const result = (await conn.cdp.send('Target.createTarget', {
|
|
357
434
|
url: 'about:blank',
|
package/dist/lib/daemon.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* (macOS), systemd (Linux), or as a plain detached process. PID tracking,
|
|
7
7
|
* log output, reload (SIGHUP), and graceful shutdown are handled here.
|
|
8
8
|
*/
|
|
9
|
-
import { spawn, execSync } from 'child_process';
|
|
9
|
+
import { spawn, execSync, execFileSync } from 'child_process';
|
|
10
10
|
import * as fs from 'fs';
|
|
11
11
|
import * as path from 'path';
|
|
12
12
|
import * as os from 'os';
|
|
@@ -297,10 +297,10 @@ function startDaemonLocked() {
|
|
|
297
297
|
}
|
|
298
298
|
fs.writeFileSync(plistPath, generateLaunchdPlist(), 'utf-8');
|
|
299
299
|
try {
|
|
300
|
-
|
|
300
|
+
execFileSync('launchctl', ['unload', plistPath], { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] });
|
|
301
301
|
}
|
|
302
302
|
catch { /* not loaded, expected */ }
|
|
303
|
-
|
|
303
|
+
execFileSync('launchctl', ['load', plistPath], { encoding: 'utf-8' });
|
|
304
304
|
const pid = waitForPid(3000);
|
|
305
305
|
return { pid, method: 'launchd' };
|
|
306
306
|
}
|
|
@@ -358,7 +358,7 @@ export function stopDaemon() {
|
|
|
358
358
|
const plistPath = getLaunchdPlistPath();
|
|
359
359
|
if (fs.existsSync(plistPath)) {
|
|
360
360
|
try {
|
|
361
|
-
|
|
361
|
+
execFileSync('launchctl', ['unload', plistPath], { encoding: 'utf-8' });
|
|
362
362
|
fs.unlinkSync(plistPath);
|
|
363
363
|
}
|
|
364
364
|
catch (err) {
|
package/dist/lib/events.d.ts
CHANGED
|
@@ -3,8 +3,15 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Structured JSONL logs at ~/.agents/logs/events-YYYY-MM-DD.jsonl
|
|
5
5
|
* with automatic daily rotation and rich metadata for debugging/auditing.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Rich metadata: hostname, platform, arch, pid, timezone
|
|
9
|
+
* - Timing helpers: measure operation duration automatically
|
|
10
|
+
* - Truncation: long inputs/outputs are trimmed with ellipsis
|
|
11
|
+
* - Permissions: logs dir is 0700, files are 0600 (owner-only)
|
|
12
|
+
* - Performance tracking: withTiming() wrapper for any async function
|
|
6
13
|
*/
|
|
7
|
-
export type EventType = 'agent.run.start' | 'agent.run.end' | 'version.install' | 'version.switch' | 'version.remove' | 'skill.install' | 'skill.remove' | 'browser.launch' | 'browser.close' | 'secrets.get' | 'secrets.set' | 'secrets.delete' | 'cloud.dispatch' | 'cloud.complete' | 'teams.create' | 'teams.start' | 'teams.complete' | 'hook.fire' | 'hook.error' | 'resource.sync' | 'error' | 'warn' | 'info';
|
|
14
|
+
export type EventType = 'agent.run.start' | 'agent.run.end' | 'agent.spawn.start' | 'agent.spawn.end' | 'version.install' | 'version.switch' | 'version.remove' | 'skill.install' | 'skill.remove' | 'browser.launch' | 'browser.close' | 'browser.navigate' | 'browser.screenshot' | 'secrets.get' | 'secrets.set' | 'secrets.delete' | 'cloud.dispatch' | 'cloud.complete' | 'teams.create' | 'teams.add' | 'teams.start' | 'teams.complete' | 'hook.fire' | 'hook.complete' | 'hook.error' | 'resource.sync' | 'command.start' | 'command.end' | 'perf.timing' | 'session.start' | 'session.end' | 'error' | 'warn' | 'info' | 'debug';
|
|
8
15
|
export interface EventMeta {
|
|
9
16
|
ts: string;
|
|
10
17
|
tz: string;
|
|
@@ -13,17 +20,33 @@ export interface EventMeta {
|
|
|
13
20
|
platform: NodeJS.Platform;
|
|
14
21
|
arch: string;
|
|
15
22
|
pid: number;
|
|
23
|
+
ppid: number;
|
|
16
24
|
event: EventType;
|
|
17
25
|
}
|
|
18
26
|
export interface EventPayload {
|
|
19
27
|
agent?: string;
|
|
20
28
|
version?: string;
|
|
29
|
+
sessionId?: string;
|
|
21
30
|
cwd?: string;
|
|
31
|
+
command?: string;
|
|
32
|
+
args?: string[];
|
|
33
|
+
input?: string;
|
|
34
|
+
output?: string;
|
|
35
|
+
prompt?: string;
|
|
22
36
|
durationMs?: number;
|
|
37
|
+
startupMs?: number;
|
|
38
|
+
exitCode?: number;
|
|
39
|
+
status?: string;
|
|
23
40
|
error?: string;
|
|
41
|
+
errorStack?: string;
|
|
24
42
|
[key: string]: unknown;
|
|
25
43
|
}
|
|
26
44
|
export type EventRecord = EventMeta & EventPayload;
|
|
45
|
+
/**
|
|
46
|
+
* Truncate a string to maxLength, adding ellipsis if truncated.
|
|
47
|
+
* Returns undefined for null/undefined input.
|
|
48
|
+
*/
|
|
49
|
+
export declare function truncate(str: string | null | undefined, maxLength?: number): string | undefined;
|
|
27
50
|
/**
|
|
28
51
|
* Emit a structured event to the daily log file.
|
|
29
52
|
*
|
|
@@ -41,6 +64,62 @@ export declare function emit(event: EventType, payload?: EventPayload): void;
|
|
|
41
64
|
* done({ exitCode: 0 }); // emits agent.run.end with durationMs
|
|
42
65
|
*/
|
|
43
66
|
export declare function emitStart(startEvent: EventType, payload?: EventPayload): (endPayload?: EventPayload) => void;
|
|
67
|
+
/**
|
|
68
|
+
* Measure execution time of a synchronous function.
|
|
69
|
+
* Emits a perf.timing event with the duration.
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* const result = time('parse-config', () => parseConfig(path));
|
|
73
|
+
*/
|
|
74
|
+
export declare function time<T>(label: string, fn: () => T, payload?: EventPayload): T;
|
|
75
|
+
/**
|
|
76
|
+
* Measure execution time of an async function.
|
|
77
|
+
* Emits a perf.timing event with the duration.
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* const result = await timeAsync('fetch-data', () => fetchData(url));
|
|
81
|
+
*/
|
|
82
|
+
export declare function timeAsync<T>(label: string, fn: () => Promise<T>, payload?: EventPayload): Promise<T>;
|
|
83
|
+
/**
|
|
84
|
+
* Create a timing context for measuring multiple phases of an operation.
|
|
85
|
+
* Useful for tracking startup time vs execution time.
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* const timer = createTimer('agent.run', { agent: 'claude' });
|
|
89
|
+
* // ... setup work ...
|
|
90
|
+
* timer.mark('startup'); // records startup time
|
|
91
|
+
* // ... main work ...
|
|
92
|
+
* timer.end({ exitCode: 0 }); // records total time and emits event
|
|
93
|
+
*/
|
|
94
|
+
export declare function createTimer(label: string, payload?: EventPayload): {
|
|
95
|
+
mark: (phase: string) => number;
|
|
96
|
+
end: (endPayload?: EventPayload) => void;
|
|
97
|
+
elapsed: () => number;
|
|
98
|
+
};
|
|
99
|
+
/**
|
|
100
|
+
* Higher-order function that wraps an async function with timing.
|
|
101
|
+
* The wrapper emits start/end events automatically.
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* const timedFetch = withTiming('fetch', fetchData, { service: 'api' });
|
|
105
|
+
* const result = await timedFetch(url);
|
|
106
|
+
*/
|
|
107
|
+
export declare function withTiming<Args extends unknown[], R>(label: string, fn: (...args: Args) => Promise<R>, basePayload?: EventPayload): (...args: Args) => Promise<R>;
|
|
108
|
+
/**
|
|
109
|
+
* Emit a command.start event with CLI args.
|
|
110
|
+
* Returns a done() function to emit command.end with duration.
|
|
111
|
+
*
|
|
112
|
+
* @example
|
|
113
|
+
* // At CLI entry point:
|
|
114
|
+
* const done = emitCommand('run', process.argv.slice(2));
|
|
115
|
+
* // ... execute command ...
|
|
116
|
+
* done({ exitCode: 0 });
|
|
117
|
+
*/
|
|
118
|
+
export declare function emitCommand(command: string, args?: string[], payload?: EventPayload): (endPayload?: EventPayload) => void;
|
|
119
|
+
/**
|
|
120
|
+
* Emit an error event with full details.
|
|
121
|
+
*/
|
|
122
|
+
export declare function emitError(err: Error | string, payload?: EventPayload): void;
|
|
44
123
|
/**
|
|
45
124
|
* Remove log files older than the retention period.
|
|
46
125
|
* Called lazily on emit or explicitly via CLI.
|
|
@@ -61,6 +140,20 @@ export declare function query(options: {
|
|
|
61
140
|
endDate?: Date;
|
|
62
141
|
eventTypes?: EventType[];
|
|
63
142
|
agent?: string;
|
|
143
|
+
command?: string;
|
|
64
144
|
limit?: number;
|
|
65
145
|
}): EventRecord[];
|
|
146
|
+
/**
|
|
147
|
+
* Get performance stats for a specific label.
|
|
148
|
+
*/
|
|
149
|
+
export declare function getTimingStats(label: string, options?: {
|
|
150
|
+
days?: number;
|
|
151
|
+
}): {
|
|
152
|
+
count: number;
|
|
153
|
+
avgMs: number;
|
|
154
|
+
minMs: number;
|
|
155
|
+
maxMs: number;
|
|
156
|
+
p50Ms: number;
|
|
157
|
+
p95Ms: number;
|
|
158
|
+
} | null;
|
|
66
159
|
export declare const LOGS_PATH: string;
|