@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,303 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
|
|
3
|
+
export const USAGE = `usage: ghostget media [archive|audio|video|transcript] URL [options]
|
|
4
|
+
ghostget doctor [--json]
|
|
5
|
+
ghostget transcriber setup --engine whisper-cpp --model FILE [options]
|
|
6
|
+
ghostget verify ITEM_DIRECTORY [--json]
|
|
7
|
+
|
|
8
|
+
commands:
|
|
9
|
+
ghostget media URL save media, separate audio/video, and get a transcript
|
|
10
|
+
ghostget archive URL explicit form of the default command
|
|
11
|
+
ghostget audio URL save the source and an audio-only stream-copy derivative
|
|
12
|
+
ghostget video URL save the source and a video-only stream-copy derivative
|
|
13
|
+
ghostget transcript URL save a provider transcript, or make one locally when configured
|
|
14
|
+
ghostget transcriber setup register an existing local whisper.cpp executable and model
|
|
15
|
+
ghostget doctor inspect built-in HTTP and external media capabilities
|
|
16
|
+
ghostget verify PATH recompute and verify every artifact recorded in wrench-media.json
|
|
17
|
+
|
|
18
|
+
capture options:
|
|
19
|
+
--output DIRECTORY library root (default: GHOSTGET_MEDIA_HOME or ~/.local/share/ghostget/media)
|
|
20
|
+
--lang LANGUAGE preferred transcript language (default: en)
|
|
21
|
+
--browser SPEC explicit yt-dlp browser-cookie source
|
|
22
|
+
--auth-context NAME stable private-access realm (required with browser/config)
|
|
23
|
+
--inherit-yt-dlp-config opt into ambient yt-dlp configuration and plugins
|
|
24
|
+
--refresh re-acquire and append only when retained content changes
|
|
25
|
+
--json emit a stable JSON record
|
|
26
|
+
|
|
27
|
+
transcriber setup options:
|
|
28
|
+
--engine whisper-cpp explicit local engine (currently whisper-cpp)
|
|
29
|
+
--model FILE existing local whisper.cpp model; never downloaded by setup
|
|
30
|
+
--executable FILE existing whisper-cli (default: discover it on PATH)
|
|
31
|
+
--replace replace a different existing transcriber configuration
|
|
32
|
+
--json emit a stable JSON record
|
|
33
|
+
|
|
34
|
+
Ghostget media archives material you are authorized to access. It does not bypass DRM or access controls.`;
|
|
35
|
+
|
|
36
|
+
export type CaptureMode = "archive" | "audio" | "video" | "transcript";
|
|
37
|
+
|
|
38
|
+
interface CommandBase {
|
|
39
|
+
readonly json: boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export type CliCommand =
|
|
43
|
+
| ({ readonly kind: "help" } & CommandBase)
|
|
44
|
+
| ({ readonly kind: "version" } & CommandBase)
|
|
45
|
+
| ({ readonly kind: "doctor" } & CommandBase)
|
|
46
|
+
| ({ readonly kind: "verify"; readonly itemDirectory: string } & CommandBase)
|
|
47
|
+
| ({
|
|
48
|
+
readonly kind: "transcriber-setup";
|
|
49
|
+
readonly engine: "whisper-cpp";
|
|
50
|
+
readonly modelPath: string;
|
|
51
|
+
readonly executablePath?: string;
|
|
52
|
+
readonly replace: boolean;
|
|
53
|
+
} & CommandBase)
|
|
54
|
+
| ({
|
|
55
|
+
readonly kind: "capture";
|
|
56
|
+
readonly mode: CaptureMode;
|
|
57
|
+
readonly url: string;
|
|
58
|
+
readonly outputDirectory?: string;
|
|
59
|
+
readonly language: string;
|
|
60
|
+
readonly browser?: string;
|
|
61
|
+
readonly authContext?: string;
|
|
62
|
+
readonly inheritYtDlpConfig: boolean;
|
|
63
|
+
readonly refresh: boolean;
|
|
64
|
+
} & CommandBase);
|
|
65
|
+
|
|
66
|
+
export type ParseArgsResult =
|
|
67
|
+
| { readonly ok: true; readonly command: CliCommand }
|
|
68
|
+
| { readonly ok: false; readonly json: boolean; readonly message: string };
|
|
69
|
+
|
|
70
|
+
const captureModes = new Set<CaptureMode>(["archive", "audio", "video", "transcript"]);
|
|
71
|
+
const valueOptions = new Set([
|
|
72
|
+
"--output",
|
|
73
|
+
"--lang",
|
|
74
|
+
"--browser",
|
|
75
|
+
"--auth-context",
|
|
76
|
+
"--engine",
|
|
77
|
+
"--model",
|
|
78
|
+
"--executable",
|
|
79
|
+
]);
|
|
80
|
+
const booleanOptions = new Set(["--json", "--inherit-yt-dlp-config", "--refresh", "--replace"]);
|
|
81
|
+
const transcriberSetupOptions = new Set([
|
|
82
|
+
"--engine",
|
|
83
|
+
"--model",
|
|
84
|
+
"--executable",
|
|
85
|
+
"--replace",
|
|
86
|
+
"--json",
|
|
87
|
+
]);
|
|
88
|
+
|
|
89
|
+
interface ParsedTokens {
|
|
90
|
+
readonly positionals: readonly string[];
|
|
91
|
+
readonly options: ReadonlyMap<string, string | true>;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function failure(json: boolean, message: string): ParseArgsResult {
|
|
95
|
+
return { ok: false, json, message };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function hasControlCharacter(value: string): boolean {
|
|
99
|
+
for (const character of value) {
|
|
100
|
+
const codePoint = character.codePointAt(0);
|
|
101
|
+
if (codePoint !== undefined && (codePoint <= 0x1f || codePoint === 0x7f)) return true;
|
|
102
|
+
}
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function parseTokens(argv: readonly string[]): ParsedTokens | null {
|
|
107
|
+
const positionals: string[] = [];
|
|
108
|
+
const options = new Map<string, string | true>();
|
|
109
|
+
let optionsEnded = false;
|
|
110
|
+
|
|
111
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
112
|
+
const token = argv[index];
|
|
113
|
+
if (token === undefined) return null;
|
|
114
|
+
if (optionsEnded) {
|
|
115
|
+
positionals.push(token);
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
if (token === "--") {
|
|
119
|
+
optionsEnded = true;
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
if (!token.startsWith("-")) {
|
|
123
|
+
positionals.push(token);
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
if (token === "-h" || token === "--help" || token === "-V" || token === "--version") {
|
|
127
|
+
if (options.has(token)) return null;
|
|
128
|
+
options.set(token, true);
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
if (booleanOptions.has(token)) {
|
|
132
|
+
if (options.has(token)) return null;
|
|
133
|
+
options.set(token, true);
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
if (!valueOptions.has(token) || options.has(token)) return null;
|
|
137
|
+
const value = argv[index + 1];
|
|
138
|
+
if (value === undefined || value.startsWith("-") || value.includes("\0")) return null;
|
|
139
|
+
options.set(token, value);
|
|
140
|
+
index += 1;
|
|
141
|
+
}
|
|
142
|
+
return { positionals, options };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function hasFlag(tokens: ParsedTokens, name: string): boolean {
|
|
146
|
+
return tokens.options.get(name) === true;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function option(tokens: ParsedTokens, name: string): string | undefined {
|
|
150
|
+
const value = tokens.options.get(name);
|
|
151
|
+
return typeof value === "string" ? value : undefined;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function parseSourceUrl(value: string): string | null {
|
|
155
|
+
if (value.length === 0 || value.length > 8_192 || hasControlCharacter(value)) return null;
|
|
156
|
+
try {
|
|
157
|
+
const url = new URL(value);
|
|
158
|
+
if ((url.protocol !== "https:" && url.protocol !== "http:") || url.username !== "" || url.password !== "") {
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
return url.href;
|
|
162
|
+
} catch {
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function isLiteralLanguageTag(value: string): boolean {
|
|
168
|
+
const lower = value.toLowerCase();
|
|
169
|
+
return lower !== "all"
|
|
170
|
+
&& lower !== "live_chat"
|
|
171
|
+
&& /^[A-Za-z0-9]{1,8}(?:-[A-Za-z0-9]{1,8}){0,4}$/u.test(value);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function isValidBrowserSpec(value: string): boolean {
|
|
175
|
+
return value.length > 0 && value.length <= 512 && !hasControlCharacter(value);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** Canonical, non-secret label for one user-declared authorization realm. */
|
|
179
|
+
export function normalizeAuthContextName(value: string): string | null {
|
|
180
|
+
return /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/u.test(value)
|
|
181
|
+
? value.toLowerCase()
|
|
182
|
+
: null;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function validFilesystemOption(value: string): boolean {
|
|
186
|
+
return value.length > 0 && value.length <= 4_096 && !value.includes("\0") && !hasControlCharacter(value);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function transcriberSetupCommand(tokens: ParsedTokens): ParseArgsResult {
|
|
190
|
+
const json = hasFlag(tokens, "--json");
|
|
191
|
+
if ([...tokens.options.keys()].some((name) => !transcriberSetupOptions.has(name))) {
|
|
192
|
+
return failure(json, "transcriber setup accepts only --engine, --model, --executable, --replace, and --json");
|
|
193
|
+
}
|
|
194
|
+
const engine = option(tokens, "--engine");
|
|
195
|
+
if (engine !== "whisper-cpp") {
|
|
196
|
+
return failure(json, "transcriber setup requires --engine whisper-cpp");
|
|
197
|
+
}
|
|
198
|
+
const model = option(tokens, "--model");
|
|
199
|
+
if (model === undefined || !validFilesystemOption(model)) {
|
|
200
|
+
return failure(json, "transcriber setup requires a valid --model filesystem path");
|
|
201
|
+
}
|
|
202
|
+
const executable = option(tokens, "--executable");
|
|
203
|
+
if (executable !== undefined && !validFilesystemOption(executable)) {
|
|
204
|
+
return failure(json, "--executable must be a valid filesystem path");
|
|
205
|
+
}
|
|
206
|
+
return {
|
|
207
|
+
ok: true,
|
|
208
|
+
command: {
|
|
209
|
+
kind: "transcriber-setup",
|
|
210
|
+
engine,
|
|
211
|
+
modelPath: resolve(model),
|
|
212
|
+
...(executable === undefined ? {} : { executablePath: resolve(executable) }),
|
|
213
|
+
replace: hasFlag(tokens, "--replace"),
|
|
214
|
+
json,
|
|
215
|
+
},
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function captureCommand(tokens: ParsedTokens, mode: CaptureMode, urlValue: string): ParseArgsResult {
|
|
220
|
+
const json = hasFlag(tokens, "--json");
|
|
221
|
+
const url = parseSourceUrl(urlValue);
|
|
222
|
+
if (url === null) return failure(json, "source URL must be HTTP(S) and must not contain credentials");
|
|
223
|
+
const language = option(tokens, "--lang") ?? "en";
|
|
224
|
+
if (!isLiteralLanguageTag(language)) return failure(json, "--lang must be a short literal BCP-47-style language tag");
|
|
225
|
+
const browser = option(tokens, "--browser");
|
|
226
|
+
if (browser !== undefined && !isValidBrowserSpec(browser)) return failure(json, "--browser must be nonempty, control-free, and at most 512 characters");
|
|
227
|
+
const inheritYtDlpConfig = hasFlag(tokens, "--inherit-yt-dlp-config");
|
|
228
|
+
if (browser !== undefined && inheritYtDlpConfig) {
|
|
229
|
+
return failure(json, "choose either --browser or --inherit-yt-dlp-config, not both");
|
|
230
|
+
}
|
|
231
|
+
const rawAuthContext = option(tokens, "--auth-context");
|
|
232
|
+
const authContext = rawAuthContext === undefined ? undefined : normalizeAuthContextName(rawAuthContext);
|
|
233
|
+
if (authContext === null) {
|
|
234
|
+
return failure(json, "--auth-context must be a 1-64 character ASCII name");
|
|
235
|
+
}
|
|
236
|
+
const hasPrivateAccess = browser !== undefined || inheritYtDlpConfig;
|
|
237
|
+
if (hasPrivateAccess && authContext === undefined) {
|
|
238
|
+
return failure(json, "--browser and --inherit-yt-dlp-config require --auth-context");
|
|
239
|
+
}
|
|
240
|
+
if (!hasPrivateAccess && authContext !== undefined) {
|
|
241
|
+
return failure(json, "--auth-context requires --browser or --inherit-yt-dlp-config");
|
|
242
|
+
}
|
|
243
|
+
const output = option(tokens, "--output");
|
|
244
|
+
if (output !== undefined && (output.length === 0 || output.length > 4_096 || output.includes("\0"))) {
|
|
245
|
+
return failure(json, "--output must be a nonempty filesystem path");
|
|
246
|
+
}
|
|
247
|
+
return {
|
|
248
|
+
ok: true,
|
|
249
|
+
command: {
|
|
250
|
+
kind: "capture",
|
|
251
|
+
mode,
|
|
252
|
+
url,
|
|
253
|
+
language,
|
|
254
|
+
...(output === undefined ? {} : { outputDirectory: resolve(output) }),
|
|
255
|
+
...(browser === undefined ? {} : { browser }),
|
|
256
|
+
...(authContext === undefined ? {} : { authContext }),
|
|
257
|
+
inheritYtDlpConfig,
|
|
258
|
+
refresh: hasFlag(tokens, "--refresh"),
|
|
259
|
+
json,
|
|
260
|
+
},
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export function parseArgs(argv: readonly string[]): ParseArgsResult {
|
|
265
|
+
const tokens = parseTokens(argv);
|
|
266
|
+
const guessedJson = argv.includes("--json");
|
|
267
|
+
if (tokens === null) return failure(guessedJson, "invalid or duplicate option");
|
|
268
|
+
const json = hasFlag(tokens, "--json");
|
|
269
|
+
const wantsHelp = hasFlag(tokens, "-h") || hasFlag(tokens, "--help");
|
|
270
|
+
const wantsVersion = hasFlag(tokens, "-V") || hasFlag(tokens, "--version");
|
|
271
|
+
if (wantsHelp || wantsVersion) {
|
|
272
|
+
if (wantsHelp && wantsVersion) return failure(json, "choose either --help or --version");
|
|
273
|
+
return { ok: true, command: { kind: wantsHelp ? "help" : "version", json } };
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const [first, second, ...rest] = tokens.positionals;
|
|
277
|
+
if (first === undefined) return { ok: true, command: { kind: "help", json } };
|
|
278
|
+
if (first === "doctor") {
|
|
279
|
+
if (second !== undefined || rest.length !== 0 || tokens.options.size > (json ? 1 : 0)) {
|
|
280
|
+
return failure(json, "doctor accepts only --json");
|
|
281
|
+
}
|
|
282
|
+
return { ok: true, command: { kind: "doctor", json } };
|
|
283
|
+
}
|
|
284
|
+
if (first === "verify") {
|
|
285
|
+
if (second === undefined || rest.length !== 0 || tokens.options.size > (json ? 1 : 0)) {
|
|
286
|
+
return failure(json, "verify requires exactly one item directory and accepts only --json");
|
|
287
|
+
}
|
|
288
|
+
if (second.includes("\0") || second.length > 4_096) return failure(json, "invalid item directory");
|
|
289
|
+
return { ok: true, command: { kind: "verify", itemDirectory: resolve(second), json } };
|
|
290
|
+
}
|
|
291
|
+
if (first === "transcriber") {
|
|
292
|
+
if (second !== "setup" || rest.length !== 0) {
|
|
293
|
+
return failure(json, "transcriber requires the setup subcommand");
|
|
294
|
+
}
|
|
295
|
+
return transcriberSetupCommand(tokens);
|
|
296
|
+
}
|
|
297
|
+
if (captureModes.has(first as CaptureMode)) {
|
|
298
|
+
if (second === undefined || rest.length !== 0) return failure(json, `${first} requires exactly one URL`);
|
|
299
|
+
return captureCommand(tokens, first as CaptureMode, second);
|
|
300
|
+
}
|
|
301
|
+
if (rest.length !== 0 || second !== undefined) return failure(json, "default capture accepts exactly one URL");
|
|
302
|
+
return captureCommand(tokens, "archive", first);
|
|
303
|
+
}
|
package/src/media/cli.ts
ADDED
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import { homedir } from "node:os";
|
|
2
|
+
import { MediaArchiveError, mediaUrl, type MediaArchiveOptions, type MediaArchiveResult } from "./archive";
|
|
3
|
+
import { parseArgs, USAGE } from "./args";
|
|
4
|
+
import { renderDoctorReport, runDoctor, type DoctorOptions, type DoctorReport } from "./doctor";
|
|
5
|
+
import {
|
|
6
|
+
GHOSTGET_MEDIA_VERSION,
|
|
7
|
+
verifyMediaItem,
|
|
8
|
+
type MediaManifest,
|
|
9
|
+
type VerifyItemResult,
|
|
10
|
+
} from "./manifest";
|
|
11
|
+
import { redactDiagnostic, sanitizeTerminalText, urlDerivedRedactions } from "./process";
|
|
12
|
+
import {
|
|
13
|
+
setupWhisperCppTranscriber,
|
|
14
|
+
WhisperCppTranscriberSetupError,
|
|
15
|
+
type ReadyTranscriber,
|
|
16
|
+
type SetupWhisperCppTranscriberOptions,
|
|
17
|
+
} from "./transcriber-config";
|
|
18
|
+
|
|
19
|
+
export interface CliIo {
|
|
20
|
+
readonly stdout: (value: string) => void;
|
|
21
|
+
readonly stderr: (value: string) => void;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export type MediaCliEnvironment = Readonly<Record<string, string | undefined>>;
|
|
25
|
+
|
|
26
|
+
export interface MediaCliDependencies {
|
|
27
|
+
readonly mediaUrl: (options: MediaArchiveOptions) => Promise<MediaArchiveResult>;
|
|
28
|
+
readonly runDoctor: (options: DoctorOptions) => Promise<DoctorReport>;
|
|
29
|
+
readonly verifyMediaItem: (itemDirectory: string) => Promise<VerifyItemResult>;
|
|
30
|
+
readonly setupWhisperCppTranscriber: (
|
|
31
|
+
options: SetupWhisperCppTranscriberOptions,
|
|
32
|
+
) => Promise<ReadyTranscriber>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface RunCliOptions {
|
|
36
|
+
readonly io?: CliIo;
|
|
37
|
+
readonly environment?: MediaCliEnvironment;
|
|
38
|
+
readonly homeDirectory?: string;
|
|
39
|
+
readonly signal?: AbortSignal;
|
|
40
|
+
readonly dependencies?: MediaCliDependencies;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const processIo: CliIo = {
|
|
44
|
+
stdout: (value) => { void process.stdout.write(value); },
|
|
45
|
+
stderr: (value) => { void process.stderr.write(value); },
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const defaultDependencies: MediaCliDependencies = {
|
|
49
|
+
mediaUrl: async (options) => await mediaUrl(options),
|
|
50
|
+
runDoctor: async (options) => await runDoctor(options),
|
|
51
|
+
verifyMediaItem: async (itemDirectory) => await verifyMediaItem(itemDirectory),
|
|
52
|
+
setupWhisperCppTranscriber: async (options) => await setupWhisperCppTranscriber(options),
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const archiveExitCodes: Readonly<Record<MediaArchiveError["code"], number>> = {
|
|
56
|
+
CANCELLED: 130,
|
|
57
|
+
DEPENDENCY_MISSING: 3,
|
|
58
|
+
UNSUPPORTED_SOURCE: 4,
|
|
59
|
+
PROBE_FAILED: 4,
|
|
60
|
+
TRANSCRIPT_UNAVAILABLE: 5,
|
|
61
|
+
CAPTURE_FAILED: 6,
|
|
62
|
+
DERIVATION_FAILED: 7,
|
|
63
|
+
TRANSCRIPTION_FAILED: 7,
|
|
64
|
+
ARCHIVE_CONFLICT: 8,
|
|
65
|
+
ARCHIVE_INVALID: 8,
|
|
66
|
+
BUSY: 9,
|
|
67
|
+
IO_ERROR: 10,
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
function writeJson(io: CliIo, channel: "stdout" | "stderr", value: unknown): void {
|
|
71
|
+
const source = JSON.stringify(value).replace(/[\u007f-\u009f]/gu, (character) =>
|
|
72
|
+
`\\u${character.charCodeAt(0).toString(16).padStart(4, "0")}`,
|
|
73
|
+
);
|
|
74
|
+
io[channel](`${source}\n`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function safeDetails(
|
|
78
|
+
details: Readonly<Record<string, unknown>>,
|
|
79
|
+
homeDirectory: string,
|
|
80
|
+
secrets: readonly string[] = [],
|
|
81
|
+
): Readonly<Record<string, unknown>> {
|
|
82
|
+
const redacted = redactDiagnostic(JSON.stringify(details), { homeDirectory, secrets });
|
|
83
|
+
try {
|
|
84
|
+
const value: unknown = JSON.parse(redacted);
|
|
85
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value as Readonly<Record<string, unknown>> : {};
|
|
86
|
+
} catch {
|
|
87
|
+
return {};
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function captureSummary(result: MediaArchiveResult): Readonly<Record<string, unknown>> {
|
|
92
|
+
const revision = "revision" in result.manifest
|
|
93
|
+
? {
|
|
94
|
+
sequence: result.manifest.revision.sequence,
|
|
95
|
+
subjectAssetKey: result.manifest.revision.subjectAssetKey,
|
|
96
|
+
previousAssetKey: result.manifest.revision.previousAssetKey ?? null,
|
|
97
|
+
contentSha256: result.manifest.revision.content.sha256,
|
|
98
|
+
}
|
|
99
|
+
: null;
|
|
100
|
+
return {
|
|
101
|
+
ok: true,
|
|
102
|
+
status: result.status,
|
|
103
|
+
itemDirectory: result.itemDirectory,
|
|
104
|
+
assetKey: result.manifest.assetKey,
|
|
105
|
+
title: result.manifest.source.title ?? null,
|
|
106
|
+
artifactCount: result.manifest.artifacts.length,
|
|
107
|
+
transcript: result.manifest.transcript,
|
|
108
|
+
revision,
|
|
109
|
+
warnings: result.warnings,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function humanCaptureSummary(result: MediaArchiveResult): string {
|
|
114
|
+
const titleCandidate = sanitizeTerminalText(
|
|
115
|
+
result.manifest.source.title ?? result.manifest.assetKey,
|
|
116
|
+
);
|
|
117
|
+
const title = titleCandidate.length === 0 ? result.manifest.assetKey : titleCandidate;
|
|
118
|
+
const action = result.status === "created" ? "Saved" : "Already archived";
|
|
119
|
+
const revision = "revision" in result.manifest
|
|
120
|
+
? ` · revision ${String(result.manifest.revision.sequence)}`
|
|
121
|
+
: "";
|
|
122
|
+
const transcript = humanTranscriptSummary(result.manifest.transcript);
|
|
123
|
+
const warningLines = result.warnings
|
|
124
|
+
.map((warning) => sanitizeTerminalText(warning))
|
|
125
|
+
.filter((warning) => warning.length > 0)
|
|
126
|
+
.map((warning) => `warning: ${warning}\n`)
|
|
127
|
+
.join("");
|
|
128
|
+
return `${action} ${title}${revision}\n${sanitizeTerminalText(result.itemDirectory)}\n${String(result.manifest.artifacts.length)} artifacts · ${transcript}\n${warningLines}`;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function humanTranscriptSummary(transcript: MediaManifest["transcript"]): string {
|
|
132
|
+
if (transcript.status === "available") {
|
|
133
|
+
const language = sanitizeTerminalText(transcript.language) || "unknown-language";
|
|
134
|
+
return `${transcript.source} ${language} transcript`;
|
|
135
|
+
}
|
|
136
|
+
switch (transcript.reason) {
|
|
137
|
+
case "provider_has_no_captions":
|
|
138
|
+
return "no provider transcript";
|
|
139
|
+
case "not_requested":
|
|
140
|
+
return "transcript not requested";
|
|
141
|
+
case "transcriber_not_configured":
|
|
142
|
+
return "no transcript · local transcriber not configured";
|
|
143
|
+
case "audio_not_present":
|
|
144
|
+
return "no transcript · audio not present";
|
|
145
|
+
case "no_speech":
|
|
146
|
+
return "no speech detected";
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function sanitizeHumanReport(value: string): string {
|
|
151
|
+
return value
|
|
152
|
+
.split("\n")
|
|
153
|
+
.map((line) => sanitizeTerminalText(line))
|
|
154
|
+
.join("\n");
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function sourceLabel(url: string): string {
|
|
158
|
+
try { return new URL(url).hostname; } catch { return "source"; }
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export async function runCli(argv: readonly string[], options: RunCliOptions = {}): Promise<number> {
|
|
162
|
+
const io = options.io ?? processIo;
|
|
163
|
+
const environment = options.environment ?? process.env;
|
|
164
|
+
const homeDirectory = options.homeDirectory ?? homedir();
|
|
165
|
+
const dependencies = options.dependencies ?? defaultDependencies;
|
|
166
|
+
const parsed = parseArgs(argv);
|
|
167
|
+
if (!parsed.ok) {
|
|
168
|
+
if (parsed.json) writeJson(io, "stderr", { ok: false, error: { code: "USAGE", message: parsed.message } });
|
|
169
|
+
else io.stderr(`ghostget media: ${parsed.message}\n\n${USAGE}\n`);
|
|
170
|
+
return 2;
|
|
171
|
+
}
|
|
172
|
+
const { command } = parsed;
|
|
173
|
+
if (command.kind === "help") {
|
|
174
|
+
if (command.json) writeJson(io, "stdout", { ok: true, usage: USAGE });
|
|
175
|
+
else io.stdout(`${USAGE}\n`);
|
|
176
|
+
return 0;
|
|
177
|
+
}
|
|
178
|
+
if (command.kind === "version") {
|
|
179
|
+
if (command.json) writeJson(io, "stdout", { ok: true, version: GHOSTGET_MEDIA_VERSION });
|
|
180
|
+
else io.stdout(`ghostget media ${GHOSTGET_MEDIA_VERSION}\n`);
|
|
181
|
+
return 0;
|
|
182
|
+
}
|
|
183
|
+
if (command.kind === "doctor") {
|
|
184
|
+
const report = await dependencies.runDoctor({ env: environment, homeDirectory });
|
|
185
|
+
if (command.json) writeJson(io, report.ok ? "stdout" : "stderr", { ok: report.ok, report });
|
|
186
|
+
else io[report.ok ? "stdout" : "stderr"](sanitizeHumanReport(renderDoctorReport(report)));
|
|
187
|
+
return report.ok ? 0 : 3;
|
|
188
|
+
}
|
|
189
|
+
if (command.kind === "verify") {
|
|
190
|
+
const result = await dependencies.verifyMediaItem(command.itemDirectory);
|
|
191
|
+
if (command.json) writeJson(io, result.ok ? "stdout" : "stderr", { ok: result.ok, verification: result });
|
|
192
|
+
else if (result.ok) io.stdout(`Verified ${sanitizeTerminalText(result.assetKey ?? command.itemDirectory)}: ${String(result.checkedArtifacts)} artifacts\n`);
|
|
193
|
+
else io.stderr(`ghostget media: verification failed\n${result.failures.map((failure) => `- ${redactDiagnostic(failure, { homeDirectory })}`).join("\n")}\n`);
|
|
194
|
+
return result.ok ? 0 : 8;
|
|
195
|
+
}
|
|
196
|
+
if (command.kind === "transcriber-setup") {
|
|
197
|
+
try {
|
|
198
|
+
const transcriber = await dependencies.setupWhisperCppTranscriber({
|
|
199
|
+
modelPath: command.modelPath,
|
|
200
|
+
...(command.executablePath === undefined
|
|
201
|
+
? {}
|
|
202
|
+
: { executablePath: command.executablePath }),
|
|
203
|
+
replace: command.replace,
|
|
204
|
+
env: environment,
|
|
205
|
+
homeDirectory,
|
|
206
|
+
});
|
|
207
|
+
const configured = {
|
|
208
|
+
adapter: transcriber.descriptor.adapter,
|
|
209
|
+
profile: transcriber.descriptor.profile,
|
|
210
|
+
executableSha256: transcriber.descriptor.executableSha256,
|
|
211
|
+
modelSha256: transcriber.descriptor.modelSha256,
|
|
212
|
+
modelBytes: transcriber.descriptor.modelBytes,
|
|
213
|
+
runtimeProfile: transcriber.descriptor.runtimeProfile,
|
|
214
|
+
runtimeSha256: transcriber.descriptor.runtimeSha256,
|
|
215
|
+
runtimeDependencyCount: transcriber.descriptor.runtimeDependencyCount,
|
|
216
|
+
};
|
|
217
|
+
if (command.json) writeJson(io, "stdout", { ok: true, transcriber: configured });
|
|
218
|
+
else {
|
|
219
|
+
io.stdout(
|
|
220
|
+
`Configured offline whisper.cpp transcription.\n${String(configured.modelBytes)} model bytes · ${String(configured.runtimeDependencyCount)} runtime ${configured.runtimeDependencyCount === 1 ? "dependency" : "dependencies"} · ${configured.profile}\n`,
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
return 0;
|
|
224
|
+
} catch (error) {
|
|
225
|
+
const code = error instanceof WhisperCppTranscriberSetupError
|
|
226
|
+
? error.code
|
|
227
|
+
: "CONFIG_WRITE_FAILED";
|
|
228
|
+
const message = redactDiagnostic(
|
|
229
|
+
error instanceof Error ? error.message : "local transcriber setup failed",
|
|
230
|
+
{ homeDirectory },
|
|
231
|
+
);
|
|
232
|
+
if (command.json) {
|
|
233
|
+
writeJson(io, "stderr", {
|
|
234
|
+
ok: false,
|
|
235
|
+
error: { code: `TRANSCRIBER_${code}`, message },
|
|
236
|
+
});
|
|
237
|
+
} else {
|
|
238
|
+
io.stderr(`ghostget media: TRANSCRIBER_${code}: ${message}\n`);
|
|
239
|
+
}
|
|
240
|
+
return 3;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
try {
|
|
245
|
+
if (!command.json) io.stderr(`Archiving media from ${sourceLabel(command.url)}…\n`);
|
|
246
|
+
const result = await dependencies.mediaUrl({
|
|
247
|
+
url: command.url,
|
|
248
|
+
mode: command.mode,
|
|
249
|
+
language: command.language,
|
|
250
|
+
...(options.signal === undefined ? {} : { signal: options.signal }),
|
|
251
|
+
...(command.outputDirectory === undefined ? {} : { libraryDirectory: command.outputDirectory }),
|
|
252
|
+
...(command.browser === undefined ? {} : { browser: command.browser }),
|
|
253
|
+
...(command.authContext === undefined ? {} : { authContext: command.authContext }),
|
|
254
|
+
inheritYtDlpConfig: command.inheritYtDlpConfig,
|
|
255
|
+
refresh: command.refresh,
|
|
256
|
+
environment,
|
|
257
|
+
homeDirectory,
|
|
258
|
+
});
|
|
259
|
+
if (command.json) writeJson(io, "stdout", captureSummary(result));
|
|
260
|
+
else io.stdout(humanCaptureSummary(result));
|
|
261
|
+
return 0;
|
|
262
|
+
} catch (error) {
|
|
263
|
+
const secrets = [
|
|
264
|
+
command.url,
|
|
265
|
+
...urlDerivedRedactions(command.url),
|
|
266
|
+
...(command.browser === undefined ? [] : [command.browser]),
|
|
267
|
+
...(command.authContext === undefined ? [] : [command.authContext]),
|
|
268
|
+
];
|
|
269
|
+
if (error instanceof MediaArchiveError) {
|
|
270
|
+
const message = redactDiagnostic(error.message, { homeDirectory, secrets });
|
|
271
|
+
if (command.json) {
|
|
272
|
+
writeJson(io, "stderr", { ok: false, error: { code: error.code, message, details: safeDetails(error.details, homeDirectory, secrets) } });
|
|
273
|
+
} else {
|
|
274
|
+
io.stderr(`ghostget media: ${error.code}: ${message}\n`);
|
|
275
|
+
const staging = error.details["stagingDirectory"];
|
|
276
|
+
if (typeof staging === "string") {
|
|
277
|
+
io.stderr(`diagnostic staging: ${redactDiagnostic(staging, { homeDirectory, secrets })}\n`);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return archiveExitCodes[error.code];
|
|
281
|
+
}
|
|
282
|
+
const message = redactDiagnostic(error instanceof Error ? error.message : "unexpected failure", { homeDirectory, secrets });
|
|
283
|
+
if (command.json) writeJson(io, "stderr", { ok: false, error: { code: "INTERNAL", message } });
|
|
284
|
+
else io.stderr(`ghostget media: INTERNAL: ${message}\n`);
|
|
285
|
+
return 1;
|
|
286
|
+
}
|
|
287
|
+
}
|