@hraness/ghostget 0.17.2
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/CHANGELOG.md +458 -0
- package/LICENSE +21 -0
- package/README.md +1359 -0
- package/bunfig.toml +2 -0
- package/dist/apple-photos-client.js +704 -0
- package/dist/beeper-client.js +4602 -0
- package/dist/client.js +1605 -0
- package/dist/index-26yq8q16.js +31 -0
- package/dist/index-9t13b4k7.js +508 -0
- package/dist/index-axwm9b3v.js +5 -0
- package/dist/index-gwk7rbyj.js +30 -0
- package/dist/index.js +701 -0
- package/dist/messaging.js +1130 -0
- package/dist/omni-client.js +1406 -0
- package/dist/whatsapp-client.js +355 -0
- package/docs/imessage-direct-provider.md +106 -0
- package/docs/rental-listings.md +94 -0
- package/package.json +600 -0
- package/skills/ghostget/SKILL.md +251 -0
- package/skills/ghostget/agents/openai.yaml +4 -0
- package/skills/ghostget/references/adapter-contract.md +181 -0
- package/skills/ghostget/references/apple-photos.md +115 -0
- package/skills/ghostget/references/article-drafts.md +235 -0
- package/skills/ghostget/references/code-owned-provider-scaffold.md +134 -0
- package/skills/ghostget/references/cross-posting-video.md +139 -0
- package/skills/ghostget/references/cross-posting.md +82 -0
- package/skills/ghostget/references/derivation.md +390 -0
- package/skills/ghostget/references/hraness-social-profile-stats.json +269 -0
- package/skills/ghostget/references/install.md +66 -0
- package/skills/ghostget/references/linkedin-adapter.md +442 -0
- package/skills/ghostget/references/messaging.md +168 -0
- package/skills/ghostget/references/meta-comet-contract.md +153 -0
- package/skills/ghostget/references/platform-patterns.md +122 -0
- package/skills/ghostget/references/provider-plugins.md +496 -0
- package/skills/ghostget/references/reddit-flair.md +78 -0
- package/skills/ghostget/references/safety-and-state.md +214 -0
- package/skills/ghostget/references/settlement-and-duplicate-cleanup.md +79 -0
- package/skills/ghostget/references/social-platform-routing.md +66 -0
- package/skills/ghostget/references/social-profile-stats.md +195 -0
- package/skills/ghostget/references/social-video-platform-routing.md +89 -0
- package/skills/ghostget/references/whatsapp-adapter.md +101 -0
- package/skills/ghostget/references/x-adapter.md +332 -0
- package/skills/ghostget/references/x-ai-disclosure.md +71 -0
- package/src/apple-photos-cli.ts +639 -0
- package/src/apple-photos-client-types.ts +168 -0
- package/src/apple-photos-client.ts +201 -0
- package/src/apple-photos-contact-evidence.ts +838 -0
- package/src/apple-photos-local-source.ts +1131 -0
- package/src/args.ts +2053 -0
- package/src/article-draft-document.ts +417 -0
- package/src/article-draft-embeds.ts +123 -0
- package/src/article-draft-images.ts +214 -0
- package/src/assets/adapter-template/wrench-adapter.json +40 -0
- package/src/assets/adapters/beeper/wrench-web-adapter.json +1500 -0
- package/src/assets/adapters/beeper/wrench-web-adapter.v1.0.0.json +129 -0
- package/src/assets/adapters/beeper/wrench-web-adapter.v1.1.0.json +207 -0
- package/src/assets/adapters/beeper/wrench-web-adapter.v2.0.0.json +1463 -0
- package/src/assets/adapters/beeper/wrench-web-adapter.v2.1.0.json +1463 -0
- package/src/assets/adapters/beeper/wrench-web-adapter.v2.2.0.json +1475 -0
- package/src/assets/adapters/beeper/wrench-web-adapter.v2.3.0.json +1488 -0
- package/src/assets/adapters/bluesky/wrench-web-adapter.json +684 -0
- package/src/assets/adapters/bluesky/wrench-web-adapter.v1.0.0.json +581 -0
- package/src/assets/adapters/bluesky/wrench-web-adapter.v1.1.0.json +581 -0
- package/src/assets/adapters/bluesky/wrench-web-adapter.v1.2.0.json +581 -0
- package/src/assets/adapters/bluesky/wrench-web-adapter.v1.3.0.json +615 -0
- package/src/assets/adapters/bluesky/wrench-web-adapter.v1.4.0.json +684 -0
- package/src/assets/adapters/bluesky/wrench-web-adapter.v1.5.0.json +684 -0
- package/src/assets/adapters/bluesky/wrench-web-adapter.v1.6.0.json +684 -0
- package/src/assets/adapters/clasificados/wrench-web-adapter.json +54 -0
- package/src/assets/adapters/facebook/wrench-web-adapter.json +750 -0
- package/src/assets/adapters/facebook/wrench-web-adapter.v1.0.0.json +706 -0
- package/src/assets/adapters/facebook/wrench-web-adapter.v1.1.0.json +703 -0
- package/src/assets/adapters/facebook/wrench-web-adapter.v1.2.0.json +750 -0
- package/src/assets/adapters/facebook-group/wrench-web-adapter.json +682 -0
- package/src/assets/adapters/facebook-group/wrench-web-adapter.v1.0.0.json +583 -0
- package/src/assets/adapters/facebook-group/wrench-web-adapter.v1.1.0.json +682 -0
- package/src/assets/adapters/facebook-marketplace/wrench-web-adapter.json +437 -0
- package/src/assets/adapters/facebook-marketplace/wrench-web-adapter.v1.0.0.json +383 -0
- package/src/assets/adapters/facebook-marketplace/wrench-web-adapter.v1.1.0.json +437 -0
- package/src/assets/adapters/facebook-marketplace/wrench-web-adapter.v1.1.1.json +437 -0
- package/src/assets/adapters/facebook-page/wrench-web-adapter.json +880 -0
- package/src/assets/adapters/facebook-page/wrench-web-adapter.v1.0.0.json +740 -0
- package/src/assets/adapters/facebook-page/wrench-web-adapter.v1.1.0.json +880 -0
- package/src/assets/adapters/github/wrench-web-adapter.json +69 -0
- package/src/assets/adapters/github/wrench-web-adapter.v1.0.0.json +42 -0
- package/src/assets/adapters/github/wrench-web-adapter.v1.1.0.json +69 -0
- package/src/assets/adapters/gmail/wrench-adapter.json +161 -0
- package/src/assets/adapters/gmail/wrench-adapter.v1.2.0.json +157 -0
- package/src/assets/adapters/hacker-news/wrench-web-adapter.json +315 -0
- package/src/assets/adapters/hacker-news/wrench-web-adapter.v1.0.0.json +315 -0
- package/src/assets/adapters/imessage/wrench-web-adapter.json +236 -0
- package/src/assets/adapters/instagram/wrench-web-adapter.json +719 -0
- package/src/assets/adapters/instagram/wrench-web-adapter.v1.0.0.json +627 -0
- package/src/assets/adapters/instagram/wrench-web-adapter.v1.1.0.json +609 -0
- package/src/assets/adapters/instagram/wrench-web-adapter.v1.2.0.json +640 -0
- package/src/assets/adapters/instagram/wrench-web-adapter.v1.3.0.json +671 -0
- package/src/assets/adapters/instagram/wrench-web-adapter.v1.4.0.json +710 -0
- package/src/assets/adapters/instagram/wrench-web-adapter.v1.5.0.json +710 -0
- package/src/assets/adapters/instagram/wrench-web-adapter.v1.6.0.json +719 -0
- package/src/assets/adapters/instagram/wrench-web-adapter.v1.7.0.json +719 -0
- package/src/assets/adapters/linkedin/wrench-adapter.json +442 -0
- package/src/assets/adapters/linkedin/wrench-adapter.v0.4.0.json +124 -0
- package/src/assets/adapters/linkedin/wrench-adapter.v1.0.0.json +411 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.json +914 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.0.0.json +604 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.1.0.json +604 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.10.0.json +809 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.11.0.json +809 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.12.0.json +820 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.13.0.json +819 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.14.0.json +819 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.15.0.json +819 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.16.0.json +874 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.17.0.json +874 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.18.0.json +874 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.19.0.json +874 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.2.0.json +731 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.3.0.json +768 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.4.0.json +808 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.5.0.json +808 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.6.0.json +808 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.7.0.json +825 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.8.0.json +792 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.9.0.json +809 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.json +855 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.v1.0.0.json +577 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.v1.1.0.json +604 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.v1.10.0.json +712 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.v1.11.0.json +712 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.v1.12.0.json +712 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.v1.2.0.json +653 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.v1.3.0.json +712 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.v1.4.0.json +713 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.v1.5.0.json +712 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.v1.6.0.json +713 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.v1.7.0.json +713 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.v1.8.0.json +713 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.v1.9.0.json +713 -0
- package/src/assets/adapters/substack/wrench-web-adapter.json +936 -0
- package/src/assets/adapters/substack/wrench-web-adapter.v1.0.0.json +817 -0
- package/src/assets/adapters/substack/wrench-web-adapter.v1.1.0.json +812 -0
- package/src/assets/adapters/substack/wrench-web-adapter.v1.2.0.json +812 -0
- package/src/assets/adapters/substack/wrench-web-adapter.v1.3.0.json +902 -0
- package/src/assets/adapters/substack/wrench-web-adapter.v1.4.0.json +936 -0
- package/src/assets/adapters/substack/wrench-web-adapter.v1.5.0.json +936 -0
- package/src/assets/adapters/substack/wrench-web-adapter.v1.6.0.json +936 -0
- package/src/assets/adapters/threads/wrench-web-adapter.json +692 -0
- package/src/assets/adapters/threads/wrench-web-adapter.v1.0.0.json +629 -0
- package/src/assets/adapters/threads/wrench-web-adapter.v1.1.0.json +623 -0
- package/src/assets/adapters/threads/wrench-web-adapter.v1.2.0.json +622 -0
- package/src/assets/adapters/threads/wrench-web-adapter.v1.3.0.json +622 -0
- package/src/assets/adapters/threads/wrench-web-adapter.v1.4.0.json +622 -0
- package/src/assets/adapters/threads/wrench-web-adapter.v1.5.0.json +699 -0
- package/src/assets/adapters/threads/wrench-web-adapter.v1.6.0.json +693 -0
- package/src/assets/adapters/threads/wrench-web-adapter.v1.7.0.json +692 -0
- package/src/assets/adapters/tiktok/wrench-web-adapter.json +762 -0
- package/src/assets/adapters/tiktok/wrench-web-adapter.v1.0.0.json +681 -0
- package/src/assets/adapters/tiktok/wrench-web-adapter.v1.1.0.json +708 -0
- package/src/assets/adapters/tiktok/wrench-web-adapter.v1.2.0.json +762 -0
- package/src/assets/adapters/tiktok/wrench-web-adapter.v1.3.0.json +762 -0
- package/src/assets/adapters/twitch/wrench-web-adapter.json +42 -0
- package/src/assets/adapters/twitch/wrench-web-adapter.v1.0.0.json +42 -0
- package/src/assets/adapters/whatsapp/wrench-web-adapter.json +400 -0
- package/src/assets/adapters/whatsapp/wrench-web-adapter.v1.0.0.json +355 -0
- package/src/assets/adapters/whatsapp/wrench-web-adapter.v1.1.0.json +386 -0
- package/src/assets/adapters/whatsapp/wrench-web-adapter.v1.2.0.json +386 -0
- package/src/assets/adapters/whatsapp/wrench-web-adapter.v1.3.0.json +400 -0
- package/src/assets/adapters/x/wrench-adapter.json +749 -0
- package/src/assets/adapters/x/wrench-adapter.v1.0.0.json +700 -0
- package/src/assets/adapters/x/wrench-adapter.v1.1.0.json +707 -0
- package/src/assets/adapters/x/wrench-web-adapter.json +712 -0
- package/src/assets/adapters/x/wrench-web-adapter.v1.1.0.json +579 -0
- package/src/assets/adapters/x/wrench-web-adapter.v1.10.0.json +685 -0
- package/src/assets/adapters/x/wrench-web-adapter.v1.11.0.json +712 -0
- package/src/assets/adapters/x/wrench-web-adapter.v1.12.0.json +712 -0
- package/src/assets/adapters/x/wrench-web-adapter.v1.13.0.json +712 -0
- package/src/assets/adapters/x/wrench-web-adapter.v1.2.0.json +578 -0
- package/src/assets/adapters/x/wrench-web-adapter.v1.3.0.json +610 -0
- package/src/assets/adapters/x/wrench-web-adapter.v1.4.0.json +617 -0
- package/src/assets/adapters/x/wrench-web-adapter.v1.5.0.json +634 -0
- package/src/assets/adapters/x/wrench-web-adapter.v1.6.0.json +632 -0
- package/src/assets/adapters/x/wrench-web-adapter.v1.7.0.json +649 -0
- package/src/assets/adapters/x/wrench-web-adapter.v1.8.0.json +649 -0
- package/src/assets/adapters/x/wrench-web-adapter.v1.9.0.json +683 -0
- package/src/assets/adapters/youtube/wrench-web-adapter.json +561 -0
- package/src/assets/adapters/youtube/wrench-web-adapter.v1.0.0.json +465 -0
- package/src/assets/adapters/youtube/wrench-web-adapter.v1.1.0.json +519 -0
- package/src/assets/adapters/youtube/wrench-web-adapter.v1.2.0.json +561 -0
- package/src/assets/adapters/youtube/wrench-web-adapter.v1.3.0.json +561 -0
- package/src/assets/code-owned-provider-template/plugin.test.ts.template +54 -0
- package/src/assets/code-owned-provider-template/plugin.ts.template +102 -0
- package/src/assets/code-owned-provider-template/runtime.internal.test.ts.template +44 -0
- package/src/assets/code-owned-provider-template/runtime.ts.template +59 -0
- package/src/assets/code-owned-provider-template/wrench-adapter.json.template +33 -0
- package/src/assets/fonts/nebula-sans/LICENSE.txt +96 -0
- package/src/assets/fonts/nebula-sans/NebulaSans-Book.woff2 +0 -0
- package/src/assets/fonts/nebula-sans/PROVENANCE.md +14 -0
- package/src/assets.d.ts +4 -0
- package/src/auth.ts +1157 -0
- package/src/beeper-client-types.ts +169 -0
- package/src/beeper-client.ts +250 -0
- package/src/beeper-contact-interactions-cli.ts +180 -0
- package/src/beeper-contact-interactions.ts +1612 -0
- package/src/beeper-message-bundle-v1.ts +257 -0
- package/src/beeper-message-like-me-cli.ts +84 -0
- package/src/beeper-message-like-me-export.ts +2274 -0
- package/src/beeper-message-like-me-recovery.ts +931 -0
- package/src/beeper-message-like-me-source.ts +4034 -0
- package/src/browser-admission.ts +928 -0
- package/src/browser-snapshots.ts +260 -0
- package/src/browser.ts +3873 -0
- package/src/canonical-json.ts +60 -0
- package/src/catalog-cli.ts +638 -0
- package/src/cli.ts +208 -0
- package/src/client-types.ts +301 -0
- package/src/client.ts +2726 -0
- package/src/confirmed-write-failure.ts +44 -0
- package/src/confirmed-write-model.ts +72 -0
- package/src/confirmed-write-platform.ts +1055 -0
- package/src/confirmed-write-program.ts +224 -0
- package/src/confirmed-write-runtime.ts +16 -0
- package/src/cursor-token.ts +228 -0
- package/src/derivation-dnr-readiness.ts +367 -0
- package/src/derivation-file-chooser.ts +384 -0
- package/src/derivation-network-boundary.ts +835 -0
- package/src/derivation-network-guard.ts +782 -0
- package/src/derivation-network-proxy-helper.ts +440 -0
- package/src/derivation-network-proxy.ts +385 -0
- package/src/derive-command-helper.ts +693 -0
- package/src/derive-fixtures.ts +343 -0
- package/src/derive-review.ts +980 -0
- package/src/derive.ts +4754 -0
- package/src/fixtures/beeper-message-like-me-v1/accounts.ndjson +2 -0
- package/src/fixtures/beeper-message-like-me-v1/conversations.ndjson +1 -0
- package/src/fixtures/beeper-message-like-me-v1/manifest.json +1 -0
- package/src/fixtures/beeper-message-like-me-v1/messages.ndjson +2 -0
- package/src/fixtures/beeper-message-like-me-v1/participants.ndjson +3 -0
- package/src/fixtures/beeper-message-like-me-v1/reactions.ndjson +1 -0
- package/src/fixtures/beeper-message-like-me-v1/tombstones.ndjson +1 -0
- package/src/ghostget.ts +3672 -0
- package/src/gmail-capture.ts +1244 -0
- package/src/har-internal.ts +1242 -0
- package/src/har.ts +743 -0
- package/src/index.ts +218 -0
- package/src/invocation-read-platform.ts +55 -0
- package/src/invocation-read-program.ts +93 -0
- package/src/invocation-read-runtime.ts +11 -0
- package/src/linked-device-lifecycle-admission.ts +450 -0
- package/src/linked-device-lifecycle-journal.ts +1560 -0
- package/src/linked-device-lifecycle-runtime.ts +1602 -0
- package/src/local-cli-admission.ts +135 -0
- package/src/local-cli-contracts.ts +271 -0
- package/src/local-cli-execution.ts +98 -0
- package/src/local-cli-surface-contract.ts +1510 -0
- package/src/local-cli-tool-identity.ts +336 -0
- package/src/media/archive.ts +2325 -0
- package/src/media/args.ts +303 -0
- package/src/media/cli.ts +287 -0
- package/src/media/doctor.ts +475 -0
- package/src/media/ffmpeg.ts +1330 -0
- package/src/media/http-capture.ts +711 -0
- package/src/media/http-probe.ts +593 -0
- package/src/media/http.ts +387 -0
- package/src/media/index.ts +10 -0
- package/src/media/local-transcription.ts +613 -0
- package/src/media/lock.ts +321 -0
- package/src/media/manifest.ts +1712 -0
- package/src/media/metadata.ts +906 -0
- package/src/media/process.ts +723 -0
- package/src/media/revision.ts +166 -0
- package/src/media/runtime-closure.ts +1063 -0
- package/src/media/source-router.ts +187 -0
- package/src/media/transcriber-config.ts +1122 -0
- package/src/media/transcript-persistence-model.ts +15 -0
- package/src/media/transcript-persistence-platform.ts +89 -0
- package/src/media/transcript-persistence-program.ts +39 -0
- package/src/media/transcript-persistence-runtime.ts +24 -0
- package/src/media/transcript.ts +616 -0
- package/src/media/utf8-order.ts +30 -0
- package/src/media/whisper-cpp.ts +464 -0
- package/src/media/whisper-language.ts +58 -0
- package/src/media/yt-dlp.ts +439 -0
- package/src/message-like-me-agentic-messaging.ts +882 -0
- package/src/messaging-action-store.ts +793 -0
- package/src/messaging-runtime.ts +2136 -0
- package/src/messaging-store.ts +539 -0
- package/src/messaging-types.ts +1663 -0
- package/src/messaging.ts +334 -0
- package/src/model.ts +2671 -0
- package/src/oauth-google.ts +888 -0
- package/src/omni-client-types.ts +306 -0
- package/src/omni-client.ts +2225 -0
- package/src/omni-limits.ts +34 -0
- package/src/omni-model.ts +1614 -0
- package/src/omni-request.ts +379 -0
- package/src/omni-runtime.ts +1632 -0
- package/src/operation-deadline.ts +189 -0
- package/src/path-helper.ts +1656 -0
- package/src/pinned-https.ts +194 -0
- package/src/plan-assets.ts +832 -0
- package/src/platform-catalog.ts +1832 -0
- package/src/plugins/beeper-linked-device/plugin.ts +310 -0
- package/src/plugins/bluesky-web/plugin.ts +178 -0
- package/src/plugins/clasificados-web/plugin.ts +66 -0
- package/src/plugins/github-web/plugin.ts +80 -0
- package/src/plugins/gmail-official/plugin.ts +110 -0
- package/src/plugins/hacker-news-web/plugin.ts +52 -0
- package/src/plugins/imessage-direct/plugin.ts +195 -0
- package/src/plugins/imessage-direct/vendor/0001-fix-keep-AppleScript-send-payloads-out-of-child-argv.patch +1430 -0
- package/src/plugins/imessage-direct/vendor/0002-feat-rpc-add-exact-chat-lookup.patch +225 -0
- package/src/plugins/imessage-direct/vendor/provenance.json +80 -0
- package/src/plugins/linkedin-official/plugin.ts +84 -0
- package/src/plugins/linkedin-web/plugin.ts +412 -0
- package/src/plugins/meta-web/plugin.ts +317 -0
- package/src/plugins/reddit-web/flair.ts +492 -0
- package/src/plugins/reddit-web/plugin.ts +477 -0
- package/src/plugins/substack-web/plugin.ts +124 -0
- package/src/plugins/tiktok-web/plugin.ts +67 -0
- package/src/plugins/twitch-web/plugin.ts +54 -0
- package/src/plugins/whatsapp-linked-device/plugin.ts +105 -0
- package/src/plugins/x-official/plugin.ts +64 -0
- package/src/plugins/x-web/plugin.ts +488 -0
- package/src/plugins/youtube-web/plugin.ts +101 -0
- package/src/portable-run-recovery.ts +466 -0
- package/src/process-identity.ts +501 -0
- package/src/profile-clone-helper.ts +104 -0
- package/src/provider-catalog-views.ts +51 -0
- package/src/provider-context.ts +69 -0
- package/src/provider-contract-definitions-x.ts +45 -0
- package/src/provider-contract-definitions.ts +948 -0
- package/src/provider-contract-input-gmail.ts +176 -0
- package/src/provider-contract-input-linkedin.ts +77 -0
- package/src/provider-contract-input-x.ts +304 -0
- package/src/provider-contract-planning.ts +23 -0
- package/src/provider-contract-semantic-identity.ts +54 -0
- package/src/provider-contracts.ts +251 -0
- package/src/provider-http.ts +723 -0
- package/src/provider-plugin-auth.ts +36 -0
- package/src/provider-plugin-builtins.ts +229 -0
- package/src/provider-plugin-cleanup-barrier.ts +229 -0
- package/src/provider-plugin-cleanup-execution.ts +79 -0
- package/src/provider-plugin-cleanup-resource.ts +470 -0
- package/src/provider-plugin-contract-identity.ts +707 -0
- package/src/provider-plugin-host.ts +1972 -0
- package/src/provider-plugin-identifiers.ts +60 -0
- package/src/provider-plugin-invocation-lease.ts +889 -0
- package/src/provider-plugin-lifecycle-kernel.ts +566 -0
- package/src/provider-plugin-lifecycle.ts +1455 -0
- package/src/provider-plugin-module-analysis.ts +336 -0
- package/src/provider-plugin-package.ts +2530 -0
- package/src/provider-plugin-portable-authority.ts +106 -0
- package/src/provider-plugin-portable-catalog.ts +94 -0
- package/src/provider-plugin-portable-identity.ts +315 -0
- package/src/provider-plugin-portable-runtime.ts +2057 -0
- package/src/provider-plugin-protocol.ts +1625 -0
- package/src/provider-plugin-registry.ts +3342 -0
- package/src/provider-plugin-store.ts +2155 -0
- package/src/provider-plugin-test-fixture.ts +5 -0
- package/src/provider-plugin.ts +4907 -0
- package/src/provider-plugins.generated.ts +43 -0
- package/src/provider-plugins.ts +8 -0
- package/src/provider-subject.ts +41 -0
- package/src/provider.ts +315 -0
- package/src/providers/beeper-local-runtime.ts +5426 -0
- package/src/providers/beeper-local.ts +2739 -0
- package/src/providers/beeper-messaging.ts +462 -0
- package/src/providers/beeper-omni.ts +1159 -0
- package/src/providers/bluesky-web-runtime.ts +2895 -0
- package/src/providers/bluesky-web.ts +1614 -0
- package/src/providers/clasificados-web-runtime.ts +255 -0
- package/src/providers/clasificados-web.ts +503 -0
- package/src/providers/contact-projection.ts +301 -0
- package/src/providers/github-read-model.ts +94 -0
- package/src/providers/github-read-platform.ts +165 -0
- package/src/providers/github-read-program.ts +97 -0
- package/src/providers/github-web-runtime.ts +320 -0
- package/src/providers/github-web.ts +403 -0
- package/src/providers/gmail-api.ts +2834 -0
- package/src/providers/gmail-omni.ts +830 -0
- package/src/providers/gmail.ts +1130 -0
- package/src/providers/hacker-news-web-runtime.ts +220 -0
- package/src/providers/hacker-news-web.ts +809 -0
- package/src/providers/imessage-direct-install.ts +354 -0
- package/src/providers/imessage-direct-messaging.ts +532 -0
- package/src/providers/imessage-direct-omni.ts +400 -0
- package/src/providers/imessage-direct-runtime.ts +1498 -0
- package/src/providers/imessage-direct.ts +351 -0
- package/src/providers/instagram-video-foundations.ts +913 -0
- package/src/providers/instagram-web-profile-browser.ts +600 -0
- package/src/providers/iso-bmff.ts +338 -0
- package/src/providers/linkedin-company-platform.ts +42 -0
- package/src/providers/linkedin-company-program.ts +88 -0
- package/src/providers/linkedin-contact-failure.ts +84 -0
- package/src/providers/linkedin-contact-platform.ts +45 -0
- package/src/providers/linkedin-contact-program.ts +91 -0
- package/src/providers/linkedin-profile-activity-failure.ts +76 -0
- package/src/providers/linkedin-profile-activity-platform.ts +41 -0
- package/src/providers/linkedin-profile-activity-program.ts +67 -0
- package/src/providers/linkedin-read-failure.ts +52 -0
- package/src/providers/linkedin-self-platform.ts +53 -0
- package/src/providers/linkedin-self-program.ts +122 -0
- package/src/providers/linkedin-web-article-browser.ts +794 -0
- package/src/providers/linkedin-web-bootstrap.ts +146 -0
- package/src/providers/linkedin-web-contact.ts +830 -0
- package/src/providers/linkedin-web-feed-browser.ts +832 -0
- package/src/providers/linkedin-web-feed.ts +864 -0
- package/src/providers/linkedin-web-post-browser.ts +653 -0
- package/src/providers/linkedin-web-profile-browser.ts +683 -0
- package/src/providers/linkedin-web-runtime.ts +2388 -0
- package/src/providers/linkedin-web.ts +3305 -0
- package/src/providers/linkedin.ts +1885 -0
- package/src/providers/meta-bootstrap.ts +1043 -0
- package/src/providers/meta-facebook-group.ts +690 -0
- package/src/providers/meta-marketplace-relay.ts +747 -0
- package/src/providers/meta-omni.ts +373 -0
- package/src/providers/meta-relay-bundle.ts +1049 -0
- package/src/providers/meta-web-descriptors.ts +1740 -0
- package/src/providers/meta-web-runtime.ts +3388 -0
- package/src/providers/meta-web.ts +2931 -0
- package/src/providers/read-failure.ts +139 -0
- package/src/providers/reddit-omni.ts +462 -0
- package/src/providers/reddit-web-runtime.ts +1929 -0
- package/src/providers/reddit-web.ts +1611 -0
- package/src/providers/rental-listings.ts +460 -0
- package/src/providers/substack-omni.ts +406 -0
- package/src/providers/substack-video-mp4.ts +33 -0
- package/src/providers/substack-web-runtime.ts +2812 -0
- package/src/providers/substack-web.ts +1502 -0
- package/src/providers/tiktok-video-mp4.ts +33 -0
- package/src/providers/tiktok-web-runtime.ts +770 -0
- package/src/providers/tiktok-web.ts +2786 -0
- package/src/providers/twitch-web-runtime.ts +226 -0
- package/src/providers/twitch-web.ts +274 -0
- package/src/providers/whatsapp-account-identity.ts +55 -0
- package/src/providers/whatsapp-contact-projection-helper.ts +874 -0
- package/src/providers/whatsapp-contact-projection-protocol.ts +403 -0
- package/src/providers/whatsapp-interaction-projection-helper.ts +1452 -0
- package/src/providers/whatsapp-interaction-projection-protocol.ts +462 -0
- package/src/providers/whatsapp-message-export-projection-protocol.ts +532 -0
- package/src/providers/whatsapp-messaging.ts +132 -0
- package/src/providers/whatsapp-omni.ts +586 -0
- package/src/providers/whatsapp-web-runtime.ts +3108 -0
- package/src/providers/whatsapp-web.ts +1037 -0
- package/src/providers/x-image-provenance.ts +523 -0
- package/src/providers/x-made-with-ai.ts +106 -0
- package/src/providers/x-transaction-id.ts +413 -0
- package/src/providers/x-web-runtime.ts +3485 -0
- package/src/providers/x-web.ts +2332 -0
- package/src/providers/x.ts +1889 -0
- package/src/providers/youtube-web-runtime.ts +1400 -0
- package/src/providers/youtube-web.ts +1221 -0
- package/src/read-admission-runtime.ts +91 -0
- package/src/read-client.ts +521 -0
- package/src/read-effect-platform.ts +7 -0
- package/src/read-effect-runtime.ts +14 -0
- package/src/read-effect.ts +52 -0
- package/src/read-projection-admission.ts +866 -0
- package/src/read-projections.ts +3151 -0
- package/src/recovery.ts +1492 -0
- package/src/reviewed-template.ts +146 -0
- package/src/run-journal.ts +1272 -0
- package/src/runtime.ts +5116 -0
- package/src/scripts/generate-provider-plugin-catalog.ts +121 -0
- package/src/scripts/install-whatsapp-protocol.sh +232 -0
- package/src/scripts/recover-install-stages.ts +1491 -0
- package/src/scripts/resolve-state-home.ts +30 -0
- package/src/scripts/scaffold-web-provider.ts +973 -0
- package/src/scripts/sync-bundled-adapters.ts +695 -0
- package/src/session-secrets.ts +1183 -0
- package/src/state-helper.bunfig.toml +2 -0
- package/src/state-helper.ts +2914 -0
- package/src/storage.ts +3842 -0
- package/src/transport-policy.ts +2 -0
- package/src/usage.ts +182 -0
- package/src/version.ts +2 -0
- package/src/web-session-authentication-policy.ts +136 -0
- package/src/web-session-cleanup-admission.ts +2344 -0
- package/src/web-session-cleanup-join.ts +99 -0
- package/src/web-session-client.ts +996 -0
- package/src/web-session-contract-definitions.ts +820 -0
- package/src/web-session-contract-planning.ts +42 -0
- package/src/web-session-contracts.ts +228 -0
- package/src/web-session-cookies.ts +500 -0
- package/src/web-session-execution.ts +385 -0
- package/src/web-session-http.ts +373 -0
- package/src/web-session-read-errors.ts +19 -0
- package/src/web-session-read-runtime.ts +124 -0
- package/src/web-session-recovery.ts +818 -0
- package/src/web-session-sites.ts +58 -0
- package/src/web-session-template.ts +994 -0
- package/src/web-session.ts +93 -0
- package/src/whatsapp-client-binding.ts +21 -0
- package/src/whatsapp-client-types.ts +75 -0
- package/src/whatsapp-client.ts +395 -0
- package/src/whatsapp-export-coordinate.ts +17 -0
- package/src/whatsapp-message-bundle-v2.ts +195 -0
- package/src/whatsapp-message-like-me-cli.ts +195 -0
- package/src/whatsapp-message-like-me-export.ts +1072 -0
- package/src/whatsapp-message-like-me-source.ts +1012 -0
- package/tsconfig.json +67 -0
|
@@ -0,0 +1,723 @@
|
|
|
1
|
+
import { constants as fsConstants } from "node:fs";
|
|
2
|
+
import { access, stat } from "node:fs/promises";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { delimiter, join } from "node:path";
|
|
5
|
+
|
|
6
|
+
const DEFAULT_STDOUT_LIMIT_BYTES = 16 * 1024 * 1024;
|
|
7
|
+
const DEFAULT_STDERR_LIMIT_BYTES = 2 * 1024 * 1024;
|
|
8
|
+
const MAX_OUTPUT_LIMIT_BYTES = 64 * 1024 * 1024;
|
|
9
|
+
const DEFAULT_TIMEOUT_MS = 30 * 60 * 1_000;
|
|
10
|
+
const DEFAULT_TERMINATE_GRACE_MS = 2_000;
|
|
11
|
+
const DEFAULT_KILL_GRACE_MS = 2_000;
|
|
12
|
+
const MAX_TIMEOUT_MS = 24 * 60 * 60 * 1_000;
|
|
13
|
+
const MAX_DIAGNOSTIC_CODE_UNITS = 16 * 1024;
|
|
14
|
+
const MAX_URL_REDACTION_CODE_UNITS = 8 * 1024;
|
|
15
|
+
const MIN_DERIVED_URL_SECRET_CODE_UNITS = 4;
|
|
16
|
+
|
|
17
|
+
const SENSITIVE_SWITCHES = new Set([
|
|
18
|
+
"--add-header",
|
|
19
|
+
"--client-certificate",
|
|
20
|
+
"--client-certificate-key",
|
|
21
|
+
"--client-certificate-password",
|
|
22
|
+
"--cookies",
|
|
23
|
+
"--cookies-from-browser",
|
|
24
|
+
"--http-header",
|
|
25
|
+
"--netrc-location",
|
|
26
|
+
"--password",
|
|
27
|
+
"--proxy",
|
|
28
|
+
"--username",
|
|
29
|
+
"--video-password",
|
|
30
|
+
]);
|
|
31
|
+
|
|
32
|
+
const URL_PATTERN = /https?:\/\/[^\s<>"']+/giu;
|
|
33
|
+
const HEADER_PATTERN = /\b(?:authorization|cookie|set-cookie|x-api-key)\s*:(?![ \t]*\[REDACTED\])[^\r\n]*/giu;
|
|
34
|
+
const KEY_VALUE_SECRET_PATTERN = /\b(?:access[_-]?token|api[_-]?key|auth(?:orization)?|cookie|password|passwd|secret|sig(?:nature)?|token)=([^\s&;]+)/giu;
|
|
35
|
+
const SWITCH_SECRET_PATTERN = /(--(?:add-header|client-certificate(?:-key|-password)?|cookies(?:-from-browser)?|http-header|netrc-location|password|proxy|username|video-password))(?:=|\s+)([^\s]+)/giu;
|
|
36
|
+
/* eslint-disable no-control-regex -- These are intentional ANSI C0/C1 terminal-control boundaries. */
|
|
37
|
+
const OSC_SEQUENCE_PATTERN = /(?:\u001B\]|\u009D)[\s\S]*?(?:\u0007|\u001B\\|\u009C|$)/gu;
|
|
38
|
+
const ANSI_STRING_SEQUENCE_PATTERN = /(?:\u001B[P^_X]|[\u0090\u0098\u009E\u009F])[\s\S]*?(?:\u001B\\|\u009C|$)/gu;
|
|
39
|
+
const CSI_SEQUENCE_PATTERN = /(?:\u001B\[|\u009B)[0-?]*[ -/]*[@-~]/gu;
|
|
40
|
+
const TWO_BYTE_ESCAPE_PATTERN = /\u001B[@-_]/gu;
|
|
41
|
+
// Human diagnostics are deliberately one physical terminal line.
|
|
42
|
+
const TERMINAL_CONTROL_PATTERN = /[\u0000-\u001F\u007F-\u009F]/gu;
|
|
43
|
+
/* eslint-enable no-control-regex */
|
|
44
|
+
const DIAGNOSTIC_WHITESPACE_PATTERN = /\s+/gu;
|
|
45
|
+
|
|
46
|
+
export type CommandArgv = readonly [executable: string, ...arguments_: string[]];
|
|
47
|
+
|
|
48
|
+
export type ProcessSignal = "SIGTERM" | "SIGKILL";
|
|
49
|
+
|
|
50
|
+
export type SpawnedProcess = Readonly<{
|
|
51
|
+
stdout: ReadableStream<Uint8Array> | null;
|
|
52
|
+
stderr: ReadableStream<Uint8Array> | null;
|
|
53
|
+
exited: Promise<number>;
|
|
54
|
+
kill: (signal: ProcessSignal) => void;
|
|
55
|
+
}>;
|
|
56
|
+
|
|
57
|
+
export type ProcessSpawnOptions = Readonly<{
|
|
58
|
+
cwd?: string;
|
|
59
|
+
env?: Readonly<Record<string, string | undefined>>;
|
|
60
|
+
}>;
|
|
61
|
+
|
|
62
|
+
export type ProcessTimer = Readonly<{
|
|
63
|
+
set: (callback: () => void, delayMs: number) => unknown;
|
|
64
|
+
clear: (handle: unknown) => void;
|
|
65
|
+
}>;
|
|
66
|
+
|
|
67
|
+
export type ProcessDependencies = Readonly<{
|
|
68
|
+
spawn: (argv: CommandArgv, options: ProcessSpawnOptions) => SpawnedProcess;
|
|
69
|
+
timer: ProcessTimer;
|
|
70
|
+
now: () => number;
|
|
71
|
+
}>;
|
|
72
|
+
|
|
73
|
+
export type RunProcessOptions = Readonly<{
|
|
74
|
+
cwd?: string;
|
|
75
|
+
env?: Readonly<Record<string, string | undefined>>;
|
|
76
|
+
signal?: AbortSignal;
|
|
77
|
+
timeoutMs?: number;
|
|
78
|
+
terminateGraceMs?: number;
|
|
79
|
+
killGraceMs?: number;
|
|
80
|
+
maxOutputBytes?: number;
|
|
81
|
+
maxStdoutBytes?: number;
|
|
82
|
+
maxStderrBytes?: number;
|
|
83
|
+
redactions?: readonly string[];
|
|
84
|
+
}>;
|
|
85
|
+
|
|
86
|
+
type ProcessOutput = Readonly<{
|
|
87
|
+
command: readonly string[];
|
|
88
|
+
exitCode: number | null;
|
|
89
|
+
stdout: string;
|
|
90
|
+
stderr: string;
|
|
91
|
+
stdoutTruncated: boolean;
|
|
92
|
+
stderrTruncated: boolean;
|
|
93
|
+
elapsedMs: number;
|
|
94
|
+
}>;
|
|
95
|
+
|
|
96
|
+
export type ProcessSuccess = ProcessOutput &
|
|
97
|
+
Readonly<{
|
|
98
|
+
ok: true;
|
|
99
|
+
exitCode: 0;
|
|
100
|
+
}>;
|
|
101
|
+
|
|
102
|
+
export type ProcessFailureReason = "spawn" | "aborted" | "timeout" | "exit" | "io";
|
|
103
|
+
|
|
104
|
+
export type ProcessFailure = ProcessOutput &
|
|
105
|
+
Readonly<{
|
|
106
|
+
ok: false;
|
|
107
|
+
reason: ProcessFailureReason;
|
|
108
|
+
diagnostic: string;
|
|
109
|
+
}>;
|
|
110
|
+
|
|
111
|
+
export type ProcessResult = ProcessSuccess | ProcessFailure;
|
|
112
|
+
|
|
113
|
+
export type ExecutableDiscoveryOptions = Readonly<{
|
|
114
|
+
env?: Readonly<Record<string, string | undefined>>;
|
|
115
|
+
homeDirectory?: string;
|
|
116
|
+
platform?: NodeJS.Platform;
|
|
117
|
+
extraDirectories?: readonly string[];
|
|
118
|
+
}>;
|
|
119
|
+
|
|
120
|
+
export type ExecutableDiscoveryDependencies = Readonly<{
|
|
121
|
+
isExecutableFile: (path: string, platform: NodeJS.Platform) => Promise<boolean>;
|
|
122
|
+
}>;
|
|
123
|
+
|
|
124
|
+
type CaptureResult = Readonly<{
|
|
125
|
+
text: string;
|
|
126
|
+
truncated: boolean;
|
|
127
|
+
error: string | null;
|
|
128
|
+
}>;
|
|
129
|
+
|
|
130
|
+
type Capture = Readonly<{
|
|
131
|
+
result: Promise<CaptureResult>;
|
|
132
|
+
cancel: () => Promise<void>;
|
|
133
|
+
}>;
|
|
134
|
+
|
|
135
|
+
type WaitResult<T> =
|
|
136
|
+
| Readonly<{ kind: "value"; value: T }>
|
|
137
|
+
| Readonly<{ kind: "error"; error: unknown }>
|
|
138
|
+
| Readonly<{ kind: "aborted" }>
|
|
139
|
+
| Readonly<{ kind: "timeout" }>;
|
|
140
|
+
|
|
141
|
+
const defaultTimer: ProcessTimer = {
|
|
142
|
+
set: (callback, delayMs) => setTimeout(callback, delayMs),
|
|
143
|
+
clear: (handle) => clearTimeout(handle as ReturnType<typeof setTimeout>),
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const defaultProcessDependencies: ProcessDependencies = {
|
|
147
|
+
spawn: (argv, options) => {
|
|
148
|
+
const child = Bun.spawn([...argv], {
|
|
149
|
+
...(options.cwd === undefined ? {} : { cwd: options.cwd }),
|
|
150
|
+
...(options.env === undefined ? {} : { env: { ...options.env } }),
|
|
151
|
+
stdin: "ignore",
|
|
152
|
+
stdout: "pipe",
|
|
153
|
+
stderr: "pipe",
|
|
154
|
+
});
|
|
155
|
+
return {
|
|
156
|
+
stdout: child.stdout,
|
|
157
|
+
stderr: child.stderr,
|
|
158
|
+
exited: child.exited,
|
|
159
|
+
kill: (signal) => child.kill(signal),
|
|
160
|
+
};
|
|
161
|
+
},
|
|
162
|
+
timer: defaultTimer,
|
|
163
|
+
now: () => Date.now(),
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const defaultDiscoveryDependencies: ExecutableDiscoveryDependencies = {
|
|
167
|
+
isExecutableFile: async (path, platform) => {
|
|
168
|
+
try {
|
|
169
|
+
const metadata = await stat(path);
|
|
170
|
+
if (!metadata.isFile()) return false;
|
|
171
|
+
await access(path, platform === "win32" ? fsConstants.F_OK : fsConstants.X_OK);
|
|
172
|
+
return true;
|
|
173
|
+
} catch {
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Runs one executable directly. The first element is always passed to Bun.spawn
|
|
181
|
+
* as an executable and no shell option exists at this boundary.
|
|
182
|
+
*/
|
|
183
|
+
export async function runProcess(
|
|
184
|
+
argv: CommandArgv,
|
|
185
|
+
options: RunProcessOptions = {},
|
|
186
|
+
dependencies: ProcessDependencies = defaultProcessDependencies,
|
|
187
|
+
): Promise<ProcessResult> {
|
|
188
|
+
const startedAt = dependencies.now();
|
|
189
|
+
const command = redactArguments(argv, options.redactions);
|
|
190
|
+
const stdoutLimit = normalizeByteLimit(
|
|
191
|
+
options.maxStdoutBytes ?? options.maxOutputBytes,
|
|
192
|
+
DEFAULT_STDOUT_LIMIT_BYTES,
|
|
193
|
+
);
|
|
194
|
+
const stderrLimit = normalizeByteLimit(
|
|
195
|
+
options.maxStderrBytes ?? options.maxOutputBytes,
|
|
196
|
+
DEFAULT_STDERR_LIMIT_BYTES,
|
|
197
|
+
);
|
|
198
|
+
const timeoutMs = normalizeDuration(options.timeoutMs, DEFAULT_TIMEOUT_MS);
|
|
199
|
+
const terminateGraceMs = normalizeDuration(
|
|
200
|
+
options.terminateGraceMs,
|
|
201
|
+
DEFAULT_TERMINATE_GRACE_MS,
|
|
202
|
+
);
|
|
203
|
+
const killGraceMs = normalizeDuration(options.killGraceMs, DEFAULT_KILL_GRACE_MS);
|
|
204
|
+
|
|
205
|
+
if (options.signal?.aborted === true) {
|
|
206
|
+
return {
|
|
207
|
+
ok: false,
|
|
208
|
+
reason: "aborted",
|
|
209
|
+
command,
|
|
210
|
+
exitCode: null,
|
|
211
|
+
stdout: "",
|
|
212
|
+
stderr: "",
|
|
213
|
+
stdoutTruncated: false,
|
|
214
|
+
stderrTruncated: false,
|
|
215
|
+
elapsedMs: elapsed(startedAt, dependencies.now()),
|
|
216
|
+
diagnostic: redactDiagnostic(
|
|
217
|
+
`${renderCommand(command)}: cancelled before the process started`,
|
|
218
|
+
redactionOptions(options),
|
|
219
|
+
),
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
let child: SpawnedProcess;
|
|
224
|
+
try {
|
|
225
|
+
child = dependencies.spawn(argv, {
|
|
226
|
+
...(options.cwd === undefined ? {} : { cwd: options.cwd }),
|
|
227
|
+
...(options.env === undefined ? {} : { env: options.env }),
|
|
228
|
+
});
|
|
229
|
+
} catch (error) {
|
|
230
|
+
const detail = redactDiagnostic(errorMessage(error), redactionOptions(options));
|
|
231
|
+
const diagnostic = redactDiagnostic(
|
|
232
|
+
`Could not start ${renderCommand(command)}: ${detail}`,
|
|
233
|
+
redactionOptions(options),
|
|
234
|
+
);
|
|
235
|
+
return {
|
|
236
|
+
ok: false,
|
|
237
|
+
reason: "spawn",
|
|
238
|
+
command,
|
|
239
|
+
exitCode: null,
|
|
240
|
+
stdout: "",
|
|
241
|
+
stderr: "",
|
|
242
|
+
stdoutTruncated: false,
|
|
243
|
+
stderrTruncated: false,
|
|
244
|
+
elapsedMs: elapsed(startedAt, dependencies.now()),
|
|
245
|
+
diagnostic,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const stdoutCapture = captureBounded(child.stdout, stdoutLimit);
|
|
250
|
+
const stderrCapture = captureBounded(child.stderr, stderrLimit);
|
|
251
|
+
let termination: "aborted" | "timeout" | null = null;
|
|
252
|
+
let exitResult = await waitFor(
|
|
253
|
+
child.exited,
|
|
254
|
+
timeoutMs,
|
|
255
|
+
dependencies.timer,
|
|
256
|
+
options.signal,
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
if (exitResult.kind === "timeout" || exitResult.kind === "aborted") {
|
|
260
|
+
termination = exitResult.kind;
|
|
261
|
+
safelyKill(child, "SIGTERM");
|
|
262
|
+
exitResult = await waitFor(child.exited, terminateGraceMs, dependencies.timer);
|
|
263
|
+
if (exitResult.kind === "timeout") {
|
|
264
|
+
safelyKill(child, "SIGKILL");
|
|
265
|
+
exitResult = await waitFor(child.exited, killGraceMs, dependencies.timer);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const captures = Promise.all([stdoutCapture.result, stderrCapture.result] as const);
|
|
270
|
+
if (exitResult.kind === "timeout") {
|
|
271
|
+
await Promise.all([stdoutCapture.cancel(), stderrCapture.cancel()]);
|
|
272
|
+
} else {
|
|
273
|
+
const drained = await waitFor(captures, killGraceMs, dependencies.timer);
|
|
274
|
+
if (drained.kind === "timeout") {
|
|
275
|
+
await Promise.all([stdoutCapture.cancel(), stderrCapture.cancel()]);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const [stdout, stderr] = await captures;
|
|
280
|
+
const exitCode = exitResult.kind === "value" ? exitResult.value : null;
|
|
281
|
+
const output: ProcessOutput = {
|
|
282
|
+
command,
|
|
283
|
+
exitCode,
|
|
284
|
+
stdout: stdout.text,
|
|
285
|
+
stderr: stderr.text,
|
|
286
|
+
stdoutTruncated: stdout.truncated,
|
|
287
|
+
stderrTruncated: stderr.truncated,
|
|
288
|
+
elapsedMs: elapsed(startedAt, dependencies.now()),
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
if (termination === null && exitResult.kind === "value" && exitCode === 0 && stdout.error === null && stderr.error === null) {
|
|
292
|
+
return { ...output, ok: true, exitCode: 0 };
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const reason: ProcessFailureReason = termination
|
|
296
|
+
?? (
|
|
297
|
+
exitResult.kind === "error" || stdout.error !== null || stderr.error !== null
|
|
298
|
+
? "io"
|
|
299
|
+
: "exit"
|
|
300
|
+
);
|
|
301
|
+
const failureDetail = failureDetailFor(reason, exitCode, stdout, stderr, exitResult);
|
|
302
|
+
return {
|
|
303
|
+
...output,
|
|
304
|
+
ok: false,
|
|
305
|
+
reason,
|
|
306
|
+
diagnostic: redactDiagnostic(
|
|
307
|
+
`${renderCommand(command)}: ${failureDetail}`,
|
|
308
|
+
redactionOptions(options),
|
|
309
|
+
),
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/** Finds an executable without invoking a shell or executing the candidate. */
|
|
314
|
+
export async function findExecutable(
|
|
315
|
+
name: string,
|
|
316
|
+
options: ExecutableDiscoveryOptions = {},
|
|
317
|
+
dependencies: ExecutableDiscoveryDependencies = defaultDiscoveryDependencies,
|
|
318
|
+
): Promise<string | null> {
|
|
319
|
+
if (!isSafeExecutableName(name)) return null;
|
|
320
|
+
|
|
321
|
+
const platform = options.platform ?? process.platform;
|
|
322
|
+
if (name.includes("/") || name.includes("\\")) {
|
|
323
|
+
return (await dependencies.isExecutableFile(name, platform)) ? name : null;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const env = options.env ?? process.env;
|
|
327
|
+
const homeDirectory = options.homeDirectory ?? homedir();
|
|
328
|
+
const pathValue = env.PATH ?? env.Path ?? env.path ?? "";
|
|
329
|
+
const pathDirectories = pathValue.split(delimiter).filter((entry) => entry.length > 0);
|
|
330
|
+
const directories = unique([
|
|
331
|
+
...pathDirectories,
|
|
332
|
+
...(options.extraDirectories ?? []),
|
|
333
|
+
...commonExecutableDirectories(homeDirectory, platform),
|
|
334
|
+
]);
|
|
335
|
+
const names = executableNames(name, env, platform);
|
|
336
|
+
|
|
337
|
+
for (const directory of directories) {
|
|
338
|
+
for (const candidateName of names) {
|
|
339
|
+
const candidate = join(directory, candidateName);
|
|
340
|
+
if (await dependencies.isExecutableFile(candidate, platform)) return candidate;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
return null;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/** Redacts argv while preserving enough structure for useful diagnostics. */
|
|
347
|
+
export function redactArguments(
|
|
348
|
+
argv: readonly string[],
|
|
349
|
+
additionalSecrets: readonly string[] = [],
|
|
350
|
+
): readonly string[] {
|
|
351
|
+
const redacted: string[] = [];
|
|
352
|
+
let redactNext = false;
|
|
353
|
+
|
|
354
|
+
for (const argument of argv) {
|
|
355
|
+
if (redactNext) {
|
|
356
|
+
redacted.push("[REDACTED]");
|
|
357
|
+
redactNext = false;
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const equalsIndex = argument.indexOf("=");
|
|
362
|
+
const switchName = equalsIndex === -1 ? argument : argument.slice(0, equalsIndex);
|
|
363
|
+
if (SENSITIVE_SWITCHES.has(switchName)) {
|
|
364
|
+
if (equalsIndex === -1) {
|
|
365
|
+
redacted.push(argument);
|
|
366
|
+
redactNext = true;
|
|
367
|
+
} else {
|
|
368
|
+
redacted.push(`${switchName}=[REDACTED]`);
|
|
369
|
+
}
|
|
370
|
+
continue;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
redacted.push(redactDiagnostic(argument, { secrets: additionalSecrets }));
|
|
374
|
+
}
|
|
375
|
+
return redacted;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Derives bounded exact secrets that a downloader may echo without the URL
|
|
380
|
+
* wrapper (for example a Generic extractor's basename-derived id or title).
|
|
381
|
+
*/
|
|
382
|
+
export function urlDerivedRedactions(value: string): readonly string[] {
|
|
383
|
+
if (value.length === 0 || value.length > MAX_URL_REDACTION_CODE_UNITS) return [];
|
|
384
|
+
let url: URL;
|
|
385
|
+
try {
|
|
386
|
+
url = new URL(value);
|
|
387
|
+
} catch {
|
|
388
|
+
return [];
|
|
389
|
+
}
|
|
390
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") return [];
|
|
391
|
+
|
|
392
|
+
const candidates: string[] = [];
|
|
393
|
+
const add = (candidate: string): void => {
|
|
394
|
+
if (
|
|
395
|
+
candidate.length >= MIN_DERIVED_URL_SECRET_CODE_UNITS
|
|
396
|
+
&& candidate.length <= MAX_URL_REDACTION_CODE_UNITS
|
|
397
|
+
&& sanitizeTerminalText(candidate) === candidate
|
|
398
|
+
) candidates.push(candidate);
|
|
399
|
+
};
|
|
400
|
+
const addRawDecodedAndStem = (candidate: string): void => {
|
|
401
|
+
add(candidate);
|
|
402
|
+
const lastDot = candidate.lastIndexOf(".");
|
|
403
|
+
if (lastDot > 0) add(candidate.slice(0, lastDot));
|
|
404
|
+
try {
|
|
405
|
+
const decoded = decodeURIComponent(candidate);
|
|
406
|
+
add(decoded);
|
|
407
|
+
const decodedLastDot = decoded.lastIndexOf(".");
|
|
408
|
+
if (decodedLastDot > 0) add(decoded.slice(0, decodedLastDot));
|
|
409
|
+
} catch {
|
|
410
|
+
// Malformed percent encoding has no safe decoded variant.
|
|
411
|
+
}
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
const rawSegments = url.pathname.split("/").filter((segment) => segment.length > 0);
|
|
415
|
+
for (const segment of rawSegments) addRawDecodedAndStem(segment);
|
|
416
|
+
const rawPath = rawSegments.join("/");
|
|
417
|
+
if (rawSegments.length > 1) addRawDecodedAndStem(rawPath);
|
|
418
|
+
for (const component of url.search.slice(1).split("&")) {
|
|
419
|
+
if (component.length === 0) continue;
|
|
420
|
+
const equals = component.indexOf("=");
|
|
421
|
+
const rawKey = equals === -1 ? component : component.slice(0, equals);
|
|
422
|
+
const rawValue = equals === -1 ? "" : component.slice(equals + 1);
|
|
423
|
+
addRawDecodedAndStem(rawKey);
|
|
424
|
+
addRawDecodedAndStem(rawValue);
|
|
425
|
+
}
|
|
426
|
+
for (const [queryKey, queryValue] of url.searchParams) {
|
|
427
|
+
addRawDecodedAndStem(queryKey);
|
|
428
|
+
addRawDecodedAndStem(queryValue);
|
|
429
|
+
}
|
|
430
|
+
addRawDecodedAndStem(url.hash.slice(1));
|
|
431
|
+
addRawDecodedAndStem(url.username);
|
|
432
|
+
addRawDecodedAndStem(url.password);
|
|
433
|
+
return [...new Set(candidates)].toSorted((left, right) => right.length - left.length);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/** Sanitizes text before it crosses the human/JSON diagnostic boundary. */
|
|
437
|
+
export function redactDiagnostic(
|
|
438
|
+
input: string,
|
|
439
|
+
options: Readonly<{
|
|
440
|
+
secrets?: readonly string[];
|
|
441
|
+
homeDirectory?: string;
|
|
442
|
+
maxCodeUnits?: number;
|
|
443
|
+
}> = {},
|
|
444
|
+
): string {
|
|
445
|
+
// Exact caller-provided values can contain material that the generic passes
|
|
446
|
+
// deliberately rewrite, such as a private URL whose query is removed while
|
|
447
|
+
// a path token remains. Remove exact values first so later normalization
|
|
448
|
+
// cannot destroy the match and expose only part of the secret.
|
|
449
|
+
let output = redactExactSecrets(input, options.secrets ?? []);
|
|
450
|
+
output = redactSensitivePatterns(output);
|
|
451
|
+
output = redactInlineSensitivePatterns(sanitizeTerminalText(output));
|
|
452
|
+
if (options.homeDirectory !== undefined && options.homeDirectory.length > 1) {
|
|
453
|
+
output = output.split(options.homeDirectory).join("~");
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// Explicit redaction strings and home paths are caller-controlled. A final
|
|
457
|
+
// terminal pass makes the invariant independent of their contents.
|
|
458
|
+
output = sanitizeTerminalText(output);
|
|
459
|
+
|
|
460
|
+
const limit = normalizeDiagnosticLimit(options.maxCodeUnits);
|
|
461
|
+
return output.length <= limit ? output : `${output.slice(0, limit)}…[truncated]`;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
function redactExactSecrets(input: string, secrets: readonly string[]): string {
|
|
465
|
+
let output = input;
|
|
466
|
+
const longestFirst = [...new Set(secrets.filter((secret) => secret.length > 0))]
|
|
467
|
+
.sort((left, right) => right.length - left.length);
|
|
468
|
+
for (const secret of longestFirst) {
|
|
469
|
+
output = output.split(secret).join("[REDACTED]");
|
|
470
|
+
}
|
|
471
|
+
return output;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
function redactSensitivePatterns(input: string): string {
|
|
475
|
+
return redactInlineSensitivePatterns(
|
|
476
|
+
input.replace(HEADER_PATTERN, (value) => `${value.slice(0, value.indexOf(":"))}: [REDACTED]`),
|
|
477
|
+
);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
function redactInlineSensitivePatterns(input: string): string {
|
|
481
|
+
return input
|
|
482
|
+
.replace(URL_PATTERN, (value) => redactUrl(value))
|
|
483
|
+
.replace(SWITCH_SECRET_PATTERN, (_match, switchName: string) => `${switchName} [REDACTED]`)
|
|
484
|
+
.replace(KEY_VALUE_SECRET_PATTERN, (match) => `${match.slice(0, match.indexOf("=") + 1)}[REDACTED]`);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* Converts untrusted text into one inert terminal line. OSC payloads (including
|
|
489
|
+
* clipboard and hyperlink commands), CSI commands, ANSI string controls, C0,
|
|
490
|
+
* and C1 controls are removed before whitespace is collapsed.
|
|
491
|
+
*/
|
|
492
|
+
export function sanitizeTerminalText(input: string): string {
|
|
493
|
+
return input
|
|
494
|
+
.replace(OSC_SEQUENCE_PATTERN, "")
|
|
495
|
+
.replace(ANSI_STRING_SEQUENCE_PATTERN, "")
|
|
496
|
+
.replace(CSI_SEQUENCE_PATTERN, "")
|
|
497
|
+
.replace(TWO_BYTE_ESCAPE_PATTERN, "")
|
|
498
|
+
.replace(TERMINAL_CONTROL_PATTERN, " ")
|
|
499
|
+
.replace(DIAGNOSTIC_WHITESPACE_PATTERN, " ")
|
|
500
|
+
.trim();
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/** The fixed fallback locations searched after the caller's PATH. */
|
|
504
|
+
export function commonExecutableDirectories(
|
|
505
|
+
homeDirectory: string,
|
|
506
|
+
platform: NodeJS.Platform,
|
|
507
|
+
): readonly string[] {
|
|
508
|
+
const userDirectories = [
|
|
509
|
+
join(homeDirectory, ".local", "bin"),
|
|
510
|
+
join(homeDirectory, "bin"),
|
|
511
|
+
join(homeDirectory, ".bun", "bin"),
|
|
512
|
+
];
|
|
513
|
+
if (platform === "win32") return userDirectories;
|
|
514
|
+
return unique([
|
|
515
|
+
...userDirectories,
|
|
516
|
+
"/opt/homebrew/bin",
|
|
517
|
+
"/usr/local/bin",
|
|
518
|
+
"/home/linuxbrew/.linuxbrew/bin",
|
|
519
|
+
"/opt/local/bin",
|
|
520
|
+
"/usr/bin",
|
|
521
|
+
"/bin",
|
|
522
|
+
]);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
function captureBounded(stream: ReadableStream<Uint8Array> | null, limit: number): Capture {
|
|
526
|
+
if (stream === null) {
|
|
527
|
+
return {
|
|
528
|
+
result: Promise.resolve({ text: "", truncated: false, error: null }),
|
|
529
|
+
cancel: () => Promise.resolve(),
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
const reader = stream.getReader();
|
|
534
|
+
let canceled = false;
|
|
535
|
+
const result = (async (): Promise<CaptureResult> => {
|
|
536
|
+
const chunks: Uint8Array[] = [];
|
|
537
|
+
let storedBytes = 0;
|
|
538
|
+
let truncated = false;
|
|
539
|
+
try {
|
|
540
|
+
while (true) {
|
|
541
|
+
const next = await reader.read();
|
|
542
|
+
if (next.done) break;
|
|
543
|
+
const chunk = next.value;
|
|
544
|
+
if (chunk.byteLength === 0) continue;
|
|
545
|
+
const remaining = limit - storedBytes;
|
|
546
|
+
if (remaining > 0) {
|
|
547
|
+
const kept = chunk.byteLength <= remaining ? chunk.slice() : chunk.slice(0, remaining);
|
|
548
|
+
chunks.push(kept);
|
|
549
|
+
storedBytes += kept.byteLength;
|
|
550
|
+
}
|
|
551
|
+
if (chunk.byteLength > remaining) truncated = true;
|
|
552
|
+
}
|
|
553
|
+
return {
|
|
554
|
+
text: decodeChunks(chunks, storedBytes),
|
|
555
|
+
truncated: truncated || canceled,
|
|
556
|
+
error: null,
|
|
557
|
+
};
|
|
558
|
+
} catch (error) {
|
|
559
|
+
if (canceled) {
|
|
560
|
+
return { text: decodeChunks(chunks, storedBytes), truncated: true, error: null };
|
|
561
|
+
}
|
|
562
|
+
return {
|
|
563
|
+
text: decodeChunks(chunks, storedBytes),
|
|
564
|
+
truncated,
|
|
565
|
+
error: errorMessage(error),
|
|
566
|
+
};
|
|
567
|
+
} finally {
|
|
568
|
+
reader.releaseLock();
|
|
569
|
+
}
|
|
570
|
+
})();
|
|
571
|
+
|
|
572
|
+
return {
|
|
573
|
+
result,
|
|
574
|
+
cancel: async () => {
|
|
575
|
+
canceled = true;
|
|
576
|
+
try {
|
|
577
|
+
await reader.cancel("process did not exit after SIGKILL");
|
|
578
|
+
} catch {
|
|
579
|
+
// A stream may already be closed between timeout observation and cancel.
|
|
580
|
+
}
|
|
581
|
+
},
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
function decodeChunks(chunks: readonly Uint8Array[], byteLength: number): string {
|
|
586
|
+
const bytes = new Uint8Array(byteLength);
|
|
587
|
+
let offset = 0;
|
|
588
|
+
for (const chunk of chunks) {
|
|
589
|
+
bytes.set(chunk, offset);
|
|
590
|
+
offset += chunk.byteLength;
|
|
591
|
+
}
|
|
592
|
+
return new TextDecoder().decode(bytes);
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
function waitFor<T>(
|
|
596
|
+
promise: Promise<T>,
|
|
597
|
+
timeoutMs: number,
|
|
598
|
+
timer: ProcessTimer,
|
|
599
|
+
signal?: AbortSignal,
|
|
600
|
+
): Promise<WaitResult<T>> {
|
|
601
|
+
return new Promise((resolve) => {
|
|
602
|
+
let settled = false;
|
|
603
|
+
const timerHandle: { value: unknown } = { value: undefined };
|
|
604
|
+
const finish = (result: WaitResult<T>): void => {
|
|
605
|
+
if (settled) return;
|
|
606
|
+
settled = true;
|
|
607
|
+
timer.clear(timerHandle.value);
|
|
608
|
+
signal?.removeEventListener("abort", abort);
|
|
609
|
+
resolve(result);
|
|
610
|
+
};
|
|
611
|
+
const abort = (): void => finish({ kind: "aborted" });
|
|
612
|
+
timerHandle.value = timer.set(() => finish({ kind: "timeout" }), timeoutMs);
|
|
613
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
614
|
+
if (signal?.aborted === true) {
|
|
615
|
+
finish({ kind: "aborted" });
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
promise.then(
|
|
620
|
+
(value) => finish({ kind: "value", value }),
|
|
621
|
+
(error: unknown) => finish({ kind: "error", error }),
|
|
622
|
+
);
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
function failureDetailFor(
|
|
627
|
+
reason: ProcessFailureReason,
|
|
628
|
+
exitCode: number | null,
|
|
629
|
+
stdout: CaptureResult,
|
|
630
|
+
stderr: CaptureResult,
|
|
631
|
+
exitResult: WaitResult<number>,
|
|
632
|
+
): string {
|
|
633
|
+
if (reason === "aborted") return "was cancelled and terminated";
|
|
634
|
+
if (reason === "timeout") return "timed out and was terminated";
|
|
635
|
+
if (exitResult.kind === "error") return `could not observe process exit: ${errorMessage(exitResult.error)}`;
|
|
636
|
+
if (stdout.error !== null) return `could not read stdout: ${stdout.error}`;
|
|
637
|
+
if (stderr.error !== null) return `could not read stderr: ${stderr.error}`;
|
|
638
|
+
const stderrSummary = stderr.text.trim();
|
|
639
|
+
const suffix = stderrSummary.length === 0 ? "" : `; stderr: ${stderrSummary}`;
|
|
640
|
+
return `exited with code ${String(exitCode)}${suffix}`;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
function safelyKill(child: SpawnedProcess, signal: ProcessSignal): void {
|
|
644
|
+
try {
|
|
645
|
+
child.kill(signal);
|
|
646
|
+
} catch {
|
|
647
|
+
// The process can exit between the timeout race and signal delivery.
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
function normalizeByteLimit(value: number | undefined, fallback: number): number {
|
|
652
|
+
if (value === undefined || !Number.isFinite(value)) return fallback;
|
|
653
|
+
return Math.min(Math.max(Math.trunc(value), 0), MAX_OUTPUT_LIMIT_BYTES);
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
function normalizeDuration(value: number | undefined, fallback: number): number {
|
|
657
|
+
if (value === undefined || !Number.isFinite(value)) return fallback;
|
|
658
|
+
return Math.min(Math.max(Math.trunc(value), 0), MAX_TIMEOUT_MS);
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
function normalizeDiagnosticLimit(value: number | undefined): number {
|
|
662
|
+
if (value === undefined || !Number.isFinite(value)) return MAX_DIAGNOSTIC_CODE_UNITS;
|
|
663
|
+
return Math.min(Math.max(Math.trunc(value), 0), MAX_DIAGNOSTIC_CODE_UNITS);
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
function elapsed(startedAt: number, finishedAt: number): number {
|
|
667
|
+
return Math.max(0, Math.trunc(finishedAt - startedAt));
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
function renderCommand(argv: readonly string[]): string {
|
|
671
|
+
return argv.map((argument) => JSON.stringify(argument)).join(" ");
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
function errorMessage(error: unknown): string {
|
|
675
|
+
if (error instanceof Error) return error.message;
|
|
676
|
+
if (typeof error === "string") return error;
|
|
677
|
+
return "unknown process error";
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
function homeFromOptions(options: RunProcessOptions): string | undefined {
|
|
681
|
+
const candidate = options.env?.HOME;
|
|
682
|
+
return candidate === undefined || candidate.length === 0 ? undefined : candidate;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
function redactionOptions(options: RunProcessOptions): Readonly<{
|
|
686
|
+
secrets?: readonly string[];
|
|
687
|
+
homeDirectory?: string;
|
|
688
|
+
}> {
|
|
689
|
+
const homeDirectory = homeFromOptions(options);
|
|
690
|
+
return {
|
|
691
|
+
...(options.redactions === undefined ? {} : { secrets: options.redactions }),
|
|
692
|
+
...(homeDirectory === undefined ? {} : { homeDirectory }),
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
function isSafeExecutableName(name: string): boolean {
|
|
697
|
+
return name.length > 0 && name !== "." && name !== ".." && !name.includes("\0");
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
function executableNames(
|
|
701
|
+
name: string,
|
|
702
|
+
env: Readonly<Record<string, string | undefined>>,
|
|
703
|
+
platform: NodeJS.Platform,
|
|
704
|
+
): readonly string[] {
|
|
705
|
+
if (platform !== "win32" || /\.[a-z0-9]+$/iu.test(name)) return [name];
|
|
706
|
+
const extensions = (env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD")
|
|
707
|
+
.split(";")
|
|
708
|
+
.filter((extension) => extension.length > 0);
|
|
709
|
+
return [name, ...extensions.map((extension) => `${name}${extension.toLowerCase()}`)];
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
function unique(values: readonly string[]): readonly string[] {
|
|
713
|
+
return [...new Set(values.filter((value) => value.length > 0))];
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
function redactUrl(value: string): string {
|
|
717
|
+
try {
|
|
718
|
+
const url = new URL(value);
|
|
719
|
+
return `${url.origin}/[REDACTED]`;
|
|
720
|
+
} catch {
|
|
721
|
+
return "[REDACTED URL]";
|
|
722
|
+
}
|
|
723
|
+
}
|