@phnx-labs/agents-cli 1.14.7 → 1.16.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 (105) hide show
  1. package/CHANGELOG.md +78 -39
  2. package/README.md +74 -7
  3. package/dist/commands/alias.js +2 -2
  4. package/dist/commands/beta.js +6 -1
  5. package/dist/commands/browser-picker.d.ts +21 -0
  6. package/dist/commands/browser-picker.js +114 -0
  7. package/dist/commands/browser.js +546 -75
  8. package/dist/commands/commands.js +72 -22
  9. package/dist/commands/daemon.js +2 -2
  10. package/dist/commands/exec.js +9 -2
  11. package/dist/commands/fork.js +2 -2
  12. package/dist/commands/hooks.js +71 -26
  13. package/dist/commands/mcp.js +85 -43
  14. package/dist/commands/plugins.js +48 -15
  15. package/dist/commands/prune.d.ts +0 -20
  16. package/dist/commands/prune.js +291 -16
  17. package/dist/commands/pull.js +3 -3
  18. package/dist/commands/repo.js +1 -1
  19. package/dist/commands/routines.js +2 -2
  20. package/dist/commands/secrets.js +37 -1
  21. package/dist/commands/sessions.js +62 -19
  22. package/dist/commands/{init.d.ts → setup.d.ts} +7 -6
  23. package/dist/commands/{init.js → setup.js} +32 -21
  24. package/dist/commands/skills.js +60 -19
  25. package/dist/commands/subagents.js +41 -13
  26. package/dist/commands/teams.js +2 -3
  27. package/dist/commands/usage.js +6 -0
  28. package/dist/commands/utils.d.ts +16 -0
  29. package/dist/commands/utils.js +32 -0
  30. package/dist/commands/versions.js +8 -6
  31. package/dist/commands/view.js +61 -16
  32. package/dist/index.d.ts +1 -1
  33. package/dist/index.js +17 -20
  34. package/dist/lib/agents.js +2 -2
  35. package/dist/lib/auto-pull-worker.js +2 -3
  36. package/dist/lib/auto-pull.js +2 -2
  37. package/dist/lib/browser/cdp.d.ts +7 -1
  38. package/dist/lib/browser/cdp.js +29 -1
  39. package/dist/lib/browser/chrome.js +6 -3
  40. package/dist/lib/browser/devices.d.ts +4 -0
  41. package/dist/lib/browser/devices.js +27 -0
  42. package/dist/lib/browser/drivers/local.js +9 -4
  43. package/dist/lib/browser/drivers/ssh.d.ts +1 -0
  44. package/dist/lib/browser/drivers/ssh.js +32 -4
  45. package/dist/lib/browser/ipc.js +145 -23
  46. package/dist/lib/browser/profiles.d.ts +5 -2
  47. package/dist/lib/browser/profiles.js +77 -37
  48. package/dist/lib/browser/service.d.ts +84 -13
  49. package/dist/lib/browser/service.js +806 -122
  50. package/dist/lib/browser/types.d.ts +81 -3
  51. package/dist/lib/browser/types.js +16 -0
  52. package/dist/lib/cloud/rush.js +2 -2
  53. package/dist/lib/cloud/store.js +2 -2
  54. package/dist/lib/commands.d.ts +1 -0
  55. package/dist/lib/commands.js +6 -2
  56. package/dist/lib/daemon.js +6 -7
  57. package/dist/lib/doctor-diff.js +4 -4
  58. package/dist/lib/events.d.ts +94 -1
  59. package/dist/lib/events.js +264 -6
  60. package/dist/lib/exec.js +16 -10
  61. package/dist/lib/hooks.d.ts +11 -7
  62. package/dist/lib/hooks.js +125 -49
  63. package/dist/lib/migrate.d.ts +1 -1
  64. package/dist/lib/migrate.js +1178 -21
  65. package/dist/lib/models.js +2 -2
  66. package/dist/lib/permissions.d.ts +14 -11
  67. package/dist/lib/permissions.js +46 -42
  68. package/dist/lib/plugins.d.ts +30 -1
  69. package/dist/lib/plugins.js +75 -3
  70. package/dist/lib/pty-server.js +9 -10
  71. package/dist/lib/resources/hooks.d.ts +5 -1
  72. package/dist/lib/resources/hooks.js +21 -4
  73. package/dist/lib/rotate.js +3 -4
  74. package/dist/lib/routines.d.ts +15 -0
  75. package/dist/lib/routines.js +68 -0
  76. package/dist/lib/runner.js +9 -5
  77. package/dist/lib/secrets/index.d.ts +14 -11
  78. package/dist/lib/secrets/index.js +49 -21
  79. package/dist/lib/secrets/linux.d.ts +27 -0
  80. package/dist/lib/secrets/linux.js +161 -0
  81. package/dist/lib/session/active.d.ts +3 -0
  82. package/dist/lib/session/active.js +92 -6
  83. package/dist/lib/session/cloud.js +2 -2
  84. package/dist/lib/session/db.d.ts +4 -0
  85. package/dist/lib/session/db.js +34 -3
  86. package/dist/lib/session/discover.js +30 -15
  87. package/dist/lib/session/team-filter.js +2 -2
  88. package/dist/lib/shims.d.ts +2 -2
  89. package/dist/lib/shims.js +6 -6
  90. package/dist/lib/skills.js +6 -2
  91. package/dist/lib/state.d.ts +86 -14
  92. package/dist/lib/state.js +150 -23
  93. package/dist/lib/subagents.d.ts +28 -0
  94. package/dist/lib/subagents.js +98 -1
  95. package/dist/lib/sync-manifest.d.ts +1 -1
  96. package/dist/lib/sync-manifest.js +3 -3
  97. package/dist/lib/teams/persistence.js +15 -5
  98. package/dist/lib/teams/registry.js +2 -2
  99. package/dist/lib/types.d.ts +32 -3
  100. package/dist/lib/types.js +3 -3
  101. package/dist/lib/usage.d.ts +1 -1
  102. package/dist/lib/usage.js +15 -48
  103. package/dist/lib/versions.js +31 -21
  104. package/package.json +1 -1
  105. package/scripts/postinstall.js +37 -9
@@ -16,12 +16,17 @@ export interface BrowserProfile {
16
16
  export interface ChromeOptions {
17
17
  headless?: boolean;
18
18
  args?: string[];
19
+ viewport?: {
20
+ width: number;
21
+ height: number;
22
+ };
19
23
  }
20
24
  export interface Task {
21
25
  id: string;
26
+ name: string;
22
27
  profile: string;
23
- windowTargetId?: string;
24
- tabIds: string[];
28
+ tabs: Record<string, string>;
29
+ currentTabId?: string;
25
30
  createdAt: number;
26
31
  pid: number;
27
32
  }
@@ -36,17 +41,39 @@ export interface ProfileStatus {
36
41
  running: boolean;
37
42
  port?: number;
38
43
  pid?: number;
44
+ /** The port declared in the profile's first endpoint, when it differs from the running port. */
45
+ configuredPort?: number;
39
46
  tasks: TaskStatus[];
40
47
  }
41
48
  export interface TaskStatus {
42
49
  id: string;
50
+ name: string;
43
51
  tabCount: number;
52
+ currentTabId?: string;
44
53
  createdAt: number;
54
+ endedAt?: number;
55
+ domains?: string[];
56
+ tabs?: Array<{
57
+ id: string;
58
+ url: string;
59
+ title?: string;
60
+ current?: boolean;
61
+ }>;
45
62
  }
46
- export type IPCAction = 'start' | 'stop' | 'status' | 'navigate' | 'tabs' | 'close' | 'evaluate' | 'screenshot' | 'refs' | 'click' | 'type' | 'press' | 'hover';
63
+ export interface HistoricalTask {
64
+ id: string;
65
+ name: string;
66
+ profile: string;
67
+ createdAt: number;
68
+ endedAt: number;
69
+ domains: string[];
70
+ tabCount: number;
71
+ }
72
+ export type IPCAction = 'start' | 'done' | 'stop' | 'status' | 'history' | 'navigate' | 'tab-add' | 'tab-focus' | 'tab-close' | 'tab-list' | 'evaluate' | 'screenshot' | 'refs' | 'click' | 'type' | 'press' | 'hover' | 'set-viewport' | 'set-device' | 'console' | 'errors' | 'requests' | 'response-body' | 'wait' | 'set-download-path' | 'wait-download';
47
73
  export interface IPCRequest {
48
74
  action: IPCAction;
49
75
  task?: string;
76
+ taskName?: string;
50
77
  profile?: string;
51
78
  url?: string;
52
79
  tabId?: string;
@@ -57,6 +84,20 @@ export interface IPCRequest {
57
84
  key?: string;
58
85
  interactive?: boolean;
59
86
  limit?: number;
87
+ width?: number;
88
+ height?: number;
89
+ deviceName?: string;
90
+ mobile?: boolean;
91
+ deviceScaleFactor?: number;
92
+ level?: 'log' | 'info' | 'warn' | 'error';
93
+ clear?: boolean;
94
+ filter?: string;
95
+ urlPattern?: string;
96
+ maxChars?: number;
97
+ waitType?: 'time' | 'selector' | 'url' | 'function' | 'load';
98
+ waitValue?: string | number;
99
+ timeout?: number;
100
+ downloadPath?: string;
60
101
  }
61
102
  export interface IPCResponse {
62
103
  ok: boolean;
@@ -66,10 +107,47 @@ export interface IPCResponse {
66
107
  windowTargetId?: string;
67
108
  tabs?: TabInfo[];
68
109
  profiles?: ProfileStatus[];
110
+ history?: HistoricalTask[];
69
111
  result?: unknown;
70
112
  path?: string;
71
113
  refs?: string;
114
+ logs?: ConsoleEntry[];
115
+ errors?: ErrorEntry[];
116
+ requests?: NetworkRequest[];
117
+ body?: string;
118
+ downloadPath?: string;
119
+ devices?: string[];
120
+ }
121
+ export interface ConsoleEntry {
122
+ level: 'log' | 'info' | 'warn' | 'error';
123
+ text: string;
124
+ timestamp: number;
125
+ url?: string;
126
+ line?: number;
127
+ }
128
+ export interface ErrorEntry {
129
+ message: string;
130
+ stack?: string;
131
+ timestamp: number;
132
+ url?: string;
133
+ line?: number;
134
+ }
135
+ export interface NetworkRequest {
136
+ id: string;
137
+ url: string;
138
+ method: string;
139
+ status?: number;
140
+ mimeType?: string;
141
+ timestamp: number;
142
+ }
143
+ export interface DeviceDescriptor {
144
+ width: number;
145
+ height: number;
146
+ deviceScaleFactor: number;
147
+ mobile: boolean;
72
148
  }
73
149
  export declare const TASK_ID_REGEX: RegExp;
74
150
  export declare function isValidTaskId(id: string): boolean;
75
151
  export declare function generateTaskId(): string;
152
+ export declare function generateShortId(): string;
153
+ export declare function generateFunName(): string;
@@ -5,3 +5,19 @@ export function isValidTaskId(id) {
5
5
  export function generateTaskId() {
6
6
  return crypto.randomUUID().slice(0, 8);
7
7
  }
8
+ export function generateShortId() {
9
+ return crypto.randomUUID().split('-')[0]; // 8 chars
10
+ }
11
+ const ADJECTIVES = [
12
+ 'swift', 'cosmic', 'jolly', 'quiet', 'bold', 'bright', 'calm', 'eager',
13
+ 'golden', 'happy', 'keen', 'lucky', 'noble', 'proud', 'quick', 'royal',
14
+ ];
15
+ const NOUNS = [
16
+ 'falcon', 'comet', 'tiger', 'nebula', 'phoenix', 'river', 'summit', 'wave',
17
+ 'aurora', 'breeze', 'crystal', 'dragon', 'ember', 'forest', 'glacier', 'harbor',
18
+ ];
19
+ export function generateFunName() {
20
+ const adj = ADJECTIVES[Math.floor(Math.random() * ADJECTIVES.length)];
21
+ const noun = NOUNS[Math.floor(Math.random() * NOUNS.length)];
22
+ return `${adj}-${noun}`;
23
+ }
@@ -9,7 +9,7 @@ import * as path from 'path';
9
9
  import * as os from 'os';
10
10
  import * as crypto from 'crypto';
11
11
  import * as yaml from 'yaml';
12
- import { getUserAgentsDir } from '../state.js';
12
+ import { getCloudDir } from '../state.js';
13
13
  import { resolveDispatchRepos } from './types.js';
14
14
  import { parseSSE } from './stream.js';
15
15
  import { listInstalledVersions, getVersionHomePath } from '../versions.js';
@@ -20,7 +20,7 @@ const USER_YAML = path.join(os.homedir(), '.rush', 'user.yaml');
20
20
  // Persistent consent record for uploading Claude OAuth blobs to Rush Cloud.
21
21
  // Created on first explicit consent (env var or flag); subsequent dispatches
22
22
  // see it and proceed without re-prompting.
23
- const RUSH_CONSENT_PATH = path.join(getUserAgentsDir(), 'cloud', 'rush-consent.json');
23
+ const RUSH_CONSENT_PATH = path.join(getCloudDir(), 'rush-consent.json');
24
24
  const RUSH_CONSENT_ENV = 'AGENTS_RUSH_UPLOAD_TOKENS';
25
25
  function hasRushUploadConsent(opts) {
26
26
  if (process.env[RUSH_CONSENT_ENV] === '1')
@@ -8,8 +8,8 @@
8
8
  import * as fs from 'fs';
9
9
  import * as path from 'path';
10
10
  import Database from '../sqlite.js';
11
- import { getUserAgentsDir } from '../state.js';
12
- const CLOUD_DIR = path.join(getUserAgentsDir(), 'cloud');
11
+ import { getCloudDir } from '../state.js';
12
+ const CLOUD_DIR = getCloudDir();
13
13
  const DB_PATH = path.join(CLOUD_DIR, 'tasks.db');
14
14
  const SCHEMA = `
15
15
  CREATE TABLE IF NOT EXISTS tasks (
@@ -79,6 +79,7 @@ export declare function installCommandToVersion(agent: AgentId, version: string,
79
79
  };
80
80
  /**
81
81
  * Remove a single command from a specific version home.
82
+ * Soft-deletes to ~/.agents/.trash/commands/.
82
83
  */
83
84
  export declare function removeCommandFromVersion(agent: AgentId, version: string, commandName: string): {
84
85
  success: boolean;
@@ -11,7 +11,7 @@ import * as path from 'path';
11
11
  import * as yaml from 'yaml';
12
12
  import { AGENTS, COMMANDS_CAPABLE_AGENTS, ensureCommandsDir } from './agents.js';
13
13
  import { markdownToToml } from './convert.js';
14
- import { getCommandsDir, getUserCommandsDir, getEnabledExtraRepos, getProjectAgentsDir, getSkillsDir } from './state.js';
14
+ import { getCommandsDir, getUserCommandsDir, getEnabledExtraRepos, getProjectAgentsDir, getSkillsDir, getTrashCommandsDir } from './state.js';
15
15
  import { getEffectiveHome, getVersionHomePath, listInstalledVersions } from './versions.js';
16
16
  import { commandSkillMatches, installCommandSkillToVersion, listCommandSkillsInVersion, removeCommandSkillFromVersion, shouldInstallCommandAsSkill, } from './command-skills.js';
17
17
  /** Parse command metadata (name, description) from YAML frontmatter or TOML headers. */
@@ -279,6 +279,7 @@ export function installCommandToVersion(agent, version, commandName, method = 'c
279
279
  }
280
280
  /**
281
281
  * Remove a single command from a specific version home.
282
+ * Soft-deletes to ~/.agents/.trash/commands/.
282
283
  */
283
284
  export function removeCommandFromVersion(agent, version, commandName) {
284
285
  const versionHome = getVersionHomePath(agent, version);
@@ -292,7 +293,10 @@ export function removeCommandFromVersion(agent, version, commandName) {
292
293
  return { success: true };
293
294
  }
294
295
  try {
295
- fs.unlinkSync(targetPath);
296
+ const stamp = new Date().toISOString().replace(/[:.]/g, '-');
297
+ const trashDir = path.join(getTrashCommandsDir(), agent, version, commandName);
298
+ fs.mkdirSync(trashDir, { recursive: true, mode: 0o700 });
299
+ fs.renameSync(targetPath, path.join(trashDir, `${commandName}${ext}.${stamp}`));
296
300
  }
297
301
  catch (err) {
298
302
  return { success: false, error: err.message };
@@ -6,17 +6,16 @@
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';
13
- import { getAgentsDir } from './state.js';
13
+ import { getDaemonDir as getDaemonDirRoot } from './state.js';
14
14
  import { listJobs as listAllJobs } from './routines.js';
15
15
  import { JobScheduler } from './scheduler.js';
16
16
  import { executeJobDetached, monitorRunningJobs } from './runner.js';
17
17
  import { BrowserService } from './browser/service.js';
18
18
  import { BrowserIPCServer } from './browser/ipc.js';
19
- const DAEMON_DIR = 'helpers/daemon';
20
19
  const PID_FILE = 'daemon.pid';
21
20
  const LOCK_FILE = 'daemon.lock';
22
21
  const LOG_FILE = 'logs.jsonl';
@@ -25,7 +24,7 @@ const LOG_ROTATE_COUNT = 3;
25
24
  const PLIST_NAME = 'com.phnx-labs.agents-daemon';
26
25
  const SYSTEMD_UNIT = 'agents-daemon.service';
27
26
  function getDaemonDir() {
28
- const dir = path.join(getAgentsDir(), DAEMON_DIR);
27
+ const dir = getDaemonDirRoot();
29
28
  fs.mkdirSync(dir, { recursive: true });
30
29
  return dir;
31
30
  }
@@ -297,10 +296,10 @@ function startDaemonLocked() {
297
296
  }
298
297
  fs.writeFileSync(plistPath, generateLaunchdPlist(), 'utf-8');
299
298
  try {
300
- execSync(`launchctl unload "${plistPath}" 2>/dev/null`, { encoding: 'utf-8' });
299
+ execFileSync('launchctl', ['unload', plistPath], { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] });
301
300
  }
302
301
  catch { /* not loaded, expected */ }
303
- execSync(`launchctl load "${plistPath}"`, { encoding: 'utf-8' });
302
+ execFileSync('launchctl', ['load', plistPath], { encoding: 'utf-8' });
304
303
  const pid = waitForPid(3000);
305
304
  return { pid, method: 'launchd' };
306
305
  }
@@ -358,7 +357,7 @@ export function stopDaemon() {
358
357
  const plistPath = getLaunchdPlistPath();
359
358
  if (fs.existsSync(plistPath)) {
360
359
  try {
361
- execSync(`launchctl unload "${plistPath}"`, { encoding: 'utf-8' });
360
+ execFileSync('launchctl', ['unload', plistPath], { encoding: 'utf-8' });
362
361
  fs.unlinkSync(plistPath);
363
362
  }
364
363
  catch (err) {
@@ -22,7 +22,7 @@
22
22
  import * as fs from 'fs';
23
23
  import * as path from 'path';
24
24
  import { AGENTS } from './agents.js';
25
- import { getProjectAgentsDir, getUserAgentsDir, getSystemAgentsDir, getEnabledExtraRepos, getResolvedRulesDir, getUserRulesDir, getPromptcutsPath, } from './state.js';
25
+ import { getProjectAgentsDir, getUserAgentsDir, getSystemAgentsDir, getEnabledExtraRepos, getResolvedRulesDir, getUserRulesDir, getEffectivePromptcutsPath, } from './state.js';
26
26
  import { getAvailableResources, getActuallySyncedResources, getVersionHomePath, } from './versions.js';
27
27
  import { markdownToToml } from './convert.js';
28
28
  import { resolveImports, supportsRulesImports } from './rules/compile.js';
@@ -426,10 +426,10 @@ function diffPresenceOnly(kind, available, synced) {
426
426
  return rows.sort((a, b) => a.name.localeCompare(b.name));
427
427
  }
428
428
  function diffPromptcuts() {
429
- const exists = fs.existsSync(getPromptcutsPath());
430
- if (!exists)
429
+ const sourcePath = getEffectivePromptcutsPath();
430
+ if (!fs.existsSync(sourcePath))
431
431
  return [];
432
- return [{ kind: 'promptcuts', name: 'promptcuts.yaml', status: 'ok', sourcePath: getPromptcutsPath() }];
432
+ return [{ kind: 'promptcuts', name: 'promptcuts.yaml', status: 'ok', sourcePath }];
433
433
  }
434
434
  export function diffVersionResources(agent, version, options = {}) {
435
435
  const cwd = options.cwd ?? process.cwd();
@@ -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;