diff --git a/packages/agent-cdp/README.md b/packages/agent-cdp/README.md index ab8a950..639dd5c 100644 --- a/packages/agent-cdp/README.md +++ b/packages/agent-cdp/README.md @@ -94,12 +94,11 @@ agent-cdp target clear - **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 -- **Heap snapshot tools** — `mem-snapshot` commands to capture, load, summarize, diff snapshots, inspect classes/instances/retainers, and triage leak-style comparisons -- **JS heap monitor** — `js-memory` commands for sampling, summaries, diffs, trends, and leak-oriented signals -- **JS allocation profiler** — `js-allocation` commands for sampled allocation timeline summaries, top allocators, bucketed growth, leak-oriented signals, and raw artifact export -- **JS allocation timeline** — `js-allocation-timeline` commands for DevTools-style heap allocation timeline capture, bucket summaries, linked final snapshot analysis, and raw artifact export -- **JS profiler** — `js-profile` commands to record CPU profiles, list sessions, hotspots, stacks, diffs, and optional source map help +- **Memory snapshots** — `memory snapshot` commands to capture, load, summarize, diff snapshots, inspect classes/instances/retainers, and triage leak-style comparisons +- **Memory usage** — `memory usage` commands for sampling, summaries, diffs, trends, and leak-oriented signals +- **Memory allocation** — `memory allocation` commands for sampled allocation timeline summaries, top allocators, bucketed growth, leak-oriented signals, and raw artifact export +- **Memory allocation timeline** — `memory allocation-timeline` commands for DevTools-style heap allocation timeline capture, bucket summaries, linked final snapshot analysis, and raw artifact export +- **CPU profiling** — `profile cpu` commands to record CPU profiles, list sessions, hotspots, stacks, diffs, and optional source map help **4. Stop the daemon** @@ -109,7 +108,7 @@ agent-cdp stop ## Command overview -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. +Commands are grouped as **daemon**, **target**, **console**, **runtime**, **network**, **trace**, **memory** (`snapshot`, `usage`, `allocation`, `allocation-timeline`), **profile** (`cpu`), and **skills** (bundled reference files). See `agent-cdp --help` for exact syntax and options. ## Runtime inspection diff --git a/packages/agent-cdp/skills/core.md b/packages/agent-cdp/skills/core.md index c962434..963a24d 100644 --- a/packages/agent-cdp/skills/core.md +++ b/packages/agent-cdp/skills/core.md @@ -120,14 +120,6 @@ agent-cdp trace entries Use `--file PATH` only when you need the raw trace JSON for direct inspection or external tools. -## Raw memory capture - -```bash -agent-cdp memory capture --file PATH # capture a raw heap snapshot to file -``` - -For structured heap analysis, prefer `mem-snapshot capture` below. - ## Heap snapshot analysis The heap snapshot workflow captures a V8 heap snapshot and lets you inspect @@ -135,41 +127,41 @@ retained objects, find memory leaks, and compare snapshots. ```bash # Capture -agent-cdp mem-snapshot capture [--name NAME] [--gc] [--file PATH] -agent-cdp mem-snapshot load --file PATH [--name NAME] # load an existing .heapsnapshot +agent-cdp memory snapshot capture [--name NAME] [--gc] [--file PATH] +agent-cdp memory snapshot load --file PATH [--name NAME] # load an existing .heapsnapshot # List and summarize -agent-cdp mem-snapshot list -agent-cdp mem-snapshot summary [--snapshot ID] +agent-cdp memory snapshot list +agent-cdp memory snapshot summary [--snapshot ID] # Inspect classes and objects -agent-cdp mem-snapshot classes [--snapshot ID] [--limit N] [--offset N] [--sort retained|self|count] [--filter TEXT] -agent-cdp mem-snapshot class --id CLASS_ID [--snapshot ID] -agent-cdp mem-snapshot instances --class CLASS_ID [--snapshot ID] [--limit N] [--offset N] [--sort retained|self] -agent-cdp mem-snapshot instance --id NODE_ID [--snapshot ID] -agent-cdp mem-snapshot retainers --id NODE_ID [--snapshot ID] [--depth N] [--limit N] +agent-cdp memory snapshot classes [--snapshot ID] [--limit N] [--offset N] [--sort retained|self|count] [--filter TEXT] +agent-cdp memory snapshot class --id CLASS_ID [--snapshot ID] +agent-cdp memory snapshot instances --class CLASS_ID [--snapshot ID] [--limit N] [--offset N] [--sort retained|self] +agent-cdp memory snapshot instance --id NODE_ID [--snapshot ID] +agent-cdp memory snapshot retainers --id NODE_ID [--snapshot ID] [--depth N] [--limit N] # Leak detection -agent-cdp mem-snapshot diff --base SNAPSHOT_ID --compare SNAPSHOT_ID [--sort retained|self|count] [--limit N] -agent-cdp mem-snapshot leak-candidates [--snapshot ID] [--limit N] -agent-cdp mem-snapshot leak-triplet --baseline ID --action ID --cleanup ID [--limit N] +agent-cdp memory snapshot diff --base SNAPSHOT_ID --compare SNAPSHOT_ID [--sort retained|self|count] [--limit N] +agent-cdp memory snapshot leak-candidates [--snapshot ID] [--limit N] +agent-cdp memory snapshot leak-triplet --baseline ID --action ID --cleanup ID [--limit N] ``` ### Memory leak detection workflow ```bash # 1. Baseline -agent-cdp mem-snapshot capture --name baseline --gc +agent-cdp memory snapshot capture --name baseline --gc # 2. Trigger the leaky action in the app # 3. Capture again -agent-cdp mem-snapshot capture --name after-action --gc +agent-cdp memory snapshot capture --name after-action --gc # 4. Clean up (GC, reset state) # 5. Final capture -agent-cdp mem-snapshot capture --name cleanup --gc +agent-cdp memory snapshot capture --name cleanup --gc # 6. Diff to find what grew -agent-cdp mem-snapshot diff --base 1 --compare 2 --sort retained +agent-cdp memory snapshot diff --base 1 --compare 2 --sort retained # 7. Three-snapshot leak analysis -agent-cdp mem-snapshot leak-triplet --baseline 1 --action 2 --cleanup 3 +agent-cdp memory snapshot leak-triplet --baseline 1 --action 2 --cleanup 3 ``` Use `--gc` before capturing to force a garbage collection so only truly @@ -180,15 +172,15 @@ retained objects appear in the snapshot. Lightweight heap usage sampling — faster than full snapshots. ```bash -agent-cdp js-memory sample [--label LABEL] [--gc] # take a heap usage sample -agent-cdp js-memory list [--limit N] [--offset N] # list all samples -agent-cdp js-memory summary # overall stats -agent-cdp js-memory diff --base SAMPLE_ID --compare SAMPLE_ID -agent-cdp js-memory trend [--limit N] # usage over time -agent-cdp js-memory leak-signal # heuristic leak indicator +agent-cdp memory usage sample [--label LABEL] [--gc] # take a heap usage sample +agent-cdp memory usage list [--limit N] [--offset N] # list all samples +agent-cdp memory usage summary # overall stats +agent-cdp memory usage diff --base SAMPLE_ID --compare SAMPLE_ID +agent-cdp memory usage trend [--limit N] # usage over time +agent-cdp memory usage leak-signal # heuristic leak indicator ``` -Use `js-memory` for quick "is heap growing?" checks. Use `mem-snapshot` for +Use `memory usage` for quick "is heap growing?" checks. Use `memory snapshot` for deep object-level analysis. ## JS allocation profiler @@ -199,22 +191,22 @@ the raw sampling profile to the LLM by default. ```bash # Record -agent-cdp js-allocation start [--name NAME] [--interval BYTES] [--stack-depth N] [--include-major-gc] [--include-minor-gc] +agent-cdp memory allocation start [--name NAME] [--interval BYTES] [--stack-depth N] [--include-major-gc] [--include-minor-gc] # ... run the workload you suspect is leaking ... -agent-cdp js-allocation stop +agent-cdp memory allocation stop # Inspect -agent-cdp js-allocation status -agent-cdp js-allocation list [--limit N] [--offset N] -agent-cdp js-allocation summary [--session ID] -agent-cdp js-allocation hotspots [--session ID] [--limit N] [--offset N] [--sort bytes|samples] -agent-cdp js-allocation bucketed [--session ID] [--limit N] -agent-cdp js-allocation leak-signal [--session ID] -agent-cdp js-allocation export --file PATH [--session ID] +agent-cdp memory allocation status +agent-cdp memory allocation list [--limit N] [--offset N] +agent-cdp memory allocation summary [--session ID] +agent-cdp memory allocation hotspots [--session ID] [--limit N] [--offset N] [--sort bytes|samples] +agent-cdp memory allocation bucketed [--session ID] [--limit N] +agent-cdp memory allocation leak-signal [--session ID] +agent-cdp memory allocation export --file PATH [--session ID] ``` -Use `js-allocation` when you need a compact leak-oriented summary of allocation -pressure. Use `mem-snapshot` when you need proof that objects are still retained +Use `memory allocation` when you need a compact leak-oriented summary of allocation +pressure. Use `memory snapshot` when you need proof that objects are still retained after cleanup. ## JS allocation timeline @@ -225,21 +217,21 @@ trace data. ```bash # Record -agent-cdp js-allocation-timeline start [--name NAME] +agent-cdp memory allocation-timeline start [--name NAME] # ... run the leaking interaction ... -agent-cdp js-allocation-timeline stop +agent-cdp memory allocation-timeline stop # Inspect -agent-cdp js-allocation-timeline status -agent-cdp js-allocation-timeline list [--limit N] [--offset N] -agent-cdp js-allocation-timeline summary [--session ID] -agent-cdp js-allocation-timeline buckets [--session ID] [--limit N] -agent-cdp js-allocation-timeline hotspots [--session ID] [--limit N] [--offset N] -agent-cdp js-allocation-timeline leak-signal [--session ID] -agent-cdp js-allocation-timeline export --file PATH [--session ID] +agent-cdp memory allocation-timeline status +agent-cdp memory allocation-timeline list [--limit N] [--offset N] +agent-cdp memory allocation-timeline summary [--session ID] +agent-cdp memory allocation-timeline buckets [--session ID] [--limit N] +agent-cdp memory allocation-timeline hotspots [--session ID] [--limit N] [--offset N] +agent-cdp memory allocation-timeline leak-signal [--session ID] +agent-cdp memory allocation-timeline export --file PATH [--session ID] ``` -Use `js-allocation-timeline` when you need a heavier-weight timeline capture +Use `memory allocation-timeline` when you need a heavier-weight timeline capture that tracks heap growth during the interaction and ties the result to a final heap snapshot with allocation traces. @@ -249,32 +241,32 @@ Sampling CPU profiler with source-map support. ```bash # Record -agent-cdp js-profile start [--name NAME] [--interval US] # start profiling (default 100µs interval) +agent-cdp profile cpu start [--name NAME] [--interval US] # start profiling (default 100µs interval) # ... run the workload ... -agent-cdp js-profile stop # stop and save +agent-cdp profile cpu stop # stop and save # Inspect -agent-cdp js-profile status # check if recording -agent-cdp js-profile list [--limit N] [--offset N] # list sessions -agent-cdp js-profile summary [--session ID] # top-level stats -agent-cdp js-profile hotspots [--session ID] [--limit N] [--offset N] [--sort selfMs|totalMs] [--min-self-ms N] [--include-runtime] -agent-cdp js-profile hotspot --id HOTSPOT_ID [--session ID] [--stack-limit N] -agent-cdp js-profile modules [--session ID] [--limit N] [--offset N] [--sort selfMs|totalMs] -agent-cdp js-profile stacks [--session ID] [--limit N] [--offset N] [--min-ms N] [--max-depth N] -agent-cdp js-profile slice --start MS --end MS [--session ID] [--limit N] -agent-cdp js-profile diff --base SESSION_ID --compare SESSION_ID [--limit N] [--min-delta-pct N] -agent-cdp js-profile export [--session ID] -agent-cdp js-profile source-maps [--session ID] +agent-cdp profile cpu status # check if recording +agent-cdp profile cpu list [--limit N] [--offset N] # list sessions +agent-cdp profile cpu summary [--session ID] # top-level stats +agent-cdp profile cpu hotspots [--session ID] [--limit N] [--offset N] [--sort selfMs|totalMs] [--min-self-ms N] [--include-runtime] +agent-cdp profile cpu hotspot --id HOTSPOT_ID [--session ID] [--stack-limit N] +agent-cdp profile cpu modules [--session ID] [--limit N] [--offset N] [--sort selfMs|totalMs] +agent-cdp profile cpu stacks [--session ID] [--limit N] [--offset N] [--min-ms N] [--max-depth N] +agent-cdp profile cpu slice --start MS --end MS [--session ID] [--limit N] +agent-cdp profile cpu diff --base SESSION_ID --compare SESSION_ID [--limit N] [--min-delta-pct N] +agent-cdp profile cpu export [--session ID] +agent-cdp profile cpu source-maps [--session ID] ``` ### CPU profiling workflow ```bash -agent-cdp js-profile start --name before-optimization +agent-cdp profile cpu start --name before-optimization # ... run the workload you want to profile ... -agent-cdp js-profile stop -agent-cdp js-profile hotspots --sort selfMs --limit 20 -agent-cdp js-profile hotspot --id # drill into a specific hotspot +agent-cdp profile cpu stop +agent-cdp profile cpu hotspots --sort selfMs --limit 20 +agent-cdp profile cpu hotspot --id # drill into a specific hotspot ``` `--interval US` sets the sampling interval in microseconds (default 100). @@ -313,6 +305,6 @@ Commands that take `--snapshot ID` or `--session ID` expect the numeric ID shown in `list` output, not the human-readable name. **Source maps not resolving** -Run `agent-cdp js-profile source-maps [--session ID]` to check which source +Run `agent-cdp profile cpu source-maps [--session ID]` to check which source maps were found. Source maps must be accessible at the paths referenced in the profile. diff --git a/packages/agent-cdp/src/__tests__/cli.test.ts b/packages/agent-cdp/src/__tests__/cli.test.ts index 8ccefcb..d5c763f 100644 --- a/packages/agent-cdp/src/__tests__/cli.test.ts +++ b/packages/agent-cdp/src/__tests__/cli.test.ts @@ -9,15 +9,13 @@ describe("cli", () => { expect(usage()).toContain("status"); expect(usage()).toContain("stop"); expect(usage()).toContain("target list [--url URL]"); - expect(usage()).toContain("target select [--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"); - expect(usage()).toContain("trace entries [--session ID] [--track NAME]"); - expect(usage()).toContain("js-allocation start"); - expect(usage()).toContain("js-allocation-timeline start"); + expect(usage()).toContain("network list [--session ID] [--limit N] [--offset N]"); + expect(usage()).toContain("memory snapshot capture [--name NAME] [--gc] [--file PATH]"); + expect(usage()).toContain("memory usage summary"); + expect(usage()).toContain("memory allocation hotspots [--session ID] [--limit N] [--offset N]"); + expect(usage()).toContain("memory allocation-timeline summary [--session ID]"); + expect(usage()).toContain("profile cpu hotspots [--session ID] [--limit N] [--offset N]"); }); it("prints the preserved top-level help text", async () => { @@ -31,6 +29,9 @@ describe("cli", () => { it("prints subcommand help without failing for bare command groups", async () => { await expect(main(["memory"])).resolves.toBeUndefined(); + await expect(main(["memory", "snapshot"])).resolves.toBeUndefined(); + await expect(main(["profile"])).resolves.toBeUndefined(); + await expect(main(["profile", "cpu"])).resolves.toBeUndefined(); }); it("treats --version as a normal successful exit", async () => { @@ -110,6 +111,22 @@ describe("cli", () => { return { ok: true, data: [] }; } + if (command.type === "js-memory-summary") { + return { + ok: true, + data: { + sampleCount: 0, + latestUsedSize: 0, + latestTotalSize: 0, + latestHeapSizeLimit: 0, + maxUsedSize: 0, + minUsedSize: 0, + averageUsedSize: 0, + deltaFromFirst: 0, + }, + }; + } + if (command.type === "runtime-eval") { return { ok: true, @@ -134,6 +151,7 @@ describe("cli", () => { await program.parseAsync(["target", "list", "--url", "http://127.0.0.1:9222"], { from: "user" }); await program.parseAsync(["runtime", "eval", "--expr", "process.version"], { from: "user" }); + await program.parseAsync(["memory", "usage", "summary"], { from: "user" }); expect(sendCommandMock).toHaveBeenNthCalledWith(1, { type: "list-targets", @@ -144,6 +162,7 @@ describe("cli", () => { expression: "process.version", awaitPromise: false, }); + expect(sendCommandMock).toHaveBeenNthCalledWith(3, { type: "js-memory-summary" }); logSpy.mockRestore(); }); diff --git a/packages/agent-cdp/src/__tests__/memory.test.ts b/packages/agent-cdp/src/__tests__/memory.test.ts deleted file mode 100644 index 91b8327..0000000 --- a/packages/agent-cdp/src/__tests__/memory.test.ts +++ /dev/null @@ -1,77 +0,0 @@ -import fs from "node:fs/promises"; -import os from "node:os"; -import path from "node:path"; - -import { MemorySnapshotter } from "../memory.js"; -import type { CdpEventMessage, CdpTransport, RuntimeSession, TargetDescriptor } from "../types.js"; - -class FakeMemoryTransport implements CdpTransport { - private listener: ((message: CdpEventMessage) => void) | null = null; - readonly sentMethods: string[] = []; - - connect(): Promise { - return Promise.resolve(); - } - - disconnect(): Promise { - return Promise.resolve(); - } - - isConnected(): boolean { - return true; - } - - send(method: string): Promise { - this.sentMethods.push(method); - return Promise.resolve(undefined); - } - - onEvent(listener: (message: CdpEventMessage) => void): () => void { - this.listener = listener; - return () => { - this.listener = null; - }; - } - - emit(message: CdpEventMessage): void { - this.listener?.(message); - } -} - -function createMemorySession(transport: CdpTransport): RuntimeSession { - return { - target: { - id: "chrome:test:page-1", - rawId: "page-1", - title: "Example", - kind: "chrome", - description: "Test page", - webSocketDebuggerUrl: "ws://example.test/devtools/page/1", - sourceUrl: "http://example.test", - } satisfies TargetDescriptor, - transport, - ensureConnected: () => Promise.resolve(), - close: () => Promise.resolve(), - }; -} - -describe("MemorySnapshotter", () => { - it("captures heap snapshot chunks and writes them to disk", async () => { - const transport = new FakeMemoryTransport(); - const snapshotter = new MemorySnapshotter(); - const filePath = path.join(os.tmpdir(), `agent-cdp-heap-${Date.now()}.heapsnapshot`); - - const capturePromise = snapshotter.capture(createMemorySession(transport), filePath); - transport.emit({ method: "HeapProfiler.addHeapSnapshotChunk", params: { chunk: "{\"snapshot\":1}" } }); - transport.emit({ - method: "HeapProfiler.reportHeapSnapshotProgress", - params: { finished: true }, - }); - - const summary = await capturePromise; - - expect(summary.chunkCount).toBe(1); - expect(summary.filePath).toBe(filePath); - await expect(fs.readFile(filePath, "utf8")).resolves.toContain("snapshot"); - }); -}); diff --git a/packages/agent-cdp/src/cli/commands/memory.ts b/packages/agent-cdp/src/cli/commands/memory.ts index 03a5090..9385ee0 100644 --- a/packages/agent-cdp/src/cli/commands/memory.ts +++ b/packages/agent-cdp/src/cli/commands/memory.ts @@ -1,27 +1,14 @@ import { Command } from "commander"; -import { formatMemorySummary } from "../../formatters.js"; import { formatMemLeakCandidates, formatMemLeakTriplet, formatMemSnapshotClass, formatMemSnapshotClasses, formatMemSnapshotDiff, formatMemSnapshotInstance, formatMemSnapshotInstances, formatMemSnapshotList, formatMemSnapshotMeta, formatMemSnapshotRetainers, formatMemSnapshotSummary } from "../../heap-snapshot/formatters.js"; import type { MemSnapshotMeta } from "../../heap-snapshot/types.js"; import { formatJsMemoryDiff, formatJsMemoryLeakSignal, formatJsMemoryList, formatJsMemorySample, formatJsMemorySummary, formatJsMemoryTrend } from "../../js-memory/formatters.js"; -import type { MemorySnapshotSummary } from "../../types.js"; import type { CliDeps } from "../context.js"; import { ensureTargetSelected } from "../context.js"; import { getVerbose, parseInteger, parseRequiredInteger, registerCommandGroupHelp, unwrapResponse } from "../shared.js"; -function readMemorySummary(data: unknown): MemorySnapshotSummary { - return data as MemorySnapshotSummary; -} - export function registerMemoryCommands(program: Command, deps: CliDeps): void { - const memory = registerCommandGroupHelp(program.command("memory").description("Raw memory capture commands")); - - memory.command("capture").requiredOption("--file ").action(async (options: { file: string }, command) => { - await ensureTargetSelected(deps); - const data = unwrapResponse(await deps.sendCommand({ type: "capture-memory", filePath: options.file }), "Failed to capture heap snapshot"); - console.log(formatMemorySummary(readMemorySummary(data), getVerbose(command))); - }); - - const snapshot = registerCommandGroupHelp(program.command("mem-snapshot").description("Heap snapshot analysis commands")); + const memory = registerCommandGroupHelp(program.command("memory").description("Memory inspection commands")); + const snapshot = registerCommandGroupHelp(memory.command("snapshot").description("Heap snapshot analysis commands")); snapshot.command("capture").option("--name ").option("--gc").option("--file ").action(async (options: { name?: string; gc?: boolean; file?: string }, command) => { await ensureTargetSelected(deps); @@ -78,14 +65,14 @@ export function registerMemoryCommands(program: Command, deps: CliDeps): void { snapshot.command("instance").requiredOption("--id ").option("--snapshot ").action(async (options: { id: string; snapshot?: string }, command) => { await deps.ensureDaemon(); - const nodeId = parseRequiredInteger(options.id, "Usage: agent-cdp mem-snapshot instance --id NODE_ID"); + const nodeId = parseRequiredInteger(options.id, "Usage: agent-cdp memory snapshot instance --id NODE_ID"); const data = unwrapResponse(await deps.sendCommand({ type: "mem-snapshot-instance", nodeId, snapshotId: options.snapshot }), "Failed to get instance"); console.log(formatMemSnapshotInstance(data as Parameters[0], getVerbose(command))); }); snapshot.command("retainers").requiredOption("--id ").option("--snapshot ").option("--depth ").option("--limit ").action(async (options: Record, command) => { await deps.ensureDaemon(); - const nodeId = parseRequiredInteger(options.id, "Usage: agent-cdp mem-snapshot retainers --id NODE_ID"); + const nodeId = parseRequiredInteger(options.id, "Usage: agent-cdp memory snapshot retainers --id NODE_ID"); const data = unwrapResponse( await deps.sendCommand({ type: "mem-snapshot-retainers", nodeId, snapshotId: options.snapshot, depth: parseInteger(options.depth), limit: parseInteger(options.limit) }), "Failed to get retainers", @@ -120,9 +107,9 @@ export function registerMemoryCommands(program: Command, deps: CliDeps): void { console.log(formatMemLeakCandidates(data as Parameters[0], getVerbose(command))); }); - const jsMemory = registerCommandGroupHelp(program.command("js-memory").description("JS heap usage monitor commands")); + const usage = registerCommandGroupHelp(memory.command("usage").description("JS heap usage monitor commands")); - jsMemory.command("sample").option("--label