@crouton-kit/capture 1.4.17 → 1.4.19

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Crouton Labs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,4 +1,12 @@
1
- # capture
1
+ # capture — a browser automation command line for agents, built on the Chrome DevTools Protocol
2
+
3
+ ![capture](https://raw.githubusercontent.com/crouton-labs/capture/main/assets/banner.svg)
4
+
5
+ <p align="center">
6
+ <a href="https://github.com/crouton-labs/capture/actions/workflows/freshness.yml"><img alt="ci" src="https://img.shields.io/github/actions/workflow/status/crouton-labs/capture/freshness.yml?branch=main&label=ci"></a>
7
+ <a href="https://www.npmjs.com/package/@crouton-kit/capture"><img alt="npm" src="https://img.shields.io/npm/v/@crouton-kit/capture?label=npm"></a>
8
+ <a href="LICENSE"><img alt="license" src="https://img.shields.io/badge/license-MIT-blue"></a>
9
+ </p>
2
10
 
3
11
  **Browser automation that measures instead of guessing.** A CDP command line built for agents.
4
12
 
package/bin/capture CHANGED
@@ -10559,7 +10559,7 @@ function validateHarAppendBatch(value, source) {
10559
10559
  return { entries, incompleteLifecycles };
10560
10560
  }
10561
10561
  function captureVersion() {
10562
- const version = "1.4.17";
10562
+ const version = "1.4.19";
10563
10563
  return typeof version === "string" && version.length > 0 ? version : "development";
10564
10564
  }
10565
10565
  function token() {
@@ -12661,7 +12661,7 @@ function wallTime(value) {
12661
12661
  }
12662
12662
  }
12663
12663
  function captureVersion2() {
12664
- const version = "1.4.17";
12664
+ const version = "1.4.19";
12665
12665
  return typeof version === "string" && version.length > 0 ? version : "development";
12666
12666
  }
12667
12667
  function deepFreeze(value) {
@@ -231610,7 +231610,11 @@ var init_core = __esm({
231610
231610
  walk2("");
231611
231611
  return files;
231612
231612
  }
231613
- async drainOne(live, cause) {
231613
+ drainOne(live, cause) {
231614
+ if (!live.draining) live.draining = this.drain(live, cause);
231615
+ return live.draining;
231616
+ }
231617
+ async drain(live, cause) {
231614
231618
  const outcome = await live.collector.drain(cause);
231615
231619
  unlinkPrivateFile(path24.join(live.row.dir, "collecting.json"));
231616
231620
  const files = this.finalizedFiles(live.row.dir);
@@ -231640,9 +231644,10 @@ var init_core = __esm({
231640
231644
  return { completion: drained.completion, summary: drained.outcome.summary, files: drained.outcome.files };
231641
231645
  }
231642
231646
  async teardown(trigger = "session-stop") {
231643
- const stopping = this.cutoff(this.live.values());
231647
+ const liveCollectors = [...this.live.values()];
231648
+ this.cutoff(liveCollectors);
231644
231649
  const outcomes = [];
231645
- for (const live of stopping) {
231650
+ for (const live of liveCollectors) {
231646
231651
  try {
231647
231652
  const drained = await this.drainOne(live, { trigger, clientUsable: this.clientUsable });
231648
231653
  outcomes.push({ status: "drained", id: live.row.id, kind: live.row.kind, completion: drained.completion, dir: live.row.dir });
@@ -244114,7 +244119,7 @@ async function stopComposedRecorder(opts) {
244114
244119
  const host = await liveHost(opts.sessionDir);
244115
244120
  const row = motionHandle(host, recId);
244116
244121
  if (!row) throw new Error(`No live motion collector named "${recId}".`);
244117
- const response = await sendHostRequest(host.socketPath, { type: "collector-stop", nonce: host.nonce, id: recId }, 3e4);
244122
+ const response = await sendHostRequest(host.socketPath, { type: "collector-stop", nonce: host.nonce, id: recId }, 12e4);
244118
244123
  if (!response.ok) throw new Error(response.error ?? "collector host refused to stop motion recording");
244119
244124
  if (getActiveSession()?.dir === opts.sessionDir && getActiveRecId() === recId) await clearActiveRecId();
244120
244125
  return finalFromMeta(row.dir, recId);
@@ -267357,7 +267362,7 @@ I/O contract: flags and positionals on input; one rendered prose block on stdout
267357
267362
  A CDP-enabled browser must be running; \`capture tab list\` is the probe, and \`capture tab launch\` starts one capture owns and reaps when nothing is (never hand-roll a detached browser \u2014 nothing reaps that). Capture never signals a browser it did not start, so attaching to your own with --port is unchanged. CDP_PORT / CDP_TARGET pin the browser + tab for target-selecting commands \u2014 precedence: explicit flag > active session > env. \`session start\` adopts only an explicit --target.`;
267358
267363
  }
267359
267364
  function printVersion() {
267360
- const declared = "1.4.17";
267365
+ const declared = "1.4.19";
267361
267366
  if (declared) {
267362
267367
  console.log(declared);
267363
267368
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crouton-kit/capture",
3
- "version": "1.4.17",
3
+ "version": "1.4.19",
4
4
  "description": "Browser automation and UI validation via CDP — session-based screenshots, HAR, a11y, click/type, and JS exec",
5
5
  "bin": {
6
6
  "capture": "./bin/capture"
@@ -12,7 +12,7 @@ export interface CollectorRow { id: string; kind: CollectorKind; dir: string; cl
12
12
  export interface HostSnapshot { collectors: readonly CollectorRow[]; reservations: readonly ClaimReservation[]; }
13
13
  export interface HostIdentity { pid: number; birth: PidBirth; targetId: string; }
14
14
 
15
- interface LiveCollector { row: CollectorRow; collector: Collector; startedAt: string; losses: Array<{ reason: string; detail?: Record<string, unknown> }>; stopping: boolean; }
15
+ interface LiveCollector { row: CollectorRow; collector: Collector; startedAt: string; losses: Array<{ reason: string; detail?: Record<string, unknown> }>; stopping: boolean; draining?: Promise<{ completion: Completion; outcome: DrainOutcome }>; }
16
16
 
17
17
  export class CollectorHost {
18
18
  private readonly live = new Map<string, LiveCollector>();
@@ -131,7 +131,12 @@ export class CollectorHost {
131
131
  return files;
132
132
  }
133
133
 
134
- private async drainOne(live: LiveCollector, cause: DrainCause): Promise<{ completion: Completion; outcome: DrainOutcome }> {
134
+ private drainOne(live: LiveCollector, cause: DrainCause): Promise<{ completion: Completion; outcome: DrainOutcome }> {
135
+ if (!live.draining) live.draining = this.drain(live, cause);
136
+ return live.draining;
137
+ }
138
+
139
+ private async drain(live: LiveCollector, cause: DrainCause): Promise<{ completion: Completion; outcome: DrainOutcome }> {
135
140
  const outcome = await live.collector.drain(cause);
136
141
  unlinkPrivateFile(path.join(live.row.dir, 'collecting.json'));
137
142
  const files = this.finalizedFiles(live.row.dir);
@@ -163,9 +168,10 @@ export class CollectorHost {
163
168
  }
164
169
 
165
170
  async teardown(trigger: DrainCause['trigger'] = 'session-stop'): Promise<TeardownOutcome[]> {
166
- const stopping = this.cutoff(this.live.values());
171
+ const liveCollectors = [...this.live.values()];
172
+ this.cutoff(liveCollectors);
167
173
  const outcomes: TeardownOutcome[] = [];
168
- for (const live of stopping) {
174
+ for (const live of liveCollectors) {
169
175
  try {
170
176
  const drained = await this.drainOne(live, { trigger, clientUsable: this.clientUsable });
171
177
  outcomes.push({ status: 'drained', id: live.row.id, kind: live.row.kind, completion: drained.completion, dir: live.row.dir });
@@ -100,7 +100,7 @@ export async function stopComposedRecorder(opts: { sessionDir: string; recId?: s
100
100
  const host = await liveHost(opts.sessionDir);
101
101
  const row = motionHandle(host, recId);
102
102
  if (!row) throw new Error(`No live motion collector named "${recId}".`);
103
- const response = await sendHostRequest(host.socketPath, { type: 'collector-stop', nonce: host.nonce, id: recId }, 30_000);
103
+ const response = await sendHostRequest(host.socketPath, { type: 'collector-stop', nonce: host.nonce, id: recId }, 120_000);
104
104
  if (!response.ok) throw new Error(response.error ?? 'collector host refused to stop motion recording');
105
105
  if (getActiveSession()?.dir === opts.sessionDir && getActiveRecId() === recId) await clearActiveRecId();
106
106
  return finalFromMeta(row.dir, recId);