@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
package/src/args.ts
ADDED
|
@@ -0,0 +1,2053 @@
|
|
|
1
|
+
import { cookieSources, type CookieSource } from "@hraness/kb/clip/args";
|
|
2
|
+
import { isAbsolute, resolve } from "node:path";
|
|
3
|
+
import {
|
|
4
|
+
normalizeAuthSubject,
|
|
5
|
+
normalizeOAuthScopes,
|
|
6
|
+
type LinkedDeviceProvider,
|
|
7
|
+
type OAuthProvider,
|
|
8
|
+
} from "./auth";
|
|
9
|
+
import type { HarContentMode } from "./derive";
|
|
10
|
+
import { platformSurfaceIds, type PlatformSurfaceId } from "./platform-catalog";
|
|
11
|
+
import {
|
|
12
|
+
isProviderPluginId,
|
|
13
|
+
isProviderPluginOperationName,
|
|
14
|
+
isProviderPluginSurfaceId,
|
|
15
|
+
type ProviderPluginSurfaceId,
|
|
16
|
+
} from "./provider-plugin-identifiers";
|
|
17
|
+
import {
|
|
18
|
+
isWhatsAppExportAuthId,
|
|
19
|
+
isWhatsAppExportOutputDirectory,
|
|
20
|
+
} from "./whatsapp-export-coordinate";
|
|
21
|
+
|
|
22
|
+
type MessagingCommand =
|
|
23
|
+
| "messaging-routes"
|
|
24
|
+
| "messaging-resolve"
|
|
25
|
+
| "messaging-context"
|
|
26
|
+
| "messaging-preview";
|
|
27
|
+
|
|
28
|
+
type MessagingArguments = {
|
|
29
|
+
readonly [Command in MessagingCommand]: {
|
|
30
|
+
readonly command: Command;
|
|
31
|
+
readonly inputSource: string;
|
|
32
|
+
readonly privateOutput: string;
|
|
33
|
+
readonly json: boolean;
|
|
34
|
+
};
|
|
35
|
+
}[MessagingCommand];
|
|
36
|
+
|
|
37
|
+
export type GhostgetArguments =
|
|
38
|
+
| { readonly command: "help" }
|
|
39
|
+
| { readonly command: "clip"; readonly arguments: readonly string[] }
|
|
40
|
+
| { readonly command: "read"; readonly arguments: readonly string[] }
|
|
41
|
+
| { readonly command: "media"; readonly arguments: readonly string[] }
|
|
42
|
+
| {
|
|
43
|
+
readonly command: "apple-photos-export-contact-evidence";
|
|
44
|
+
readonly library?: string;
|
|
45
|
+
readonly json: boolean;
|
|
46
|
+
}
|
|
47
|
+
| {
|
|
48
|
+
readonly command: "beeper-export-message-like-me";
|
|
49
|
+
readonly authId: string;
|
|
50
|
+
readonly output: string;
|
|
51
|
+
readonly limitChats?: number;
|
|
52
|
+
readonly limitMessages?: number;
|
|
53
|
+
readonly maxParticipants?: number;
|
|
54
|
+
readonly json: boolean;
|
|
55
|
+
}
|
|
56
|
+
| {
|
|
57
|
+
readonly command: "beeper-export-contact-interactions";
|
|
58
|
+
readonly authId: string;
|
|
59
|
+
readonly limitChats?: number;
|
|
60
|
+
readonly limitMessages?: number;
|
|
61
|
+
readonly maxParticipants?: number;
|
|
62
|
+
readonly json: boolean;
|
|
63
|
+
}
|
|
64
|
+
| {
|
|
65
|
+
readonly command: "whatsapp-export-message-like-me";
|
|
66
|
+
readonly authId: string;
|
|
67
|
+
readonly output: string;
|
|
68
|
+
readonly json: boolean;
|
|
69
|
+
}
|
|
70
|
+
| {
|
|
71
|
+
readonly command: "imessage-transport-install";
|
|
72
|
+
readonly binary: string;
|
|
73
|
+
readonly json: boolean;
|
|
74
|
+
}
|
|
75
|
+
| { readonly command: "doctor"; readonly json: boolean }
|
|
76
|
+
| MessagingArguments
|
|
77
|
+
| { readonly command: "capabilities"; readonly adapterId?: string; readonly json: boolean }
|
|
78
|
+
| { readonly command: "plugin-list"; readonly json: boolean }
|
|
79
|
+
| { readonly command: "plugin-show"; readonly id: string; readonly json: boolean }
|
|
80
|
+
| { readonly command: "platforms"; readonly surfaceId?: PlatformSurfaceId; readonly json: boolean }
|
|
81
|
+
| { readonly command: "thread-split"; readonly surfaceId: PlatformSurfaceId; readonly textSource: string; readonly json: boolean }
|
|
82
|
+
| {
|
|
83
|
+
readonly command: "thread-publish";
|
|
84
|
+
readonly surfaceId: PlatformSurfaceId;
|
|
85
|
+
readonly adapterId: string;
|
|
86
|
+
readonly textSource: string;
|
|
87
|
+
readonly authId: string;
|
|
88
|
+
readonly preview: boolean;
|
|
89
|
+
readonly headed: boolean;
|
|
90
|
+
readonly json: boolean;
|
|
91
|
+
}
|
|
92
|
+
| { readonly command: "auth-list"; readonly json: boolean }
|
|
93
|
+
| {
|
|
94
|
+
readonly command: "auth-login";
|
|
95
|
+
readonly id: string;
|
|
96
|
+
readonly provider: "gmail";
|
|
97
|
+
readonly clientFile: string;
|
|
98
|
+
readonly openBrowser: boolean;
|
|
99
|
+
readonly force: boolean;
|
|
100
|
+
readonly json: boolean;
|
|
101
|
+
}
|
|
102
|
+
| { readonly command: "auth-bind"; readonly id: string; readonly site: ProviderPluginSurfaceId; readonly force: boolean; readonly json: boolean }
|
|
103
|
+
| { readonly command: "auth-pair"; readonly id: string; readonly phone?: string }
|
|
104
|
+
| { readonly command: "auth-sync"; readonly id: string; readonly once: true; readonly json: boolean }
|
|
105
|
+
| {
|
|
106
|
+
readonly command: "auth-add";
|
|
107
|
+
readonly id: string;
|
|
108
|
+
readonly cookieSource?: CookieSource;
|
|
109
|
+
readonly cookieProfile?: string;
|
|
110
|
+
readonly cookiesFile?: string;
|
|
111
|
+
readonly browserProfile?: string;
|
|
112
|
+
readonly browserExecutable?: string;
|
|
113
|
+
readonly oauthProvider?: OAuthProvider;
|
|
114
|
+
readonly tokenFile?: string;
|
|
115
|
+
readonly scopes?: readonly string[];
|
|
116
|
+
readonly linkedDeviceProvider?: LinkedDeviceProvider;
|
|
117
|
+
readonly deviceStore?: string;
|
|
118
|
+
readonly subject?: string;
|
|
119
|
+
readonly trustProfileEgress: boolean;
|
|
120
|
+
readonly force: boolean;
|
|
121
|
+
}
|
|
122
|
+
| { readonly command: "auth-remove"; readonly id: string; readonly yes: boolean }
|
|
123
|
+
| {
|
|
124
|
+
readonly command: "adapter-init";
|
|
125
|
+
readonly id: string;
|
|
126
|
+
readonly target:
|
|
127
|
+
| { readonly kind: "origin"; readonly origin: string }
|
|
128
|
+
| { readonly kind: "platform"; readonly surfaceId: PlatformSurfaceId };
|
|
129
|
+
readonly output: string;
|
|
130
|
+
readonly force: boolean;
|
|
131
|
+
}
|
|
132
|
+
| {
|
|
133
|
+
readonly command: "plugin-scaffold";
|
|
134
|
+
readonly site: string;
|
|
135
|
+
readonly displayName: string;
|
|
136
|
+
readonly origin: string;
|
|
137
|
+
readonly operation: string;
|
|
138
|
+
readonly risk: "R1" | "R2" | "R3";
|
|
139
|
+
readonly evidence: string;
|
|
140
|
+
readonly candidate: number;
|
|
141
|
+
readonly output: string;
|
|
142
|
+
readonly json: boolean;
|
|
143
|
+
}
|
|
144
|
+
| {
|
|
145
|
+
readonly command: "plugin-init";
|
|
146
|
+
readonly id: string;
|
|
147
|
+
readonly displayName: string;
|
|
148
|
+
readonly surfaceId: string;
|
|
149
|
+
readonly origin: string;
|
|
150
|
+
readonly operation: string;
|
|
151
|
+
readonly transport:
|
|
152
|
+
| "provider-api"
|
|
153
|
+
| "web-session-api"
|
|
154
|
+
| "linked-device";
|
|
155
|
+
readonly requiredScopeSets?: readonly (readonly string[])[];
|
|
156
|
+
readonly coverage?: readonly string[];
|
|
157
|
+
readonly output: string;
|
|
158
|
+
readonly json: boolean;
|
|
159
|
+
}
|
|
160
|
+
| { readonly command: "plugin-check"; readonly path: string; readonly json: boolean }
|
|
161
|
+
| {
|
|
162
|
+
readonly command: "plugin-test";
|
|
163
|
+
readonly path: string;
|
|
164
|
+
readonly trustCode: boolean;
|
|
165
|
+
readonly json: boolean;
|
|
166
|
+
}
|
|
167
|
+
| {
|
|
168
|
+
readonly command: "plugin-pack";
|
|
169
|
+
readonly path: string;
|
|
170
|
+
readonly output: string;
|
|
171
|
+
readonly json: boolean;
|
|
172
|
+
}
|
|
173
|
+
| {
|
|
174
|
+
readonly command: "plugin-install";
|
|
175
|
+
readonly path: string;
|
|
176
|
+
readonly trustCode: boolean;
|
|
177
|
+
readonly expectedCurrent?: string;
|
|
178
|
+
readonly json: boolean;
|
|
179
|
+
}
|
|
180
|
+
| { readonly command: "plugin-doctor"; readonly json: boolean }
|
|
181
|
+
| {
|
|
182
|
+
readonly command: "plugin-disable";
|
|
183
|
+
readonly id: string;
|
|
184
|
+
readonly expectedCurrent?: string;
|
|
185
|
+
readonly json: boolean;
|
|
186
|
+
}
|
|
187
|
+
| {
|
|
188
|
+
readonly command: "plugin-remove";
|
|
189
|
+
readonly id: string;
|
|
190
|
+
readonly expectedCurrent?: string;
|
|
191
|
+
readonly yes: boolean;
|
|
192
|
+
readonly json: boolean;
|
|
193
|
+
}
|
|
194
|
+
| { readonly command: "adapter-validate"; readonly path: string; readonly json: boolean }
|
|
195
|
+
| { readonly command: "adapter-sync-bundled"; readonly json: boolean }
|
|
196
|
+
| {
|
|
197
|
+
readonly command: "adapter-install";
|
|
198
|
+
readonly path: string;
|
|
199
|
+
readonly upgradeFrom?: readonly string[];
|
|
200
|
+
readonly force: boolean;
|
|
201
|
+
}
|
|
202
|
+
| { readonly command: "adapter-remove"; readonly id: string; readonly yes: boolean }
|
|
203
|
+
| {
|
|
204
|
+
readonly command: "derive-start";
|
|
205
|
+
readonly adapterId: string;
|
|
206
|
+
readonly url: string;
|
|
207
|
+
readonly authId: string;
|
|
208
|
+
readonly allowRemoteActions: boolean;
|
|
209
|
+
readonly contentMode: HarContentMode;
|
|
210
|
+
readonly browserDomains: readonly string[];
|
|
211
|
+
readonly cookieOrigins: readonly string[];
|
|
212
|
+
readonly fixtureSources: readonly string[];
|
|
213
|
+
readonly headed: boolean;
|
|
214
|
+
}
|
|
215
|
+
| { readonly command: "derive-browser"; readonly id: string; readonly browserArguments: readonly string[]; readonly json: boolean }
|
|
216
|
+
| { readonly command: "derive-list"; readonly json: boolean }
|
|
217
|
+
| {
|
|
218
|
+
readonly command: "derive-review";
|
|
219
|
+
readonly id: string;
|
|
220
|
+
readonly reviewOrigin?: string;
|
|
221
|
+
readonly selection:
|
|
222
|
+
| { readonly kind: "list"; readonly offset: number; readonly limit: number }
|
|
223
|
+
| {
|
|
224
|
+
readonly kind: "entry";
|
|
225
|
+
readonly entryIndex: number;
|
|
226
|
+
readonly stdinMode: "none" | "fixtures" | "field-names";
|
|
227
|
+
};
|
|
228
|
+
readonly json: boolean;
|
|
229
|
+
}
|
|
230
|
+
| {
|
|
231
|
+
readonly command: "derive-finish";
|
|
232
|
+
readonly id: string;
|
|
233
|
+
readonly output: string;
|
|
234
|
+
readonly reviewOrigin?: string;
|
|
235
|
+
readonly surfaceId?: PlatformSurfaceId;
|
|
236
|
+
readonly force: boolean;
|
|
237
|
+
readonly json: boolean;
|
|
238
|
+
}
|
|
239
|
+
| { readonly command: "derive-discard"; readonly id: string; readonly yes: boolean }
|
|
240
|
+
| {
|
|
241
|
+
readonly command: "derive-analyze";
|
|
242
|
+
readonly har: string;
|
|
243
|
+
readonly adapterId: string;
|
|
244
|
+
readonly origin: string;
|
|
245
|
+
readonly output: string;
|
|
246
|
+
readonly surfaceId?: PlatformSurfaceId;
|
|
247
|
+
readonly force: boolean;
|
|
248
|
+
readonly json: boolean;
|
|
249
|
+
}
|
|
250
|
+
| {
|
|
251
|
+
readonly command: "invoke";
|
|
252
|
+
readonly adapterId: string;
|
|
253
|
+
readonly operationId: string;
|
|
254
|
+
readonly inputSource: string;
|
|
255
|
+
readonly authId?: string;
|
|
256
|
+
readonly duplicateRiskOf: readonly string[];
|
|
257
|
+
readonly preview: boolean;
|
|
258
|
+
readonly cacheOnly: boolean;
|
|
259
|
+
readonly projectionIdentityOnly: boolean;
|
|
260
|
+
readonly headed: boolean;
|
|
261
|
+
readonly json: boolean;
|
|
262
|
+
}
|
|
263
|
+
| {
|
|
264
|
+
readonly command: "omni-read";
|
|
265
|
+
readonly inputSource: string;
|
|
266
|
+
readonly cacheOnly: boolean;
|
|
267
|
+
readonly identityOnly: boolean;
|
|
268
|
+
readonly fromExactCache: boolean;
|
|
269
|
+
readonly headed: boolean;
|
|
270
|
+
readonly json: boolean;
|
|
271
|
+
}
|
|
272
|
+
| {
|
|
273
|
+
readonly command: "confirm";
|
|
274
|
+
readonly digest: string;
|
|
275
|
+
readonly headed: boolean;
|
|
276
|
+
readonly privateOutput?: string;
|
|
277
|
+
readonly receiptBindingOutput?: string;
|
|
278
|
+
readonly json: boolean;
|
|
279
|
+
}
|
|
280
|
+
| {
|
|
281
|
+
readonly command: "runs-show";
|
|
282
|
+
readonly runId: string;
|
|
283
|
+
readonly privateOutput?: string;
|
|
284
|
+
readonly receiptBindingOutput?: string;
|
|
285
|
+
readonly json: boolean;
|
|
286
|
+
}
|
|
287
|
+
| { readonly command: "runs-list"; readonly json: boolean }
|
|
288
|
+
| { readonly command: "runs-reconcile"; readonly runId: string; readonly inputSource?: string; readonly json: boolean }
|
|
289
|
+
| { readonly command: "messaging-reconcile"; readonly runId: string; readonly json: boolean }
|
|
290
|
+
| { readonly command: "plans-list"; readonly json: boolean }
|
|
291
|
+
| { readonly command: "plans-cancel"; readonly digest: string; readonly yes: boolean };
|
|
292
|
+
|
|
293
|
+
export type ParseGhostgetResult =
|
|
294
|
+
| { readonly ok: true; readonly value: GhostgetArguments }
|
|
295
|
+
| { readonly ok: false; readonly message: string };
|
|
296
|
+
|
|
297
|
+
type ParseGhostgetFailure = Extract<ParseGhostgetResult, { readonly ok: false }>;
|
|
298
|
+
|
|
299
|
+
function validId(value: string, label: string): string | null {
|
|
300
|
+
return /^[a-z][a-z0-9-]{0,47}$/u.test(value) ? null : `${label} must be lowercase kebab-case`;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function validPluginId(value: string): string | null {
|
|
304
|
+
return isProviderPluginId(value)
|
|
305
|
+
? null
|
|
306
|
+
: "plugin ID must be lowercase kebab-case with at most 63 characters";
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function validSourcePluginSiteId(value: string): string | null {
|
|
310
|
+
return value.length <= 59 && isProviderPluginSurfaceId(value)
|
|
311
|
+
? null
|
|
312
|
+
: "source plugin site ID must be lowercase kebab-case with at most 59 characters";
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function validOperation(value: string): string | null {
|
|
316
|
+
return isProviderPluginOperationName(value)
|
|
317
|
+
? null
|
|
318
|
+
: "operation ID must be a bounded dotted semantic capability with kebab-case segments, such as direct-messaging.send";
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function validRunId(value: string): string | null {
|
|
322
|
+
return /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/u.test(value)
|
|
323
|
+
? null
|
|
324
|
+
: "run ID must be a lowercase UUID";
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function duplicateRiskRunIds(
|
|
328
|
+
values: readonly string[],
|
|
329
|
+
): ParseGhostgetFailure | readonly string[] {
|
|
330
|
+
if (values.length > 1) {
|
|
331
|
+
return {
|
|
332
|
+
ok: false,
|
|
333
|
+
message: "duplicate-tolerant intent v1 accepts exactly one --duplicate-risk-of source run",
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
const seen = new Set<string>();
|
|
337
|
+
for (const value of values) {
|
|
338
|
+
if (validRunId(value) !== null) {
|
|
339
|
+
return { ok: false, message: "--duplicate-risk-of must name a lowercase UUID run ID" };
|
|
340
|
+
}
|
|
341
|
+
if (seen.has(value)) {
|
|
342
|
+
return { ok: false, message: "--duplicate-risk-of must not name the same run more than once" };
|
|
343
|
+
}
|
|
344
|
+
seen.add(value);
|
|
345
|
+
}
|
|
346
|
+
return Object.freeze([...values].sort());
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function knownPlatformSurface(value: string): PlatformSurfaceId | null {
|
|
350
|
+
return platformSurfaceIds.find((surfaceId) => surfaceId === value) ?? null;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function optionalPlatformSurface(
|
|
354
|
+
raw: readonly string[],
|
|
355
|
+
label: string,
|
|
356
|
+
): ParseGhostgetFailure | { readonly surfaceId?: PlatformSurfaceId; readonly json: boolean } {
|
|
357
|
+
const positional = raw.filter((argument) => !argument.startsWith("--"));
|
|
358
|
+
const options = raw.filter((argument) => argument.startsWith("--"));
|
|
359
|
+
if (positional.length > 1 || options.some((argument) => argument !== "--json") || options.length > 1) {
|
|
360
|
+
return { ok: false, message: `${label} accepts one optional surface ID and --json` };
|
|
361
|
+
}
|
|
362
|
+
const requested = positional[0];
|
|
363
|
+
if (requested === undefined) return { json: options.includes("--json") };
|
|
364
|
+
const surfaceId = knownPlatformSurface(requested);
|
|
365
|
+
if (surfaceId === null) return { ok: false, message: `unknown platform surface: ${requested}; run 'ghostget platforms' to list reviewed surfaces` };
|
|
366
|
+
return { surfaceId, json: options.includes("--json") };
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
type ParsedOptions = {
|
|
370
|
+
readonly values: Readonly<Record<string, string>>;
|
|
371
|
+
readonly repeatedValues: Readonly<Record<string, readonly string[]>>;
|
|
372
|
+
readonly booleans: ReadonlySet<string>;
|
|
373
|
+
};
|
|
374
|
+
type OptionValuesResult = ParseGhostgetFailure | ParsedOptions;
|
|
375
|
+
|
|
376
|
+
function optionValues(
|
|
377
|
+
raw: readonly string[],
|
|
378
|
+
valueNames: readonly string[],
|
|
379
|
+
booleanNames: readonly string[],
|
|
380
|
+
repeatedValueNames: readonly string[] = [],
|
|
381
|
+
): OptionValuesResult {
|
|
382
|
+
const allowedValues = new Set(valueNames);
|
|
383
|
+
const allowedRepeatedValues = new Set(repeatedValueNames);
|
|
384
|
+
const allowedBooleans = new Set(booleanNames);
|
|
385
|
+
const values: Record<string, string> = {};
|
|
386
|
+
const repeatedValues: Record<string, string[]> = {};
|
|
387
|
+
const booleans = new Set<string>();
|
|
388
|
+
for (let index = 0; index < raw.length; index += 1) {
|
|
389
|
+
const argument = raw[index];
|
|
390
|
+
if (argument === undefined) continue;
|
|
391
|
+
if (allowedBooleans.has(argument)) {
|
|
392
|
+
if (booleans.has(argument)) return { ok: false, message: `${argument} was provided more than once` };
|
|
393
|
+
booleans.add(argument);
|
|
394
|
+
continue;
|
|
395
|
+
}
|
|
396
|
+
if (!allowedValues.has(argument) && !allowedRepeatedValues.has(argument)) {
|
|
397
|
+
return { ok: false, message: `unknown option: ${argument}` };
|
|
398
|
+
}
|
|
399
|
+
const value = raw[index + 1];
|
|
400
|
+
if (value === undefined || value.startsWith("--")) return { ok: false, message: `${argument} requires a value` };
|
|
401
|
+
if (allowedRepeatedValues.has(argument)) {
|
|
402
|
+
(repeatedValues[argument] ??= []).push(value);
|
|
403
|
+
} else {
|
|
404
|
+
if (values[argument] !== undefined) return { ok: false, message: `${argument} was provided more than once` };
|
|
405
|
+
values[argument] = value;
|
|
406
|
+
}
|
|
407
|
+
index += 1;
|
|
408
|
+
}
|
|
409
|
+
return { values, repeatedValues, booleans };
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function isFailure(value: OptionValuesResult): value is ParseGhostgetFailure {
|
|
413
|
+
return "ok" in value && value.ok === false;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
function parseExactHttpsOriginOption(
|
|
417
|
+
value: string | undefined,
|
|
418
|
+
label: string,
|
|
419
|
+
): ParseGhostgetFailure | string | undefined {
|
|
420
|
+
if (value === undefined) return undefined;
|
|
421
|
+
let origin: URL;
|
|
422
|
+
try {
|
|
423
|
+
if (value.length > 4_096) throw new Error("oversized origin");
|
|
424
|
+
origin = new URL(value);
|
|
425
|
+
} catch {
|
|
426
|
+
return { ok: false, message: `${label} must be an exact HTTPS origin` };
|
|
427
|
+
}
|
|
428
|
+
if (
|
|
429
|
+
origin.protocol !== "https:"
|
|
430
|
+
|| origin.username !== ""
|
|
431
|
+
|| origin.password !== ""
|
|
432
|
+
|| value !== origin.origin
|
|
433
|
+
) {
|
|
434
|
+
return { ok: false, message: `${label} must be an exact HTTPS origin` };
|
|
435
|
+
}
|
|
436
|
+
return origin.origin;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
function simpleJsonOptions(raw: readonly string[], label: string): ParseGhostgetResult | boolean {
|
|
440
|
+
if (raw.some((argument) => argument !== "--json") || raw.filter((argument) => argument === "--json").length > 1) {
|
|
441
|
+
return { ok: false, message: `${label} accepts only --json` };
|
|
442
|
+
}
|
|
443
|
+
return raw.includes("--json");
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
function optionalPositiveInteger(
|
|
447
|
+
value: string | undefined,
|
|
448
|
+
label: string,
|
|
449
|
+
maximum: number,
|
|
450
|
+
): ParseGhostgetFailure | number | undefined {
|
|
451
|
+
if (value === undefined) return undefined;
|
|
452
|
+
if (!/^[1-9][0-9]*$/u.test(value)) {
|
|
453
|
+
return { ok: false, message: `${label} must be a positive integer` };
|
|
454
|
+
}
|
|
455
|
+
const parsed = Number(value);
|
|
456
|
+
if (!Number.isSafeInteger(parsed) || parsed > maximum) {
|
|
457
|
+
return {
|
|
458
|
+
ok: false,
|
|
459
|
+
message: `${label} must not exceed ${String(maximum)}`,
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
return parsed;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
function parseMessagingArguments(raw: readonly string[]): ParseGhostgetResult {
|
|
466
|
+
const operation = raw[0];
|
|
467
|
+
if (operation === "reconcile") {
|
|
468
|
+
const runId = raw[1];
|
|
469
|
+
if (runId === undefined) {
|
|
470
|
+
return { ok: false, message: "messaging reconcile requires a run ID" };
|
|
471
|
+
}
|
|
472
|
+
const issue = validRunId(runId);
|
|
473
|
+
if (issue !== null) return { ok: false, message: issue };
|
|
474
|
+
const json = simpleJsonOptions(raw.slice(2), "messaging reconcile");
|
|
475
|
+
return typeof json === "boolean"
|
|
476
|
+
? { ok: true, value: { command: "messaging-reconcile", runId, json } }
|
|
477
|
+
: json;
|
|
478
|
+
}
|
|
479
|
+
if (
|
|
480
|
+
operation !== "routes"
|
|
481
|
+
&& operation !== "resolve"
|
|
482
|
+
&& operation !== "context"
|
|
483
|
+
&& operation !== "preview"
|
|
484
|
+
) {
|
|
485
|
+
return {
|
|
486
|
+
ok: false,
|
|
487
|
+
message: "messaging requires routes, resolve, context, preview, or reconcile",
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
const parsed = optionValues(
|
|
491
|
+
raw.slice(1),
|
|
492
|
+
["--input", "--private-output"],
|
|
493
|
+
["--json"],
|
|
494
|
+
);
|
|
495
|
+
if (isFailure(parsed)) return parsed;
|
|
496
|
+
const inputSource = parsed.values["--input"];
|
|
497
|
+
const privateOutput = parsed.values["--private-output"];
|
|
498
|
+
if (
|
|
499
|
+
inputSource === undefined
|
|
500
|
+
|| (
|
|
501
|
+
inputSource !== "-"
|
|
502
|
+
&& (
|
|
503
|
+
!inputSource.startsWith("@")
|
|
504
|
+
|| !isAbsolute(inputSource.slice(1))
|
|
505
|
+
|| resolve(inputSource.slice(1)) !== inputSource.slice(1)
|
|
506
|
+
)
|
|
507
|
+
)
|
|
508
|
+
) {
|
|
509
|
+
return {
|
|
510
|
+
ok: false,
|
|
511
|
+
message:
|
|
512
|
+
"messaging requires --input <-|@normalized-absolute-owner-only-file>; inline or argv capability data is forbidden",
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
if (
|
|
516
|
+
privateOutput === undefined
|
|
517
|
+
|| !isAbsolute(privateOutput)
|
|
518
|
+
|| resolve(privateOutput) !== privateOutput
|
|
519
|
+
|| Buffer.byteLength(privateOutput, "utf8") > 4_096
|
|
520
|
+
|| /[\0\r\n]/u.test(privateOutput)
|
|
521
|
+
) {
|
|
522
|
+
return {
|
|
523
|
+
ok: false,
|
|
524
|
+
message:
|
|
525
|
+
"messaging requires --private-output <normalized-absolute-mode-0600-file>",
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
return {
|
|
529
|
+
ok: true,
|
|
530
|
+
value: {
|
|
531
|
+
command: `messaging-${operation}`,
|
|
532
|
+
inputSource,
|
|
533
|
+
privateOutput,
|
|
534
|
+
json: parsed.booleans.has("--json"),
|
|
535
|
+
},
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
function parsePluginScaffoldArguments(
|
|
540
|
+
raw: readonly string[],
|
|
541
|
+
label: "plugin scaffold" | "adapter scaffold",
|
|
542
|
+
): ParseGhostgetResult {
|
|
543
|
+
const parsed = optionValues(
|
|
544
|
+
raw,
|
|
545
|
+
[
|
|
546
|
+
"--site",
|
|
547
|
+
"--display-name",
|
|
548
|
+
"--origin",
|
|
549
|
+
"--operation",
|
|
550
|
+
"--risk",
|
|
551
|
+
"--evidence",
|
|
552
|
+
"--candidate",
|
|
553
|
+
"--output",
|
|
554
|
+
],
|
|
555
|
+
["--json"],
|
|
556
|
+
);
|
|
557
|
+
if (isFailure(parsed)) return parsed;
|
|
558
|
+
const site = parsed.values["--site"];
|
|
559
|
+
const displayName = parsed.values["--display-name"];
|
|
560
|
+
const origin = parsed.values["--origin"];
|
|
561
|
+
const operation = parsed.values["--operation"];
|
|
562
|
+
const risk = parsed.values["--risk"];
|
|
563
|
+
const evidence = parsed.values["--evidence"];
|
|
564
|
+
const candidateText = parsed.values["--candidate"];
|
|
565
|
+
const output = parsed.values["--output"];
|
|
566
|
+
if (
|
|
567
|
+
site === undefined
|
|
568
|
+
|| displayName === undefined
|
|
569
|
+
|| origin === undefined
|
|
570
|
+
|| operation === undefined
|
|
571
|
+
|| risk === undefined
|
|
572
|
+
|| evidence === undefined
|
|
573
|
+
|| candidateText === undefined
|
|
574
|
+
|| output === undefined
|
|
575
|
+
) {
|
|
576
|
+
return {
|
|
577
|
+
ok: false,
|
|
578
|
+
message:
|
|
579
|
+
`${label} requires --site, --display-name, --origin, --operation, --risk, --evidence, --candidate, and --output`,
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
const siteIssue = validSourcePluginSiteId(site);
|
|
583
|
+
if (siteIssue !== null) return { ok: false, message: siteIssue };
|
|
584
|
+
const operationIssue = validOperation(operation);
|
|
585
|
+
if (operationIssue !== null) return { ok: false, message: operationIssue };
|
|
586
|
+
if (risk !== "R1" && risk !== "R2" && risk !== "R3") {
|
|
587
|
+
return { ok: false, message: `${label} --risk must be R1, R2, or R3` };
|
|
588
|
+
}
|
|
589
|
+
if (!/^(?:0|[1-9][0-9]{0,5})$/u.test(candidateText)) {
|
|
590
|
+
return { ok: false, message: `${label} --candidate must be a zero-based integer` };
|
|
591
|
+
}
|
|
592
|
+
return {
|
|
593
|
+
ok: true,
|
|
594
|
+
value: {
|
|
595
|
+
command: "plugin-scaffold",
|
|
596
|
+
site,
|
|
597
|
+
displayName,
|
|
598
|
+
origin,
|
|
599
|
+
operation,
|
|
600
|
+
risk,
|
|
601
|
+
evidence,
|
|
602
|
+
candidate: Number.parseInt(candidateText, 10),
|
|
603
|
+
output,
|
|
604
|
+
json: parsed.booleans.has("--json"),
|
|
605
|
+
},
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
function commaList(
|
|
610
|
+
value: string,
|
|
611
|
+
label: string,
|
|
612
|
+
): ParseGhostgetFailure | readonly string[] {
|
|
613
|
+
const values = value.split(",").map((item) => item.trim());
|
|
614
|
+
if (
|
|
615
|
+
values.length < 1
|
|
616
|
+
|| values.length > 64
|
|
617
|
+
|| values.some((item) =>
|
|
618
|
+
item.length < 1
|
|
619
|
+
|| item.length > 256
|
|
620
|
+
|| /[\0\r\n]/u.test(item))
|
|
621
|
+
|| new Set(values).size !== values.length
|
|
622
|
+
) {
|
|
623
|
+
return {
|
|
624
|
+
ok: false,
|
|
625
|
+
message: `${label} must be a comma-separated list of unique bounded values`,
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
return Object.freeze(values);
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
function parsePluginInitArguments(
|
|
632
|
+
raw: readonly string[],
|
|
633
|
+
): ParseGhostgetResult {
|
|
634
|
+
const id = raw[0];
|
|
635
|
+
if (id === undefined || id.startsWith("--")) {
|
|
636
|
+
return { ok: false, message: "plugin init requires a plugin ID" };
|
|
637
|
+
}
|
|
638
|
+
const idIssue = validPluginId(id);
|
|
639
|
+
if (idIssue !== null) return { ok: false, message: idIssue };
|
|
640
|
+
const parsed = optionValues(
|
|
641
|
+
raw.slice(1),
|
|
642
|
+
[
|
|
643
|
+
"--display-name",
|
|
644
|
+
"--surface",
|
|
645
|
+
"--origin",
|
|
646
|
+
"--operation",
|
|
647
|
+
"--transport",
|
|
648
|
+
"--coverage",
|
|
649
|
+
"--output",
|
|
650
|
+
],
|
|
651
|
+
["--json"],
|
|
652
|
+
["--scope-set"],
|
|
653
|
+
);
|
|
654
|
+
if (isFailure(parsed)) return parsed;
|
|
655
|
+
const displayName = parsed.values["--display-name"];
|
|
656
|
+
const surfaceId = parsed.values["--surface"];
|
|
657
|
+
const origin = parsed.values["--origin"];
|
|
658
|
+
const operation = parsed.values["--operation"];
|
|
659
|
+
const output = parsed.values["--output"];
|
|
660
|
+
if (
|
|
661
|
+
displayName === undefined
|
|
662
|
+
|| surfaceId === undefined
|
|
663
|
+
|| origin === undefined
|
|
664
|
+
|| operation === undefined
|
|
665
|
+
|| output === undefined
|
|
666
|
+
) {
|
|
667
|
+
return {
|
|
668
|
+
ok: false,
|
|
669
|
+
message:
|
|
670
|
+
"plugin init requires --display-name, --surface, --origin, --operation, and --output",
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
if (!isProviderPluginSurfaceId(surfaceId)) {
|
|
674
|
+
return {
|
|
675
|
+
ok: false,
|
|
676
|
+
message: "plugin surface ID must be bounded lowercase kebab-case",
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
const operationIssue = validOperation(operation);
|
|
680
|
+
if (operationIssue !== null) return { ok: false, message: operationIssue };
|
|
681
|
+
const transportValue = parsed.values["--transport"]
|
|
682
|
+
?? "web-session-api";
|
|
683
|
+
if (
|
|
684
|
+
transportValue !== "provider-api"
|
|
685
|
+
&& transportValue !== "web-session-api"
|
|
686
|
+
&& transportValue !== "linked-device"
|
|
687
|
+
) {
|
|
688
|
+
return {
|
|
689
|
+
ok: false,
|
|
690
|
+
message:
|
|
691
|
+
"plugin init --transport must be provider-api, web-session-api, or linked-device",
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
const rawScopeSets = parsed.repeatedValues["--scope-set"] ?? [];
|
|
695
|
+
const coverageValue = parsed.values["--coverage"];
|
|
696
|
+
if (transportValue !== "provider-api") {
|
|
697
|
+
if (rawScopeSets.length > 0 || coverageValue !== undefined) {
|
|
698
|
+
return {
|
|
699
|
+
ok: false,
|
|
700
|
+
message:
|
|
701
|
+
"plugin init accepts --scope-set and --coverage only for provider-api",
|
|
702
|
+
};
|
|
703
|
+
}
|
|
704
|
+
return {
|
|
705
|
+
ok: true,
|
|
706
|
+
value: {
|
|
707
|
+
command: "plugin-init",
|
|
708
|
+
id,
|
|
709
|
+
displayName,
|
|
710
|
+
surfaceId,
|
|
711
|
+
origin,
|
|
712
|
+
operation,
|
|
713
|
+
transport: transportValue,
|
|
714
|
+
output,
|
|
715
|
+
json: parsed.booleans.has("--json"),
|
|
716
|
+
},
|
|
717
|
+
};
|
|
718
|
+
}
|
|
719
|
+
if (rawScopeSets.length < 1 || coverageValue === undefined) {
|
|
720
|
+
return {
|
|
721
|
+
ok: false,
|
|
722
|
+
message:
|
|
723
|
+
"provider-api plugin init requires --scope-set and --coverage",
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
const requiredScopeSets: (readonly string[])[] = [];
|
|
727
|
+
for (const [index, rawScopeSet] of rawScopeSets.entries()) {
|
|
728
|
+
const scopeSet = commaList(
|
|
729
|
+
rawScopeSet,
|
|
730
|
+
`plugin init --scope-set ${index + 1}`,
|
|
731
|
+
);
|
|
732
|
+
if ("ok" in scopeSet) return scopeSet;
|
|
733
|
+
requiredScopeSets.push(scopeSet);
|
|
734
|
+
}
|
|
735
|
+
const coverage = commaList(coverageValue, "plugin init --coverage");
|
|
736
|
+
if ("ok" in coverage) return coverage;
|
|
737
|
+
return {
|
|
738
|
+
ok: true,
|
|
739
|
+
value: {
|
|
740
|
+
command: "plugin-init",
|
|
741
|
+
id,
|
|
742
|
+
displayName,
|
|
743
|
+
surfaceId,
|
|
744
|
+
origin,
|
|
745
|
+
operation,
|
|
746
|
+
transport: transportValue,
|
|
747
|
+
requiredScopeSets: Object.freeze(requiredScopeSets),
|
|
748
|
+
coverage,
|
|
749
|
+
output,
|
|
750
|
+
json: parsed.booleans.has("--json"),
|
|
751
|
+
},
|
|
752
|
+
};
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
function validExpectedPluginDigest(
|
|
756
|
+
value: string | undefined,
|
|
757
|
+
): ParseGhostgetFailure | string | undefined {
|
|
758
|
+
if (value === undefined || /^[a-f0-9]{64}$/u.test(value)) return value;
|
|
759
|
+
return {
|
|
760
|
+
ok: false,
|
|
761
|
+
message: "--expected-current must be a 64-character lowercase SHA-256 digest",
|
|
762
|
+
};
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
export function parseGhostgetArguments(raw: readonly string[]): ParseGhostgetResult {
|
|
766
|
+
if (raw.length === 0) {
|
|
767
|
+
return { ok: true, value: { command: "help" } };
|
|
768
|
+
}
|
|
769
|
+
if (raw[0] === "help" || raw[0] === "--help" || raw[0] === "-h") {
|
|
770
|
+
if (raw.length > 1) return { ok: false, message: "help accepts no arguments" };
|
|
771
|
+
return { ok: true, value: { command: "help" } };
|
|
772
|
+
}
|
|
773
|
+
const first = raw[0] ?? "";
|
|
774
|
+
if (/^https?:\/\//iu.test(first)) return { ok: true, value: { command: "clip", arguments: raw } };
|
|
775
|
+
if (first === "clip" || first === "capture") return { ok: true, value: { command: "clip", arguments: raw.slice(1) } };
|
|
776
|
+
if (first === "read" || first === "inspect") return { ok: true, value: { command: "read", arguments: raw.slice(1) } };
|
|
777
|
+
if (first === "media") {
|
|
778
|
+
const mediaArguments = raw.slice(1);
|
|
779
|
+
const mediaMode = mediaArguments[0];
|
|
780
|
+
return {
|
|
781
|
+
ok: true,
|
|
782
|
+
value: {
|
|
783
|
+
command: "media",
|
|
784
|
+
arguments: mediaMode === "archive" || mediaMode === "audio" || mediaMode === "video" || mediaMode === "transcript"
|
|
785
|
+
? mediaArguments
|
|
786
|
+
: ["archive", ...mediaArguments],
|
|
787
|
+
},
|
|
788
|
+
};
|
|
789
|
+
}
|
|
790
|
+
if (
|
|
791
|
+
first === "archive"
|
|
792
|
+
|| first === "audio"
|
|
793
|
+
|| first === "video"
|
|
794
|
+
|| first === "transcript"
|
|
795
|
+
|| first === "verify"
|
|
796
|
+
|| first === "transcriber"
|
|
797
|
+
) {
|
|
798
|
+
return { ok: true, value: { command: "media", arguments: raw } };
|
|
799
|
+
}
|
|
800
|
+
if (first === "apple-photos") {
|
|
801
|
+
if (raw[1] !== "export-contact-evidence") {
|
|
802
|
+
return {
|
|
803
|
+
ok: false,
|
|
804
|
+
message: "apple-photos requires export-contact-evidence",
|
|
805
|
+
};
|
|
806
|
+
}
|
|
807
|
+
const parsed = optionValues(raw.slice(2), ["--library"], ["--json"]);
|
|
808
|
+
if (isFailure(parsed)) return parsed;
|
|
809
|
+
const library = parsed.values["--library"];
|
|
810
|
+
if (library !== undefined && (
|
|
811
|
+
!isAbsolute(library)
|
|
812
|
+
|| resolve(library) !== library
|
|
813
|
+
|| !library.endsWith(".photoslibrary")
|
|
814
|
+
|| Buffer.byteLength(library, "utf8") > 4_096
|
|
815
|
+
|| /[\0\r\n]/u.test(library)
|
|
816
|
+
)) {
|
|
817
|
+
return {
|
|
818
|
+
ok: false,
|
|
819
|
+
message:
|
|
820
|
+
"apple-photos export-contact-evidence requires one normalized absolute .photoslibrary path",
|
|
821
|
+
};
|
|
822
|
+
}
|
|
823
|
+
return {
|
|
824
|
+
ok: true,
|
|
825
|
+
value: {
|
|
826
|
+
command: "apple-photos-export-contact-evidence",
|
|
827
|
+
...(library === undefined ? {} : { library }),
|
|
828
|
+
json: parsed.booleans.has("--json"),
|
|
829
|
+
},
|
|
830
|
+
};
|
|
831
|
+
}
|
|
832
|
+
if (first === "beeper") {
|
|
833
|
+
const subcommand = raw[1];
|
|
834
|
+
if (
|
|
835
|
+
subcommand !== "export-message-like-me"
|
|
836
|
+
&& subcommand !== "export-contact-interactions"
|
|
837
|
+
) {
|
|
838
|
+
return {
|
|
839
|
+
ok: false,
|
|
840
|
+
message:
|
|
841
|
+
"beeper requires export-message-like-me or export-contact-interactions",
|
|
842
|
+
};
|
|
843
|
+
}
|
|
844
|
+
const parsed = optionValues(
|
|
845
|
+
raw.slice(2),
|
|
846
|
+
[
|
|
847
|
+
"--auth",
|
|
848
|
+
"--output",
|
|
849
|
+
"--limit-chats",
|
|
850
|
+
"--limit-messages",
|
|
851
|
+
"--max-participants",
|
|
852
|
+
],
|
|
853
|
+
["--json"],
|
|
854
|
+
);
|
|
855
|
+
if (isFailure(parsed)) return parsed;
|
|
856
|
+
const authId = parsed.values["--auth"];
|
|
857
|
+
const output = parsed.values["--output"];
|
|
858
|
+
if (authId === undefined || validId(authId, "auth ID") !== null) {
|
|
859
|
+
return {
|
|
860
|
+
ok: false,
|
|
861
|
+
message: `beeper ${subcommand} requires --auth <lowercase-kebab-id>`,
|
|
862
|
+
};
|
|
863
|
+
}
|
|
864
|
+
if (subcommand === "export-message-like-me" && (
|
|
865
|
+
output === undefined
|
|
866
|
+
|| !isAbsolute(output)
|
|
867
|
+
|| resolve(output) !== output
|
|
868
|
+
|| Buffer.byteLength(output, "utf8") > 4_096
|
|
869
|
+
|| /[\0\r\n]/u.test(output)
|
|
870
|
+
)) {
|
|
871
|
+
return {
|
|
872
|
+
ok: false,
|
|
873
|
+
message: "beeper export-message-like-me requires --output <normalized-absolute-directory>",
|
|
874
|
+
};
|
|
875
|
+
}
|
|
876
|
+
if (subcommand === "export-contact-interactions" && output !== undefined) {
|
|
877
|
+
return {
|
|
878
|
+
ok: false,
|
|
879
|
+
message: "beeper export-contact-interactions writes its body-free artifact to stdout and does not accept --output",
|
|
880
|
+
};
|
|
881
|
+
}
|
|
882
|
+
const limitChats = optionalPositiveInteger(
|
|
883
|
+
parsed.values["--limit-chats"],
|
|
884
|
+
"--limit-chats",
|
|
885
|
+
100_000,
|
|
886
|
+
);
|
|
887
|
+
if (typeof limitChats === "object") return limitChats;
|
|
888
|
+
const limitMessages = optionalPositiveInteger(
|
|
889
|
+
parsed.values["--limit-messages"],
|
|
890
|
+
"--limit-messages",
|
|
891
|
+
1_000_000,
|
|
892
|
+
);
|
|
893
|
+
if (typeof limitMessages === "object") return limitMessages;
|
|
894
|
+
const maxParticipants = optionalPositiveInteger(
|
|
895
|
+
parsed.values["--max-participants"],
|
|
896
|
+
"--max-participants",
|
|
897
|
+
2_000,
|
|
898
|
+
);
|
|
899
|
+
if (typeof maxParticipants === "object") return maxParticipants;
|
|
900
|
+
const common = {
|
|
901
|
+
authId,
|
|
902
|
+
...(limitChats === undefined ? {} : { limitChats }),
|
|
903
|
+
...(limitMessages === undefined ? {} : { limitMessages }),
|
|
904
|
+
...(maxParticipants === undefined ? {} : { maxParticipants }),
|
|
905
|
+
json: parsed.booleans.has("--json"),
|
|
906
|
+
} as const;
|
|
907
|
+
if (subcommand === "export-contact-interactions") {
|
|
908
|
+
return {
|
|
909
|
+
ok: true,
|
|
910
|
+
value: {
|
|
911
|
+
command: "beeper-export-contact-interactions",
|
|
912
|
+
...common,
|
|
913
|
+
},
|
|
914
|
+
};
|
|
915
|
+
}
|
|
916
|
+
return {
|
|
917
|
+
ok: true,
|
|
918
|
+
value: {
|
|
919
|
+
command: "beeper-export-message-like-me",
|
|
920
|
+
...common,
|
|
921
|
+
output: output!,
|
|
922
|
+
},
|
|
923
|
+
};
|
|
924
|
+
}
|
|
925
|
+
if (first === "whatsapp") {
|
|
926
|
+
if (raw[1] !== "export-message-like-me") {
|
|
927
|
+
return {
|
|
928
|
+
ok: false,
|
|
929
|
+
message: "whatsapp requires export-message-like-me",
|
|
930
|
+
};
|
|
931
|
+
}
|
|
932
|
+
const parsed = optionValues(raw.slice(2), ["--auth", "--output"], ["--json"]);
|
|
933
|
+
if (isFailure(parsed)) return parsed;
|
|
934
|
+
const authId = parsed.values["--auth"];
|
|
935
|
+
const output = parsed.values["--output"];
|
|
936
|
+
if (!isWhatsAppExportAuthId(authId)) {
|
|
937
|
+
return {
|
|
938
|
+
ok: false,
|
|
939
|
+
message: "whatsapp export-message-like-me requires --auth <lowercase-kebab-id>",
|
|
940
|
+
};
|
|
941
|
+
}
|
|
942
|
+
if (!isWhatsAppExportOutputDirectory(output)) {
|
|
943
|
+
return {
|
|
944
|
+
ok: false,
|
|
945
|
+
message: "whatsapp export-message-like-me requires --output <normalized-absolute-directory>",
|
|
946
|
+
};
|
|
947
|
+
}
|
|
948
|
+
return {
|
|
949
|
+
ok: true,
|
|
950
|
+
value: {
|
|
951
|
+
command: "whatsapp-export-message-like-me",
|
|
952
|
+
authId,
|
|
953
|
+
output,
|
|
954
|
+
json: parsed.booleans.has("--json"),
|
|
955
|
+
},
|
|
956
|
+
};
|
|
957
|
+
}
|
|
958
|
+
if (first === "imessage") {
|
|
959
|
+
if (raw[1] !== "transport" || raw[2] !== "install") {
|
|
960
|
+
return {
|
|
961
|
+
ok: false,
|
|
962
|
+
message: "imessage requires transport install",
|
|
963
|
+
};
|
|
964
|
+
}
|
|
965
|
+
const parsed = optionValues(raw.slice(3), ["--binary"], ["--json"]);
|
|
966
|
+
if (isFailure(parsed)) return parsed;
|
|
967
|
+
const binary = parsed.values["--binary"];
|
|
968
|
+
if (
|
|
969
|
+
binary === undefined
|
|
970
|
+
|| !isAbsolute(binary)
|
|
971
|
+
|| resolve(binary) !== binary
|
|
972
|
+
|| Buffer.byteLength(binary, "utf8") > 4_096
|
|
973
|
+
|| /[\0\r\n]/u.test(binary)
|
|
974
|
+
) {
|
|
975
|
+
return {
|
|
976
|
+
ok: false,
|
|
977
|
+
message:
|
|
978
|
+
"imessage transport install requires --binary <normalized-absolute-reviewed-imsg-file>",
|
|
979
|
+
};
|
|
980
|
+
}
|
|
981
|
+
return {
|
|
982
|
+
ok: true,
|
|
983
|
+
value: {
|
|
984
|
+
command: "imessage-transport-install",
|
|
985
|
+
binary,
|
|
986
|
+
json: parsed.booleans.has("--json"),
|
|
987
|
+
},
|
|
988
|
+
};
|
|
989
|
+
}
|
|
990
|
+
if (first === "messaging") return parseMessagingArguments(raw.slice(1));
|
|
991
|
+
if (first === "run") return parseGhostgetArguments(["invoke", ...raw.slice(1)]);
|
|
992
|
+
if (first === "doctor") {
|
|
993
|
+
const json = simpleJsonOptions(raw.slice(1), "doctor");
|
|
994
|
+
return typeof json === "boolean" ? { ok: true, value: { command: "doctor", json } } : json;
|
|
995
|
+
}
|
|
996
|
+
if (first === "capabilities") {
|
|
997
|
+
const positional = raw.slice(1).filter((argument) => !argument.startsWith("--"));
|
|
998
|
+
const options = raw.slice(1).filter((argument) => argument.startsWith("--"));
|
|
999
|
+
if (
|
|
1000
|
+
positional.length > 1
|
|
1001
|
+
|| options.some((argument) => argument !== "--json")
|
|
1002
|
+
|| options.length > 1
|
|
1003
|
+
) {
|
|
1004
|
+
return { ok: false, message: "capabilities accepts one optional adapter ID and --json" };
|
|
1005
|
+
}
|
|
1006
|
+
return { ok: true, value: { command: "capabilities", ...(positional[0] === undefined ? {} : { adapterId: positional[0] }), json: options.includes("--json") } };
|
|
1007
|
+
}
|
|
1008
|
+
if (first === "plugin" || first === "plugins") {
|
|
1009
|
+
const subcommand = raw[1];
|
|
1010
|
+
if (subcommand === "list") {
|
|
1011
|
+
const json = simpleJsonOptions(raw.slice(2), "plugin list");
|
|
1012
|
+
return typeof json === "boolean"
|
|
1013
|
+
? { ok: true, value: { command: "plugin-list", json } }
|
|
1014
|
+
: json;
|
|
1015
|
+
}
|
|
1016
|
+
if (subcommand === "show") {
|
|
1017
|
+
const id = raw[2];
|
|
1018
|
+
if (id === undefined) return { ok: false, message: "plugin show requires a plugin ID" };
|
|
1019
|
+
const issue = validPluginId(id);
|
|
1020
|
+
if (issue !== null) return { ok: false, message: issue };
|
|
1021
|
+
const json = simpleJsonOptions(raw.slice(3), "plugin show");
|
|
1022
|
+
return typeof json === "boolean"
|
|
1023
|
+
? { ok: true, value: { command: "plugin-show", id, json } }
|
|
1024
|
+
: json;
|
|
1025
|
+
}
|
|
1026
|
+
if (subcommand === "scaffold") {
|
|
1027
|
+
return parsePluginScaffoldArguments(raw.slice(2), "plugin scaffold");
|
|
1028
|
+
}
|
|
1029
|
+
if (subcommand === "init") {
|
|
1030
|
+
return parsePluginInitArguments(raw.slice(2));
|
|
1031
|
+
}
|
|
1032
|
+
if (subcommand === "check") {
|
|
1033
|
+
const path = raw[2];
|
|
1034
|
+
if (path === undefined || path.startsWith("--")) {
|
|
1035
|
+
return { ok: false, message: "plugin check requires a plugin directory" };
|
|
1036
|
+
}
|
|
1037
|
+
const json = simpleJsonOptions(raw.slice(3), "plugin check");
|
|
1038
|
+
return typeof json === "boolean"
|
|
1039
|
+
? { ok: true, value: { command: "plugin-check", path, json } }
|
|
1040
|
+
: json;
|
|
1041
|
+
}
|
|
1042
|
+
if (subcommand === "test") {
|
|
1043
|
+
const path = raw[2];
|
|
1044
|
+
if (path === undefined || path.startsWith("--")) {
|
|
1045
|
+
return { ok: false, message: "plugin test requires a portable plugin directory" };
|
|
1046
|
+
}
|
|
1047
|
+
const parsed = optionValues(
|
|
1048
|
+
raw.slice(3),
|
|
1049
|
+
[],
|
|
1050
|
+
["--trust-code", "--json"],
|
|
1051
|
+
);
|
|
1052
|
+
if (isFailure(parsed)) return parsed;
|
|
1053
|
+
return {
|
|
1054
|
+
ok: true,
|
|
1055
|
+
value: {
|
|
1056
|
+
command: "plugin-test",
|
|
1057
|
+
path,
|
|
1058
|
+
trustCode: parsed.booleans.has("--trust-code"),
|
|
1059
|
+
json: parsed.booleans.has("--json"),
|
|
1060
|
+
},
|
|
1061
|
+
};
|
|
1062
|
+
}
|
|
1063
|
+
if (subcommand === "pack") {
|
|
1064
|
+
const path = raw[2];
|
|
1065
|
+
if (path === undefined || path.startsWith("--")) {
|
|
1066
|
+
return { ok: false, message: "plugin pack requires a portable plugin source directory" };
|
|
1067
|
+
}
|
|
1068
|
+
const parsed = optionValues(
|
|
1069
|
+
raw.slice(3),
|
|
1070
|
+
["--output"],
|
|
1071
|
+
["--json"],
|
|
1072
|
+
);
|
|
1073
|
+
if (isFailure(parsed)) return parsed;
|
|
1074
|
+
const output = parsed.values["--output"];
|
|
1075
|
+
return output === undefined
|
|
1076
|
+
? { ok: false, message: "plugin pack requires --output" }
|
|
1077
|
+
: {
|
|
1078
|
+
ok: true,
|
|
1079
|
+
value: {
|
|
1080
|
+
command: "plugin-pack",
|
|
1081
|
+
path,
|
|
1082
|
+
output,
|
|
1083
|
+
json: parsed.booleans.has("--json"),
|
|
1084
|
+
},
|
|
1085
|
+
};
|
|
1086
|
+
}
|
|
1087
|
+
if (subcommand === "install") {
|
|
1088
|
+
const path = raw[2];
|
|
1089
|
+
if (path === undefined || path.startsWith("--")) {
|
|
1090
|
+
return { ok: false, message: "plugin install requires a portable plugin package directory" };
|
|
1091
|
+
}
|
|
1092
|
+
const parsed = optionValues(
|
|
1093
|
+
raw.slice(3),
|
|
1094
|
+
["--expected-current"],
|
|
1095
|
+
["--trust-code", "--json"],
|
|
1096
|
+
);
|
|
1097
|
+
if (isFailure(parsed)) return parsed;
|
|
1098
|
+
const expectedCurrent = validExpectedPluginDigest(
|
|
1099
|
+
parsed.values["--expected-current"],
|
|
1100
|
+
);
|
|
1101
|
+
if (typeof expectedCurrent === "object") return expectedCurrent;
|
|
1102
|
+
return {
|
|
1103
|
+
ok: true,
|
|
1104
|
+
value: {
|
|
1105
|
+
command: "plugin-install",
|
|
1106
|
+
path,
|
|
1107
|
+
trustCode: parsed.booleans.has("--trust-code"),
|
|
1108
|
+
...(expectedCurrent === undefined ? {} : { expectedCurrent }),
|
|
1109
|
+
json: parsed.booleans.has("--json"),
|
|
1110
|
+
},
|
|
1111
|
+
};
|
|
1112
|
+
}
|
|
1113
|
+
if (subcommand === "doctor") {
|
|
1114
|
+
const json = simpleJsonOptions(raw.slice(2), "plugin doctor");
|
|
1115
|
+
return typeof json === "boolean"
|
|
1116
|
+
? { ok: true, value: { command: "plugin-doctor", json } }
|
|
1117
|
+
: json;
|
|
1118
|
+
}
|
|
1119
|
+
if (subcommand === "disable" || subcommand === "remove") {
|
|
1120
|
+
const id = raw[2];
|
|
1121
|
+
if (id === undefined || id.startsWith("--")) {
|
|
1122
|
+
return {
|
|
1123
|
+
ok: false,
|
|
1124
|
+
message: `plugin ${subcommand} requires a plugin ID`,
|
|
1125
|
+
};
|
|
1126
|
+
}
|
|
1127
|
+
const issue = validPluginId(id);
|
|
1128
|
+
if (issue !== null) return { ok: false, message: issue };
|
|
1129
|
+
const parsed = optionValues(
|
|
1130
|
+
raw.slice(3),
|
|
1131
|
+
["--expected-current"],
|
|
1132
|
+
subcommand === "remove" ? ["--yes", "--json"] : ["--json"],
|
|
1133
|
+
);
|
|
1134
|
+
if (isFailure(parsed)) return parsed;
|
|
1135
|
+
const expectedCurrent = validExpectedPluginDigest(
|
|
1136
|
+
parsed.values["--expected-current"],
|
|
1137
|
+
);
|
|
1138
|
+
if (typeof expectedCurrent === "object") return expectedCurrent;
|
|
1139
|
+
return subcommand === "disable"
|
|
1140
|
+
? {
|
|
1141
|
+
ok: true,
|
|
1142
|
+
value: {
|
|
1143
|
+
command: "plugin-disable",
|
|
1144
|
+
id,
|
|
1145
|
+
...(expectedCurrent === undefined ? {} : { expectedCurrent }),
|
|
1146
|
+
json: parsed.booleans.has("--json"),
|
|
1147
|
+
},
|
|
1148
|
+
}
|
|
1149
|
+
: {
|
|
1150
|
+
ok: true,
|
|
1151
|
+
value: {
|
|
1152
|
+
command: "plugin-remove",
|
|
1153
|
+
id,
|
|
1154
|
+
...(expectedCurrent === undefined ? {} : { expectedCurrent }),
|
|
1155
|
+
yes: parsed.booleans.has("--yes"),
|
|
1156
|
+
json: parsed.booleans.has("--json"),
|
|
1157
|
+
},
|
|
1158
|
+
};
|
|
1159
|
+
}
|
|
1160
|
+
return {
|
|
1161
|
+
ok: false,
|
|
1162
|
+
message:
|
|
1163
|
+
"plugin requires list, show, scaffold, init, check, test, pack, install, doctor, disable, or remove",
|
|
1164
|
+
};
|
|
1165
|
+
}
|
|
1166
|
+
if (first === "platforms") {
|
|
1167
|
+
const selected = optionalPlatformSurface(raw.slice(1), "platforms");
|
|
1168
|
+
return "ok" in selected
|
|
1169
|
+
? selected
|
|
1170
|
+
: {
|
|
1171
|
+
ok: true,
|
|
1172
|
+
value: {
|
|
1173
|
+
command: "platforms",
|
|
1174
|
+
...(selected.surfaceId === undefined ? {} : { surfaceId: selected.surfaceId }),
|
|
1175
|
+
json: selected.json,
|
|
1176
|
+
},
|
|
1177
|
+
};
|
|
1178
|
+
}
|
|
1179
|
+
if (first === "thread") {
|
|
1180
|
+
if (raw[1] !== "split" && raw[1] !== "publish") return { ok: false, message: "thread requires split or publish" };
|
|
1181
|
+
const requested = raw[2];
|
|
1182
|
+
if (requested === undefined) return { ok: false, message: `thread ${raw[1]} requires a platform surface ID` };
|
|
1183
|
+
const surfaceId = knownPlatformSurface(requested);
|
|
1184
|
+
if (surfaceId === null) return { ok: false, message: `unknown platform surface: ${requested}; run 'ghostget platforms' to list reviewed surfaces` };
|
|
1185
|
+
const publishing = raw[1] === "publish";
|
|
1186
|
+
const parsed = optionValues(
|
|
1187
|
+
raw.slice(3),
|
|
1188
|
+
publishing ? ["--adapter", "--text", "--auth"] : ["--text"],
|
|
1189
|
+
publishing ? ["--preview", "--headed", "--json"] : ["--json"],
|
|
1190
|
+
);
|
|
1191
|
+
if (isFailure(parsed)) return parsed;
|
|
1192
|
+
const textSource = parsed.values["--text"];
|
|
1193
|
+
if (textSource === undefined) return { ok: false, message: `thread ${raw[1]} requires --text <inline|@file|->` };
|
|
1194
|
+
if (publishing) {
|
|
1195
|
+
const adapterId = parsed.values["--adapter"];
|
|
1196
|
+
const authId = parsed.values["--auth"];
|
|
1197
|
+
if (adapterId === undefined || validId(adapterId, "adapter ID") !== null) {
|
|
1198
|
+
return { ok: false, message: "thread publish requires --adapter <lowercase-kebab-id>" };
|
|
1199
|
+
}
|
|
1200
|
+
if (authId === undefined || validId(authId, "auth ID") !== null) {
|
|
1201
|
+
return { ok: false, message: "thread publish requires --auth <lowercase-kebab-id>" };
|
|
1202
|
+
}
|
|
1203
|
+
return {
|
|
1204
|
+
ok: true,
|
|
1205
|
+
value: {
|
|
1206
|
+
command: "thread-publish",
|
|
1207
|
+
surfaceId,
|
|
1208
|
+
adapterId,
|
|
1209
|
+
textSource,
|
|
1210
|
+
authId,
|
|
1211
|
+
preview: parsed.booleans.has("--preview"),
|
|
1212
|
+
headed: parsed.booleans.has("--headed"),
|
|
1213
|
+
json: parsed.booleans.has("--json"),
|
|
1214
|
+
},
|
|
1215
|
+
};
|
|
1216
|
+
}
|
|
1217
|
+
return {
|
|
1218
|
+
ok: true,
|
|
1219
|
+
value: {
|
|
1220
|
+
command: "thread-split",
|
|
1221
|
+
surfaceId,
|
|
1222
|
+
textSource,
|
|
1223
|
+
json: parsed.booleans.has("--json"),
|
|
1224
|
+
},
|
|
1225
|
+
};
|
|
1226
|
+
}
|
|
1227
|
+
if (first === "auth") {
|
|
1228
|
+
const subcommand = raw[1];
|
|
1229
|
+
if (subcommand === "list") {
|
|
1230
|
+
const json = simpleJsonOptions(raw.slice(2), "auth list");
|
|
1231
|
+
return typeof json === "boolean" ? { ok: true, value: { command: "auth-list", json } } : json;
|
|
1232
|
+
}
|
|
1233
|
+
if (subcommand === "login") {
|
|
1234
|
+
const id = raw[2];
|
|
1235
|
+
if (id === undefined) return { ok: false, message: "auth login requires an ID" };
|
|
1236
|
+
const issue = validId(id, "auth ID");
|
|
1237
|
+
if (issue !== null) return { ok: false, message: issue };
|
|
1238
|
+
const parsed = optionValues(
|
|
1239
|
+
raw.slice(3),
|
|
1240
|
+
["--provider", "--client-file"],
|
|
1241
|
+
["--no-open", "--force", "--json"],
|
|
1242
|
+
);
|
|
1243
|
+
if (isFailure(parsed)) return parsed;
|
|
1244
|
+
if (
|
|
1245
|
+
parsed.values["--provider"] !== undefined
|
|
1246
|
+
&& parsed.values["--provider"] !== "gmail"
|
|
1247
|
+
) {
|
|
1248
|
+
return { ok: false, message: "auth login supports only --provider gmail" };
|
|
1249
|
+
}
|
|
1250
|
+
const clientFile = parsed.values["--client-file"];
|
|
1251
|
+
if (clientFile === undefined || clientFile.length > 4_096) {
|
|
1252
|
+
return { ok: false, message: "auth login requires --client-file <downloaded-desktop-client.json>" };
|
|
1253
|
+
}
|
|
1254
|
+
return {
|
|
1255
|
+
ok: true,
|
|
1256
|
+
value: {
|
|
1257
|
+
command: "auth-login",
|
|
1258
|
+
id,
|
|
1259
|
+
provider: "gmail",
|
|
1260
|
+
clientFile,
|
|
1261
|
+
openBrowser: !parsed.booleans.has("--no-open"),
|
|
1262
|
+
force: parsed.booleans.has("--force"),
|
|
1263
|
+
json: parsed.booleans.has("--json"),
|
|
1264
|
+
},
|
|
1265
|
+
};
|
|
1266
|
+
}
|
|
1267
|
+
if (subcommand === "add") {
|
|
1268
|
+
const id = raw[2];
|
|
1269
|
+
if (id === undefined) return { ok: false, message: "auth add requires an ID" };
|
|
1270
|
+
const issue = validId(id, "auth ID");
|
|
1271
|
+
if (issue !== null) return { ok: false, message: issue };
|
|
1272
|
+
const parsed = optionValues(
|
|
1273
|
+
raw.slice(3),
|
|
1274
|
+
[
|
|
1275
|
+
"--cookie-source",
|
|
1276
|
+
"--cookie-profile",
|
|
1277
|
+
"--cookies-file",
|
|
1278
|
+
"--browser-profile",
|
|
1279
|
+
"--browser-executable",
|
|
1280
|
+
"--oauth-provider",
|
|
1281
|
+
"--token-file",
|
|
1282
|
+
"--scopes",
|
|
1283
|
+
"--linked-device",
|
|
1284
|
+
"--device-store",
|
|
1285
|
+
"--subject",
|
|
1286
|
+
],
|
|
1287
|
+
["--trust-profile-egress", "--force"],
|
|
1288
|
+
);
|
|
1289
|
+
if (isFailure(parsed)) return parsed;
|
|
1290
|
+
const source = parsed.values["--cookie-source"];
|
|
1291
|
+
if (source !== undefined && !cookieSources.includes(source as CookieSource)) return { ok: false, message: `--cookie-source must be one of ${cookieSources.join(", ")}` };
|
|
1292
|
+
const cookiesFile = parsed.values["--cookies-file"];
|
|
1293
|
+
const browserProfile = parsed.values["--browser-profile"];
|
|
1294
|
+
const browserExecutable = parsed.values["--browser-executable"];
|
|
1295
|
+
const oauthProvider = parsed.values["--oauth-provider"];
|
|
1296
|
+
const tokenFile = parsed.values["--token-file"];
|
|
1297
|
+
const rawScopes = parsed.values["--scopes"];
|
|
1298
|
+
const linkedDeviceProvider = parsed.values["--linked-device"];
|
|
1299
|
+
const deviceStore = parsed.values["--device-store"];
|
|
1300
|
+
const subject = parsed.values["--subject"];
|
|
1301
|
+
if (subject !== undefined) {
|
|
1302
|
+
try {
|
|
1303
|
+
normalizeAuthSubject(subject);
|
|
1304
|
+
} catch (error) {
|
|
1305
|
+
return { ok: false, message: error instanceof Error ? error.message : "auth locator subject is invalid" };
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
const hasOAuthOption = oauthProvider !== undefined || tokenFile !== undefined || rawScopes !== undefined;
|
|
1309
|
+
const hasLinkedDeviceOption = linkedDeviceProvider !== undefined || deviceStore !== undefined;
|
|
1310
|
+
if (hasOAuthOption && hasLinkedDeviceOption) {
|
|
1311
|
+
return { ok: false, message: "linked-device and OAuth auth options cannot be combined" };
|
|
1312
|
+
}
|
|
1313
|
+
if (hasLinkedDeviceOption) {
|
|
1314
|
+
if (
|
|
1315
|
+
source !== undefined
|
|
1316
|
+
|| parsed.values["--cookie-profile"] !== undefined
|
|
1317
|
+
|| cookiesFile !== undefined
|
|
1318
|
+
|| browserProfile !== undefined
|
|
1319
|
+
|| browserExecutable !== undefined
|
|
1320
|
+
|| parsed.booleans.has("--trust-profile-egress")
|
|
1321
|
+
) {
|
|
1322
|
+
return {
|
|
1323
|
+
ok: false,
|
|
1324
|
+
message: "linked-device options cannot be combined with cookie or browser-profile options",
|
|
1325
|
+
};
|
|
1326
|
+
}
|
|
1327
|
+
if (
|
|
1328
|
+
linkedDeviceProvider === undefined
|
|
1329
|
+
|| !isProviderPluginSurfaceId(linkedDeviceProvider)
|
|
1330
|
+
) {
|
|
1331
|
+
return {
|
|
1332
|
+
ok: false,
|
|
1333
|
+
message: "--linked-device must be a lowercase kebab-case provider surface ID",
|
|
1334
|
+
};
|
|
1335
|
+
}
|
|
1336
|
+
return {
|
|
1337
|
+
ok: true,
|
|
1338
|
+
value: {
|
|
1339
|
+
command: "auth-add",
|
|
1340
|
+
id,
|
|
1341
|
+
linkedDeviceProvider,
|
|
1342
|
+
...(deviceStore === undefined ? {} : { deviceStore }),
|
|
1343
|
+
...(subject === undefined ? {} : { subject }),
|
|
1344
|
+
trustProfileEgress: false,
|
|
1345
|
+
force: parsed.booleans.has("--force"),
|
|
1346
|
+
},
|
|
1347
|
+
};
|
|
1348
|
+
}
|
|
1349
|
+
if (hasOAuthOption) {
|
|
1350
|
+
if (
|
|
1351
|
+
source !== undefined
|
|
1352
|
+
|| parsed.values["--cookie-profile"] !== undefined
|
|
1353
|
+
|| cookiesFile !== undefined
|
|
1354
|
+
|| browserProfile !== undefined
|
|
1355
|
+
|| browserExecutable !== undefined
|
|
1356
|
+
|| parsed.booleans.has("--trust-profile-egress")
|
|
1357
|
+
) return { ok: false, message: "OAuth token-file options cannot be combined with cookie or browser-profile options" };
|
|
1358
|
+
if (oauthProvider === undefined || tokenFile === undefined || rawScopes === undefined) {
|
|
1359
|
+
return { ok: false, message: "OAuth auth requires --oauth-provider, --token-file, and --scopes" };
|
|
1360
|
+
}
|
|
1361
|
+
if (!isProviderPluginSurfaceId(oauthProvider)) {
|
|
1362
|
+
return {
|
|
1363
|
+
ok: false,
|
|
1364
|
+
message: "--oauth-provider must be a lowercase kebab-case provider surface ID",
|
|
1365
|
+
};
|
|
1366
|
+
}
|
|
1367
|
+
let scopes: readonly string[];
|
|
1368
|
+
try {
|
|
1369
|
+
scopes = normalizeOAuthScopes(rawScopes.split(",").map((scope) => scope.trim()));
|
|
1370
|
+
} catch (error) {
|
|
1371
|
+
return { ok: false, message: error instanceof Error ? error.message : "OAuth locator fields are invalid" };
|
|
1372
|
+
}
|
|
1373
|
+
return {
|
|
1374
|
+
ok: true,
|
|
1375
|
+
value: {
|
|
1376
|
+
command: "auth-add",
|
|
1377
|
+
id,
|
|
1378
|
+
oauthProvider,
|
|
1379
|
+
tokenFile,
|
|
1380
|
+
scopes,
|
|
1381
|
+
...(subject === undefined ? {} : { subject }),
|
|
1382
|
+
trustProfileEgress: false,
|
|
1383
|
+
force: parsed.booleans.has("--force"),
|
|
1384
|
+
},
|
|
1385
|
+
};
|
|
1386
|
+
}
|
|
1387
|
+
if (
|
|
1388
|
+
(cookiesFile !== undefined && (source !== undefined || browserProfile !== undefined))
|
|
1389
|
+
|| (cookiesFile === undefined && source === undefined && browserProfile === undefined)
|
|
1390
|
+
) return { ok: false, message: "auth add requires exactly one cookie file or browser profile, or a cookie source; browser profiles may add one cookie source" };
|
|
1391
|
+
if (parsed.values["--cookie-profile"] !== undefined && source === undefined) return { ok: false, message: "--cookie-profile requires --cookie-source" };
|
|
1392
|
+
if (browserProfile !== undefined && !parsed.booleans.has("--trust-profile-egress")) {
|
|
1393
|
+
return { ok: false, message: "--browser-profile requires --trust-profile-egress" };
|
|
1394
|
+
}
|
|
1395
|
+
if (browserProfile === undefined && parsed.booleans.has("--trust-profile-egress")) {
|
|
1396
|
+
return { ok: false, message: "--trust-profile-egress requires --browser-profile" };
|
|
1397
|
+
}
|
|
1398
|
+
if (browserExecutable !== undefined && browserProfile === undefined) {
|
|
1399
|
+
return { ok: false, message: "--browser-executable requires --browser-profile" };
|
|
1400
|
+
}
|
|
1401
|
+
return {
|
|
1402
|
+
ok: true,
|
|
1403
|
+
value: {
|
|
1404
|
+
command: "auth-add",
|
|
1405
|
+
id,
|
|
1406
|
+
...(source === undefined ? {} : { cookieSource: source as CookieSource }),
|
|
1407
|
+
...(parsed.values["--cookie-profile"] === undefined ? {} : { cookieProfile: parsed.values["--cookie-profile"] }),
|
|
1408
|
+
...(cookiesFile === undefined ? {} : { cookiesFile }),
|
|
1409
|
+
...(browserProfile === undefined ? {} : { browserProfile }),
|
|
1410
|
+
...(browserExecutable === undefined ? {} : { browserExecutable }),
|
|
1411
|
+
...(subject === undefined ? {} : { subject }),
|
|
1412
|
+
trustProfileEgress: parsed.booleans.has("--trust-profile-egress"),
|
|
1413
|
+
force: parsed.booleans.has("--force"),
|
|
1414
|
+
},
|
|
1415
|
+
};
|
|
1416
|
+
}
|
|
1417
|
+
if (subcommand === "pair") {
|
|
1418
|
+
const id = raw[2];
|
|
1419
|
+
if (id === undefined) return { ok: false, message: "auth pair requires an ID" };
|
|
1420
|
+
const issue = validId(id, "auth ID");
|
|
1421
|
+
if (issue !== null) return { ok: false, message: issue };
|
|
1422
|
+
const parsed = optionValues(raw.slice(3), ["--phone"], []);
|
|
1423
|
+
if (isFailure(parsed)) return parsed;
|
|
1424
|
+
const phone = parsed.values["--phone"];
|
|
1425
|
+
if (phone !== undefined && !/^\+?[0-9]{5,20}$/u.test(phone)) {
|
|
1426
|
+
return { ok: false, message: "--phone must be one international phone number" };
|
|
1427
|
+
}
|
|
1428
|
+
return {
|
|
1429
|
+
ok: true,
|
|
1430
|
+
value: {
|
|
1431
|
+
command: "auth-pair",
|
|
1432
|
+
id,
|
|
1433
|
+
...(phone === undefined ? {} : { phone }),
|
|
1434
|
+
},
|
|
1435
|
+
};
|
|
1436
|
+
}
|
|
1437
|
+
if (subcommand === "sync") {
|
|
1438
|
+
const id = raw[2];
|
|
1439
|
+
if (id === undefined) return { ok: false, message: "auth sync requires an ID" };
|
|
1440
|
+
const issue = validId(id, "auth ID");
|
|
1441
|
+
if (issue !== null) return { ok: false, message: issue };
|
|
1442
|
+
const parsed = optionValues(raw.slice(3), [], ["--once", "--json"]);
|
|
1443
|
+
if (isFailure(parsed)) return parsed;
|
|
1444
|
+
if (!parsed.booleans.has("--once")) {
|
|
1445
|
+
return { ok: false, message: "auth sync requires --once; unbounded background sync is not exposed" };
|
|
1446
|
+
}
|
|
1447
|
+
return {
|
|
1448
|
+
ok: true,
|
|
1449
|
+
value: {
|
|
1450
|
+
command: "auth-sync",
|
|
1451
|
+
id,
|
|
1452
|
+
once: true,
|
|
1453
|
+
json: parsed.booleans.has("--json"),
|
|
1454
|
+
},
|
|
1455
|
+
};
|
|
1456
|
+
}
|
|
1457
|
+
if (subcommand === "bind") {
|
|
1458
|
+
const id = raw[2];
|
|
1459
|
+
if (id === undefined) return { ok: false, message: "auth bind requires an ID" };
|
|
1460
|
+
const issue = validId(id, "auth ID");
|
|
1461
|
+
if (issue !== null) return { ok: false, message: issue };
|
|
1462
|
+
const parsed = optionValues(raw.slice(3), ["--site"], ["--force", "--json"]);
|
|
1463
|
+
if (isFailure(parsed)) return parsed;
|
|
1464
|
+
const site = parsed.values["--site"];
|
|
1465
|
+
if (site === undefined || !isProviderPluginSurfaceId(site)) {
|
|
1466
|
+
return {
|
|
1467
|
+
ok: false,
|
|
1468
|
+
message: "auth bind requires --site <lowercase-kebab-case-surface-id>",
|
|
1469
|
+
};
|
|
1470
|
+
}
|
|
1471
|
+
return {
|
|
1472
|
+
ok: true,
|
|
1473
|
+
value: {
|
|
1474
|
+
command: "auth-bind",
|
|
1475
|
+
id,
|
|
1476
|
+
site,
|
|
1477
|
+
force: parsed.booleans.has("--force"),
|
|
1478
|
+
json: parsed.booleans.has("--json"),
|
|
1479
|
+
},
|
|
1480
|
+
};
|
|
1481
|
+
}
|
|
1482
|
+
if (subcommand === "remove" || subcommand === "forget") {
|
|
1483
|
+
const id = raw[2];
|
|
1484
|
+
if (id === undefined) return { ok: false, message: "auth remove requires an ID" };
|
|
1485
|
+
const issue = validId(id, "auth ID");
|
|
1486
|
+
if (issue !== null) return { ok: false, message: issue };
|
|
1487
|
+
const parsed = optionValues(raw.slice(3), [], ["--yes"]);
|
|
1488
|
+
return isFailure(parsed) ? parsed : { ok: true, value: { command: "auth-remove", id, yes: parsed.booleans.has("--yes") } };
|
|
1489
|
+
}
|
|
1490
|
+
return { ok: false, message: "auth requires list, login, add, pair, sync, bind, or remove" };
|
|
1491
|
+
}
|
|
1492
|
+
if (first === "adapter") {
|
|
1493
|
+
const subcommand = raw[1];
|
|
1494
|
+
if (subcommand === "sync-bundled") {
|
|
1495
|
+
const json = simpleJsonOptions(raw.slice(2), "adapter sync-bundled");
|
|
1496
|
+
return typeof json === "boolean"
|
|
1497
|
+
? { ok: true, value: { command: "adapter-sync-bundled", json } }
|
|
1498
|
+
: json;
|
|
1499
|
+
}
|
|
1500
|
+
if (subcommand === "scaffold") {
|
|
1501
|
+
return parsePluginScaffoldArguments(raw.slice(2), "adapter scaffold");
|
|
1502
|
+
}
|
|
1503
|
+
if (subcommand === "init") {
|
|
1504
|
+
const id = raw[2];
|
|
1505
|
+
if (id === undefined) return { ok: false, message: "adapter init requires an ID" };
|
|
1506
|
+
const issue = validId(id, "adapter ID");
|
|
1507
|
+
if (issue !== null) return { ok: false, message: issue };
|
|
1508
|
+
const parsed = optionValues(raw.slice(3), ["--origin", "--platform", "--output"], ["--force"]);
|
|
1509
|
+
if (isFailure(parsed)) return parsed;
|
|
1510
|
+
const origin = parsed.values["--origin"];
|
|
1511
|
+
const requestedPlatform = parsed.values["--platform"];
|
|
1512
|
+
const output = parsed.values["--output"];
|
|
1513
|
+
if ((origin === undefined) === (requestedPlatform === undefined)) {
|
|
1514
|
+
return { ok: false, message: "adapter init requires exactly one of --origin or --platform" };
|
|
1515
|
+
}
|
|
1516
|
+
if (output === undefined) return { ok: false, message: "adapter init requires --output" };
|
|
1517
|
+
if (origin !== undefined) {
|
|
1518
|
+
return {
|
|
1519
|
+
ok: true,
|
|
1520
|
+
value: {
|
|
1521
|
+
command: "adapter-init",
|
|
1522
|
+
id,
|
|
1523
|
+
target: { kind: "origin", origin },
|
|
1524
|
+
output,
|
|
1525
|
+
force: parsed.booleans.has("--force"),
|
|
1526
|
+
},
|
|
1527
|
+
};
|
|
1528
|
+
}
|
|
1529
|
+
const surfaceId = knownPlatformSurface(requestedPlatform ?? "");
|
|
1530
|
+
if (surfaceId === null) {
|
|
1531
|
+
return { ok: false, message: `unknown platform surface: ${requestedPlatform ?? ""}; run 'ghostget platforms' to list reviewed surfaces` };
|
|
1532
|
+
}
|
|
1533
|
+
return {
|
|
1534
|
+
ok: true,
|
|
1535
|
+
value: {
|
|
1536
|
+
command: "adapter-init",
|
|
1537
|
+
id,
|
|
1538
|
+
target: { kind: "platform", surfaceId },
|
|
1539
|
+
output,
|
|
1540
|
+
force: parsed.booleans.has("--force"),
|
|
1541
|
+
},
|
|
1542
|
+
};
|
|
1543
|
+
}
|
|
1544
|
+
if (subcommand === "validate") {
|
|
1545
|
+
const path = raw[2];
|
|
1546
|
+
if (path === undefined) return { ok: false, message: "adapter validate requires a manifest path" };
|
|
1547
|
+
const json = simpleJsonOptions(raw.slice(3), "adapter validate");
|
|
1548
|
+
return typeof json === "boolean" ? { ok: true, value: { command: "adapter-validate", path, json } } : json;
|
|
1549
|
+
}
|
|
1550
|
+
if (subcommand === "install") {
|
|
1551
|
+
const path = raw[2];
|
|
1552
|
+
if (path === undefined) return { ok: false, message: "adapter install requires a manifest path" };
|
|
1553
|
+
const parsed = optionValues(raw.slice(3), [], ["--force"], ["--upgrade-from"]);
|
|
1554
|
+
if (isFailure(parsed)) return parsed;
|
|
1555
|
+
const upgradeFrom = parsed.repeatedValues["--upgrade-from"] ?? [];
|
|
1556
|
+
if (parsed.booleans.has("--force") && upgradeFrom.length > 0) {
|
|
1557
|
+
return { ok: false, message: "adapter install accepts --force or --upgrade-from, not both" };
|
|
1558
|
+
}
|
|
1559
|
+
return {
|
|
1560
|
+
ok: true,
|
|
1561
|
+
value: {
|
|
1562
|
+
command: "adapter-install",
|
|
1563
|
+
path,
|
|
1564
|
+
...(upgradeFrom.length === 0 ? {} : { upgradeFrom }),
|
|
1565
|
+
force: parsed.booleans.has("--force"),
|
|
1566
|
+
},
|
|
1567
|
+
};
|
|
1568
|
+
}
|
|
1569
|
+
if (subcommand === "remove" || subcommand === "uninstall") {
|
|
1570
|
+
const id = raw[2];
|
|
1571
|
+
if (id === undefined) return { ok: false, message: "adapter remove requires an ID" };
|
|
1572
|
+
const issue = validId(id, "adapter ID");
|
|
1573
|
+
if (issue !== null) return { ok: false, message: issue };
|
|
1574
|
+
const parsed = optionValues(raw.slice(3), [], ["--yes"]);
|
|
1575
|
+
return isFailure(parsed) ? parsed : { ok: true, value: { command: "adapter-remove", id, yes: parsed.booleans.has("--yes") } };
|
|
1576
|
+
}
|
|
1577
|
+
return { ok: false, message: "adapter requires sync-bundled, init, validate, install, remove, or the scaffold compatibility alias" };
|
|
1578
|
+
}
|
|
1579
|
+
if (first === "derive") {
|
|
1580
|
+
const subcommand = raw[1];
|
|
1581
|
+
if (subcommand === "list" || subcommand === "status") {
|
|
1582
|
+
const json = simpleJsonOptions(raw.slice(2), "derive list");
|
|
1583
|
+
return typeof json === "boolean" ? { ok: true, value: { command: "derive-list", json } } : json;
|
|
1584
|
+
}
|
|
1585
|
+
if (subcommand === "start" || subcommand === "begin") {
|
|
1586
|
+
const adapterId = raw[2];
|
|
1587
|
+
const url = raw[3];
|
|
1588
|
+
if (adapterId === undefined || url === undefined) return { ok: false, message: "derive start requires an adapter ID and URL" };
|
|
1589
|
+
const issue = validId(adapterId, "adapter ID");
|
|
1590
|
+
if (issue !== null) return { ok: false, message: issue };
|
|
1591
|
+
const parsed = optionValues(
|
|
1592
|
+
raw.slice(4),
|
|
1593
|
+
["--auth", "--content", "--domains"],
|
|
1594
|
+
["--allow-remote-actions", "--headed", "--json"],
|
|
1595
|
+
["--fixture", "--cookie-origin"],
|
|
1596
|
+
);
|
|
1597
|
+
if (isFailure(parsed)) return parsed;
|
|
1598
|
+
const content = parsed.values["--content"] ?? "none";
|
|
1599
|
+
if (content !== "none" && content !== "text") return { ok: false, message: "--content must be none or text" };
|
|
1600
|
+
let hostname: string;
|
|
1601
|
+
try {
|
|
1602
|
+
hostname = new URL(url).hostname;
|
|
1603
|
+
} catch {
|
|
1604
|
+
return { ok: false, message: "derive start URL is invalid" };
|
|
1605
|
+
}
|
|
1606
|
+
const browserDomains = (parsed.values["--domains"] ?? hostname).split(",").map((value) => value.trim()).filter((value) => value !== "");
|
|
1607
|
+
if (browserDomains.length < 1 || browserDomains.length > 100 || browserDomains.some((value) => !/^(?:\*\.)?[a-z0-9](?:[a-z0-9.-]{0,251}[a-z0-9])?$/u.test(value))) {
|
|
1608
|
+
return { ok: false, message: "--domains must be a comma-separated list of exact or wildcard hostnames" };
|
|
1609
|
+
}
|
|
1610
|
+
const cookieOrigins: string[] = [];
|
|
1611
|
+
for (const value of parsed.repeatedValues["--cookie-origin"] ?? []) {
|
|
1612
|
+
let origin: URL;
|
|
1613
|
+
try {
|
|
1614
|
+
origin = new URL(value);
|
|
1615
|
+
} catch {
|
|
1616
|
+
return { ok: false, message: "derive start --cookie-origin must be an exact HTTPS origin" };
|
|
1617
|
+
}
|
|
1618
|
+
if (
|
|
1619
|
+
origin.protocol !== "https:"
|
|
1620
|
+
|| origin.username !== ""
|
|
1621
|
+
|| origin.password !== ""
|
|
1622
|
+
|| (value !== origin.origin && value !== `${origin.origin}/`)
|
|
1623
|
+
) {
|
|
1624
|
+
return { ok: false, message: "derive start --cookie-origin must be an exact HTTPS origin" };
|
|
1625
|
+
}
|
|
1626
|
+
if (cookieOrigins.includes(origin.origin)) {
|
|
1627
|
+
return { ok: false, message: "derive start --cookie-origin values must be unique exact HTTPS origins" };
|
|
1628
|
+
}
|
|
1629
|
+
cookieOrigins.push(origin.origin);
|
|
1630
|
+
}
|
|
1631
|
+
if (cookieOrigins.length > 16) {
|
|
1632
|
+
return { ok: false, message: "derive start accepts at most 16 --cookie-origin values" };
|
|
1633
|
+
}
|
|
1634
|
+
const normalizedBrowserDomains = browserDomains.map((value) => value.toLowerCase());
|
|
1635
|
+
const cookieOriginCovered = (origin: string): boolean => {
|
|
1636
|
+
const hostname = new URL(origin).hostname.toLowerCase();
|
|
1637
|
+
return normalizedBrowserDomains.some((domain) =>
|
|
1638
|
+
domain === hostname
|
|
1639
|
+
|| (domain.startsWith("*.") && (hostname === domain.slice(2) || hostname.endsWith(`.${domain.slice(2)}`))));
|
|
1640
|
+
};
|
|
1641
|
+
if (cookieOrigins.some((origin) => !cookieOriginCovered(origin))) {
|
|
1642
|
+
return { ok: false, message: "every --cookie-origin hostname must be covered by --domains" };
|
|
1643
|
+
}
|
|
1644
|
+
const fixtureSources = parsed.repeatedValues["--fixture"] ?? [];
|
|
1645
|
+
if (fixtureSources.length > 20 || fixtureSources.some((value) => value.length > 4_096 || value.includes("\u0000"))) {
|
|
1646
|
+
return { ok: false, message: "derive start accepts at most 20 bounded --fixture media paths" };
|
|
1647
|
+
}
|
|
1648
|
+
if (fixtureSources.length > 0 && !parsed.booleans.has("--allow-remote-actions")) {
|
|
1649
|
+
return { ok: false, message: "derive start --fixture requires --allow-remote-actions" };
|
|
1650
|
+
}
|
|
1651
|
+
return {
|
|
1652
|
+
ok: true,
|
|
1653
|
+
value: {
|
|
1654
|
+
command: "derive-start",
|
|
1655
|
+
adapterId,
|
|
1656
|
+
url,
|
|
1657
|
+
authId: parsed.values["--auth"] ?? adapterId,
|
|
1658
|
+
allowRemoteActions: parsed.booleans.has("--allow-remote-actions"),
|
|
1659
|
+
contentMode: content,
|
|
1660
|
+
browserDomains,
|
|
1661
|
+
cookieOrigins,
|
|
1662
|
+
fixtureSources,
|
|
1663
|
+
headed: parsed.booleans.has("--headed"),
|
|
1664
|
+
},
|
|
1665
|
+
};
|
|
1666
|
+
}
|
|
1667
|
+
if (subcommand === "browser") {
|
|
1668
|
+
const id = raw[2];
|
|
1669
|
+
if (id === undefined) return { ok: false, message: "derive browser requires a derivation ID" };
|
|
1670
|
+
let browserArguments = raw.slice(3);
|
|
1671
|
+
let json = false;
|
|
1672
|
+
if (browserArguments[0] === "--json") {
|
|
1673
|
+
json = true;
|
|
1674
|
+
browserArguments = browserArguments.slice(1);
|
|
1675
|
+
}
|
|
1676
|
+
if (browserArguments[0] === "--") browserArguments = browserArguments.slice(1);
|
|
1677
|
+
if (browserArguments.length === 0) return { ok: false, message: "derive browser requires an agent-browser command after --" };
|
|
1678
|
+
return { ok: true, value: { command: "derive-browser", id, browserArguments, json } };
|
|
1679
|
+
}
|
|
1680
|
+
if (subcommand === "review") {
|
|
1681
|
+
const id = raw[2];
|
|
1682
|
+
if (id === undefined) return { ok: false, message: "derive review requires a derivation ID" };
|
|
1683
|
+
const parsed = optionValues(
|
|
1684
|
+
raw.slice(3),
|
|
1685
|
+
["--entry", "--offset", "--limit", "--fixtures", "--field-names", "--review-origin"],
|
|
1686
|
+
["--json"],
|
|
1687
|
+
);
|
|
1688
|
+
if (isFailure(parsed)) return parsed;
|
|
1689
|
+
const entryValue = parsed.values["--entry"];
|
|
1690
|
+
const offsetValue = parsed.values["--offset"];
|
|
1691
|
+
const limitValue = parsed.values["--limit"];
|
|
1692
|
+
const fixtureSource = parsed.values["--fixtures"];
|
|
1693
|
+
const fieldNameSource = parsed.values["--field-names"];
|
|
1694
|
+
const reviewOrigin = parseExactHttpsOriginOption(
|
|
1695
|
+
parsed.values["--review-origin"],
|
|
1696
|
+
"derive review --review-origin",
|
|
1697
|
+
);
|
|
1698
|
+
if (typeof reviewOrigin === "object") return reviewOrigin;
|
|
1699
|
+
const boundedInteger = (value: string | undefined, fallback: number, maximum: number): number | null => {
|
|
1700
|
+
if (value === undefined) return fallback;
|
|
1701
|
+
if (!/^\d{1,5}$/u.test(value)) return null;
|
|
1702
|
+
const parsedValue = Number(value);
|
|
1703
|
+
return parsedValue <= maximum ? parsedValue : null;
|
|
1704
|
+
};
|
|
1705
|
+
if (entryValue !== undefined) {
|
|
1706
|
+
if (offsetValue !== undefined || limitValue !== undefined) {
|
|
1707
|
+
return { ok: false, message: "derive review --entry cannot be combined with --offset or --limit" };
|
|
1708
|
+
}
|
|
1709
|
+
const entryIndex = boundedInteger(entryValue, 0, 19_999);
|
|
1710
|
+
if (entryIndex === null) return { ok: false, message: "derive review --entry must be an integer from 0 to 19999" };
|
|
1711
|
+
if (fixtureSource !== undefined && fixtureSource !== "-") {
|
|
1712
|
+
return { ok: false, message: "derive review fixtures must be supplied on stdin with --fixtures -" };
|
|
1713
|
+
}
|
|
1714
|
+
if (fieldNameSource !== undefined && fieldNameSource !== "-") {
|
|
1715
|
+
return { ok: false, message: "derive review field names must be supplied on stdin with --field-names -" };
|
|
1716
|
+
}
|
|
1717
|
+
if (fixtureSource !== undefined && fieldNameSource !== undefined) {
|
|
1718
|
+
return { ok: false, message: "derive review --fixtures and --field-names are mutually exclusive" };
|
|
1719
|
+
}
|
|
1720
|
+
return {
|
|
1721
|
+
ok: true,
|
|
1722
|
+
value: {
|
|
1723
|
+
command: "derive-review",
|
|
1724
|
+
id,
|
|
1725
|
+
...(reviewOrigin === undefined ? {} : { reviewOrigin }),
|
|
1726
|
+
selection: {
|
|
1727
|
+
kind: "entry",
|
|
1728
|
+
entryIndex,
|
|
1729
|
+
stdinMode: fixtureSource === "-"
|
|
1730
|
+
? "fixtures"
|
|
1731
|
+
: fieldNameSource === "-"
|
|
1732
|
+
? "field-names"
|
|
1733
|
+
: "none",
|
|
1734
|
+
},
|
|
1735
|
+
json: parsed.booleans.has("--json"),
|
|
1736
|
+
},
|
|
1737
|
+
};
|
|
1738
|
+
}
|
|
1739
|
+
if (fixtureSource !== undefined) return { ok: false, message: "derive review --fixtures requires --entry" };
|
|
1740
|
+
if (fieldNameSource !== undefined) return { ok: false, message: "derive review --field-names requires --entry" };
|
|
1741
|
+
const offset = boundedInteger(offsetValue, 0, 20_000);
|
|
1742
|
+
const limit = boundedInteger(limitValue, 50, 100);
|
|
1743
|
+
if (offset === null) return { ok: false, message: "derive review --offset must be an integer from 0 to 20000" };
|
|
1744
|
+
if (limit === null || limit < 1) return { ok: false, message: "derive review --limit must be an integer from 1 to 100" };
|
|
1745
|
+
return {
|
|
1746
|
+
ok: true,
|
|
1747
|
+
value: {
|
|
1748
|
+
command: "derive-review",
|
|
1749
|
+
id,
|
|
1750
|
+
...(reviewOrigin === undefined ? {} : { reviewOrigin }),
|
|
1751
|
+
selection: { kind: "list", offset, limit },
|
|
1752
|
+
json: parsed.booleans.has("--json"),
|
|
1753
|
+
},
|
|
1754
|
+
};
|
|
1755
|
+
}
|
|
1756
|
+
if (subcommand === "finish") {
|
|
1757
|
+
const id = raw[2];
|
|
1758
|
+
if (id === undefined) return { ok: false, message: "derive finish requires a derivation ID" };
|
|
1759
|
+
const parsed = optionValues(
|
|
1760
|
+
raw.slice(3),
|
|
1761
|
+
["--output", "--platform", "--review-origin"],
|
|
1762
|
+
["--force", "--json"],
|
|
1763
|
+
);
|
|
1764
|
+
if (isFailure(parsed)) return parsed;
|
|
1765
|
+
const output = parsed.values["--output"];
|
|
1766
|
+
if (output === undefined) return { ok: false, message: "derive finish requires --output" };
|
|
1767
|
+
const reviewOrigin = parseExactHttpsOriginOption(
|
|
1768
|
+
parsed.values["--review-origin"],
|
|
1769
|
+
"derive finish --review-origin",
|
|
1770
|
+
);
|
|
1771
|
+
if (typeof reviewOrigin === "object") return reviewOrigin;
|
|
1772
|
+
const requestedPlatform = parsed.values["--platform"];
|
|
1773
|
+
const surfaceId = requestedPlatform === undefined ? undefined : knownPlatformSurface(requestedPlatform);
|
|
1774
|
+
if (surfaceId === null) {
|
|
1775
|
+
return { ok: false, message: `unknown platform surface: ${requestedPlatform}; run 'ghostget platforms' to list reviewed surfaces` };
|
|
1776
|
+
}
|
|
1777
|
+
return {
|
|
1778
|
+
ok: true,
|
|
1779
|
+
value: {
|
|
1780
|
+
command: "derive-finish",
|
|
1781
|
+
id,
|
|
1782
|
+
output,
|
|
1783
|
+
...(reviewOrigin === undefined ? {} : { reviewOrigin }),
|
|
1784
|
+
...(surfaceId === undefined ? {} : { surfaceId }),
|
|
1785
|
+
force: parsed.booleans.has("--force"),
|
|
1786
|
+
json: parsed.booleans.has("--json"),
|
|
1787
|
+
},
|
|
1788
|
+
};
|
|
1789
|
+
}
|
|
1790
|
+
if (subcommand === "discard") {
|
|
1791
|
+
const id = raw[2];
|
|
1792
|
+
if (id === undefined) return { ok: false, message: "derive discard requires a derivation ID" };
|
|
1793
|
+
const parsed = optionValues(raw.slice(3), [], ["--yes"]);
|
|
1794
|
+
return isFailure(parsed) ? parsed : { ok: true, value: { command: "derive-discard", id, yes: parsed.booleans.has("--yes") } };
|
|
1795
|
+
}
|
|
1796
|
+
if (subcommand === "analyze") {
|
|
1797
|
+
const har = raw[2];
|
|
1798
|
+
if (har === undefined) return { ok: false, message: "derive analyze requires a HAR path" };
|
|
1799
|
+
const parsed = optionValues(raw.slice(3), ["--adapter", "--origin", "--output", "--platform"], ["--force", "--json"]);
|
|
1800
|
+
if (isFailure(parsed)) return parsed;
|
|
1801
|
+
const adapterId = parsed.values["--adapter"];
|
|
1802
|
+
const origin = parsed.values["--origin"];
|
|
1803
|
+
const output = parsed.values["--output"];
|
|
1804
|
+
if (adapterId === undefined || origin === undefined || output === undefined) {
|
|
1805
|
+
return { ok: false, message: "derive analyze requires --adapter, --origin, and --output" };
|
|
1806
|
+
}
|
|
1807
|
+
const issue = validId(adapterId, "adapter ID");
|
|
1808
|
+
if (issue !== null) return { ok: false, message: issue };
|
|
1809
|
+
const requestedPlatform = parsed.values["--platform"];
|
|
1810
|
+
const surfaceId = requestedPlatform === undefined ? undefined : knownPlatformSurface(requestedPlatform);
|
|
1811
|
+
if (surfaceId === null) {
|
|
1812
|
+
return { ok: false, message: `unknown platform surface: ${requestedPlatform}; run 'ghostget platforms' to list reviewed surfaces` };
|
|
1813
|
+
}
|
|
1814
|
+
return {
|
|
1815
|
+
ok: true,
|
|
1816
|
+
value: {
|
|
1817
|
+
command: "derive-analyze",
|
|
1818
|
+
har,
|
|
1819
|
+
adapterId,
|
|
1820
|
+
origin,
|
|
1821
|
+
output,
|
|
1822
|
+
...(surfaceId === undefined ? {} : { surfaceId }),
|
|
1823
|
+
force: parsed.booleans.has("--force"),
|
|
1824
|
+
json: parsed.booleans.has("--json"),
|
|
1825
|
+
},
|
|
1826
|
+
};
|
|
1827
|
+
}
|
|
1828
|
+
return { ok: false, message: "derive requires list, start, browser, review, finish, analyze, or discard" };
|
|
1829
|
+
}
|
|
1830
|
+
if (first === "invoke") {
|
|
1831
|
+
const adapterId = raw[1];
|
|
1832
|
+
const operationId = raw[2];
|
|
1833
|
+
if (adapterId === undefined || operationId === undefined) return { ok: false, message: "invoke requires an adapter ID and operation ID" };
|
|
1834
|
+
const adapterIssue = validId(adapterId, "adapter ID");
|
|
1835
|
+
if (adapterIssue !== null) return { ok: false, message: adapterIssue };
|
|
1836
|
+
const operationIssue = validOperation(operationId);
|
|
1837
|
+
if (operationIssue !== null) return { ok: false, message: operationIssue };
|
|
1838
|
+
const parsed = optionValues(
|
|
1839
|
+
raw.slice(3),
|
|
1840
|
+
["--input", "--auth"],
|
|
1841
|
+
["--preview", "--cache-only", "--projection-identity-only", "--headed", "--json"],
|
|
1842
|
+
["--duplicate-risk-of"],
|
|
1843
|
+
);
|
|
1844
|
+
if (isFailure(parsed)) return parsed;
|
|
1845
|
+
const duplicateRiskOf = duplicateRiskRunIds(
|
|
1846
|
+
parsed.repeatedValues["--duplicate-risk-of"] ?? [],
|
|
1847
|
+
);
|
|
1848
|
+
if ("ok" in duplicateRiskOf) return duplicateRiskOf;
|
|
1849
|
+
if (
|
|
1850
|
+
parsed.booleans.has("--cache-only")
|
|
1851
|
+
&& parsed.booleans.has("--preview")
|
|
1852
|
+
) {
|
|
1853
|
+
return { ok: false, message: "invoke --cache-only cannot be combined with --preview" };
|
|
1854
|
+
}
|
|
1855
|
+
if (
|
|
1856
|
+
parsed.booleans.has("--cache-only")
|
|
1857
|
+
&& parsed.booleans.has("--headed")
|
|
1858
|
+
) {
|
|
1859
|
+
return { ok: false, message: "invoke --cache-only never opens a browser and cannot be combined with --headed" };
|
|
1860
|
+
}
|
|
1861
|
+
if (
|
|
1862
|
+
parsed.booleans.has("--projection-identity-only")
|
|
1863
|
+
&& (
|
|
1864
|
+
parsed.booleans.has("--preview")
|
|
1865
|
+
|| parsed.booleans.has("--cache-only")
|
|
1866
|
+
)
|
|
1867
|
+
) {
|
|
1868
|
+
return { ok: false, message: "invoke --projection-identity-only cannot be combined with --preview or --cache-only" };
|
|
1869
|
+
}
|
|
1870
|
+
if (
|
|
1871
|
+
parsed.booleans.has("--projection-identity-only")
|
|
1872
|
+
&& parsed.booleans.has("--headed")
|
|
1873
|
+
) {
|
|
1874
|
+
return { ok: false, message: "invoke --projection-identity-only never opens a browser and cannot be combined with --headed" };
|
|
1875
|
+
}
|
|
1876
|
+
return {
|
|
1877
|
+
ok: true,
|
|
1878
|
+
value: {
|
|
1879
|
+
command: "invoke",
|
|
1880
|
+
adapterId,
|
|
1881
|
+
operationId,
|
|
1882
|
+
inputSource: parsed.values["--input"] ?? "{}",
|
|
1883
|
+
...(parsed.values["--auth"] === undefined
|
|
1884
|
+
? {}
|
|
1885
|
+
: { authId: parsed.values["--auth"] }),
|
|
1886
|
+
duplicateRiskOf,
|
|
1887
|
+
preview: parsed.booleans.has("--preview"),
|
|
1888
|
+
cacheOnly: parsed.booleans.has("--cache-only"),
|
|
1889
|
+
projectionIdentityOnly:
|
|
1890
|
+
parsed.booleans.has("--projection-identity-only"),
|
|
1891
|
+
headed: parsed.booleans.has("--headed"),
|
|
1892
|
+
json: parsed.booleans.has("--json"),
|
|
1893
|
+
},
|
|
1894
|
+
};
|
|
1895
|
+
}
|
|
1896
|
+
if (first === "omni") {
|
|
1897
|
+
if (raw[1] !== "read") {
|
|
1898
|
+
return { ok: false, message: "omni requires read" };
|
|
1899
|
+
}
|
|
1900
|
+
const parsed = optionValues(
|
|
1901
|
+
raw.slice(2),
|
|
1902
|
+
["--input"],
|
|
1903
|
+
["--cache-only", "--identity-only", "--from-exact-cache", "--headed", "--json"],
|
|
1904
|
+
);
|
|
1905
|
+
if (isFailure(parsed)) return parsed;
|
|
1906
|
+
const inputSource = parsed.values["--input"];
|
|
1907
|
+
if (inputSource === undefined) {
|
|
1908
|
+
return { ok: false, message: "omni read requires --input <json|@file|->" };
|
|
1909
|
+
}
|
|
1910
|
+
const modes = [
|
|
1911
|
+
parsed.booleans.has("--cache-only"),
|
|
1912
|
+
parsed.booleans.has("--identity-only"),
|
|
1913
|
+
parsed.booleans.has("--from-exact-cache"),
|
|
1914
|
+
].filter(Boolean).length;
|
|
1915
|
+
if (modes > 1) {
|
|
1916
|
+
return {
|
|
1917
|
+
ok: false,
|
|
1918
|
+
message: "omni read accepts only one of --cache-only, --identity-only, or --from-exact-cache",
|
|
1919
|
+
};
|
|
1920
|
+
}
|
|
1921
|
+
if (parsed.booleans.has("--headed") && modes > 0) {
|
|
1922
|
+
return {
|
|
1923
|
+
ok: false,
|
|
1924
|
+
message: "omni read cache, identity, and exact-cache rebuild modes never open a browser and cannot use --headed",
|
|
1925
|
+
};
|
|
1926
|
+
}
|
|
1927
|
+
return {
|
|
1928
|
+
ok: true,
|
|
1929
|
+
value: {
|
|
1930
|
+
command: "omni-read",
|
|
1931
|
+
inputSource,
|
|
1932
|
+
cacheOnly: parsed.booleans.has("--cache-only"),
|
|
1933
|
+
identityOnly: parsed.booleans.has("--identity-only"),
|
|
1934
|
+
fromExactCache: parsed.booleans.has("--from-exact-cache"),
|
|
1935
|
+
headed: parsed.booleans.has("--headed"),
|
|
1936
|
+
json: parsed.booleans.has("--json"),
|
|
1937
|
+
},
|
|
1938
|
+
};
|
|
1939
|
+
}
|
|
1940
|
+
if (first === "confirm") {
|
|
1941
|
+
const digest = raw[1];
|
|
1942
|
+
if (digest === undefined) return { ok: false, message: "confirm requires a plan digest" };
|
|
1943
|
+
const parsed = optionValues(
|
|
1944
|
+
raw.slice(2),
|
|
1945
|
+
["--private-output", "--receipt-binding-output"],
|
|
1946
|
+
["--headed", "--json"],
|
|
1947
|
+
);
|
|
1948
|
+
if (isFailure(parsed)) return parsed;
|
|
1949
|
+
const privateOutput = parsed.values["--private-output"];
|
|
1950
|
+
const receiptBindingOutput = parsed.values["--receipt-binding-output"];
|
|
1951
|
+
for (const [label, path] of [
|
|
1952
|
+
["--private-output", privateOutput],
|
|
1953
|
+
["--receipt-binding-output", receiptBindingOutput],
|
|
1954
|
+
] as const) {
|
|
1955
|
+
if (path !== undefined && (
|
|
1956
|
+
!isAbsolute(path)
|
|
1957
|
+
|| resolve(path) !== path
|
|
1958
|
+
|| Buffer.byteLength(path, "utf8") > 4_096
|
|
1959
|
+
|| /[\0\r\n]/u.test(path)
|
|
1960
|
+
)) return { ok: false, message: `confirm ${label} must be a normalized absolute path` };
|
|
1961
|
+
}
|
|
1962
|
+
return {
|
|
1963
|
+
ok: true,
|
|
1964
|
+
value: {
|
|
1965
|
+
command: "confirm",
|
|
1966
|
+
digest,
|
|
1967
|
+
headed: parsed.booleans.has("--headed"),
|
|
1968
|
+
...(privateOutput === undefined ? {} : { privateOutput }),
|
|
1969
|
+
...(receiptBindingOutput === undefined ? {} : { receiptBindingOutput }),
|
|
1970
|
+
json: parsed.booleans.has("--json"),
|
|
1971
|
+
},
|
|
1972
|
+
};
|
|
1973
|
+
}
|
|
1974
|
+
if (first === "runs") {
|
|
1975
|
+
if (raw[1] === "list") {
|
|
1976
|
+
const json = simpleJsonOptions(raw.slice(2), "runs list");
|
|
1977
|
+
return typeof json === "boolean" ? { ok: true, value: { command: "runs-list", json } } : json;
|
|
1978
|
+
}
|
|
1979
|
+
if (raw[1] === "show") {
|
|
1980
|
+
const runId = raw[2];
|
|
1981
|
+
if (runId === undefined) return { ok: false, message: "runs show requires a run ID" };
|
|
1982
|
+
const issue = validRunId(runId);
|
|
1983
|
+
if (issue !== null) return { ok: false, message: issue };
|
|
1984
|
+
const parsed = optionValues(
|
|
1985
|
+
raw.slice(3),
|
|
1986
|
+
["--private-output", "--receipt-binding-output"],
|
|
1987
|
+
["--json"],
|
|
1988
|
+
);
|
|
1989
|
+
if (isFailure(parsed)) return parsed;
|
|
1990
|
+
const privateOutput = parsed.values["--private-output"];
|
|
1991
|
+
const receiptBindingOutput = parsed.values["--receipt-binding-output"];
|
|
1992
|
+
for (const [label, path] of [
|
|
1993
|
+
["--private-output", privateOutput],
|
|
1994
|
+
["--receipt-binding-output", receiptBindingOutput],
|
|
1995
|
+
] as const) {
|
|
1996
|
+
if (path !== undefined && (
|
|
1997
|
+
!isAbsolute(path)
|
|
1998
|
+
|| resolve(path) !== path
|
|
1999
|
+
|| Buffer.byteLength(path, "utf8") > 4_096
|
|
2000
|
+
|| /[\0\r\n]/u.test(path)
|
|
2001
|
+
)) return { ok: false, message: `runs show ${label} must be a normalized absolute path` };
|
|
2002
|
+
}
|
|
2003
|
+
return {
|
|
2004
|
+
ok: true,
|
|
2005
|
+
value: {
|
|
2006
|
+
command: "runs-show",
|
|
2007
|
+
runId,
|
|
2008
|
+
...(privateOutput === undefined ? {} : { privateOutput }),
|
|
2009
|
+
...(receiptBindingOutput === undefined ? {} : { receiptBindingOutput }),
|
|
2010
|
+
json: parsed.booleans.has("--json"),
|
|
2011
|
+
},
|
|
2012
|
+
};
|
|
2013
|
+
}
|
|
2014
|
+
if (raw[1] === "reconcile") {
|
|
2015
|
+
const runId = raw[2];
|
|
2016
|
+
if (runId === undefined) return { ok: false, message: "runs reconcile requires a run ID" };
|
|
2017
|
+
const issue = validRunId(runId);
|
|
2018
|
+
if (issue !== null) return { ok: false, message: issue };
|
|
2019
|
+
const parsed = optionValues(raw.slice(3), ["--input"], ["--json"]);
|
|
2020
|
+
if (isFailure(parsed)) return parsed;
|
|
2021
|
+
const inputSource = parsed.values["--input"];
|
|
2022
|
+
return {
|
|
2023
|
+
ok: true,
|
|
2024
|
+
value: {
|
|
2025
|
+
command: "runs-reconcile",
|
|
2026
|
+
runId,
|
|
2027
|
+
...(inputSource === undefined ? {} : { inputSource }),
|
|
2028
|
+
json: parsed.booleans.has("--json"),
|
|
2029
|
+
},
|
|
2030
|
+
};
|
|
2031
|
+
}
|
|
2032
|
+
return { ok: false, message: "runs requires list, show, or reconcile" };
|
|
2033
|
+
}
|
|
2034
|
+
if (first === "plans") {
|
|
2035
|
+
if (raw[1] === "list") {
|
|
2036
|
+
const json = simpleJsonOptions(raw.slice(2), "plans list");
|
|
2037
|
+
return typeof json === "boolean" ? { ok: true, value: { command: "plans-list", json } } : json;
|
|
2038
|
+
}
|
|
2039
|
+
if (raw[1] === "cancel") {
|
|
2040
|
+
const digest = raw[2];
|
|
2041
|
+
if (digest === undefined || !/^[a-f0-9]{64}$/u.test(digest)) return { ok: false, message: "plans cancel requires a 64-character lowercase hex digest" };
|
|
2042
|
+
const parsed = optionValues(raw.slice(3), [], ["--yes"]);
|
|
2043
|
+
return isFailure(parsed) ? parsed : { ok: true, value: { command: "plans-cancel", digest, yes: parsed.booleans.has("--yes") } };
|
|
2044
|
+
}
|
|
2045
|
+
return { ok: false, message: "plans requires list or cancel" };
|
|
2046
|
+
}
|
|
2047
|
+
if (validId(first, "adapter ID") === null && raw[1] !== undefined && validOperation(raw[1]) === null) {
|
|
2048
|
+
return parseGhostgetArguments(["invoke", ...raw]);
|
|
2049
|
+
}
|
|
2050
|
+
return { ok: false, message: `unknown command: ${first}` };
|
|
2051
|
+
}
|
|
2052
|
+
|
|
2053
|
+
export { ghostgetUsage } from "./usage";
|