Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 23 additions & 2 deletions packages/agent-cdp/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# agent-cdp

**agent-cdp** is a command-line tool that connects to apps and pages through the **Chrome DevTools Protocol (CDP)**. Use it to list debuggable targets, inspect network traffic, stream console output, record traces, inspect JavaScript heap usage, capture and analyze heap snapshots, and run JavaScript CPU profiles, all without opening DevTools yourself.
**agent-cdp** is a command-line tool that connects to apps and pages through the **Chrome DevTools Protocol (CDP)**. Use it to list debuggable targets, evaluate runtime expressions, inspect network traffic, stream console output, record traces, inspect JavaScript heap usage, capture and analyze heap snapshots, and run JavaScript CPU profiles, all without opening DevTools yourself.

## Compatibility

Expand Down Expand Up @@ -91,6 +91,7 @@ agent-cdp target clear
**3. Use the features you need**

- **Console** — list and fetch log lines: `console list`, `console get <id>`
- **Runtime** — evaluate expressions, inspect returned object handles, and release preserved inspector references: `runtime eval`, `runtime props`, `runtime release`, `runtime release-group`
- **Network** — bounded live capture plus persisted sessions: `network status`, `network start`, `network summary`, `network list`, `network request`, `network request-headers`, `network response-headers`, `network request-body`, `network response-body`
- **Trace** — explicit trace capture plus in-memory session analysis for `performance.measure`, `performance.mark`, `console.timeStamp`, and custom DevTools tracks: `trace start`, `trace stop`, `trace summary`, `trace tracks`, `trace entries`, `trace entry`
- **Memory (raw)** — `memory capture --file PATH` for a heap snapshot file
Expand All @@ -108,7 +109,27 @@ agent-cdp stop

## Command overview

Commands are grouped as **daemon**, **target**, **console**, **network**, **trace**, **memory**, **mem-snapshot**, **js-memory**, **js-allocation**, **js-allocation-timeline**, **js-profile**, and **skills** (bundled reference files). See `agent-cdp --help` for exact syntax and options.
Commands are grouped as **daemon**, **target**, **console**, **runtime**, **network**, **trace**, **memory**, **mem-snapshot**, **js-memory**, **js-allocation**, **js-allocation-timeline**, **js-profile**, and **skills** (bundled reference files). See `agent-cdp --help` for exact syntax and options.

## Runtime inspection

Use `runtime` for live state inspection when you need more than captured console output.

Quick start:

```sh
agent-cdp runtime eval --expr "process.version"
agent-cdp runtime eval --expr "globalThis.store" --await
agent-cdp runtime props --id <OBJECT_ID> --own
agent-cdp runtime release --id <OBJECT_ID>
agent-cdp runtime release-group
```

Notes:

- Remote object handles are preserved by default so you can inspect them with `runtime props` after `runtime eval`.
- Preserved handles should be released when you no longer need them in a long-lived daemon session.
- `runtime eval` can have side effects if the expression mutates application state.

## Network inspection

Expand Down
15 changes: 12 additions & 3 deletions packages/agent-cdp/skills/core.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
---
name: core
description: Core agent-cdp usage guide. Read this before running any agent-cdp commands. Covers the daemon lifecycle, target selection, network inspection, console capture, trace recording, heap snapshot analysis, JS heap monitoring, and CPU profiling workflows. Use when you need to analyze network failures, memory leaks, CPU hotspots, or runtime behavior of a Chrome/Node.js target via Chrome DevTools Protocol.
description: Core agent-cdp usage guide. Read this before running any agent-cdp commands. Covers the daemon lifecycle, target selection, runtime inspection, network inspection, console capture, trace recording, heap snapshot analysis, JS heap monitoring, and CPU profiling workflows. Use when you need to analyze network failures, memory leaks, CPU hotspots, or runtime behavior of a Chrome/Node.js target via Chrome DevTools Protocol.
allowed-tools: Bash(agent-cdp:*)
---

# agent-cdp core

CLI for deep runtime analysis of Chrome and Node.js processes via Chrome
DevTools Protocol (CDP). Captures heap snapshots, CPU profiles, JS memory
samples, network traffic, console output, and performance traces — all without modifying
source code.
samples, network traffic, console output, live runtime values, and performance traces — all without modifying source code.

## The core loop

Expand Down Expand Up @@ -79,6 +78,16 @@ agent-cdp console get <id> # get full details of a specific message

Console messages are collected while the daemon is running with an active target.

## Runtime inspection

For Runtime workflows, run:

```bash
agent-cdp skills get runtime
```

That skill covers expression evaluation, object handle inspection, and release guidance for preserved Runtime objects.

## Network inspection

For network workflows, run:
Expand Down
109 changes: 109 additions & 0 deletions packages/agent-cdp/skills/runtime.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
---
name: runtime
description: Runtime inspection workflows for agent-cdp. Use after reading the core skill and selecting a target. Covers expression evaluation, preserved remote object handles, property inspection, and explicit release for Chrome, Node.js, and React Native targets.
allowed-tools: Bash(agent-cdp:*)
---

# agent-cdp runtime

Use `runtime` when you need live state inspection against the currently selected target.

This is the fastest path for an LLM agent to answer questions like:

- What is the current value of a global or module-level variable?
- What properties exist on this object right now?
- Does the runtime state match what the logs suggest?

## Commands

```bash
agent-cdp runtime eval --expr EXPR [--await] [--json]
agent-cdp runtime props --id OBJECT_ID [--own] [--accessor-properties-only]
agent-cdp runtime release --id OBJECT_ID
agent-cdp runtime release-group [--group NAME]
```

## Safe workflow

```bash
agent-cdp runtime eval --expr "globalThis.store"
agent-cdp runtime props --id <OBJECT_ID> --own
agent-cdp runtime release --id <OBJECT_ID>
```

If you evaluate several objects during a session, release the whole group when done:

```bash
agent-cdp runtime release-group
```

## Preserved by default

Runtime object handles are preserved by default.

That means:

- `runtime eval` may return an `objectId` for a live remote object
- you can pass that `objectId` to `runtime props`
- the handle stays available until you explicitly release it or release its group

This is intentional because LLM agents often need a follow-up inspection step after evaluation.

Releasing a handle does not delete the real application object. It only drops the inspector-side reference used by the debugging session.

## `objectId` and object groups

- `objectId` is a remote inspector handle, not a serialized value
- preserved handles are placed in the default group `agent-cdp-runtime`
- use `runtime release --id ...` for one handle
- use `runtime release-group` to clean up the default group in bulk

In long-lived daemon sessions, release handles you no longer need.

## Side effects

`runtime eval` runs code in the target runtime. It is not automatically read-only.

Prefer expressions that inspect state without mutating it, for example:

```bash
agent-cdp runtime eval --expr "process.version"
agent-cdp runtime eval --expr "globalThis.__APP_STATE__"
agent-cdp runtime eval --expr "Array.isArray(globalThis.items) ? globalThis.items.length : null"
```

Avoid expressions that trigger writes, network calls, or state transitions unless you mean to do that.

## Cross-target notes

The Runtime commands are intended to work with:

- Chrome / Chromium pages with CDP enabled
- Node.js processes started with `--inspect` or `--inspect-brk`
- React Native targets exposed through Metro / the RN debugger endpoint

Examples:

```bash
agent-cdp target list --url http://localhost:9222
agent-cdp target list --url http://localhost:9229
agent-cdp target list --url http://localhost:8081
```

Then select the target and inspect runtime state.

## React Native / Hermes promises

On React Native targets backed by Hermes, do not assume `runtime eval --await` will unwrap a promise into its fulfillment value.

Some Hermes targets also reject `async`/`await` syntax at parse time, so avoid using async functions as a probe unless you have already confirmed the target accepts them.

If `--await` returns a promise handle instead of the resolved value:

1. Re-run `runtime eval` without `--await` to get the remote object handle.
2. Inspect the handle with `runtime props --id <OBJECT_ID> --own`.
3. Look for Hermes promise internals such as `_h`, `_i`, `_j`, and `_k`.
4. In practice, `_j` often holds the fulfilled value once the promise settles, while `_k` may be `null`.
5. If you only need to confirm settlement, treat a non-null `_j` as the most useful clue and avoid assuming the inspector will serialize the resolved value for you.

Use this workflow for debugging RN promise state instead of relying on native async syntax or promise unwrapping in the inspector path.
2 changes: 2 additions & 0 deletions packages/agent-cdp/src/__tests__/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ describe("cli", () => {
expect(usage()).toContain("stop");
expect(usage()).toContain("target list [--url URL]");
expect(usage()).toContain("target select <id> [--url URL]");
expect(usage()).toContain("runtime eval --expr EXPR [--await] [--json]");
expect(usage()).toContain("runtime props --id OBJECT_ID [--own] [--accessor-properties-only]");
expect(usage()).toContain("network start [--name NAME] [--preserve-across-navigation]");
expect(usage()).toContain("network response-body --id REQ_ID [--session ID] [--file PATH]");
expect(usage()).toContain("trace status");
Expand Down
90 changes: 90 additions & 0 deletions packages/agent-cdp/src/__tests__/runtime.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { describe, expect, it } from "vitest";
import { formatRuntimeEval, formatRuntimeEvalJson, formatRuntimeProperties } from "../runtime/formatters.js";
import { RuntimeManager } from "../runtime/index.js";

describe("runtime formatters", () => {
it("formats primitive eval results", () => {
expect(
formatRuntimeEval({
type: "number",
value: 42,
}),
).toBe("number: 42");
});

it("formats string eval results as json-safe text", () => {
expect(
formatRuntimeEval({
type: "string",
value: "hello",
}),
).toBe('string: "hello"');
expect(
formatRuntimeEvalJson({
type: "string",
value: "hello",
}),
).toBe('"hello"');
});

it("formats remote object eval results with object ids", () => {
expect(
formatRuntimeEval({
type: "object",
subtype: "array",
description: "Array(3)",
objectId: "obj-1",
objectGroup: "agent-cdp-runtime",
}),
).toBe("array Array(3)\nobjectId: obj-1");
});

it("formats property listings with nested object handles", () => {
expect(
formatRuntimeProperties({
objectId: "root-1",
properties: [
{
name: "count",
enumerable: true,
isAccessor: false,
type: "number",
value: 2,
},
{
name: "items",
enumerable: true,
isAccessor: false,
type: "object",
subtype: "array",
description: "Array(2)",
objectId: "child-1",
},
],
}),
).toBe("count = number: 2\nitems = array Array(2) (objectId: child-1)");
});
});

describe("runtime manager", () => {
it("releases a runtime object", async () => {
const sent: Array<{ method: string; params?: Record<string, unknown> }> = [];
const manager = new RuntimeManager();
const session = {
transport: {
connect: async () => {},
disconnect: async () => {},
isConnected: () => true,
send: async (method: string, params?: Record<string, unknown>) => {
sent.push({ method, params });
return {};
},
onEvent: () => () => {},
},
};

await manager.releaseObject(session as never, "obj-9");

expect(sent).toEqual([{ method: "Runtime.releaseObject", params: { objectId: "obj-9" } }]);
});
});
80 changes: 80 additions & 0 deletions packages/agent-cdp/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ import {
formatStatus,
formatTargetList,
} from "./formatters.js";
import {
DEFAULT_RUNTIME_OBJECT_GROUP,
formatRuntimeEval,
formatRuntimeEvalJson,
formatRuntimeProperties,
} from "./runtime/index.js";
import {
formatTraceEntries,
formatTraceEntry,
Expand Down Expand Up @@ -117,6 +123,12 @@ Console:
console list [--limit N]
console get <id>

Runtime:
runtime eval --expr EXPR [--await] [--json]
runtime props --id OBJECT_ID [--own] [--accessor-properties-only]
runtime release --id OBJECT_ID
runtime release-group [--group NAME]

Network:
network status
network start [--name NAME] [--preserve-across-navigation]
Expand Down Expand Up @@ -469,6 +481,74 @@ export async function main(): Promise<void> {
return;
}

if (cmd === "runtime" && command[1] === "eval") {
const expression = typeof flags.expr === "string" ? flags.expr : undefined;
if (!expression) {
throw new Error("Usage: agent-cdp runtime eval --expr EXPR [--await] [--json]");
}

const awaitPromise = flags.await === true;
const json = flags.json === true;
await ensureDaemon();
const response = await sendCommand({ type: "runtime-eval", expression, awaitPromise });
if (!response.ok) {
throw new Error(response.error || "Failed to evaluate runtime expression");
}

console.log(
json
? formatRuntimeEvalJson(response.data as Parameters<typeof formatRuntimeEvalJson>[0])
: formatRuntimeEval(response.data as Parameters<typeof formatRuntimeEval>[0], verbose),
);
return;
}

if (cmd === "runtime" && command[1] === "props") {
const objectId = typeof flags.id === "string" ? flags.id : undefined;
if (!objectId) {
throw new Error("Usage: agent-cdp runtime props --id OBJECT_ID [--own] [--accessor-properties-only]");
}

const ownProperties = flags.own === true;
const accessorPropertiesOnly = flags["accessor-properties-only"] === true;
await ensureDaemon();
const response = await sendCommand({ type: "runtime-get-properties", objectId, ownProperties, accessorPropertiesOnly });
if (!response.ok) {
throw new Error(response.error || "Failed to inspect runtime object properties");
}

console.log(formatRuntimeProperties(response.data as Parameters<typeof formatRuntimeProperties>[0], verbose));
return;
}

if (cmd === "runtime" && command[1] === "release") {
const objectId = typeof flags.id === "string" ? flags.id : undefined;
if (!objectId) {
throw new Error("Usage: agent-cdp runtime release --id OBJECT_ID");
}

await ensureDaemon();
const response = await sendCommand({ type: "runtime-release-object", objectId });
if (!response.ok) {
throw new Error(response.error || "Failed to release runtime object");
}

console.log(`Released runtime object: ${objectId}`);
return;
}

if (cmd === "runtime" && command[1] === "release-group") {
const objectGroup = typeof flags.group === "string" ? flags.group : DEFAULT_RUNTIME_OBJECT_GROUP;
await ensureDaemon();
const response = await sendCommand({ type: "runtime-release-object-group", objectGroup });
if (!response.ok) {
throw new Error(response.error || "Failed to release runtime object group");
}

console.log(`Released runtime object group: ${objectGroup}`);
return;
}

if (cmd === "network" && command[1] === "status") {
await ensureDaemon();
const response = await sendCommand({ type: "network-status" });
Expand Down
Loading
Loading