@getopencrawl/cli 0.4.9 → 0.4.11

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 CHANGED
@@ -47,17 +47,18 @@ or forbidden, 64 invalid arguments, 130 interrupted.
47
47
  ## Updates and distribution
48
48
 
49
49
  TypeScript compiled with Bun 1.3.14 produces a self-contained binary. Users need
50
- Node 22+ and npm for the bootstrap, never Bun. npm platform packages cover macOS
50
+ Node 22.15+ and npm for the bootstrap, never Bun. npm platform packages cover macOS
51
51
  arm64/x64, Linux glibc arm64/x64 and Windows x64. Alpine/musl and Windows arm64
52
52
  are not supported in v0. Platform packages are optional dependencies at the exact
53
53
  launcher version; there are no install scripts.
54
54
 
55
- After a command, the launcher starts a detached updater at most once per day.
55
+ Before ordinary commands, the launcher checks for updates at most once per day.
56
+ It awaits the update in the invoking process; hooks never check for updates.
56
57
  It checks the npm `next` release channel, installs into a private user-owned cache with
57
58
  npm integrity verification and install scripts disabled, then atomically selects
58
- the complete release. The next invocation uses it. No sudo or global-directory
59
+ the complete release. The command uses the selected binary. No sudo or global-directory
59
60
  writes are needed. Offline/update failures preserve the working copy and retry
60
- after 15 minutes. Concurrent updater processes share a lock; updates never
61
+ after 15 minutes. Concurrent invocations share an update lock; updates never
61
62
  downgrade. An unused installation updates when it is next invoked, not on a timer.
62
63
 
63
64
  `OPENCRAWL_NO_UPDATE=1` disables automatic updates for reproducible agent runs.
@@ -107,28 +108,42 @@ After signing in, run `opencrawl collect install` to install Claude Code and Cod
107
108
 
108
109
  Messages, tool arguments/results and attachments are collected with native session IDs and a stable installation ID. System/developer prompts are excluded. The hosted service determines the current team from login and validates human attribution. Existing collectors and queued personal-scope uploads automatically use that team after updating, without reinstalling or resetting. Self-hosted users can supply `OPENCRAWL_SESSIONS_TOKEN` and `--api` for the portable receiver. See [capture architecture and limits](../docs/session-capture.md).
109
110
 
110
- Hooks durably save their event locally and wake one detached worker. They do not
111
- read transcripts, wait for an upload lock, or make network requests. The worker
112
- parses permitted records, spools immutable batches, and keeps each batch until a
113
- verified durable receipt arrives. Concurrent workers exit without waiting;
114
- foreground flush/backfill waits for upload ownership rather than failing after
115
- 20 seconds. Backfill completes only after its queued data has receipts.
116
-
117
- No systemd, launchd, cron, or installed daemon is required on any supported OS.
118
- An updated worker retires legacy OpenCrawl timers when it first runs; reinstall
119
- also removes them. Existing cursors, queue, identity and hook dispatcher remain
120
- compatible. The worker polls known transcripts for 30 seconds after activity,
121
- including after Stop, and retries failed uploads with backoff while it can run.
122
- Later hooks and ordinary CLI invocations recover pending work and recheck known
123
- sources after process loss. An idle interval is not a final-session receipt:
124
- unsent data needs retained files and another execution opportunity. A destroyed
125
- sandbox cannot deliver bytes that never left it. `collect status` reports
126
- `pendingHooks` separately from queued upload batches. Uninstall preserves both.
111
+ Newly spooled events receive a versioned ID after attachment extraction and all
112
+ event normalization. The ID covers the source record/notice identity and every
113
+ field of the final JSON event. For the same source identity, equivalent final-event
114
+ JSON property ordering gives the same ID; changed file bytes, attachment
115
+ availability, or event metadata give a new ID.
116
+ Queued batches retain their existing bytes and IDs across upgrades and retries.
117
+ This upgrade preserves transcript cursors and does not trigger history backfill.
118
+ Explicitly rereading history captured before this identity format, replacing or
119
+ truncating a transcript, or recovering an older enqueue-before-checkpoint crash
120
+ can produce a one-time duplicate of an unchanged legacy event under a new ID.
121
+ Previous normalized payloads are not retained locally after acknowledgement, so
122
+ the collector cannot safely infer that an old raw-record ID represents the same
123
+ attachment content. It does not rewrite or choose between historical revisions.
124
+
125
+ The harness runs each hook asynchronously and owns its process. The hook saves
126
+ its notice locally, then competes for one nonblocking uploader lock. The winner
127
+ parses permitted transcripts, spools immutable batches and uploads; other hooks
128
+ exit after saving their notices. On Unix the shell and Node dispatcher replace
129
+ themselves with the native CLI using exec, keeping the harness-owned PID. Capture
130
+ never detaches a child, starts a service or launches a replacement worker.
131
+ Foreground flush/backfill waits for upload ownership and confirmed receipts.
132
+
133
+ No systemd, launchd, cron, or installed daemon is required. Reinstall removes
134
+ legacy OpenCrawl timers. Existing installations upgrade their owned hook settings
135
+ on the next CLI invocation; restart existing sessions to load async settings.
136
+ Legacy synchronous hooks remain local-only until those settings reload.
137
+ The uploader polls known transcripts for 30 seconds after activity, including
138
+ late writes after Stop, and retries failures with backoff while the harness lets
139
+ it run. Later hooks or explicit sync/flush recover retained work after process
140
+ loss. Harness teardown may cancel an async hook; no delivery is possible after
141
+ a sandbox destroys unsent files. An idle interval is not a final-session receipt.
142
+ `collect status` reports pending hooks separately from queued batches.
127
143
 
128
144
  Cloud image setup uses `collect install --harness claude-code --defer-auth
129
- --backfill skip`: it requires no runtime credential or network identity lookup.
130
- The first detached worker binds the authenticated identity and installation ID
131
- before parsing or uploading. First-run hooks remain network-nonblocking.
145
+ --backfill skip` without a runtime credential or identity lookup. The first async
146
+ hook binds identity before parsing or uploading, without blocking the agent.
132
147
 
133
148
  ### Optional history import
134
149
 
package/bin/opencrawl.cjs CHANGED
@@ -6,7 +6,7 @@ const fs = require('node:fs');
6
6
  const path = require('node:path');
7
7
  const os = require('node:os');
8
8
  const {createRequire} = require('node:module');
9
- const {spawn, spawnSync} = require('node:child_process');
9
+ const {spawnSync} = require('node:child_process');
10
10
  const {randomUUID} = require('node:crypto');
11
11
  const {parseArgs} = require('node:util');
12
12
  const ROOT = path.resolve(__dirname, '..');
@@ -123,14 +123,10 @@ async function installRelease(version, {registry = REGISTRY} = {}) {
123
123
  fs.renameSync(stage, destination);
124
124
  } finally { fs.rmSync(stage, {recursive: true, force: true}); }
125
125
  }
126
- function scheduleUpdate() {
126
+ async function automaticUpdate() {
127
127
  if (process.env.OPENCRAWL_NO_UPDATE === '1') return;
128
- try {
129
- if (read(path.join(cacheRoot(), 'update.json'))?.checkedAt > Date.now() - DAY) return;
130
- const child = spawn(process.execPath, [__filename, '--internal-update'], {detached: true, stdio: 'ignore', windowsHide: true});
131
- child.on('error', () => {});
132
- child.unref();
133
- } catch {} // updating can never make an ordinary command fail
128
+ try { await update(); }
129
+ catch { try { write(path.join(cacheRoot(), 'update.json'), {checkedAt: Date.now() - DAY + 15 * 60_000}); } catch {} }
134
130
  }
135
131
  async function main(args = process.argv.slice(2)) {
136
132
  if (args[0] === '--internal-update') {
@@ -140,9 +136,9 @@ async function main(args = process.argv.slice(2)) {
140
136
  const selected = activeRoot();
141
137
  // Delegate the launcher too, so later releases can fix bootstrap behavior.
142
138
  if (selected !== ROOT) {
143
- const delegated = spawnSync(process.execPath, [path.join(selected, 'bin/opencrawl.cjs'), ...args], {stdio: 'inherit', windowsHide: true, env: {...process.env, OPENCRAWL_LAUNCHER: process.env.OPENCRAWL_LAUNCHER || __filename, OPENCRAWL_NODE: process.execPath}});
144
- if (delegated.error) throw new Error('Could not start the cached launcher.');
145
- return delegated.signal === 'SIGINT' ? 130 : delegated.status ?? 1;
139
+ process.env.OPENCRAWL_LAUNCHER ||= __filename;
140
+ process.env.OPENCRAWL_NODE = process.execPath;
141
+ return require(path.join(selected, 'bin/opencrawl.cjs')).main(args);
146
142
  }
147
143
  let parsed;
148
144
  try { parsed = parseArgs({args, allowPositionals: true, options: {json: {type: 'boolean'}, 'no-browser': {type: 'boolean'}, api: {type: 'string'}, version: {type: 'boolean', short: 'v'}, help: {type: 'boolean', short: 'h'}}}); }
@@ -152,9 +148,9 @@ async function main(args = process.argv.slice(2)) {
152
148
  process.stdout.write(args.includes('--json') ? JSON.stringify({schemaVersion: 1, data}) + '\n' : data.busy ? 'An update is already running.\n' : data.updated ? `Installed opencrawl ${data.version}. Your next invocation will use it.\n` : `opencrawl ${data.version} is current.\n`);
153
149
  return 0;
154
150
  }
151
+ if (!(args[0] === 'collect' && ['hook','worker'].includes(args[1]))) await automaticUpdate();
155
152
  const root = activeRoot();
156
153
  const result = spawnSync(binary(root), args, {stdio: 'inherit', windowsHide: true, env: {...process.env, OPENCRAWL_LAUNCHER: process.env.OPENCRAWL_LAUNCHER || __filename, OPENCRAWL_NODE: process.execPath}});
157
- scheduleUpdate();
158
154
  if (result.error) throw new Error('Could not start the OpenCrawl binary. Reinstall @getopencrawl/cli.');
159
155
  if (result.signal === 'SIGINT') return 130;
160
156
  return result.status ?? 1;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getopencrawl/cli",
3
- "version": "0.4.9",
3
+ "version": "0.4.11",
4
4
  "description": "OpenCrawl CLI: sign in now, query the index as features become available",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",
@@ -17,17 +17,17 @@
17
17
  "directory": "cli"
18
18
  },
19
19
  "engines": {
20
- "node": ">=22"
20
+ "node": ">=22.15"
21
21
  },
22
22
  "publishConfig": {
23
23
  "access": "public",
24
24
  "registry": "https://registry.npmjs.org"
25
25
  },
26
26
  "optionalDependencies": {
27
- "@getopencrawl/cli-darwin-arm64": "0.4.9",
28
- "@getopencrawl/cli-darwin-x64": "0.4.9",
29
- "@getopencrawl/cli-linux-arm64": "0.4.9",
30
- "@getopencrawl/cli-linux-x64": "0.4.9",
31
- "@getopencrawl/cli-win32-x64": "0.4.9"
27
+ "@getopencrawl/cli-darwin-arm64": "0.4.11",
28
+ "@getopencrawl/cli-darwin-x64": "0.4.11",
29
+ "@getopencrawl/cli-linux-arm64": "0.4.11",
30
+ "@getopencrawl/cli-linux-x64": "0.4.11",
31
+ "@getopencrawl/cli-win32-x64": "0.4.11"
32
32
  }
33
33
  }