@getopencrawl/cli 0.1.1 → 0.2.1

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 (58) hide show
  1. package/README.md +75 -298
  2. package/bin/opencrawl.cjs +168 -0
  3. package/package.json +20 -14
  4. package/bin/opencrawl.mjs +0 -553
  5. package/browser.mjs +0 -166
  6. package/src/accounts.test.mjs +0 -223
  7. package/src/agent.mjs +0 -95
  8. package/src/agent.test.mjs +0 -58
  9. package/src/answer-contract.mjs +0 -112
  10. package/src/answer-view.mjs +0 -118
  11. package/src/browser.test.mjs +0 -72
  12. package/src/capture.mjs +0 -273
  13. package/src/capture.test.mjs +0 -171
  14. package/src/client.mjs +0 -183
  15. package/src/client.test.mjs +0 -34
  16. package/src/connect.mjs +0 -230
  17. package/src/connect.test.mjs +0 -131
  18. package/src/context.mjs +0 -34
  19. package/src/context.test.mjs +0 -32
  20. package/src/display.mjs +0 -4
  21. package/src/fixtures/letter-connections.json +0 -59
  22. package/src/fixtures/product-monitor-contract.json +0 -312
  23. package/src/fixtures/research-completion-hosted.json +0 -502
  24. package/src/fixtures/research-completion-v1.json +0 -58
  25. package/src/fixtures/research-completion-v2.json +0 -33
  26. package/src/fixtures/research-presentation-hosted.json +0 -720
  27. package/src/jobs.mjs +0 -117
  28. package/src/jobs.test.mjs +0 -67
  29. package/src/mcp-conversations.test.mjs +0 -80
  30. package/src/monitors.mjs +0 -146
  31. package/src/monitors.test.mjs +0 -62
  32. package/src/product-letter-capabilities.mjs +0 -71
  33. package/src/product-letter-read.mjs +0 -178
  34. package/src/product-monitor-login.test.mjs +0 -140
  35. package/src/product-monitors.mjs +0 -321
  36. package/src/product-monitors.test.mjs +0 -69
  37. package/src/research-agent-errors.test.mjs +0 -110
  38. package/src/research-agent-feedback.test.mjs +0 -28
  39. package/src/research-agent.mjs +0 -164
  40. package/src/research-agent.test.mjs +0 -231
  41. package/src/research-answer-contract.mjs +0 -3
  42. package/src/research-answer-contract.test.mjs +0 -14
  43. package/src/research-answer-view.mjs +0 -7
  44. package/src/research-answer-view.test.mjs +0 -27
  45. package/src/research-completion.mjs +0 -94
  46. package/src/research-completion.test.mjs +0 -110
  47. package/src/research-event.mjs +0 -39
  48. package/src/research-event.test.mjs +0 -57
  49. package/src/research-presentation.test.mjs +0 -56
  50. package/src/research.mjs +0 -141
  51. package/src/research.test.mjs +0 -97
  52. package/src/sessions.mjs +0 -165
  53. package/src/sessions.test.mjs +0 -151
  54. package/src/tasks.mjs +0 -115
  55. package/src/tasks.test.mjs +0 -83
  56. package/src/upload-commands.test.mjs +0 -140
  57. package/src/uploads.mjs +0 -141
  58. package/src/uploads.test.mjs +0 -64
package/README.md CHANGED
@@ -1,323 +1,100 @@
1
- # OpenCrawl CLI
1
+ # opencrawl
2
2
 
3
- Node.js 20 or later. Core CLI commands have no required package dependencies; browser capture uses optional Playwright.
3
+ The OpenCrawl CLI. This initial release installs an automatically updating
4
+ command and signs you in. **Index queries are not available yet.**
4
5
 
5
- The canonical deployment is live at `https://app.getopencrawl.com`; [STATUS.md](../STATUS.md) records its verification. Copy the exact immutable `npm install -g` command from **Connect an agent**, or use `packages.cli.url` from the [current manifest](https://app.getopencrawl.com/downloads/manifest.json) and use `opencrawl login --server https://app.getopencrawl.com`. Existing installations keep their original origin and account pins. Follow the [domain migration steps](../docs/cli.md#moving-to-the-canonical-domain), including flushing old capture and starting a new native chat after reinstalling.
6
-
7
- ## Start here: new to OpenCrawl
8
-
9
- No account, card or browser is needed. These commands create a free organization, describe the data you need to the OpenCrawl agent, and build a project that crawls and refreshes in the background. The same steps over HTTP or MCP are in [the agent guide](https://app.getopencrawl.com/start.md).
10
-
11
- ```sh
12
- npm install -g "$(curl -fsS https://app.getopencrawl.com/downloads/manifest.json | node -pe 'JSON.parse(require("fs").readFileSync(0)).packages.cli.url')"
13
- opencrawl agent signup --organization "Acme" --email you@acme.com --server https://app.getopencrawl.com
14
- opencrawl agent "Watch the planning commission agendas at https://example.gov/agendas daily, including linked PDFs, for data-center rezonings"
15
- opencrawl agent "answers to its questions" --session SESSION_ID
16
- opencrawl agent --session SESSION_ID --build
17
- ```
18
-
19
- - `signup` saves the organization key as the default CLI account and in an owner-only key file, and prints where, never the key. `--build` does the same for the project key.
20
- - The OpenCrawl agent checks every page with OpenCrawl's crawler before a plan is ready. A page it cannot read (blocked by robots.txt or by the site) is listed in `questions`; replace it or drop it.
21
- - Read a project's watches, jobs and research with its project account: `opencrawl --account PROJECT_ACCOUNT research list`, `research job FIRST_CHECK_JOB --wait`, `research search "question" --collection WATCH_ID`, `search "question"`. The organization key cannot read inside projects (404).
22
- - Research questions run after their watches' first check, then daily. To change a built project, send the change on the same session, then `--build` again: new watches and research are added and existing ones keep running.
23
- - For MCP or server code, use the project key file: `OPENCRAWL_API_KEY="$(cat KEY_FILE)"`. Never paste keys into chat or commit them.
24
-
25
- From this repository:
26
-
27
- ```sh
28
- npm install -g ./cli
29
- opencrawl login --server http://localhost:8000
30
- opencrawl search "What did we decide about onboarding?"
31
- opencrawl context "Write the launch plan" --budget 3000
32
- ```
33
-
34
- Create a workspace API key in **Connect an agent → API keys and developer tools → Create key** and paste it into the hidden login prompt. Production servers require HTTPS. The key is saved in `~/.config/opencrawl/config.json` with owner-only file permissions, bound to the verified server, user, and workspace. Server URLs must be origins, such as `https://crawl.example.com`, without a path, credentials, query, or fragment. Standard ports and trailing slashes normalize to the same origin. Do not put API keys in command-line arguments.
35
-
36
- ## Run hosted research
37
-
38
- With a matching server/client release, save the customer, goal, optional existing collection IDs and `schedule_minutes: 0` in `brief.json`. Save a random request key before sending:
39
-
40
- ```sh
41
- opencrawl research agent create --file brief.json --request-key SAVED_RANDOM_REQUEST_KEY --json
42
- opencrawl research agent-run AGENT_ID RUN_ID --wait --timeout 300 --json
43
- opencrawl research agent receipt --operation create --request-key SAVED_RANDOM_REQUEST_KEY --json
44
- ```
45
-
46
- Saved findings JSON preserves fetched-page provenance under `source_kind: "web_page"` and `page`. The page record contains the requested/final URLs, full extracted-page hash and supplied window bounds. Citation hashes/offsets still describe that window; add `page.start` to a citation offset for its full-page position. The CLI validates metadata and bounds, not full hashes from a partial quote. Older `web_search`/`indexed` findings remain readable without `page`, and these reads do not fetch the source URL.
47
-
48
- Replace placeholders with your saved key and returned IDs. The first command starts bounded external research; the second only reads its persisted run; the third recovers the original admission after uncertainty. Never create a new key because a response was lost. Findings retain source quotes, qualifications and gaps. Older relevance text remains visible; new runs omit the redundant selection label in text output and preserve it in JSON. Use the intended workspace account or customer child key. See the [brief format, SDK/MCP equivalents and recovery contract](../docs/research-agents.md).
49
-
50
- ### Explicit bounded completion policy
51
-
52
- Where the operator has enabled it, add `"research_policy":"completion_v1"` to an agent creation JSON file, or explicitly request it for one manual run:
53
-
54
- ```sh
55
- opencrawl research agent run AGENT_ID --revision REVISION --request-key SAVED_RANDOM_REQUEST_KEY --research-policy completion_v1 --json
56
- ```
57
-
58
- The setting applies to that admission, not the saved agent or daily schedule. Omit it to preserve the ordinary request. The server refuses an unavailable policy; the CLI never enables it, changes the key or submits a fallback request. Recover an uncertain admission with the original operation and request key, as above.
59
-
60
- Valid optional completion metadata adds fixed notices alongside all saved gaps. It describes bounded work and cannot certify whole-question completeness; malformed or absent optional metadata adds no notice and leaves findings, execution status and JSON unchanged. A recorded lower bound on completed model usage is not a total for an interrupted run. This CLI implementation does not establish that the policy is enabled on the selected server.
61
-
62
-
63
- For an operator-enabled final presentation run, explicitly select `completion_v2` in the same `research_policy` field (or CLI manual-run `--research-policy completion_v2`). Omitting the field keeps the existing request unchanged. The optional `usage.presentation` record reports bounded presentation status and known/uncertain usage; it does not certify completeness or replace findings, gaps, quotes, or `usage.answer_view` validation. SDK/shared readers expose it through `readResearchPresentation(run)` and ignore malformed optional metadata.
64
-
65
- For a validated `completion_v2` receipt, the default research-agent read wait ends 540 seconds after that wait began, allowing 480 seconds of server work plus settlement time. Repeated polls never renew it. An explicit SDK `maxWaitMs` or CLI `--timeout` still takes precedence; ordinary/v1 waits remain 300 seconds and individual default GET requests remain capped at 30 seconds. Durable customer-research poll batches and operation leases are unchanged. A timeout only stops local waiting: continue reading the same run, without another admission.
66
-
67
- Daily research is optional and uses a separate schedule revision. After the initial run finishes:
68
-
69
- ```sh
70
- opencrawl research agent schedule AGENT_ID --json
71
- opencrawl research agent schedule AGENT_ID daily --revision SCHEDULE_REVISION --json
72
- opencrawl research agent schedule AGENT_ID pause --revision SCHEDULE_REVISION --json
73
- opencrawl research agent schedule AGENT_ID resume --revision SCHEDULE_REVISION --json
74
- opencrawl research agent repeats AGENT_ID RUN_ID --json
75
- ```
76
-
77
- Enable/resume sets the first due time 24 hours later; it does not start a run immediately. Schedule pause stops future admissions and pending undispatched daily work, preserving manual and already dispatched daily runs. Ordinary agent pause also cancels unfinished work. Read current schedule state after an uncertain write; never replace its revision automatically. Inspect blocked and deferred reasons. Repeat references preserve every saved finding and distinguish `not_assessed` from `first_seen`; neither means a verified new business event. Updated clients are required for scheduled-agent reads and mixed agent lists because truthful daily cadence is `1440`, rejected by older strict Agent validators. Direct saved runs and historical manual receipts keep their original DTOs.
78
-
79
- Use admission history for recurring consumption rather than only the latest run:
80
-
81
- ```sh
82
- opencrawl research agent runs AGENT_ID --limit 25 --json
83
- opencrawl research agent runs AGENT_ID --limit 25 --cursor RETURNED_CURSOR --json
84
- opencrawl research agent-run AGENT_ID PENDING_RUN_ID --json
85
- ```
86
-
87
- Each history command reads exactly one page `{items,next_cursor,has_more}`. The nonnull opaque cursor persists even on an empty tail page; poll it again for new admissions. Default 25/max 100 items and a 1 MiB envelope cap may return fewer. Save run rows, pending IDs and cursor atomically with one serialized consumer per agent, then read pending IDs until terminal. This is an admission feed, not a completion-update feed; never rerun work to recover a read failure. Whole-page denial on inaccessible historical evidence leaves your checkpoint unchanged and needs operator attention. Preserve all terminal statuses, including partial with no findings and unknown failures. The SDK [durable polling example](../sdk/README.md#poll-saved-daily-findings-from-your-server) describes the persistence contract.
88
-
89
-
90
- ## Choose a workspace account
91
-
92
- Each successful login selects a default account and retains previously saved accounts. List their non-secret identifiers with `profiles`, then use `--account` to select one explicitly:
93
-
94
- ```sh
95
- opencrawl profiles
96
- opencrawl search "What did we decide?" --account PROFILE_ID
97
- opencrawl install claude-code --account PROFILE_ID
98
- opencrawl logout --account PROFILE_ID
99
- ```
100
-
101
- Replace `PROFILE_ID` with an identifier returned by `profiles`. Replacing a key for the same server, user, and workspace keeps that identifier, so installed clients can continue using the account. A different workspace has its own account even on the same server.
102
-
103
- `configure --server`, `--server`, and `OPENCRAWL_URL` can change an unpinned command's destination, but never carry a saved key to a different origin. Sign in explicitly to authorize that destination. `--account` pins both server and account: it ignores ambient `OPENCRAWL_URL` and `OPENCRAWL_API_KEY`, and rejects a conflicting `--server`.
104
-
105
- `OPENCRAWL_URL`, `OPENCRAWL_API_KEY`, and `OPENCRAWL_CONFIG_DIR` support automation and isolated configurations. An explicitly supplied environment key authorizes the selected destination for an unpinned command; set its intended server alongside it. An empty environment key fails instead of falling back to a saved key. Environment keys are not saved unless `login` succeeds. `--config-dir DIRECTORY` selects a configuration directory explicitly.
106
-
107
- Older configurations stored an unbound key. They now require a fresh `login` with an explicitly supplied key; the CLI does not guess which server originally issued it. A failed login preserves the old file. After successful login, rerun each project's installer so existing MCP and hook entries gain account pins. An old unpinned MCP entry using a saved key stops with a reinstall message; an old prompt hook quietly skips retrieval until reinstalled.
108
-
109
- ## Connect and index an app
110
-
111
- ```sh
112
- opencrawl connect --query GitHub
113
- opencrawl connect start github --account PROFILE_ID
114
- # Open the returned URL yourself and finish provider authorization.
115
- opencrawl connect check SOURCE_ID --account PROFILE_ID
116
- opencrawl connect configure SOURCE_ID --repository OWNER/REPOSITORY \
117
- --max-pages 1 --include-comments false --sync-every 0 --account PROFILE_ID
118
- opencrawl connect sync SOURCE_ID --account PROFILE_ID
119
- opencrawl jobs wait JOB_ID --account PROFILE_ID
120
- opencrawl connect setup SOURCE_ID --account PROFILE_ID
121
- ```
122
-
123
- Use IDs returned by your own commands. `connect TOOLKIT` remains a start alias. Authorization, required source scope, background processing, listing coverage and searchable document count are separate states. `check` can queue indexing immediately when the scope is already configured. `configure` changes only explicit settings and can cancel an active sync; it never changes provider account identity. `sync` is explicit, can resume a paused source, and returns an already-active sync instead of submitting another. `--sync-every 0` is manual; a positive interval enables scheduled syncs. Unsupported indexing adapters remain identified in setup even when provider authorization works.
124
-
125
- Private nonce receipts are saved atomically with owner-only permissions under `connections/` in the selected config directory, bound to verified server/user/workspace identity. A logged-out browser callback can be completed with `connect check`; nonce values are never printed or passed in arguments. Do not share authorization URLs. `status` and `setup` only read server state. Unknown writes are never automatically repeated. If the initial link response was lost, inspect `connect status`, select the intended source, and explicitly `connect reconnect SOURCE_ID`; its missing nonce cannot be recovered or guessed. If no source ID was returned and you cannot select an intended source, status followed by `start TOOLKIT --acknowledge-unknown` permits one new explicit authorization that may replace or create a pending attempt. After an unknown sync, inspect jobs before using `sync --acknowledge-unknown` to request another potentially repeated run. See [connection recovery and bounds](../docs/cli.md#upload-files-and-connect-sources).
126
-
127
- ## Import conversations
128
-
129
- ```sh
130
- opencrawl import conversations.json --provider chatgpt
131
- opencrawl import chatgpt-export.zip --provider chatgpt
132
- opencrawl import claude-export.json --provider claude
133
- opencrawl import session.jsonl --provider claude-code
134
- opencrawl import rollout.jsonl --provider codex
135
- opencrawl import notes.md
136
- opencrawl watch ./exports --provider auto
137
- ```
138
-
139
- The watcher uploads only JSON/JSONL files below the directory you explicitly choose, skips hidden paths and symbolic links, and records file signatures after upload acceptance to avoid repeats. Use `--once` for a single pass. Queued imports return a durable upload receipt; use `--wait` or inspect the job for parsing completion and errors. Small plain-text imports normally index immediately. If parsing fails, retry that job or explicitly import the file again: an unchanged file is not automatically reuploaded. Watcher state is bound to the verified account, folder, provider, source and visibility; key rotation preserves that identity. Changing source or visibility is a distinct explicit watched scope and can import the same files there. Older unscoped state is preserved and requires review before starting a new scoped import. Pending request keys are saved before submission; uncertain acceptance is looked up after restart and never triggers automatic retransmission. Explicitly retry a prepared/retryable receipt with `import --request-key` and its original file/scope. Expired or unresolved watcher entries remain blocked until reviewed. The watcher holds a per-folder lock and stays in the foreground until Ctrl+C; it does not install a background service. Exported ChatGPT branches and timestamps are preserved. Attachments are labeled in conversation text; their binary content needs a separate upload.
140
-
141
- JSON, JSONL, and ZIP chat exports upload their original bytes as multipart files. ZIP exports are unpacked by the server's bounded archive parser and must contain `conversations.json` or `chat.json`. JSON, JSONL, and plain-text imports are limited to 25,000,000 bytes; ZIP uploads are limited to 200 MiB and have additional expanded-size safety limits on the server. Plain text that would exceed the 8 MiB JSON request limit also uses the background file-upload pipeline. Use `--source SOURCE_ID` or `--visibility workspace` when needed; imports default to private.
142
-
143
- ## Wait for indexing and recover acceptance
6
+ Once published:
144
7
 
145
8
  ```sh
146
- opencrawl ingest product-spec.pdf --wait --timeout 300 --json
147
- opencrawl import conversations.json --provider chatgpt --wait
148
- opencrawl jobs list --status active
149
- opencrawl jobs show JOB_ID --json
150
- opencrawl jobs wait JOB_ID --timeout 300 --json
151
- opencrawl uploads lookup REQUEST_KEY --json
152
- opencrawl uploads show RECEIPT_ID --json
9
+ npm install -g @getopencrawl/cli@next
10
+ opencrawl login
11
+ opencrawl status --json
153
12
  ```
154
13
 
155
- Copy IDs and request keys from your own receipts. Queued uploads hash and prepare a checked descriptor, then send file bytes at most once per invocation. The request key is printed before sending; after a dropped response, only a lookup is attempted. Keep an unconfirmed request key instead of starting a duplicate upload. Explicitly retry a prepared/retryable receipt with the same file, source, visibility and provider plus `--request-key REQUEST_KEY`. Accepted keys return their existing job. Receiving requests are only checked, and expired requests require a new explicit upload intent. Small text normally stays synchronous; `--request-key` selects the queued receipt pipeline.
156
-
157
- `--wait` and `jobs wait` stop after 1–3,600 seconds (default 300), measured after the upload attempt. The separate transfer deadline is five minutes. Ctrl+C stops local waiting, not server work. Only completed jobs exit successfully from wait; failure, cancellation, discard, timeout or access loss keep the known IDs in the unsuccessful receipt. No automatic cancellation, parsing retry, or reupload occurs. `--json` leaves stdout as one JSON result, with progress on stderr. Source listing coverage, file parsing and provider cleanup remain separate from an individual job's completion. Human search/job/conversation displays escape terminal controls; JSON and context export bytes are unchanged.
158
-
159
- ## Read imported conversations
14
+ For SSH, remote machines and coding agents:
160
15
 
161
16
  ```sh
162
- opencrawl sessions --help
163
- opencrawl sessions list --limit 20
164
- opencrawl sessions read DOCUMENT_ID --branch selected --limit 30
165
- opencrawl sessions read DOCUMENT_ID --branch selected --cursor OPAQUE_CURSOR
166
- opencrawl sessions export DOCUMENT_ID --revision REVISION --branch selected \
167
- --selection t3:120:400 --budget 8000 > conversation-context.txt
17
+ opencrawl login --no-browser --json
168
18
  ```
169
19
 
170
- Human-readable pages show terminal controls as visible escapes; JSON and exported context retain source text. Use the document ID from the list, then copy the revision and exact fragment selectors returned by `read`. A selector identifies a visible character range; do not guess it from a turn number or select unseen text. Every export requires an explicit revision, branch, one or more ranges (up to 20), and byte budget. Alternate branches require `--branch all`; missing branch information remains labeled unknown. Old records with no validated structured turns can be read as plain text but cannot be exported as guessed turn ranges.
171
-
172
- Export stdout contains only the server's cited reference block, without an added newline, bounded to the requested 256–32,000 UTF-8 bytes including headers and wrapper. Omitted/truncated selections are reported on stderr. `--json` preserves the complete selection receipt; that JSON envelope is outside the context byte budget. Permissions and revisions are checked again at export. A conflict requires a fresh read, not an automatic retry using stale ranges. `--revision` is the conversation version; global `--version` remains the package version. All commands honor `--account`, `--server` and `--config-dir` account binding.
173
-
174
- This exports imported evidence for a task; it does not continue a hosted conversation or restore hidden model state. See [the CLI guide](../docs/cli.md#read-and-export-selected-conversation-evidence) for pagination and exact-range semantics.
175
-
176
- ## Ingest documents and recordings
177
-
178
- ```sh
179
- opencrawl ingest product-spec.pdf
180
- opencrawl ingest customer-call.m4a
181
- opencrawl ingest demo.mp4 --source SOURCE_ID
182
- opencrawl ingest forecast.xlsx --visibility workspace
183
- ```
184
-
185
- Uploads stream binary files up to 200 MiB into the background parsing queue. Visibility defaults to private. The server selects document parsing, OCR, or transcription and records progress in **Activity**; available formats and advanced parsing depend on server configuration. `import` also recognizes common document and media extensions and routes them through this upload pipeline. Use `ingest` for documents and media, and `import --provider` for conversation exports.
186
-
187
- ## Optional concise answers
188
-
189
- When a matching server supplies a valid optional concise answer, the human
190
- research-run display prints its question-ordered sections before the exact
191
- findings. Every original qualification, citation, quote and gap remains in the
192
- output. Missing or invalid concise metadata falls back to the existing display;
193
- it never triggers another request. `--json` retains the original run exactly.
194
- Concise prose has a separate fallible review and is not a completeness claim.
195
- Source-scoped unknown and missing delivered answers remain distinct.
20
+ Open the printed URL on any machine, confirm the displayed code and sign in.
21
+ The CLI waits for approval. It needs no client secret, inbound port, localhost
22
+ callback or browser on the machine running the command. No authentication runs
23
+ during package installation. Installation does not configure agents or telemetry.
196
24
 
197
- ## Feedback on research findings
198
-
199
- With a feedback-capable server and CLI, read the feedback on an exact saved finding before changing it:
200
-
201
- ```sh
202
- opencrawl research agent feedback AGENT_ID RUN_ID FINDING_ID --json
203
- opencrawl research agent feedback AGENT_ID RUN_ID FINDING_ID set --revision FEEDBACK_REVISION --rating useful --note 'Prioritize pricing changes for this customer.' --json
204
- opencrawl research agent feedback AGENT_ID RUN_ID FINDING_ID delete --revision LATEST_FEEDBACK_REVISION --json
205
- ```
206
-
207
- Ratings are `useful`, `irrelevant`, `incorrect` or `outdated`. Writes send once; after a conflict or uncertain response, read the same finding's feedback and review its value/revision before another write. Feedback guides later research selection without editing the saved finding, verifying facts or starting another run. See [current release status](../STATUS.md) and the [feedback contract](../docs/research-agents.md#finding-feedback-and-bounded-memory).
208
-
209
- ## Bring context into agents
210
-
211
- Run from the project where you want the integration:
212
-
213
- ```sh
214
- opencrawl install claude-code
215
- opencrawl install cursor
216
- opencrawl install codex
217
- ```
218
-
219
- For new private conversation capture in this project, use `opencrawl install claude-code --capture` or `opencrawl install codex --capture`. This records future user prompts and final assistant messages from client hooks; it never scans old history, transcripts, tools or other projects. Codex requires project trust and explicit hook review with `/hooks`. Restart the client, then inspect delivery:
220
-
221
- ```sh
222
- opencrawl capture status --provider codex --json
223
- opencrawl capture flush --provider codex --timeout 10 --json
224
- opencrawl capture pause --provider codex
225
- opencrawl capture resume --provider codex
226
- ```
25
+ `whoami` verifies your identity with the service. `status` reports the version,
26
+ sign-in state and unavailable index. `logout` removes this deployment's local
27
+ credentials; it does not revoke other devices or the server session. Use your
28
+ identity provider's session controls to revoke access remotely.
227
29
 
228
- Use `--provider claude-code` for Claude Code. The first acknowledged message is immediately indexed for keyword search; semantic readiness is reported separately. Queued immutable events survive a lost response in an owner-only local spool and replay idempotently. Current-read failures are distinct from historical acknowledgements. Deleted sessions or conflicting event identities are blocked rather than silently recreated. Reinstalling preserves existing capture hooks; pause explicitly to stop new collection. See [capture limits, recovery and sandbox setup](../docs/cli.md#capture-new-project-conversations).
30
+ `--api https://your-deployment.example` or `OPENCRAWL_API_URL` selects a deployment.
31
+ Credentials stay separate for each origin. The deployment publishes the public
32
+ OAuth configuration at `/.well-known/opencrawl-cli`; the CLI has no provider SDK,
33
+ cloud credential, server secret or dependency on the hosted shell. Only HTTPS
34
+ is accepted, except HTTP loopback for local development.
229
35
 
230
- Installers normally use a saved, verified account; the explicit environment-key option below supports sandbox runtimes. They preserve existing settings, back up files before changes, and are idempotent. Each generated entry pins the account identifier, server, and absolute configuration directory, so later logins, default changes, and shell environment changes do not retarget that project. Saved credentials remain in the OpenCrawl configuration; they are not copied into project files. Rerun the installer with `--account` to choose a different account. If its saved key is removed, the integration fails without falling back to another account. Restart the client. Codex project settings require a trusted project.
36
+ Credentials live in `$XDG_CONFIG_HOME/opencrawl` (default `~/.config/opencrawl`),
37
+ with directory mode 0700 and file mode 0600 on POSIX. `OPENCRAWL_CONFIG_HOME`
38
+ overrides the configuration base directory. Refresh-token rotation uses a
39
+ per-deployment process lock and atomic writes. Tokens never appear in output.
40
+ Windows uses the user's profile directory and its inherited filesystem ACLs.
231
41
 
232
- Without `--capture`, Claude Code receives a `UserPromptSubmit` hook that retrieves relevant knowledge for each prompt and a local MCP connection. That hook sends up to the first 1,000 prompt characters for retrieval, waits up to 3.5 seconds, and fails open if retrieval fails. It does not upload the conversation. With `--capture`, Claude Code and Codex retrieve previous context before capturing the current prompt; Stop captures the final response and does not inject context. Explicit historical imports and `watch` remain available separately.
42
+ `--json` returns `{schemaVersion:1,data:...}` or
43
+ `{schemaVersion:1,error:{code,message}}`. Progress and approval instructions stay
44
+ on stderr. Exit codes: 0 success, 1 service/network failure, 2 sign-in required
45
+ or forbidden, 64 invalid arguments, 130 interrupted.
233
46
 
234
- `context` reports temporary keyword fallback or partial semantic coverage on stderr, keeping stdout for cited evidence within `--budget` UTF-8 bytes. `--json` preserves the complete response and coverage metadata. The Claude Code hook includes coverage warnings in its reference block and reserves space for them: its complete injected text, including the wrapper, is limited to 1,500 UTF-8 bytes.
47
+ ## Updates and distribution
235
48
 
236
- Cursor and Codex receive MCP tools through the API-key bridge. Their agents can call `opencrawl_search`, `opencrawl_context`, `opencrawl_list_conversations`, `opencrawl_read_conversation`, and `opencrawl_conversation_context`. API-key connections also expose `opencrawl_remember`; read-only OAuth connections cannot save notes, capture sessions or administer the workspace. Prompt retrieval is installed for Claude Code and also for Codex with `--capture`. Hosted ChatGPT and Claude history require user exports; installing a CLI does not grant access to those histories. Use the [hosted chat onboarding guide](../docs/onboarding.md) for OAuth setup; ChatGPT does not use the manual API-key header flow. Claude should select automatic client registration because OpenCrawl does not implement client metadata documents.
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
51
+ arm64/x64, Linux glibc arm64/x64 and Windows x64. Alpine/musl and Windows arm64
52
+ are not supported in v0. Platform packages are optional dependencies at the exact
53
+ launcher version; there are no install scripts.
237
54
 
238
- In disposable sandboxes, install inside the runtime and persist a private OpenCrawl configuration volume for queued events. An explicit `--credential-env OPENCRAWL_API_KEY --server ORIGIN` installer option verifies and pins the environment key's identity without copying the key to project files. Keep that environment variable available to the client and review hooks normally; no trust bypass is installed. Capture never needs native transcript persistence or access to the user's home directory.
55
+ After a command, the launcher starts a detached updater at most once per day.
56
+ It checks the npm `next` release channel, installs into a private user-owned cache with
57
+ 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
+ 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
+ downgrade. An unused installation updates when it is next invoked, not on a timer.
239
62
 
240
- Conversation tools let an agent discover imported chats, inspect an explicit `selected` or `all` branch, then retrieve exact character ranges using the returned version. Reads return at most two fragments per call; opaque cursors continue the same version and branch. Context requires 1–20 ranges and an explicit 256–32,000 UTF-8 byte budget including citation headers and wrapper. Missing permissions or a changed version require a fresh read, never a silent fallback to another branch. No tool selects unseen turns or resumes a hosted chat. The bridge forwards the server tools unchanged; refresh the client's tool list after a server update. Only a nonempty selected context response counts as successful conversation evidence retrieval for activation.
63
+ `OPENCRAWL_NO_UPDATE=1` disables automatic updates for reproducible agent runs.
64
+ `opencrawl update [--json]` explicitly updates from that same channel. Cached releases live under
65
+ `$XDG_CACHE_HOME/opencrawl` (default `~/.cache/opencrawl`); previous releases remain
66
+ available for recovery. Install a newer npm version directly to override an older
67
+ cached version. Set the opt-out and remove `current.json` to return to the npm
68
+ installed version. Standalone binary installation/self-update is not offered in
69
+ this release; distribute the npm command.
241
70
 
242
- ## Other commands
71
+ ## Development and release
243
72
 
244
73
  ```sh
245
- opencrawl connect
246
- opencrawl connect gmail
247
- opencrawl connect --query Salesforce
248
- opencrawl status
249
- opencrawl doctor
250
- opencrawl logout
74
+ bun install --cwd cli --frozen-lockfile --ignore-scripts
75
+ bun run --cwd cli typecheck
76
+ bun test cli
77
+ cd cli
78
+ bun run src/cli.ts --help
79
+ bun scripts/build.ts --current # local smoke
80
+ bun scripts/build.ts # all five platform packages
251
81
  ```
252
82
 
253
- `connect` without a toolkit discovers the live Composio catalogue. Choose a returned slug to get its hosted authorization link. An authorized source still needs the relevant indexing adapter; the catalogue reports this separately.
254
-
255
- `logout` removes the default account's saved local key, or the account selected with `--account`. Other saved accounts remain available. It does not revoke the key on the server, unset environment credentials, stop watchers, or remove browser profiles/client settings. Revoke a key in **Connect an agent → API keys and developer tools**. `doctor` checks server health and API-key access, not provider ingestion or client installation; it exits unsuccessfully when the selected destination has no authorized key.
256
-
257
- `opencrawl mcp --account PROFILE_ID` is a newline-framed JSON-RPC stdio bridge to the authenticated `/mcp` endpoint. Standard output contains protocol messages only. Manual automation with an explicit environment key can also run `opencrawl mcp`; saved credentials require an explicit account pin.
258
-
259
- The CLI package uses an immutable `/downloads/sha256/<digest>/opencrawl-cli.tgz` URL supplied by the deployment. The old rolling address remains a compatibility alias. See [package installation and upgrades](../docs/client-packages.md); no public npm registry release is assumed.
260
-
261
- ## Capture authorized browser pages
262
-
263
- ```sh
264
- opencrawl browser setup
265
- opencrawl browser login https://your-company.my.salesforce.com --profile salesforce
266
- opencrawl browser capture https://your-company.my.salesforce.com/record-page --profile salesforce --interactive
267
- opencrawl browser crawl https://crm.example/accounts/ --profile crm --path-prefix /accounts/ --max-pages 20 --headless
268
- ```
269
-
270
- Browser support uses the optional Playwright package. `setup` installs its Chromium runtime. Login happens in a separate OpenCrawl browser profile; it does not attach to your everyday browser. Each profile is bound to one origin. You enter credentials yourself, and local session state stays inside your OpenCrawl configuration directory.
271
-
272
- Capture imports visible page text with the original URL and private visibility. `--urls pages.txt` accepts an explicit list of URLs on the same origin. Crawl follows links only inside the chosen origin and path prefix, with a bounded page count; it does not click controls. During capture, network writes are blocked except explicitly read-only GraphQL queries. For applications whose rendering uses other POST reads, use `--interactive`: render the page manually, then press Enter to capture its current content. Some applications require a dedicated adapter for complete automated extraction.
273
-
274
- `--screenshots ./captures` saves private screenshots with form fields masked. Add `--ocr` to queue those screenshots through the server's upload/OCR pipeline. OCR completion appears in background jobs and requires the server's OCR dependencies. Screenshots and the browser profile may contain sensitive business data; keep them outside version control. `--headless` is available for previously authenticated unattended reads.
275
-
276
- For exact project files, credential/workspace selection, import recovery, and manual removal instructions, see [the CLI guide](../docs/cli.md).
277
-
278
- ### Saved questions, current evidence
279
-
280
- Create a private task in OpenCrawl, then reuse its saved question and source scope:
281
-
282
- ```sh
283
- opencrawl tasks list
284
- opencrawl tasks show task_0123456789abcdef0123456789abcdef
285
- opencrawl tasks run task_0123456789abcdef0123456789abcdef --revision 2 > context.txt
286
- ```
287
-
288
- `--revision` pins a reviewed positive integer revision; omit it to run the current
289
- descriptor. Selected-source access loss blocks the run instead of widening its
290
- scope. Runs retrieve current indexed evidence without syncing sources or caching
291
- answers. The saved byte budget is at most 16,384 UTF-8 bytes. Plain stdout is exact
292
- context; notices go to stderr. `--json` returns the complete receipt or an error
293
- object. An empty successful context means no current evidence was returned.
294
- Read-only MCP clients can use `opencrawl_list_saved_tasks` and
295
- `opencrawl_run_saved_task` with the same account and permissions.
296
-
297
- ## Monitor selected public pages
298
-
299
- Create a monitor in OpenCrawl, then inspect its exact page observations:
300
-
301
- ```sh
302
- opencrawl monitors list
303
- opencrawl monitors show SOURCE_ID
304
- opencrawl monitors check SOURCE_ID --generation N
305
- opencrawl monitors changes --source SOURCE_ID
306
- opencrawl monitors context CHANGE_ID --revision VERSION_HASH --budget 4096 > change-context.txt
307
- ```
308
-
309
- Use IDs, the generation and immutable version from your own receipts. A check only queues work; follow its job and inspect per-page outcomes. Static public pages are checked without sign-in, JavaScript or link discovery. Failed checks and missing pages never prove removals or business events. Latest attempt and last good observation are shown separately.
310
-
311
- Context exports one exact retained before/after comparison within 256–16,384 UTF-8 bytes. Stdout is unchanged evidence; omissions, truncation and warnings go to stderr. `--json` preserves the complete receipt. A version/access conflict stops without a newer substitution or retry. Changes support bounded `--limit` and opaque `--cursor` pagination. `opencrawl monitors --help` lists exact options; global account pins remain enforced.
312
-
313
- The server also exposes read-only MCP tools `opencrawl_list_monitored_changes` and `opencrawl_monitor_change_context`. Context takes 1–3 explicit change ID/version selections and a byte budget. These retrieve observed historical evidence; saved-task searches use current indexed observations instead. Neither operation infers when a remote author changed a page or whether a job opened or closed.
314
-
315
- ## Private product-screen monitoring (source feature)
316
-
317
- With a matching server/client release, `opencrawl browser monitor --help` provides local login, masked preview, one-shot check, foreground watch, exact-receipt recovery and retained comparison/image reads. Cookies stay in an account-bound local profile. A watch runs only while the local process and computer remain available; it is not a hosted browser schedule. Unknown capture uploads stop and recover by GET without automatic resend. See [the complete CLI workflow and limits](../docs/product-monitoring-cli.md). Local synthetic tests do not establish compatibility with a real third-party account or current deployment availability.
318
-
319
- ### Local Letter denial diagnostics
83
+ The `next` dist-tag keeps this new client separate from the existing v1 `latest`
84
+ release. Publishing must never move that v1 tag. The dedicated CLI workflow builds and signs on macOS, tests those same
85
+ binaries on macOS/Linux/Windows, then publishes the five platform packages first, then publishes `@getopencrawl/cli` last. A failed platform
86
+ publication cannot advertise an incomplete stable launcher. Retry skips already
87
+ published immutable versions. Increment `cli/package.json` for every release.
88
+ The main core package retains its separate private GitHub Packages release.
320
89
 
321
- The private local diagnostic report may include `blocked_requests` version1: up to eight fatal-denial samples with finite method, rejection reason, origin class and recognized operation labels, plus a SHA-256 fingerprint of the parsed origin/path. Queries, fragments, URL credentials, bodies, headers, cookies, arbitrary origins/paths and account markers are not retained. The first eight distinct samples are kept; repeated and omitted observations saturate at64. No operation is newly permitted or treated as harmless because it appears in diagnostics. Existing allow/deny decisions, required reads and deadlines remain authoritative.
90
+ Create the free npm `getopencrawl` organization and grant the publisher access to
91
+ its packages before the first release. Bootstrap publication uses an npm token
92
+ or interactive npm authentication; configure npm trusted publishing for each
93
+ package afterwards. A public npm package does not require a public GitHub repo,
94
+ but its tarballs and binaries are public. Review the package allowlist with
95
+ `npm pack --dry-run` before publishing. The current construction license remains
96
+ UNLICENSED pending the repository's separate public-license decision.
322
97
 
323
- Fingerprints are query-free and pseudonymous, not anonymous; guessed low-entropy paths can be compared offline. URL parser normalization is a diagnostic grouping, never an authorization rule. A hash can match a separately inspected route, but does not identify an unknown operation by itself. Diagnostic reports remain local0600 files under the existing operation-history limits, at most16KiB each; nothing is added to capture uploads or command error text. No generic POST body operation is inferred or logged.
98
+ Prior art: OnePatch's npm/native launcher, device login, automatic refresh and
99
+ background upgrades; Glen's small npm entry point for agents. No query command,
100
+ agent hooks, MCP integration or background daemon is included in this release.
@@ -0,0 +1,168 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+ // npm owns the bootstrap. Updates live in a private per-user cache so global
4
+ // install permissions and running processes never prevent the next upgrade.
5
+ const fs = require('node:fs');
6
+ const path = require('node:path');
7
+ const os = require('node:os');
8
+ const {createRequire} = require('node:module');
9
+ const {spawn, spawnSync} = require('node:child_process');
10
+ const {randomUUID} = require('node:crypto');
11
+ const {parseArgs} = require('node:util');
12
+ const ROOT = path.resolve(__dirname, '..');
13
+ const PACKAGE = '@getopencrawl/cli';
14
+ const REGISTRY = 'https://registry.npmjs.org';
15
+ const CHANNEL = 'next';
16
+ const DAY = 24 * 60 * 60 * 1000;
17
+ const cacheRoot = () => path.join(process.env.XDG_CACHE_HOME || path.join(os.homedir(), '.cache'), 'opencrawl');
18
+ const stable = value => typeof value === 'string' && /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/.test(value);
19
+ function compare(a, b) {
20
+ if (!stable(a) || !stable(b)) throw new Error('Invalid release version.');
21
+ const left = a.split('.').map(BigInt), right = b.split('.').map(BigInt);
22
+ for (let i = 0; i < 3; i++) if (left[i] !== right[i]) return left[i] > right[i] ? 1 : -1;
23
+ return 0;
24
+ }
25
+ function read(file) { try { return JSON.parse(fs.readFileSync(file, 'utf8')); } catch { return null; } }
26
+ function write(file, value) {
27
+ fs.mkdirSync(path.dirname(file), {recursive: true, mode: 0o700});
28
+ const tmp = `${file}.${randomUUID()}.tmp`;
29
+ try { fs.writeFileSync(tmp, JSON.stringify(value) + '\n', {mode: 0o600, flag: 'wx'}); fs.renameSync(tmp, file); }
30
+ finally { try { fs.unlinkSync(tmp); } catch {} }
31
+ }
32
+ function target(platform = process.platform, arch = process.arch) {
33
+ const key = `${platform}-${arch}`;
34
+ if (!['darwin-arm64', 'darwin-x64', 'linux-arm64', 'linux-x64', 'win32-x64'].includes(key)) throw new Error(`No OpenCrawl binary for ${key}.`);
35
+ return key;
36
+ }
37
+ function binary(root) {
38
+ const key = target();
39
+ const localRequire = createRequire(path.join(root, 'package.json'));
40
+ let manifest;
41
+ try { manifest = localRequire.resolve(`${PACKAGE}-${key}/package.json`); }
42
+ catch { throw new Error('Missing platform package. Reinstall with npm install -g @getopencrawl/cli@next --include=optional.'); }
43
+ const pkg = read(manifest);
44
+ if (pkg?.name !== `${PACKAGE}-${key}` || pkg.version !== read(path.join(root, 'package.json'))?.version) throw new Error('The platform package version does not match the CLI.');
45
+ const file = path.join(path.dirname(manifest), 'bin', process.platform === 'win32' ? 'opencrawl.exe' : 'opencrawl');
46
+ if (!fs.existsSync(file)) throw new Error('The OpenCrawl platform binary is missing. Reinstall @getopencrawl/cli.');
47
+ return file;
48
+ }
49
+ function activeRoot(root = ROOT) {
50
+ const pkg = read(path.join(root, 'package.json'));
51
+ const current = read(path.join(cacheRoot(), 'current.json'));
52
+ if (stable(current?.version) && stable(pkg?.version) && compare(current.version, pkg.version) > 0) {
53
+ const cached = path.join(cacheRoot(), 'releases', current.version, 'node_modules', '@getopencrawl', 'cli');
54
+ if (read(path.join(cached, 'package.json'))?.version === current.version) {
55
+ try { binary(cached); return cached; } catch {} // a damaged cache must not break the installed copy
56
+ }
57
+ }
58
+ return root;
59
+ }
60
+ function npmCli(nodeExecutable = process.execPath) {
61
+ const candidates = [process.env.npm_execpath, path.join(path.dirname(nodeExecutable), 'node_modules/npm/bin/npm-cli.js'), path.resolve(path.dirname(nodeExecutable), '../lib/node_modules/npm/bin/npm-cli.js')];
62
+ for (const directory of (process.env.PATH || '').split(path.delimiter)) candidates.push(path.join(directory, 'npm'));
63
+ for (const file of candidates) {
64
+ if (!file) continue;
65
+ try { const resolved = fs.realpathSync(file); if (path.basename(resolved) === 'npm-cli.js') return resolved; } catch {}
66
+ }
67
+ throw new Error('npm is required for updates. Install Node.js with npm and retry.');
68
+ }
69
+ function acquireUpdateLock() {
70
+ const lock = path.join(cacheRoot(), 'update.lock');
71
+ fs.mkdirSync(cacheRoot(), {recursive: true, mode: 0o700});
72
+ try { const fd = fs.openSync(lock, 'wx', 0o600); fs.writeFileSync(fd, String(process.pid)); fs.closeSync(fd); }
73
+ catch (error) {
74
+ if (error.code !== 'EEXIST') throw error;
75
+ const pid = Number(fs.readFileSync(lock, 'utf8'));
76
+ if (pid > 0) {
77
+ try { process.kill(pid, 0); return null; }
78
+ catch (check) { if (check.code !== 'ESRCH') return null; }
79
+ } else if (Date.now() - fs.statSync(lock).mtimeMs < 5 * 60_000) return null;
80
+ try { fs.unlinkSync(lock); } catch {}
81
+ return acquireUpdateLock();
82
+ }
83
+ return () => fs.unlinkSync(lock);
84
+ }
85
+ async function update({force = false, root = ROOT, fetcher = fetch, install = installRelease} = {}) {
86
+ const unlock = acquireUpdateLock();
87
+ if (!unlock) return {updated: false, busy: true};
88
+ try {
89
+ const stateFile = path.join(cacheRoot(), 'update.json');
90
+ const state = read(stateFile);
91
+ const current = read(path.join(activeRoot(root), 'package.json')).version;
92
+ if (!force && state?.checkedAt > Date.now() - DAY) return {updated: false, version: current};
93
+ const response = await fetcher(`${REGISTRY}/@getopencrawl%2fcli/${CHANNEL}`, {redirect: 'error', signal: AbortSignal.timeout(10_000)});
94
+ if (!response.ok) throw new Error('The npm registry could not check for updates.');
95
+ const latest = await response.json();
96
+ if (latest.name !== PACKAGE || !stable(latest.version)) throw new Error('The registry returned invalid release metadata.');
97
+ if (compare(latest.version, current) <= 0) { write(stateFile, {checkedAt: Date.now()}); return {updated: false, version: current}; }
98
+ await install(latest.version);
99
+ write(path.join(cacheRoot(), 'current.json'), {version: latest.version});
100
+ write(stateFile, {checkedAt: Date.now()});
101
+ return {updated: true, version: latest.version};
102
+ } finally { unlock(); }
103
+ }
104
+ async function installRelease(version, {registry = REGISTRY} = {}) {
105
+ const origin = new URL(registry);
106
+ if (registry !== REGISTRY && !(origin.protocol === 'http:' && ['127.0.0.1', 'localhost'].includes(origin.hostname))) throw new Error('Invalid release registry.');
107
+ const releases = path.join(cacheRoot(), 'releases');
108
+ fs.mkdirSync(releases, {recursive: true, mode: 0o700});
109
+ const destination = path.join(releases, version);
110
+ // Reuse a fully staged version if pointer publication was interrupted.
111
+ if (fs.existsSync(destination)) { binary(path.join(destination, 'node_modules/@getopencrawl/cli')); return; }
112
+ const stage = fs.mkdtempSync(path.join(releases, '.stage-'));
113
+ try {
114
+ fs.writeFileSync(path.join(stage, 'package.json'), '{"private":true}\n');
115
+ fs.writeFileSync(path.join(stage, '.npmrc'), `@getopencrawl:registry=${registry}\n`);
116
+ const args = [npmCli(), 'install', '--prefix', stage, '--ignore-scripts', '--include=optional', '--no-audit', '--no-fund', '--no-package-lock', `--registry=${registry}`, `${PACKAGE}@${version}`];
117
+ const result = spawnSync(process.execPath, args, {cwd: stage, stdio: 'pipe', timeout: 180_000, windowsHide: true});
118
+ if (result.status !== 0) throw new Error('Could not install the update. The existing CLI is unchanged.');
119
+ const installed = path.join(stage, 'node_modules/@getopencrawl/cli');
120
+ const pkg = read(path.join(installed, 'package.json'));
121
+ if (pkg?.name !== PACKAGE || pkg.version !== version) throw new Error('The installed release does not match the requested version.');
122
+ binary(installed);
123
+ fs.renameSync(stage, destination);
124
+ } finally { fs.rmSync(stage, {recursive: true, force: true}); }
125
+ }
126
+ function scheduleUpdate() {
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
134
+ }
135
+ async function main(args = process.argv.slice(2)) {
136
+ if (args[0] === '--internal-update') {
137
+ try { await update(); } catch { write(path.join(cacheRoot(), 'update.json'), {checkedAt: Date.now() - DAY + 15 * 60_000}); }
138
+ return 0;
139
+ }
140
+ const selected = activeRoot();
141
+ // Delegate the launcher too, so later releases can fix bootstrap behavior.
142
+ if (selected !== ROOT) {
143
+ const delegated = spawnSync(process.execPath, [path.join(selected, 'bin/opencrawl.cjs'), ...args], {stdio: 'inherit', windowsHide: true});
144
+ if (delegated.error) throw new Error('Could not start the cached launcher.');
145
+ return delegated.signal === 'SIGINT' ? 130 : delegated.status ?? 1;
146
+ }
147
+ let parsed;
148
+ 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'}}}); }
149
+ catch {} // The native parser produces the public, versioned usage error.
150
+ if (parsed?.positionals.length === 1 && parsed.positionals[0] === 'update' && !parsed.values.help && !parsed.values.version) {
151
+ const data = await update({force: true});
152
+ 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
+ return 0;
154
+ }
155
+ const root = activeRoot();
156
+ const result = spawnSync(binary(root), args, {stdio: 'inherit', windowsHide: true});
157
+ scheduleUpdate();
158
+ if (result.error) throw new Error('Could not start the OpenCrawl binary. Reinstall @getopencrawl/cli.');
159
+ if (result.signal === 'SIGINT') return 130;
160
+ return result.status ?? 1;
161
+ }
162
+ module.exports = {channel: CHANNEL, target, stable, compare, activeRoot, update, installRelease, binary, npmCli, main};
163
+ if (require.main === module) main().then(code => { process.exitCode = code; }).catch(() => {
164
+ const message = 'Could not run or update OpenCrawl. Retry, or reinstall with npm install -g @getopencrawl/cli@next --include=optional.';
165
+ if (process.argv.includes('--json')) process.stdout.write(JSON.stringify({schemaVersion: 1, error: {code: 'LAUNCHER_ERROR', message}}) + '\n');
166
+ else process.stderr.write(`opencrawl: ${message}\n`);
167
+ process.exitCode = 1;
168
+ });