@hraness/ghostget 0.17.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +458 -0
- package/LICENSE +21 -0
- package/README.md +1359 -0
- package/bunfig.toml +2 -0
- package/dist/apple-photos-client.js +704 -0
- package/dist/beeper-client.js +4602 -0
- package/dist/client.js +1605 -0
- package/dist/index-26yq8q16.js +31 -0
- package/dist/index-9t13b4k7.js +508 -0
- package/dist/index-axwm9b3v.js +5 -0
- package/dist/index-gwk7rbyj.js +30 -0
- package/dist/index.js +701 -0
- package/dist/messaging.js +1130 -0
- package/dist/omni-client.js +1406 -0
- package/dist/whatsapp-client.js +355 -0
- package/docs/imessage-direct-provider.md +106 -0
- package/docs/rental-listings.md +94 -0
- package/package.json +600 -0
- package/skills/ghostget/SKILL.md +251 -0
- package/skills/ghostget/agents/openai.yaml +4 -0
- package/skills/ghostget/references/adapter-contract.md +181 -0
- package/skills/ghostget/references/apple-photos.md +115 -0
- package/skills/ghostget/references/article-drafts.md +235 -0
- package/skills/ghostget/references/code-owned-provider-scaffold.md +134 -0
- package/skills/ghostget/references/cross-posting-video.md +139 -0
- package/skills/ghostget/references/cross-posting.md +82 -0
- package/skills/ghostget/references/derivation.md +390 -0
- package/skills/ghostget/references/hraness-social-profile-stats.json +269 -0
- package/skills/ghostget/references/install.md +66 -0
- package/skills/ghostget/references/linkedin-adapter.md +442 -0
- package/skills/ghostget/references/messaging.md +168 -0
- package/skills/ghostget/references/meta-comet-contract.md +153 -0
- package/skills/ghostget/references/platform-patterns.md +122 -0
- package/skills/ghostget/references/provider-plugins.md +496 -0
- package/skills/ghostget/references/reddit-flair.md +78 -0
- package/skills/ghostget/references/safety-and-state.md +214 -0
- package/skills/ghostget/references/settlement-and-duplicate-cleanup.md +79 -0
- package/skills/ghostget/references/social-platform-routing.md +66 -0
- package/skills/ghostget/references/social-profile-stats.md +195 -0
- package/skills/ghostget/references/social-video-platform-routing.md +89 -0
- package/skills/ghostget/references/whatsapp-adapter.md +101 -0
- package/skills/ghostget/references/x-adapter.md +332 -0
- package/skills/ghostget/references/x-ai-disclosure.md +71 -0
- package/src/apple-photos-cli.ts +639 -0
- package/src/apple-photos-client-types.ts +168 -0
- package/src/apple-photos-client.ts +201 -0
- package/src/apple-photos-contact-evidence.ts +838 -0
- package/src/apple-photos-local-source.ts +1131 -0
- package/src/args.ts +2053 -0
- package/src/article-draft-document.ts +417 -0
- package/src/article-draft-embeds.ts +123 -0
- package/src/article-draft-images.ts +214 -0
- package/src/assets/adapter-template/wrench-adapter.json +40 -0
- package/src/assets/adapters/beeper/wrench-web-adapter.json +1500 -0
- package/src/assets/adapters/beeper/wrench-web-adapter.v1.0.0.json +129 -0
- package/src/assets/adapters/beeper/wrench-web-adapter.v1.1.0.json +207 -0
- package/src/assets/adapters/beeper/wrench-web-adapter.v2.0.0.json +1463 -0
- package/src/assets/adapters/beeper/wrench-web-adapter.v2.1.0.json +1463 -0
- package/src/assets/adapters/beeper/wrench-web-adapter.v2.2.0.json +1475 -0
- package/src/assets/adapters/beeper/wrench-web-adapter.v2.3.0.json +1488 -0
- package/src/assets/adapters/bluesky/wrench-web-adapter.json +684 -0
- package/src/assets/adapters/bluesky/wrench-web-adapter.v1.0.0.json +581 -0
- package/src/assets/adapters/bluesky/wrench-web-adapter.v1.1.0.json +581 -0
- package/src/assets/adapters/bluesky/wrench-web-adapter.v1.2.0.json +581 -0
- package/src/assets/adapters/bluesky/wrench-web-adapter.v1.3.0.json +615 -0
- package/src/assets/adapters/bluesky/wrench-web-adapter.v1.4.0.json +684 -0
- package/src/assets/adapters/bluesky/wrench-web-adapter.v1.5.0.json +684 -0
- package/src/assets/adapters/bluesky/wrench-web-adapter.v1.6.0.json +684 -0
- package/src/assets/adapters/clasificados/wrench-web-adapter.json +54 -0
- package/src/assets/adapters/facebook/wrench-web-adapter.json +750 -0
- package/src/assets/adapters/facebook/wrench-web-adapter.v1.0.0.json +706 -0
- package/src/assets/adapters/facebook/wrench-web-adapter.v1.1.0.json +703 -0
- package/src/assets/adapters/facebook/wrench-web-adapter.v1.2.0.json +750 -0
- package/src/assets/adapters/facebook-group/wrench-web-adapter.json +682 -0
- package/src/assets/adapters/facebook-group/wrench-web-adapter.v1.0.0.json +583 -0
- package/src/assets/adapters/facebook-group/wrench-web-adapter.v1.1.0.json +682 -0
- package/src/assets/adapters/facebook-marketplace/wrench-web-adapter.json +437 -0
- package/src/assets/adapters/facebook-marketplace/wrench-web-adapter.v1.0.0.json +383 -0
- package/src/assets/adapters/facebook-marketplace/wrench-web-adapter.v1.1.0.json +437 -0
- package/src/assets/adapters/facebook-marketplace/wrench-web-adapter.v1.1.1.json +437 -0
- package/src/assets/adapters/facebook-page/wrench-web-adapter.json +880 -0
- package/src/assets/adapters/facebook-page/wrench-web-adapter.v1.0.0.json +740 -0
- package/src/assets/adapters/facebook-page/wrench-web-adapter.v1.1.0.json +880 -0
- package/src/assets/adapters/github/wrench-web-adapter.json +69 -0
- package/src/assets/adapters/github/wrench-web-adapter.v1.0.0.json +42 -0
- package/src/assets/adapters/github/wrench-web-adapter.v1.1.0.json +69 -0
- package/src/assets/adapters/gmail/wrench-adapter.json +161 -0
- package/src/assets/adapters/gmail/wrench-adapter.v1.2.0.json +157 -0
- package/src/assets/adapters/hacker-news/wrench-web-adapter.json +315 -0
- package/src/assets/adapters/hacker-news/wrench-web-adapter.v1.0.0.json +315 -0
- package/src/assets/adapters/imessage/wrench-web-adapter.json +236 -0
- package/src/assets/adapters/instagram/wrench-web-adapter.json +719 -0
- package/src/assets/adapters/instagram/wrench-web-adapter.v1.0.0.json +627 -0
- package/src/assets/adapters/instagram/wrench-web-adapter.v1.1.0.json +609 -0
- package/src/assets/adapters/instagram/wrench-web-adapter.v1.2.0.json +640 -0
- package/src/assets/adapters/instagram/wrench-web-adapter.v1.3.0.json +671 -0
- package/src/assets/adapters/instagram/wrench-web-adapter.v1.4.0.json +710 -0
- package/src/assets/adapters/instagram/wrench-web-adapter.v1.5.0.json +710 -0
- package/src/assets/adapters/instagram/wrench-web-adapter.v1.6.0.json +719 -0
- package/src/assets/adapters/instagram/wrench-web-adapter.v1.7.0.json +719 -0
- package/src/assets/adapters/linkedin/wrench-adapter.json +442 -0
- package/src/assets/adapters/linkedin/wrench-adapter.v0.4.0.json +124 -0
- package/src/assets/adapters/linkedin/wrench-adapter.v1.0.0.json +411 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.json +914 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.0.0.json +604 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.1.0.json +604 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.10.0.json +809 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.11.0.json +809 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.12.0.json +820 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.13.0.json +819 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.14.0.json +819 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.15.0.json +819 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.16.0.json +874 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.17.0.json +874 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.18.0.json +874 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.19.0.json +874 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.2.0.json +731 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.3.0.json +768 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.4.0.json +808 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.5.0.json +808 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.6.0.json +808 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.7.0.json +825 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.8.0.json +792 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.9.0.json +809 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.json +855 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.v1.0.0.json +577 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.v1.1.0.json +604 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.v1.10.0.json +712 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.v1.11.0.json +712 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.v1.12.0.json +712 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.v1.2.0.json +653 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.v1.3.0.json +712 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.v1.4.0.json +713 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.v1.5.0.json +712 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.v1.6.0.json +713 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.v1.7.0.json +713 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.v1.8.0.json +713 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.v1.9.0.json +713 -0
- package/src/assets/adapters/substack/wrench-web-adapter.json +936 -0
- package/src/assets/adapters/substack/wrench-web-adapter.v1.0.0.json +817 -0
- package/src/assets/adapters/substack/wrench-web-adapter.v1.1.0.json +812 -0
- package/src/assets/adapters/substack/wrench-web-adapter.v1.2.0.json +812 -0
- package/src/assets/adapters/substack/wrench-web-adapter.v1.3.0.json +902 -0
- package/src/assets/adapters/substack/wrench-web-adapter.v1.4.0.json +936 -0
- package/src/assets/adapters/substack/wrench-web-adapter.v1.5.0.json +936 -0
- package/src/assets/adapters/substack/wrench-web-adapter.v1.6.0.json +936 -0
- package/src/assets/adapters/threads/wrench-web-adapter.json +692 -0
- package/src/assets/adapters/threads/wrench-web-adapter.v1.0.0.json +629 -0
- package/src/assets/adapters/threads/wrench-web-adapter.v1.1.0.json +623 -0
- package/src/assets/adapters/threads/wrench-web-adapter.v1.2.0.json +622 -0
- package/src/assets/adapters/threads/wrench-web-adapter.v1.3.0.json +622 -0
- package/src/assets/adapters/threads/wrench-web-adapter.v1.4.0.json +622 -0
- package/src/assets/adapters/threads/wrench-web-adapter.v1.5.0.json +699 -0
- package/src/assets/adapters/threads/wrench-web-adapter.v1.6.0.json +693 -0
- package/src/assets/adapters/threads/wrench-web-adapter.v1.7.0.json +692 -0
- package/src/assets/adapters/tiktok/wrench-web-adapter.json +762 -0
- package/src/assets/adapters/tiktok/wrench-web-adapter.v1.0.0.json +681 -0
- package/src/assets/adapters/tiktok/wrench-web-adapter.v1.1.0.json +708 -0
- package/src/assets/adapters/tiktok/wrench-web-adapter.v1.2.0.json +762 -0
- package/src/assets/adapters/tiktok/wrench-web-adapter.v1.3.0.json +762 -0
- package/src/assets/adapters/twitch/wrench-web-adapter.json +42 -0
- package/src/assets/adapters/twitch/wrench-web-adapter.v1.0.0.json +42 -0
- package/src/assets/adapters/whatsapp/wrench-web-adapter.json +400 -0
- package/src/assets/adapters/whatsapp/wrench-web-adapter.v1.0.0.json +355 -0
- package/src/assets/adapters/whatsapp/wrench-web-adapter.v1.1.0.json +386 -0
- package/src/assets/adapters/whatsapp/wrench-web-adapter.v1.2.0.json +386 -0
- package/src/assets/adapters/whatsapp/wrench-web-adapter.v1.3.0.json +400 -0
- package/src/assets/adapters/x/wrench-adapter.json +749 -0
- package/src/assets/adapters/x/wrench-adapter.v1.0.0.json +700 -0
- package/src/assets/adapters/x/wrench-adapter.v1.1.0.json +707 -0
- package/src/assets/adapters/x/wrench-web-adapter.json +712 -0
- package/src/assets/adapters/x/wrench-web-adapter.v1.1.0.json +579 -0
- package/src/assets/adapters/x/wrench-web-adapter.v1.10.0.json +685 -0
- package/src/assets/adapters/x/wrench-web-adapter.v1.11.0.json +712 -0
- package/src/assets/adapters/x/wrench-web-adapter.v1.12.0.json +712 -0
- package/src/assets/adapters/x/wrench-web-adapter.v1.13.0.json +712 -0
- package/src/assets/adapters/x/wrench-web-adapter.v1.2.0.json +578 -0
- package/src/assets/adapters/x/wrench-web-adapter.v1.3.0.json +610 -0
- package/src/assets/adapters/x/wrench-web-adapter.v1.4.0.json +617 -0
- package/src/assets/adapters/x/wrench-web-adapter.v1.5.0.json +634 -0
- package/src/assets/adapters/x/wrench-web-adapter.v1.6.0.json +632 -0
- package/src/assets/adapters/x/wrench-web-adapter.v1.7.0.json +649 -0
- package/src/assets/adapters/x/wrench-web-adapter.v1.8.0.json +649 -0
- package/src/assets/adapters/x/wrench-web-adapter.v1.9.0.json +683 -0
- package/src/assets/adapters/youtube/wrench-web-adapter.json +561 -0
- package/src/assets/adapters/youtube/wrench-web-adapter.v1.0.0.json +465 -0
- package/src/assets/adapters/youtube/wrench-web-adapter.v1.1.0.json +519 -0
- package/src/assets/adapters/youtube/wrench-web-adapter.v1.2.0.json +561 -0
- package/src/assets/adapters/youtube/wrench-web-adapter.v1.3.0.json +561 -0
- package/src/assets/code-owned-provider-template/plugin.test.ts.template +54 -0
- package/src/assets/code-owned-provider-template/plugin.ts.template +102 -0
- package/src/assets/code-owned-provider-template/runtime.internal.test.ts.template +44 -0
- package/src/assets/code-owned-provider-template/runtime.ts.template +59 -0
- package/src/assets/code-owned-provider-template/wrench-adapter.json.template +33 -0
- package/src/assets/fonts/nebula-sans/LICENSE.txt +96 -0
- package/src/assets/fonts/nebula-sans/NebulaSans-Book.woff2 +0 -0
- package/src/assets/fonts/nebula-sans/PROVENANCE.md +14 -0
- package/src/assets.d.ts +4 -0
- package/src/auth.ts +1157 -0
- package/src/beeper-client-types.ts +169 -0
- package/src/beeper-client.ts +250 -0
- package/src/beeper-contact-interactions-cli.ts +180 -0
- package/src/beeper-contact-interactions.ts +1612 -0
- package/src/beeper-message-bundle-v1.ts +257 -0
- package/src/beeper-message-like-me-cli.ts +84 -0
- package/src/beeper-message-like-me-export.ts +2274 -0
- package/src/beeper-message-like-me-recovery.ts +931 -0
- package/src/beeper-message-like-me-source.ts +4034 -0
- package/src/browser-admission.ts +928 -0
- package/src/browser-snapshots.ts +260 -0
- package/src/browser.ts +3873 -0
- package/src/canonical-json.ts +60 -0
- package/src/catalog-cli.ts +638 -0
- package/src/cli.ts +208 -0
- package/src/client-types.ts +301 -0
- package/src/client.ts +2726 -0
- package/src/confirmed-write-failure.ts +44 -0
- package/src/confirmed-write-model.ts +72 -0
- package/src/confirmed-write-platform.ts +1055 -0
- package/src/confirmed-write-program.ts +224 -0
- package/src/confirmed-write-runtime.ts +16 -0
- package/src/cursor-token.ts +228 -0
- package/src/derivation-dnr-readiness.ts +367 -0
- package/src/derivation-file-chooser.ts +384 -0
- package/src/derivation-network-boundary.ts +835 -0
- package/src/derivation-network-guard.ts +782 -0
- package/src/derivation-network-proxy-helper.ts +440 -0
- package/src/derivation-network-proxy.ts +385 -0
- package/src/derive-command-helper.ts +693 -0
- package/src/derive-fixtures.ts +343 -0
- package/src/derive-review.ts +980 -0
- package/src/derive.ts +4754 -0
- package/src/fixtures/beeper-message-like-me-v1/accounts.ndjson +2 -0
- package/src/fixtures/beeper-message-like-me-v1/conversations.ndjson +1 -0
- package/src/fixtures/beeper-message-like-me-v1/manifest.json +1 -0
- package/src/fixtures/beeper-message-like-me-v1/messages.ndjson +2 -0
- package/src/fixtures/beeper-message-like-me-v1/participants.ndjson +3 -0
- package/src/fixtures/beeper-message-like-me-v1/reactions.ndjson +1 -0
- package/src/fixtures/beeper-message-like-me-v1/tombstones.ndjson +1 -0
- package/src/ghostget.ts +3672 -0
- package/src/gmail-capture.ts +1244 -0
- package/src/har-internal.ts +1242 -0
- package/src/har.ts +743 -0
- package/src/index.ts +218 -0
- package/src/invocation-read-platform.ts +55 -0
- package/src/invocation-read-program.ts +93 -0
- package/src/invocation-read-runtime.ts +11 -0
- package/src/linked-device-lifecycle-admission.ts +450 -0
- package/src/linked-device-lifecycle-journal.ts +1560 -0
- package/src/linked-device-lifecycle-runtime.ts +1602 -0
- package/src/local-cli-admission.ts +135 -0
- package/src/local-cli-contracts.ts +271 -0
- package/src/local-cli-execution.ts +98 -0
- package/src/local-cli-surface-contract.ts +1510 -0
- package/src/local-cli-tool-identity.ts +336 -0
- package/src/media/archive.ts +2325 -0
- package/src/media/args.ts +303 -0
- package/src/media/cli.ts +287 -0
- package/src/media/doctor.ts +475 -0
- package/src/media/ffmpeg.ts +1330 -0
- package/src/media/http-capture.ts +711 -0
- package/src/media/http-probe.ts +593 -0
- package/src/media/http.ts +387 -0
- package/src/media/index.ts +10 -0
- package/src/media/local-transcription.ts +613 -0
- package/src/media/lock.ts +321 -0
- package/src/media/manifest.ts +1712 -0
- package/src/media/metadata.ts +906 -0
- package/src/media/process.ts +723 -0
- package/src/media/revision.ts +166 -0
- package/src/media/runtime-closure.ts +1063 -0
- package/src/media/source-router.ts +187 -0
- package/src/media/transcriber-config.ts +1122 -0
- package/src/media/transcript-persistence-model.ts +15 -0
- package/src/media/transcript-persistence-platform.ts +89 -0
- package/src/media/transcript-persistence-program.ts +39 -0
- package/src/media/transcript-persistence-runtime.ts +24 -0
- package/src/media/transcript.ts +616 -0
- package/src/media/utf8-order.ts +30 -0
- package/src/media/whisper-cpp.ts +464 -0
- package/src/media/whisper-language.ts +58 -0
- package/src/media/yt-dlp.ts +439 -0
- package/src/message-like-me-agentic-messaging.ts +882 -0
- package/src/messaging-action-store.ts +793 -0
- package/src/messaging-runtime.ts +2136 -0
- package/src/messaging-store.ts +539 -0
- package/src/messaging-types.ts +1663 -0
- package/src/messaging.ts +334 -0
- package/src/model.ts +2671 -0
- package/src/oauth-google.ts +888 -0
- package/src/omni-client-types.ts +306 -0
- package/src/omni-client.ts +2225 -0
- package/src/omni-limits.ts +34 -0
- package/src/omni-model.ts +1614 -0
- package/src/omni-request.ts +379 -0
- package/src/omni-runtime.ts +1632 -0
- package/src/operation-deadline.ts +189 -0
- package/src/path-helper.ts +1656 -0
- package/src/pinned-https.ts +194 -0
- package/src/plan-assets.ts +832 -0
- package/src/platform-catalog.ts +1832 -0
- package/src/plugins/beeper-linked-device/plugin.ts +310 -0
- package/src/plugins/bluesky-web/plugin.ts +178 -0
- package/src/plugins/clasificados-web/plugin.ts +66 -0
- package/src/plugins/github-web/plugin.ts +80 -0
- package/src/plugins/gmail-official/plugin.ts +110 -0
- package/src/plugins/hacker-news-web/plugin.ts +52 -0
- package/src/plugins/imessage-direct/plugin.ts +195 -0
- package/src/plugins/imessage-direct/vendor/0001-fix-keep-AppleScript-send-payloads-out-of-child-argv.patch +1430 -0
- package/src/plugins/imessage-direct/vendor/0002-feat-rpc-add-exact-chat-lookup.patch +225 -0
- package/src/plugins/imessage-direct/vendor/provenance.json +80 -0
- package/src/plugins/linkedin-official/plugin.ts +84 -0
- package/src/plugins/linkedin-web/plugin.ts +412 -0
- package/src/plugins/meta-web/plugin.ts +317 -0
- package/src/plugins/reddit-web/flair.ts +492 -0
- package/src/plugins/reddit-web/plugin.ts +477 -0
- package/src/plugins/substack-web/plugin.ts +124 -0
- package/src/plugins/tiktok-web/plugin.ts +67 -0
- package/src/plugins/twitch-web/plugin.ts +54 -0
- package/src/plugins/whatsapp-linked-device/plugin.ts +105 -0
- package/src/plugins/x-official/plugin.ts +64 -0
- package/src/plugins/x-web/plugin.ts +488 -0
- package/src/plugins/youtube-web/plugin.ts +101 -0
- package/src/portable-run-recovery.ts +466 -0
- package/src/process-identity.ts +501 -0
- package/src/profile-clone-helper.ts +104 -0
- package/src/provider-catalog-views.ts +51 -0
- package/src/provider-context.ts +69 -0
- package/src/provider-contract-definitions-x.ts +45 -0
- package/src/provider-contract-definitions.ts +948 -0
- package/src/provider-contract-input-gmail.ts +176 -0
- package/src/provider-contract-input-linkedin.ts +77 -0
- package/src/provider-contract-input-x.ts +304 -0
- package/src/provider-contract-planning.ts +23 -0
- package/src/provider-contract-semantic-identity.ts +54 -0
- package/src/provider-contracts.ts +251 -0
- package/src/provider-http.ts +723 -0
- package/src/provider-plugin-auth.ts +36 -0
- package/src/provider-plugin-builtins.ts +229 -0
- package/src/provider-plugin-cleanup-barrier.ts +229 -0
- package/src/provider-plugin-cleanup-execution.ts +79 -0
- package/src/provider-plugin-cleanup-resource.ts +470 -0
- package/src/provider-plugin-contract-identity.ts +707 -0
- package/src/provider-plugin-host.ts +1972 -0
- package/src/provider-plugin-identifiers.ts +60 -0
- package/src/provider-plugin-invocation-lease.ts +889 -0
- package/src/provider-plugin-lifecycle-kernel.ts +566 -0
- package/src/provider-plugin-lifecycle.ts +1455 -0
- package/src/provider-plugin-module-analysis.ts +336 -0
- package/src/provider-plugin-package.ts +2530 -0
- package/src/provider-plugin-portable-authority.ts +106 -0
- package/src/provider-plugin-portable-catalog.ts +94 -0
- package/src/provider-plugin-portable-identity.ts +315 -0
- package/src/provider-plugin-portable-runtime.ts +2057 -0
- package/src/provider-plugin-protocol.ts +1625 -0
- package/src/provider-plugin-registry.ts +3342 -0
- package/src/provider-plugin-store.ts +2155 -0
- package/src/provider-plugin-test-fixture.ts +5 -0
- package/src/provider-plugin.ts +4907 -0
- package/src/provider-plugins.generated.ts +43 -0
- package/src/provider-plugins.ts +8 -0
- package/src/provider-subject.ts +41 -0
- package/src/provider.ts +315 -0
- package/src/providers/beeper-local-runtime.ts +5426 -0
- package/src/providers/beeper-local.ts +2739 -0
- package/src/providers/beeper-messaging.ts +462 -0
- package/src/providers/beeper-omni.ts +1159 -0
- package/src/providers/bluesky-web-runtime.ts +2895 -0
- package/src/providers/bluesky-web.ts +1614 -0
- package/src/providers/clasificados-web-runtime.ts +255 -0
- package/src/providers/clasificados-web.ts +503 -0
- package/src/providers/contact-projection.ts +301 -0
- package/src/providers/github-read-model.ts +94 -0
- package/src/providers/github-read-platform.ts +165 -0
- package/src/providers/github-read-program.ts +97 -0
- package/src/providers/github-web-runtime.ts +320 -0
- package/src/providers/github-web.ts +403 -0
- package/src/providers/gmail-api.ts +2834 -0
- package/src/providers/gmail-omni.ts +830 -0
- package/src/providers/gmail.ts +1130 -0
- package/src/providers/hacker-news-web-runtime.ts +220 -0
- package/src/providers/hacker-news-web.ts +809 -0
- package/src/providers/imessage-direct-install.ts +354 -0
- package/src/providers/imessage-direct-messaging.ts +532 -0
- package/src/providers/imessage-direct-omni.ts +400 -0
- package/src/providers/imessage-direct-runtime.ts +1498 -0
- package/src/providers/imessage-direct.ts +351 -0
- package/src/providers/instagram-video-foundations.ts +913 -0
- package/src/providers/instagram-web-profile-browser.ts +600 -0
- package/src/providers/iso-bmff.ts +338 -0
- package/src/providers/linkedin-company-platform.ts +42 -0
- package/src/providers/linkedin-company-program.ts +88 -0
- package/src/providers/linkedin-contact-failure.ts +84 -0
- package/src/providers/linkedin-contact-platform.ts +45 -0
- package/src/providers/linkedin-contact-program.ts +91 -0
- package/src/providers/linkedin-profile-activity-failure.ts +76 -0
- package/src/providers/linkedin-profile-activity-platform.ts +41 -0
- package/src/providers/linkedin-profile-activity-program.ts +67 -0
- package/src/providers/linkedin-read-failure.ts +52 -0
- package/src/providers/linkedin-self-platform.ts +53 -0
- package/src/providers/linkedin-self-program.ts +122 -0
- package/src/providers/linkedin-web-article-browser.ts +794 -0
- package/src/providers/linkedin-web-bootstrap.ts +146 -0
- package/src/providers/linkedin-web-contact.ts +830 -0
- package/src/providers/linkedin-web-feed-browser.ts +832 -0
- package/src/providers/linkedin-web-feed.ts +864 -0
- package/src/providers/linkedin-web-post-browser.ts +653 -0
- package/src/providers/linkedin-web-profile-browser.ts +683 -0
- package/src/providers/linkedin-web-runtime.ts +2388 -0
- package/src/providers/linkedin-web.ts +3305 -0
- package/src/providers/linkedin.ts +1885 -0
- package/src/providers/meta-bootstrap.ts +1043 -0
- package/src/providers/meta-facebook-group.ts +690 -0
- package/src/providers/meta-marketplace-relay.ts +747 -0
- package/src/providers/meta-omni.ts +373 -0
- package/src/providers/meta-relay-bundle.ts +1049 -0
- package/src/providers/meta-web-descriptors.ts +1740 -0
- package/src/providers/meta-web-runtime.ts +3388 -0
- package/src/providers/meta-web.ts +2931 -0
- package/src/providers/read-failure.ts +139 -0
- package/src/providers/reddit-omni.ts +462 -0
- package/src/providers/reddit-web-runtime.ts +1929 -0
- package/src/providers/reddit-web.ts +1611 -0
- package/src/providers/rental-listings.ts +460 -0
- package/src/providers/substack-omni.ts +406 -0
- package/src/providers/substack-video-mp4.ts +33 -0
- package/src/providers/substack-web-runtime.ts +2812 -0
- package/src/providers/substack-web.ts +1502 -0
- package/src/providers/tiktok-video-mp4.ts +33 -0
- package/src/providers/tiktok-web-runtime.ts +770 -0
- package/src/providers/tiktok-web.ts +2786 -0
- package/src/providers/twitch-web-runtime.ts +226 -0
- package/src/providers/twitch-web.ts +274 -0
- package/src/providers/whatsapp-account-identity.ts +55 -0
- package/src/providers/whatsapp-contact-projection-helper.ts +874 -0
- package/src/providers/whatsapp-contact-projection-protocol.ts +403 -0
- package/src/providers/whatsapp-interaction-projection-helper.ts +1452 -0
- package/src/providers/whatsapp-interaction-projection-protocol.ts +462 -0
- package/src/providers/whatsapp-message-export-projection-protocol.ts +532 -0
- package/src/providers/whatsapp-messaging.ts +132 -0
- package/src/providers/whatsapp-omni.ts +586 -0
- package/src/providers/whatsapp-web-runtime.ts +3108 -0
- package/src/providers/whatsapp-web.ts +1037 -0
- package/src/providers/x-image-provenance.ts +523 -0
- package/src/providers/x-made-with-ai.ts +106 -0
- package/src/providers/x-transaction-id.ts +413 -0
- package/src/providers/x-web-runtime.ts +3485 -0
- package/src/providers/x-web.ts +2332 -0
- package/src/providers/x.ts +1889 -0
- package/src/providers/youtube-web-runtime.ts +1400 -0
- package/src/providers/youtube-web.ts +1221 -0
- package/src/read-admission-runtime.ts +91 -0
- package/src/read-client.ts +521 -0
- package/src/read-effect-platform.ts +7 -0
- package/src/read-effect-runtime.ts +14 -0
- package/src/read-effect.ts +52 -0
- package/src/read-projection-admission.ts +866 -0
- package/src/read-projections.ts +3151 -0
- package/src/recovery.ts +1492 -0
- package/src/reviewed-template.ts +146 -0
- package/src/run-journal.ts +1272 -0
- package/src/runtime.ts +5116 -0
- package/src/scripts/generate-provider-plugin-catalog.ts +121 -0
- package/src/scripts/install-whatsapp-protocol.sh +232 -0
- package/src/scripts/recover-install-stages.ts +1491 -0
- package/src/scripts/resolve-state-home.ts +30 -0
- package/src/scripts/scaffold-web-provider.ts +973 -0
- package/src/scripts/sync-bundled-adapters.ts +695 -0
- package/src/session-secrets.ts +1183 -0
- package/src/state-helper.bunfig.toml +2 -0
- package/src/state-helper.ts +2914 -0
- package/src/storage.ts +3842 -0
- package/src/transport-policy.ts +2 -0
- package/src/usage.ts +182 -0
- package/src/version.ts +2 -0
- package/src/web-session-authentication-policy.ts +136 -0
- package/src/web-session-cleanup-admission.ts +2344 -0
- package/src/web-session-cleanup-join.ts +99 -0
- package/src/web-session-client.ts +996 -0
- package/src/web-session-contract-definitions.ts +820 -0
- package/src/web-session-contract-planning.ts +42 -0
- package/src/web-session-contracts.ts +228 -0
- package/src/web-session-cookies.ts +500 -0
- package/src/web-session-execution.ts +385 -0
- package/src/web-session-http.ts +373 -0
- package/src/web-session-read-errors.ts +19 -0
- package/src/web-session-read-runtime.ts +124 -0
- package/src/web-session-recovery.ts +818 -0
- package/src/web-session-sites.ts +58 -0
- package/src/web-session-template.ts +994 -0
- package/src/web-session.ts +93 -0
- package/src/whatsapp-client-binding.ts +21 -0
- package/src/whatsapp-client-types.ts +75 -0
- package/src/whatsapp-client.ts +395 -0
- package/src/whatsapp-export-coordinate.ts +17 -0
- package/src/whatsapp-message-bundle-v2.ts +195 -0
- package/src/whatsapp-message-like-me-cli.ts +195 -0
- package/src/whatsapp-message-like-me-export.ts +1072 -0
- package/src/whatsapp-message-like-me-source.ts +1012 -0
- package/tsconfig.json +67 -0
|
@@ -0,0 +1,2739 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fixed policy, input parsing, and argv planning for the official Beeper CLI.
|
|
3
|
+
*
|
|
4
|
+
* The provider never accepts a command, argv fragment, target, base URL, token,
|
|
5
|
+
* path, or fuzzy chat selector from its caller. File arguments are plan-bound
|
|
6
|
+
* references which the kernel materializes only after confirmation.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { isAbsolute } from "node:path";
|
|
10
|
+
import { types as nodeTypes } from "node:util";
|
|
11
|
+
|
|
12
|
+
import beeperAdapterManifest from "../assets/adapters/beeper/wrench-web-adapter.json";
|
|
13
|
+
import type { FileInputValue, OperationInput } from "../model";
|
|
14
|
+
import {
|
|
15
|
+
defineLocalCliSurfaceContractV1,
|
|
16
|
+
type LocalCliSurfaceArgumentV1,
|
|
17
|
+
type LocalCliSurfaceAdditionalEntryV1,
|
|
18
|
+
type LocalCliSurfaceCommandDefinitionV1,
|
|
19
|
+
type LocalCliSurfaceDecisionV1,
|
|
20
|
+
type LocalCliSurfaceDefaultV1,
|
|
21
|
+
type LocalCliSurfaceFlagV1,
|
|
22
|
+
type LocalCliSurfaceReconciliationV1,
|
|
23
|
+
} from "../local-cli-surface-contract";
|
|
24
|
+
|
|
25
|
+
export type BeeperCliArtifactPin = Readonly<{
|
|
26
|
+
platform: "darwin" | "linux";
|
|
27
|
+
arch: "arm64" | "x64";
|
|
28
|
+
archiveSha256: string;
|
|
29
|
+
executableSha256: string;
|
|
30
|
+
downloadUrl: string;
|
|
31
|
+
}>;
|
|
32
|
+
|
|
33
|
+
export const BEEPER_CLI_DARWIN_ARM64_ARTIFACT_PIN = Object.freeze({
|
|
34
|
+
platform: "darwin",
|
|
35
|
+
arch: "arm64",
|
|
36
|
+
archiveSha256:
|
|
37
|
+
"688ccde7e7d044d33980cd06474bf1ae7215ccf8ca79967262fa3bfb85a2589a",
|
|
38
|
+
executableSha256:
|
|
39
|
+
"48aa895449129c793a212ea19f69a534adc34a8adc4037ca1d7da9e648716425",
|
|
40
|
+
downloadUrl:
|
|
41
|
+
"https://github.com/beeper/cli/releases/download/v0.6.2/beeper-cli-0.6.2-macos-arm64.zip",
|
|
42
|
+
} as const);
|
|
43
|
+
|
|
44
|
+
export const BEEPER_CLI_PIN = Object.freeze({
|
|
45
|
+
id: "beeper-cli",
|
|
46
|
+
implementation: "github.com/beeper/cli",
|
|
47
|
+
version: "0.6.2",
|
|
48
|
+
commit: "a416af06023449a87312dc11e54643fd9dc94b8c",
|
|
49
|
+
releaseManifestSha256:
|
|
50
|
+
"5c52b533180151b97e26138ef687b6b819170687b34a478184e5648335356950",
|
|
51
|
+
releaseManifestUrl:
|
|
52
|
+
"https://github.com/beeper/cli/releases/download/v0.6.2/binaries.json",
|
|
53
|
+
releaseUrl: "https://github.com/beeper/cli/releases/tag/v0.6.2",
|
|
54
|
+
sourceUrl:
|
|
55
|
+
"https://github.com/beeper/cli/tree/a416af06023449a87312dc11e54643fd9dc94b8c",
|
|
56
|
+
// Frozen v0.14 specialized-export identity. New local-cli contracts bind
|
|
57
|
+
// the complete artifact table below; the released schema-1 receipt keeps
|
|
58
|
+
// these exact Darwin arm64 field names and values.
|
|
59
|
+
darwinArm64ArchiveSha256:
|
|
60
|
+
BEEPER_CLI_DARWIN_ARM64_ARTIFACT_PIN.archiveSha256,
|
|
61
|
+
darwinArm64BinarySha256:
|
|
62
|
+
BEEPER_CLI_DARWIN_ARM64_ARTIFACT_PIN.executableSha256,
|
|
63
|
+
downloadUrl: BEEPER_CLI_DARWIN_ARM64_ARTIFACT_PIN.downloadUrl,
|
|
64
|
+
artifacts: Object.freeze([
|
|
65
|
+
BEEPER_CLI_DARWIN_ARM64_ARTIFACT_PIN,
|
|
66
|
+
Object.freeze({
|
|
67
|
+
platform: "darwin",
|
|
68
|
+
arch: "x64",
|
|
69
|
+
archiveSha256:
|
|
70
|
+
"4113a1979cfbd7839f14743158e70c12efa941313afb77ab2b11a08309196186",
|
|
71
|
+
executableSha256:
|
|
72
|
+
"83bb89edb6eeb9c61ebdb6ec940e0db30c90ecbca61d60a7408fe336e255f22e",
|
|
73
|
+
downloadUrl:
|
|
74
|
+
"https://github.com/beeper/cli/releases/download/v0.6.2/beeper-cli-0.6.2-macos-x64.zip",
|
|
75
|
+
}),
|
|
76
|
+
Object.freeze({
|
|
77
|
+
platform: "linux",
|
|
78
|
+
arch: "arm64",
|
|
79
|
+
archiveSha256:
|
|
80
|
+
"2bd37043a4ed863621edc59e28aaa652e8193e55abca0e9477f5aeae1c65d629",
|
|
81
|
+
executableSha256:
|
|
82
|
+
"102b8725bd99b03905dcff9fff645f3742e1697ce8d43ab9d8656896aafd12a8",
|
|
83
|
+
downloadUrl:
|
|
84
|
+
"https://github.com/beeper/cli/releases/download/v0.6.2/beeper-cli-0.6.2-linux-arm64.tar.gz",
|
|
85
|
+
}),
|
|
86
|
+
Object.freeze({
|
|
87
|
+
platform: "linux",
|
|
88
|
+
arch: "x64",
|
|
89
|
+
archiveSha256:
|
|
90
|
+
"a881e1d2bc91e31218b251716644ec5f8d161d5ccb30e7eab66cf2ba6410511d",
|
|
91
|
+
executableSha256:
|
|
92
|
+
"723cc3a6c556fa21b6ba11db8377d6a29776aca1660da48f0072883d6452ae3d",
|
|
93
|
+
downloadUrl:
|
|
94
|
+
"https://github.com/beeper/cli/releases/download/v0.6.2/beeper-cli-0.6.2-linux-x64.tar.gz",
|
|
95
|
+
}),
|
|
96
|
+
] satisfies readonly BeeperCliArtifactPin[]),
|
|
97
|
+
} as const);
|
|
98
|
+
|
|
99
|
+
/** SDK contract reviewed behind the pinned CLI's Desktop command surface. */
|
|
100
|
+
export const BEEPER_DESKTOP_API_PIN = Object.freeze({
|
|
101
|
+
package: "@beeper/desktop-api",
|
|
102
|
+
version: "5.0.0",
|
|
103
|
+
commit: "b9c1714410139c2139b597338cd002d785653e85",
|
|
104
|
+
} as const);
|
|
105
|
+
|
|
106
|
+
export function beeperCliArtifactForRuntime(
|
|
107
|
+
platform: NodeJS.Platform = process.platform,
|
|
108
|
+
arch: string = process.arch,
|
|
109
|
+
): BeeperCliArtifactPin {
|
|
110
|
+
const artifact = BEEPER_CLI_PIN.artifacts.find(
|
|
111
|
+
(candidate) => candidate.platform === platform && candidate.arch === arch,
|
|
112
|
+
);
|
|
113
|
+
if (artifact === undefined) {
|
|
114
|
+
throw new Error(`Beeper CLI ${BEEPER_CLI_PIN.version} has no pinned artifact for this runtime`);
|
|
115
|
+
}
|
|
116
|
+
return artifact;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export const BEEPER_ORIGIN = "https://www.beeper.com" as const;
|
|
120
|
+
export const BEEPER_DESKTOP_TARGET = "desktop" as const;
|
|
121
|
+
export const BEEPER_MAX_FILE_BYTES = 500 * 1024 * 1024;
|
|
122
|
+
export const BEEPER_DESKTOP_BUNDLE_IDS = Object.freeze([
|
|
123
|
+
"com.automattic.beeper.desktop",
|
|
124
|
+
"com.automattic.beeper.desktop.nightly",
|
|
125
|
+
] as const);
|
|
126
|
+
|
|
127
|
+
export const BEEPER_LOCAL_OPERATION_NAMES = Object.freeze([
|
|
128
|
+
"accounts.list",
|
|
129
|
+
"accounts.read",
|
|
130
|
+
"bridges.list",
|
|
131
|
+
"bridges.read",
|
|
132
|
+
"contacts.list",
|
|
133
|
+
"contacts.search",
|
|
134
|
+
"contacts.read",
|
|
135
|
+
"messaging.list",
|
|
136
|
+
"messaging.search",
|
|
137
|
+
"conversations.read",
|
|
138
|
+
"messaging.read",
|
|
139
|
+
"messaging.content.search",
|
|
140
|
+
"messaging.message.read",
|
|
141
|
+
"messaging.context.read",
|
|
142
|
+
"messaging.send",
|
|
143
|
+
"reactions.set",
|
|
144
|
+
"messaging.edit",
|
|
145
|
+
"conversations.start",
|
|
146
|
+
"conversations.archive.set",
|
|
147
|
+
"conversations.pin.set",
|
|
148
|
+
"conversations.mute.set",
|
|
149
|
+
"conversations.read-state.set",
|
|
150
|
+
"conversations.priority.set",
|
|
151
|
+
"conversations.notify",
|
|
152
|
+
"conversations.title.set",
|
|
153
|
+
"conversations.description.set",
|
|
154
|
+
"conversations.avatar.set",
|
|
155
|
+
"conversations.draft.set",
|
|
156
|
+
"conversations.disappearing.set",
|
|
157
|
+
"conversations.reminder.set",
|
|
158
|
+
"conversations.focus",
|
|
159
|
+
"presence.set",
|
|
160
|
+
] as const);
|
|
161
|
+
|
|
162
|
+
export type BeeperLocalOperationName =
|
|
163
|
+
(typeof BEEPER_LOCAL_OPERATION_NAMES)[number];
|
|
164
|
+
|
|
165
|
+
export const BEEPER_LOCAL_OPERATION_CONTRACT_VERSIONS = Object.freeze({
|
|
166
|
+
"accounts.list": 2,
|
|
167
|
+
"accounts.read": 1,
|
|
168
|
+
"bridges.list": 2,
|
|
169
|
+
"bridges.read": 1,
|
|
170
|
+
"contacts.list": 3,
|
|
171
|
+
"contacts.search": 1,
|
|
172
|
+
"contacts.read": 1,
|
|
173
|
+
"messaging.list": 1,
|
|
174
|
+
"messaging.search": 2,
|
|
175
|
+
"conversations.read": 2,
|
|
176
|
+
"messaging.read": 3,
|
|
177
|
+
"messaging.content.search": 2,
|
|
178
|
+
"messaging.message.read": 1,
|
|
179
|
+
"messaging.context.read": 1,
|
|
180
|
+
"messaging.send": 1,
|
|
181
|
+
"reactions.set": 1,
|
|
182
|
+
"messaging.edit": 1,
|
|
183
|
+
"conversations.start": 1,
|
|
184
|
+
"conversations.archive.set": 1,
|
|
185
|
+
"conversations.pin.set": 1,
|
|
186
|
+
"conversations.mute.set": 1,
|
|
187
|
+
"conversations.read-state.set": 1,
|
|
188
|
+
"conversations.priority.set": 1,
|
|
189
|
+
"conversations.notify": 1,
|
|
190
|
+
"conversations.title.set": 1,
|
|
191
|
+
"conversations.description.set": 1,
|
|
192
|
+
"conversations.avatar.set": 1,
|
|
193
|
+
"conversations.draft.set": 1,
|
|
194
|
+
"conversations.disappearing.set": 1,
|
|
195
|
+
"conversations.reminder.set": 1,
|
|
196
|
+
"conversations.focus": 1,
|
|
197
|
+
"presence.set": 1,
|
|
198
|
+
} as const satisfies Readonly<Record<BeeperLocalOperationName, number>>);
|
|
199
|
+
|
|
200
|
+
export type BeeperLocalOperationContractVersion = 1 | 2 | 3;
|
|
201
|
+
|
|
202
|
+
const BEEPER_LOCAL_CONTRACT_V2_OPERATIONS = Object.freeze([
|
|
203
|
+
"accounts.list",
|
|
204
|
+
"bridges.list",
|
|
205
|
+
"contacts.list",
|
|
206
|
+
"messaging.search",
|
|
207
|
+
"conversations.read",
|
|
208
|
+
"messaging.read",
|
|
209
|
+
"messaging.content.search",
|
|
210
|
+
] as const satisfies readonly BeeperLocalOperationName[]);
|
|
211
|
+
|
|
212
|
+
const BEEPER_LOCAL_CONTRACT_V3_OPERATIONS = Object.freeze([
|
|
213
|
+
"contacts.list",
|
|
214
|
+
"messaging.read",
|
|
215
|
+
] as const satisfies readonly BeeperLocalOperationName[]);
|
|
216
|
+
|
|
217
|
+
export function isBeeperLocalOperationContractVersion(
|
|
218
|
+
action: BeeperLocalOperationName,
|
|
219
|
+
contractVersion: number,
|
|
220
|
+
): contractVersion is BeeperLocalOperationContractVersion {
|
|
221
|
+
return contractVersion === 1
|
|
222
|
+
|| contractVersion === 2
|
|
223
|
+
&& BEEPER_LOCAL_CONTRACT_V2_OPERATIONS.includes(
|
|
224
|
+
action as typeof BEEPER_LOCAL_CONTRACT_V2_OPERATIONS[number],
|
|
225
|
+
)
|
|
226
|
+
|| contractVersion === 3
|
|
227
|
+
&& BEEPER_LOCAL_CONTRACT_V3_OPERATIONS.includes(
|
|
228
|
+
action as typeof BEEPER_LOCAL_CONTRACT_V3_OPERATIONS[number],
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const BEEPER_DESKTOP_LOOPBACK_V2_OPERATIONS = Object.freeze([
|
|
233
|
+
"accounts.list",
|
|
234
|
+
"messaging.search",
|
|
235
|
+
"conversations.read",
|
|
236
|
+
"messaging.read",
|
|
237
|
+
"messaging.content.search",
|
|
238
|
+
] as const satisfies readonly BeeperLocalOperationName[]);
|
|
239
|
+
|
|
240
|
+
export type BeeperLocalOperationRuntimeTransport =
|
|
241
|
+
| "official-cli"
|
|
242
|
+
| "desktop-loopback";
|
|
243
|
+
|
|
244
|
+
export function isBeeperDesktopLoopbackOperationContract(
|
|
245
|
+
action: BeeperLocalOperationName,
|
|
246
|
+
contractVersion: number,
|
|
247
|
+
): boolean {
|
|
248
|
+
return contractVersion === 2
|
|
249
|
+
&& BEEPER_DESKTOP_LOOPBACK_V2_OPERATIONS.includes(
|
|
250
|
+
action as typeof BEEPER_DESKTOP_LOOPBACK_V2_OPERATIONS[number],
|
|
251
|
+
)
|
|
252
|
+
|| contractVersion === 3
|
|
253
|
+
&& BEEPER_LOCAL_CONTRACT_V3_OPERATIONS.includes(
|
|
254
|
+
action as typeof BEEPER_LOCAL_CONTRACT_V3_OPERATIONS[number],
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export const BEEPER_LOCAL_OPERATION_RUNTIME_TRANSPORTS = Object.freeze(
|
|
259
|
+
Object.fromEntries(BEEPER_LOCAL_OPERATION_NAMES.map((operation) => [
|
|
260
|
+
operation,
|
|
261
|
+
isBeeperDesktopLoopbackOperationContract(
|
|
262
|
+
operation,
|
|
263
|
+
BEEPER_LOCAL_OPERATION_CONTRACT_VERSIONS[operation],
|
|
264
|
+
) ? "desktop-loopback" : "official-cli",
|
|
265
|
+
])),
|
|
266
|
+
) as Readonly<Record<BeeperLocalOperationName, BeeperLocalOperationRuntimeTransport>>;
|
|
267
|
+
|
|
268
|
+
type BeeperLocalSurfaceInputType = "string" | "number" | "boolean" | "array" | "file";
|
|
269
|
+
|
|
270
|
+
function beeperLocalSurfaceInputType(value: unknown): BeeperLocalSurfaceInputType {
|
|
271
|
+
if (
|
|
272
|
+
value !== "string"
|
|
273
|
+
&& value !== "number"
|
|
274
|
+
&& value !== "boolean"
|
|
275
|
+
&& value !== "array"
|
|
276
|
+
&& value !== "file"
|
|
277
|
+
) throw new Error("Beeper adapter contains an unsupported operation input type");
|
|
278
|
+
return value;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const adapterOperations = beeperAdapterManifest.operations as Readonly<Record<
|
|
282
|
+
string,
|
|
283
|
+
Readonly<{ input: Readonly<{ properties: Readonly<Record<string, Readonly<{ type: string }>>> }> }>
|
|
284
|
+
>>;
|
|
285
|
+
|
|
286
|
+
export const BEEPER_LOCAL_OPERATION_INPUT_TYPES = Object.freeze(Object.fromEntries(
|
|
287
|
+
Object.keys(adapterOperations).sort().map((operation) => [
|
|
288
|
+
operation,
|
|
289
|
+
Object.freeze(Object.fromEntries(
|
|
290
|
+
Object.keys(adapterOperations[operation]!.input.properties).sort().map((field) => [
|
|
291
|
+
field,
|
|
292
|
+
beeperLocalSurfaceInputType(
|
|
293
|
+
adapterOperations[operation]!.input.properties[field]!.type,
|
|
294
|
+
),
|
|
295
|
+
]),
|
|
296
|
+
)),
|
|
297
|
+
]),
|
|
298
|
+
)) as Readonly<Record<
|
|
299
|
+
BeeperLocalOperationName,
|
|
300
|
+
Readonly<Record<string, BeeperLocalSurfaceInputType>>
|
|
301
|
+
>>;
|
|
302
|
+
|
|
303
|
+
type BeeperOperationPolicy = Readonly<{
|
|
304
|
+
effect: "read" | "write";
|
|
305
|
+
risk: "R1" | "R2" | "R3";
|
|
306
|
+
state: "observed" | "desired";
|
|
307
|
+
reason: string;
|
|
308
|
+
}>;
|
|
309
|
+
|
|
310
|
+
const readPolicy = (reason: string): BeeperOperationPolicy => Object.freeze({
|
|
311
|
+
effect: "read", risk: "R1", state: "observed", reason,
|
|
312
|
+
});
|
|
313
|
+
const reversiblePolicy = (reason: string): BeeperOperationPolicy => Object.freeze({
|
|
314
|
+
effect: "write", risk: "R2", state: "desired", reason,
|
|
315
|
+
});
|
|
316
|
+
const visiblePolicy = (reason: string): BeeperOperationPolicy => Object.freeze({
|
|
317
|
+
effect: "write", risk: "R3", state: "desired", reason,
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
export const BEEPER_LOCAL_OPERATIONS = Object.freeze({
|
|
321
|
+
"accounts.list": readPolicy("list the exact account realm bound to local Beeper Desktop"),
|
|
322
|
+
"accounts.read": readPolicy("read one exact connected-account projection"),
|
|
323
|
+
"bridges.list": readPolicy("list a bounded bridge capability catalog"),
|
|
324
|
+
"bridges.read": readPolicy("read one exact bridge and its non-secret capabilities"),
|
|
325
|
+
"contacts.list": readPolicy("list one bounded account-aware contact projection"),
|
|
326
|
+
"contacts.search": readPolicy("search one bounded account-aware contact candidate window"),
|
|
327
|
+
"contacts.read": readPolicy("read one exact account-bound contact identity"),
|
|
328
|
+
"messaging.list": readPolicy("list one bounded account-aware conversation projection"),
|
|
329
|
+
"messaging.search": readPolicy("search one bounded conversation candidate window"),
|
|
330
|
+
"conversations.read": readPolicy("read one exact account-bound conversation"),
|
|
331
|
+
"messaging.read": readPolicy("read one bounded page from an exact conversation"),
|
|
332
|
+
"messaging.content.search": readPolicy("search one bounded message-content candidate window"),
|
|
333
|
+
"messaging.message.read": readPolicy("read one exact message from an exact conversation"),
|
|
334
|
+
"messaging.context.read": readPolicy("read bounded context around one exact message"),
|
|
335
|
+
"messaging.send": visiblePolicy("submit one confirmed text, file, sticker, or voice request to Beeper Desktop; network delivery is not asserted"),
|
|
336
|
+
"reactions.set": reversiblePolicy("set or clear one exact reaction desired state"),
|
|
337
|
+
"messaging.edit": visiblePolicy("edit one exact message authored by the current account"),
|
|
338
|
+
"conversations.start": visiblePolicy("start one conversation with an exact account-bound user"),
|
|
339
|
+
"conversations.archive.set": reversiblePolicy("set one conversation archive desired state"),
|
|
340
|
+
"conversations.pin.set": reversiblePolicy("set one conversation pin desired state"),
|
|
341
|
+
"conversations.mute.set": reversiblePolicy("set one conversation mute desired state"),
|
|
342
|
+
"conversations.read-state.set": visiblePolicy("set one conversation read marker, which may emit a network read receipt"),
|
|
343
|
+
"conversations.priority.set": reversiblePolicy("set one conversation inbox priority desired state"),
|
|
344
|
+
"conversations.notify": visiblePolicy("send one explicit iMessage Notify Anyway alert"),
|
|
345
|
+
"conversations.title.set": visiblePolicy("set one group-conversation title"),
|
|
346
|
+
"conversations.description.set": visiblePolicy("set or clear one group-conversation description"),
|
|
347
|
+
"conversations.avatar.set": visiblePolicy("set or clear one group-conversation avatar"),
|
|
348
|
+
"conversations.draft.set": reversiblePolicy("set or clear one private conversation draft"),
|
|
349
|
+
"conversations.disappearing.set": visiblePolicy("set a retention timer whose effects cannot be undone after messages expire"),
|
|
350
|
+
"conversations.reminder.set": reversiblePolicy("set or clear one private conversation reminder"),
|
|
351
|
+
"conversations.focus": reversiblePolicy("focus local Beeper Desktop on one exact conversation"),
|
|
352
|
+
"presence.set": visiblePolicy("send one bounded typing or paused indicator"),
|
|
353
|
+
} satisfies Readonly<Record<BeeperLocalOperationName, BeeperOperationPolicy>>);
|
|
354
|
+
|
|
355
|
+
export type BeeperContactsListInputV1 = Readonly<{
|
|
356
|
+
accountId: string | null;
|
|
357
|
+
limit: number;
|
|
358
|
+
}>;
|
|
359
|
+
export type BeeperContactsListInput = BeeperContactsListInputV1 & Readonly<{
|
|
360
|
+
query: string | null;
|
|
361
|
+
}>;
|
|
362
|
+
export type BeeperContactsListInputV3 = BeeperContactsListInput & Readonly<{
|
|
363
|
+
beforeCursor: string | null;
|
|
364
|
+
afterCursor: string | null;
|
|
365
|
+
}>;
|
|
366
|
+
export type BeeperContactsSearchInput = BeeperContactsListInput & Readonly<{ query: string }>;
|
|
367
|
+
export type BeeperMessagingListInput = Readonly<{
|
|
368
|
+
accountId: string | null;
|
|
369
|
+
limit: number;
|
|
370
|
+
archived: boolean | null;
|
|
371
|
+
pinned: boolean | null;
|
|
372
|
+
muted: boolean | null;
|
|
373
|
+
unread: boolean | null;
|
|
374
|
+
lowPriority: boolean | null;
|
|
375
|
+
}>;
|
|
376
|
+
export type BeeperMessagingSearchInput = BeeperContactsSearchInput;
|
|
377
|
+
export type BeeperMessagingReadInputV2 = Readonly<{
|
|
378
|
+
accountId: string;
|
|
379
|
+
conversationId: string;
|
|
380
|
+
beforeCursor: string | null;
|
|
381
|
+
afterCursor: string | null;
|
|
382
|
+
limit: number;
|
|
383
|
+
}>;
|
|
384
|
+
export type BeeperMessagingReadInput = BeeperMessagingReadInputV2 & Readonly<{
|
|
385
|
+
sender: "me" | "others" | string | null;
|
|
386
|
+
}>;
|
|
387
|
+
|
|
388
|
+
type EmptyInput = Readonly<Record<never, never>>;
|
|
389
|
+
type AccountInput = Readonly<{ accountId: string }>;
|
|
390
|
+
type BridgeListInput = Readonly<{
|
|
391
|
+
provider: "local" | "cloud" | "self-hosted" | "platform-sdk" | null;
|
|
392
|
+
available: boolean | null;
|
|
393
|
+
limit: number;
|
|
394
|
+
}>;
|
|
395
|
+
type BridgeInput = Readonly<{ bridgeId: string }>;
|
|
396
|
+
type ContactInput = AccountInput & Readonly<{ contactId: string }>;
|
|
397
|
+
type ConversationInput = AccountInput & Readonly<{ conversationId: string }>;
|
|
398
|
+
type ConversationReadInput = ConversationInput & Readonly<{ maxParticipants: number }>;
|
|
399
|
+
type MessageInput = ConversationInput & Readonly<{ messageId: string }>;
|
|
400
|
+
type MessageContextInput = MessageInput & Readonly<{ before: number; after: number }>;
|
|
401
|
+
export type BeeperMessageContentSearchInput = Readonly<{
|
|
402
|
+
query: string | null;
|
|
403
|
+
accountId: string | null;
|
|
404
|
+
conversationId: string | null;
|
|
405
|
+
chatType: "group" | "single" | null;
|
|
406
|
+
after: string | null;
|
|
407
|
+
before: string | null;
|
|
408
|
+
beforeCursor: string | null;
|
|
409
|
+
afterCursor: string | null;
|
|
410
|
+
excludeLowPriority: boolean;
|
|
411
|
+
includeMuted: boolean;
|
|
412
|
+
media: readonly ("any" | "video" | "image" | "link" | "file")[];
|
|
413
|
+
sender: string | null;
|
|
414
|
+
limit: number;
|
|
415
|
+
}>;
|
|
416
|
+
type SendInput = ConversationInput & Readonly<{
|
|
417
|
+
kind: "text" | "file" | "sticker" | "voice";
|
|
418
|
+
text: string | null;
|
|
419
|
+
file: FileInputValue | null;
|
|
420
|
+
filename: string | null;
|
|
421
|
+
mimeType: string | null;
|
|
422
|
+
durationSeconds: number | null;
|
|
423
|
+
replyTo: string | null;
|
|
424
|
+
mentions: readonly string[];
|
|
425
|
+
noPreview: boolean;
|
|
426
|
+
}>;
|
|
427
|
+
type ReactionInput = MessageInput & Readonly<{ reaction: string; enabled: boolean }>;
|
|
428
|
+
type EditInput = MessageInput & Readonly<{ text: string }>;
|
|
429
|
+
type StartInput = AccountInput & Readonly<{ userId: string }>;
|
|
430
|
+
type BooleanStateInput = ConversationInput & Readonly<{ enabled: boolean }>;
|
|
431
|
+
type ReadStateInput = ConversationInput & Readonly<{ unread: boolean; messageId: string | null }>;
|
|
432
|
+
type PriorityInput = ConversationInput & Readonly<{ level: "inbox" | "low" }>;
|
|
433
|
+
type TextStateInput = ConversationInput & Readonly<{ value: string | null }>;
|
|
434
|
+
type FileStateInput = ConversationInput & Readonly<{ file: FileInputValue | null }>;
|
|
435
|
+
type DraftInput = ConversationInput & Readonly<{
|
|
436
|
+
text: string | null;
|
|
437
|
+
file: FileInputValue | null;
|
|
438
|
+
filename: string | null;
|
|
439
|
+
mimeType: string | null;
|
|
440
|
+
clear: boolean;
|
|
441
|
+
}>;
|
|
442
|
+
type DisappearingInput = ConversationInput & Readonly<{ seconds: number }>;
|
|
443
|
+
type ReminderInput = ConversationInput & Readonly<{ when: string | null; dismissOnMessage: boolean }>;
|
|
444
|
+
type FocusInput = ConversationInput & Readonly<{ messageId: string | null; draft: string | null; attachment: FileInputValue | null }>;
|
|
445
|
+
type PresenceInput = ConversationInput & Readonly<{ state: "typing" | "paused"; durationSeconds: number | null }>;
|
|
446
|
+
|
|
447
|
+
export type BeeperOperationInput =
|
|
448
|
+
| EmptyInput | AccountInput | BridgeListInput | BridgeInput | ContactInput
|
|
449
|
+
| BeeperContactsListInputV1 | BeeperContactsListInput | BeeperContactsListInputV3
|
|
450
|
+
| BeeperContactsSearchInput
|
|
451
|
+
| BeeperMessagingListInput | BeeperMessagingSearchInput | ConversationReadInput
|
|
452
|
+
| BeeperMessagingReadInputV2 | BeeperMessagingReadInput
|
|
453
|
+
| BeeperMessageContentSearchInput | MessageInput | MessageContextInput | SendInput
|
|
454
|
+
| ReactionInput | EditInput | StartInput | BooleanStateInput | ReadStateInput
|
|
455
|
+
| PriorityInput | TextStateInput | FileStateInput | DraftInput
|
|
456
|
+
| DisappearingInput | ReminderInput | FocusInput | PresenceInput;
|
|
457
|
+
|
|
458
|
+
export type BeeperCommand = Readonly<{
|
|
459
|
+
action: "version" | "target-status" | BeeperLocalOperationName;
|
|
460
|
+
argv: readonly string[];
|
|
461
|
+
mutation: boolean;
|
|
462
|
+
}>;
|
|
463
|
+
export type BeeperReadCommand = BeeperCommand;
|
|
464
|
+
|
|
465
|
+
export type BeeperMessageLikeMeExportCommandOptions = Readonly<{
|
|
466
|
+
outputDirectory: string;
|
|
467
|
+
limitChats: number;
|
|
468
|
+
limitMessages: number;
|
|
469
|
+
maxParticipants: number;
|
|
470
|
+
}>;
|
|
471
|
+
|
|
472
|
+
function record(value: unknown, label: string): Readonly<Record<string, unknown>> {
|
|
473
|
+
if (
|
|
474
|
+
typeof value !== "object"
|
|
475
|
+
|| value === null
|
|
476
|
+
|| Array.isArray(value)
|
|
477
|
+
|| nodeTypes.isProxy(value)
|
|
478
|
+
|| (Object.getPrototypeOf(value) !== Object.prototype
|
|
479
|
+
&& Object.getPrototypeOf(value) !== null)
|
|
480
|
+
) throw new Error(`${label} must be a plain data object`);
|
|
481
|
+
const descriptors = Object.getOwnPropertyDescriptors(value);
|
|
482
|
+
if (Reflect.ownKeys(descriptors).some((key) => typeof key !== "string")) {
|
|
483
|
+
throw new Error(`${label} must not contain symbol fields`);
|
|
484
|
+
}
|
|
485
|
+
const result: Record<string, unknown> = Object.create(null) as Record<string, unknown>;
|
|
486
|
+
for (const [key, descriptor] of Object.entries(descriptors)) {
|
|
487
|
+
if (!descriptor.enumerable || !("value" in descriptor)) {
|
|
488
|
+
throw new Error(`${label} must contain only enumerable data fields`);
|
|
489
|
+
}
|
|
490
|
+
result[key] = descriptor.value;
|
|
491
|
+
}
|
|
492
|
+
return Object.freeze(result);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
function exactKeys(value: Readonly<Record<string, unknown>>, required: readonly string[], optional: readonly string[], label: string): void {
|
|
496
|
+
const keys = new Set(Object.keys(value));
|
|
497
|
+
for (const key of required) if (!keys.delete(key)) throw new Error(`${label} omitted ${key}`);
|
|
498
|
+
for (const key of optional) keys.delete(key);
|
|
499
|
+
if (keys.size > 0) throw new Error(`${label} contained unsupported fields`);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
function integer(value: unknown, label: string, minimum: number, maximum: number): number {
|
|
503
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < minimum || value > maximum) throw new Error(`${label} must be an integer from ${minimum} through ${maximum}`);
|
|
504
|
+
return value;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
function bool(value: unknown, label: string): boolean {
|
|
508
|
+
if (typeof value !== "boolean") throw new Error(`${label} must be boolean`);
|
|
509
|
+
return value;
|
|
510
|
+
}
|
|
511
|
+
function optionalBool(value: unknown, label: string): boolean | null {
|
|
512
|
+
return value === undefined ? null : bool(value, label);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
function boundedOpaque(value: unknown, label: string, maximum: number): string {
|
|
516
|
+
if (typeof value !== "string" || value.length < 1 || Buffer.byteLength(value, "utf8") > maximum || /[\0\r\n]/u.test(value) || !hasWellFormedUnicode(value)) throw new Error(`${label} must be bounded well-formed opaque text`);
|
|
517
|
+
return value;
|
|
518
|
+
}
|
|
519
|
+
function optionalOpaque(value: unknown, label: string, maximum: number): string | null {
|
|
520
|
+
return value === undefined ? null : boundedOpaque(value, label, maximum);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
function boundedControlFreeOpaque(
|
|
524
|
+
value: unknown,
|
|
525
|
+
label: string,
|
|
526
|
+
maximum: number,
|
|
527
|
+
): string {
|
|
528
|
+
const opaque = boundedOpaque(value, label, maximum);
|
|
529
|
+
if (/[\u0000-\u001f\u007f-\u009f]/u.test(opaque)) {
|
|
530
|
+
throw new Error(`${label} must be bounded well-formed control-free opaque text`);
|
|
531
|
+
}
|
|
532
|
+
return opaque;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
function optionalControlFreeOpaque(
|
|
536
|
+
value: unknown,
|
|
537
|
+
label: string,
|
|
538
|
+
maximum: number,
|
|
539
|
+
): string | null {
|
|
540
|
+
return value === undefined
|
|
541
|
+
? null
|
|
542
|
+
: boundedControlFreeOpaque(value, label, maximum);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
function hasWellFormedUnicode(value: string): boolean {
|
|
546
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
547
|
+
const code = value.charCodeAt(index);
|
|
548
|
+
if (code >= 0xd800 && code <= 0xdbff) {
|
|
549
|
+
const next = value.charCodeAt(index + 1);
|
|
550
|
+
if (!(next >= 0xdc00 && next <= 0xdfff)) return false;
|
|
551
|
+
index += 1;
|
|
552
|
+
} else if (code >= 0xdc00 && code <= 0xdfff) return false;
|
|
553
|
+
}
|
|
554
|
+
return true;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
function boundedText(value: unknown, label: string, maximum: number, allowEmpty = false): string {
|
|
558
|
+
if (typeof value !== "string" || (!allowEmpty && value.length === 0) || Buffer.byteLength(value, "utf8") > maximum || value.includes("\0") || !hasWellFormedUnicode(value)) throw new Error(`${label} must be bounded well-formed Unicode text`);
|
|
559
|
+
return value;
|
|
560
|
+
}
|
|
561
|
+
function optionalText(value: unknown, label: string, maximum: number): string | null {
|
|
562
|
+
return value === undefined ? null : boundedText(value, label, maximum, true);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
function normalizedSearchQuery(value: unknown, label: string): string {
|
|
566
|
+
if (typeof value !== "string") throw new Error(`${label} must be text`);
|
|
567
|
+
const normalized = value.normalize("NFKC").trim();
|
|
568
|
+
if (
|
|
569
|
+
normalized.length < 1
|
|
570
|
+
|| normalized.startsWith("-")
|
|
571
|
+
|| !hasWellFormedUnicode(normalized)
|
|
572
|
+
|| Buffer.byteLength(normalized, "utf8") > 256
|
|
573
|
+
|| /[\u0000-\u001f\u007f-\u009f]/u.test(normalized)
|
|
574
|
+
) throw new Error(`${label} must be nonempty normalized non-flag text of at most 256 UTF-8 bytes`);
|
|
575
|
+
return normalized;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
function exactEnum<T extends string>(value: unknown, label: string, values: readonly T[]): T {
|
|
579
|
+
if (typeof value !== "string" || !values.includes(value as T)) throw new Error(`${label} must be one of ${values.join(", ")}`);
|
|
580
|
+
return value as T;
|
|
581
|
+
}
|
|
582
|
+
function optionalEnum<T extends string>(value: unknown, label: string, values: readonly T[]): T | null {
|
|
583
|
+
return value === undefined ? null : exactEnum(value, label, values);
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
function isoTimestamp(value: unknown, label: string): string {
|
|
587
|
+
const text = boundedOpaque(value, label, 64);
|
|
588
|
+
const milliseconds = Date.parse(text);
|
|
589
|
+
if (!Number.isFinite(milliseconds) || new Date(milliseconds).toISOString() !== text) throw new Error(`${label} must be a canonical UTC ISO timestamp`);
|
|
590
|
+
return text;
|
|
591
|
+
}
|
|
592
|
+
function optionalTimestamp(value: unknown, label: string): string | null {
|
|
593
|
+
return value === undefined ? null : isoTimestamp(value, label);
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
function fileInput(value: unknown, label: string): FileInputValue {
|
|
597
|
+
const source = record(value, label);
|
|
598
|
+
exactKeys(source, ["kind", "reference"], [], label);
|
|
599
|
+
if (source.kind !== "file") throw new Error(`${label}.kind must be file`);
|
|
600
|
+
return Object.freeze({ kind: "file" as const, reference: boundedOpaque(source.reference, `${label}.reference`, 1_024) });
|
|
601
|
+
}
|
|
602
|
+
function optionalFile(value: unknown, label: string): FileInputValue | null {
|
|
603
|
+
return value === undefined ? null : fileInput(value, label);
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
function stringArray(value: unknown, label: string, maximumItems: number, itemMaximum: number): readonly string[] {
|
|
607
|
+
if (
|
|
608
|
+
!Array.isArray(value)
|
|
609
|
+
|| nodeTypes.isProxy(value)
|
|
610
|
+
|| Object.getPrototypeOf(value) !== Array.prototype
|
|
611
|
+
|| value.length > maximumItems
|
|
612
|
+
|| Reflect.ownKeys(value).length !== value.length + 1
|
|
613
|
+
) throw new Error(`${label} must be a dense plain array of at most ${maximumItems} items`);
|
|
614
|
+
const items: string[] = [];
|
|
615
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
616
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
|
|
617
|
+
if (descriptor === undefined || !descriptor.enumerable || !("value" in descriptor)) {
|
|
618
|
+
throw new Error(`${label} must contain only dense data items`);
|
|
619
|
+
}
|
|
620
|
+
items.push(boundedOpaque(descriptor.value, `${label}[${index}]`, itemMaximum));
|
|
621
|
+
}
|
|
622
|
+
if (new Set(items).size !== items.length) throw new Error(`${label} must not repeat items`);
|
|
623
|
+
return Object.freeze(items);
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
function accountId(source: Readonly<Record<string, unknown>>, label: string): string {
|
|
627
|
+
return boundedOpaque(source.account_id, `${label}.account_id`, 512);
|
|
628
|
+
}
|
|
629
|
+
export function isCanonicalBeeperConversationId(value: string): boolean {
|
|
630
|
+
return Buffer.byteLength(value, "utf8") <= 2_048
|
|
631
|
+
&& hasWellFormedUnicode(value)
|
|
632
|
+
&& !/[\u0000-\u001f\u007f-\u009f]/u.test(value)
|
|
633
|
+
&& /^![^:\s]{1,255}:[^:\s]{1,255}(?::[0-9]{1,5})?$/u.test(value);
|
|
634
|
+
}
|
|
635
|
+
function conversationId(source: Readonly<Record<string, unknown>>, label: string): string {
|
|
636
|
+
const id = boundedControlFreeOpaque(
|
|
637
|
+
source.conversation_id,
|
|
638
|
+
`${label}.conversation_id`,
|
|
639
|
+
2_048,
|
|
640
|
+
);
|
|
641
|
+
if (!isCanonicalBeeperConversationId(id)) {
|
|
642
|
+
throw new Error(`${label}.conversation_id must be one exact full Beeper/Matrix chat ID`);
|
|
643
|
+
}
|
|
644
|
+
return id;
|
|
645
|
+
}
|
|
646
|
+
function messageId(source: Readonly<Record<string, unknown>>, label: string): string {
|
|
647
|
+
return boundedOpaque(source.message_id, `${label}.message_id`, 2_048);
|
|
648
|
+
}
|
|
649
|
+
function canonicalMatrixUserId(value: unknown, label: string): string {
|
|
650
|
+
const id = boundedOpaque(value, label, 2_048);
|
|
651
|
+
if (!/^@[^:\s]{1,255}:[^:\s]{1,255}(?::[0-9]{1,5})?$/u.test(id)) {
|
|
652
|
+
throw new Error(`${label} must be one exact Matrix/Beeper user ID`);
|
|
653
|
+
}
|
|
654
|
+
return id;
|
|
655
|
+
}
|
|
656
|
+
function canonicalMessageSender(value: unknown, label: string): string {
|
|
657
|
+
const sender = boundedOpaque(value, label, 2_048);
|
|
658
|
+
if (sender === "me" || sender === "others") return sender;
|
|
659
|
+
if (sender.startsWith("-")) {
|
|
660
|
+
throw new Error(`${label} must be one bounded opaque non-flag user ID`);
|
|
661
|
+
}
|
|
662
|
+
return sender;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
function canonicalMessageSenderV3(value: unknown, label: string): string {
|
|
666
|
+
const sender = boundedControlFreeOpaque(value, label, 2_048);
|
|
667
|
+
if (sender === "me" || sender === "others") return sender;
|
|
668
|
+
if (sender.startsWith("-")) {
|
|
669
|
+
throw new Error(`${label} must be one bounded opaque non-flag user ID`);
|
|
670
|
+
}
|
|
671
|
+
return sender;
|
|
672
|
+
}
|
|
673
|
+
function conversationInput(source: Readonly<Record<string, unknown>>, label: string): ConversationInput {
|
|
674
|
+
return Object.freeze({ accountId: accountId(source, label), conversationId: conversationId(source, label) });
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
function parseBeeperContactsListInputV1(
|
|
678
|
+
input: OperationInput,
|
|
679
|
+
): BeeperContactsListInputV1 {
|
|
680
|
+
const source = record(input, "contacts.list input");
|
|
681
|
+
exactKeys(source, [], ["account_id", "limit"], "contacts.list input");
|
|
682
|
+
return Object.freeze({
|
|
683
|
+
accountId: optionalOpaque(source.account_id, "contacts.list input.account_id", 512),
|
|
684
|
+
limit: source.limit === undefined
|
|
685
|
+
? 200
|
|
686
|
+
: integer(source.limit, "contacts.list input.limit", 1, 200),
|
|
687
|
+
});
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
export function parseBeeperContactsListInput(input: OperationInput): BeeperContactsListInput {
|
|
691
|
+
const source = record(input, "contacts.list input");
|
|
692
|
+
exactKeys(source, [], ["account_id", "query", "limit"], "contacts.list input");
|
|
693
|
+
return Object.freeze({
|
|
694
|
+
accountId: optionalOpaque(source.account_id, "contacts.list input.account_id", 512),
|
|
695
|
+
query: source.query === undefined
|
|
696
|
+
? null
|
|
697
|
+
: normalizedSearchQuery(source.query, "contacts.list input.query"),
|
|
698
|
+
limit: source.limit === undefined
|
|
699
|
+
? 200
|
|
700
|
+
: integer(source.limit, "contacts.list input.limit", 1, 200),
|
|
701
|
+
});
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
export function parseBeeperContactsListInputV3(
|
|
705
|
+
input: OperationInput,
|
|
706
|
+
): BeeperContactsListInputV3 {
|
|
707
|
+
const source = record(input, "contacts.list input");
|
|
708
|
+
exactKeys(
|
|
709
|
+
source,
|
|
710
|
+
[],
|
|
711
|
+
["account_id", "query", "before_cursor", "after_cursor", "limit"],
|
|
712
|
+
"contacts.list input",
|
|
713
|
+
);
|
|
714
|
+
const beforeCursor = optionalOpaque(
|
|
715
|
+
source.before_cursor,
|
|
716
|
+
"contacts.list input.before_cursor",
|
|
717
|
+
2_048,
|
|
718
|
+
);
|
|
719
|
+
const afterCursor = optionalOpaque(
|
|
720
|
+
source.after_cursor,
|
|
721
|
+
"contacts.list input.after_cursor",
|
|
722
|
+
2_048,
|
|
723
|
+
);
|
|
724
|
+
if (beforeCursor !== null && afterCursor !== null) {
|
|
725
|
+
throw new Error("contacts.list input accepts only one cursor direction");
|
|
726
|
+
}
|
|
727
|
+
if (
|
|
728
|
+
(beforeCursor !== null || afterCursor !== null)
|
|
729
|
+
&& source.account_id === undefined
|
|
730
|
+
) {
|
|
731
|
+
throw new Error("contacts.list cursor input requires one exact account_id");
|
|
732
|
+
}
|
|
733
|
+
return Object.freeze({
|
|
734
|
+
accountId: optionalOpaque(source.account_id, "contacts.list input.account_id", 512),
|
|
735
|
+
query: source.query === undefined
|
|
736
|
+
? null
|
|
737
|
+
: normalizedSearchQuery(source.query, "contacts.list input.query"),
|
|
738
|
+
beforeCursor,
|
|
739
|
+
afterCursor,
|
|
740
|
+
limit: source.limit === undefined
|
|
741
|
+
? 200
|
|
742
|
+
: integer(source.limit, "contacts.list input.limit", 1, 200),
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
export function parseBeeperContactsSearchInput(input: OperationInput): BeeperContactsSearchInput {
|
|
747
|
+
const source = record(input, "contacts.search input");
|
|
748
|
+
exactKeys(source, ["query"], ["account_id", "limit"], "contacts.search input");
|
|
749
|
+
return Object.freeze({ accountId: optionalOpaque(source.account_id, "contacts.search input.account_id", 512), query: normalizedSearchQuery(source.query, "contacts.search input.query"), limit: source.limit === undefined ? 20 : integer(source.limit, "contacts.search input.limit", 1, 20) });
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
export function parseBeeperMessagingListInput(input: OperationInput): BeeperMessagingListInput {
|
|
753
|
+
const source = record(input, "messaging.list input");
|
|
754
|
+
exactKeys(source, [], ["account_id", "limit", "archived", "pinned", "muted", "unread", "low_priority"], "messaging.list input");
|
|
755
|
+
return Object.freeze({
|
|
756
|
+
accountId: optionalOpaque(source.account_id, "messaging.list input.account_id", 512),
|
|
757
|
+
limit: source.limit === undefined ? 200 : integer(source.limit, "messaging.list input.limit", 1, 200),
|
|
758
|
+
archived: optionalBool(source.archived, "messaging.list input.archived"),
|
|
759
|
+
pinned: optionalBool(source.pinned, "messaging.list input.pinned"),
|
|
760
|
+
muted: optionalBool(source.muted, "messaging.list input.muted"),
|
|
761
|
+
unread: optionalBool(source.unread, "messaging.list input.unread"),
|
|
762
|
+
lowPriority: optionalBool(source.low_priority, "messaging.list input.low_priority"),
|
|
763
|
+
});
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
export function parseBeeperMessagingSearchInput(input: OperationInput): BeeperMessagingSearchInput {
|
|
767
|
+
const source = record(input, "messaging.search input");
|
|
768
|
+
exactKeys(source, ["query"], ["account_id", "limit"], "messaging.search input");
|
|
769
|
+
return Object.freeze({ accountId: optionalOpaque(source.account_id, "messaging.search input.account_id", 512), query: normalizedSearchQuery(source.query, "messaging.search input.query"), limit: source.limit === undefined ? 20 : integer(source.limit, "messaging.search input.limit", 1, 20) });
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
export function parseBeeperMessagingReadInputV2(
|
|
773
|
+
input: OperationInput,
|
|
774
|
+
): BeeperMessagingReadInputV2 {
|
|
775
|
+
const source = record(input, "messaging.read input");
|
|
776
|
+
exactKeys(source, ["account_id", "conversation_id"], ["before_cursor", "after_cursor", "limit"], "messaging.read input");
|
|
777
|
+
const beforeCursor = optionalOpaque(source.before_cursor, "messaging.read input.before_cursor", 2_048);
|
|
778
|
+
const afterCursor = optionalOpaque(source.after_cursor, "messaging.read input.after_cursor", 2_048);
|
|
779
|
+
if (beforeCursor !== null && afterCursor !== null) throw new Error("messaging.read input accepts only one cursor direction");
|
|
780
|
+
return Object.freeze({
|
|
781
|
+
accountId: accountId(source, "messaging.read input"),
|
|
782
|
+
conversationId: conversationId(source, "messaging.read input"),
|
|
783
|
+
beforeCursor,
|
|
784
|
+
afterCursor,
|
|
785
|
+
limit: source.limit === undefined
|
|
786
|
+
? 200
|
|
787
|
+
: integer(source.limit, "messaging.read input.limit", 1, 200),
|
|
788
|
+
});
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
export function parseBeeperMessagingReadInput(input: OperationInput): BeeperMessagingReadInput {
|
|
792
|
+
const source = record(input, "messaging.read input");
|
|
793
|
+
exactKeys(source, ["account_id", "conversation_id"], ["before_cursor", "after_cursor", "sender", "limit"], "messaging.read input");
|
|
794
|
+
const beforeCursor = optionalControlFreeOpaque(
|
|
795
|
+
source.before_cursor,
|
|
796
|
+
"messaging.read input.before_cursor",
|
|
797
|
+
2_048,
|
|
798
|
+
);
|
|
799
|
+
const afterCursor = optionalControlFreeOpaque(
|
|
800
|
+
source.after_cursor,
|
|
801
|
+
"messaging.read input.after_cursor",
|
|
802
|
+
2_048,
|
|
803
|
+
);
|
|
804
|
+
if (beforeCursor !== null && afterCursor !== null) throw new Error("messaging.read input accepts only one cursor direction");
|
|
805
|
+
return Object.freeze({
|
|
806
|
+
accountId: accountId(source, "messaging.read input"),
|
|
807
|
+
conversationId: conversationId(source, "messaging.read input"),
|
|
808
|
+
beforeCursor,
|
|
809
|
+
afterCursor,
|
|
810
|
+
sender: source.sender === undefined
|
|
811
|
+
? null
|
|
812
|
+
: canonicalMessageSenderV3(source.sender, "messaging.read input.sender"),
|
|
813
|
+
limit: source.limit === undefined
|
|
814
|
+
? 200
|
|
815
|
+
: integer(source.limit, "messaging.read input.limit", 1, 200),
|
|
816
|
+
});
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
function parseMessageSearchInput(input: OperationInput): BeeperMessageContentSearchInput {
|
|
820
|
+
const label = "messaging.content.search input";
|
|
821
|
+
const source = record(input, label);
|
|
822
|
+
exactKeys(source, [], ["query", "account_id", "conversation_id", "chat_type", "after", "before", "before_cursor", "after_cursor", "exclude_low_priority", "include_muted", "media", "sender", "limit"], label);
|
|
823
|
+
const query = source.query === undefined ? null : normalizedSearchQuery(source.query, `${label}.query`);
|
|
824
|
+
const account = optionalOpaque(source.account_id, `${label}.account_id`, 512);
|
|
825
|
+
const conversation = source.conversation_id === undefined
|
|
826
|
+
? null
|
|
827
|
+
: conversationId(source, label);
|
|
828
|
+
const chatType = optionalEnum(source.chat_type, `${label}.chat_type`, ["group", "single"] as const);
|
|
829
|
+
const after = optionalTimestamp(source.after, `${label}.after`);
|
|
830
|
+
const before = optionalTimestamp(source.before, `${label}.before`);
|
|
831
|
+
if (after !== null && before !== null && Date.parse(after) > Date.parse(before)) throw new Error(`${label}.after must not follow before`);
|
|
832
|
+
const beforeCursor = optionalOpaque(source.before_cursor, `${label}.before_cursor`, 2_048);
|
|
833
|
+
const afterCursor = optionalOpaque(source.after_cursor, `${label}.after_cursor`, 2_048);
|
|
834
|
+
if (beforeCursor !== null && afterCursor !== null) throw new Error(`${label} accepts only one cursor direction`);
|
|
835
|
+
const media: readonly ("any" | "video" | "image" | "link" | "file")[] =
|
|
836
|
+
source.media === undefined
|
|
837
|
+
? Object.freeze([])
|
|
838
|
+
: stringArray(source.media, `${label}.media`, 5, 16).map((item) =>
|
|
839
|
+
exactEnum(item, `${label}.media item`, ["any", "video", "image", "link", "file"] as const));
|
|
840
|
+
if (media.includes("any") && media.length > 1) throw new Error(`${label}.media any must be the only filter`);
|
|
841
|
+
const sender = source.sender === undefined
|
|
842
|
+
? null
|
|
843
|
+
: canonicalMessageSender(source.sender, `${label}.sender`);
|
|
844
|
+
if (query === null && account === null && conversation === null && chatType === null && after === null && before === null && media.length === 0 && sender === null) throw new Error(`${label} requires a query or an exact filter`);
|
|
845
|
+
return Object.freeze({
|
|
846
|
+
query, accountId: account, conversationId: conversation, chatType, after, before,
|
|
847
|
+
beforeCursor, afterCursor,
|
|
848
|
+
excludeLowPriority: source.exclude_low_priority === undefined ? true : bool(source.exclude_low_priority, `${label}.exclude_low_priority`),
|
|
849
|
+
includeMuted: source.include_muted === undefined ? true : bool(source.include_muted, `${label}.include_muted`),
|
|
850
|
+
media: Object.freeze(media), sender,
|
|
851
|
+
limit: source.limit === undefined ? 50 : integer(source.limit, `${label}.limit`, 1, 200),
|
|
852
|
+
});
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
function parseSendInput(input: OperationInput): SendInput {
|
|
856
|
+
const label = "messaging.send input";
|
|
857
|
+
const source = record(input, label);
|
|
858
|
+
exactKeys(source, ["account_id", "conversation_id", "kind"], ["text", "file", "filename", "mime_type", "duration_seconds", "reply_to", "mentions", "no_preview"], label);
|
|
859
|
+
const kind = exactEnum(source.kind, `${label}.kind`, ["text", "file", "sticker", "voice"] as const);
|
|
860
|
+
const text = optionalText(source.text, `${label}.text`, 65_536);
|
|
861
|
+
const file = optionalFile(source.file, `${label}.file`);
|
|
862
|
+
const filename = optionalOpaque(source.filename, `${label}.filename`, 512);
|
|
863
|
+
const mimeType = optionalOpaque(source.mime_type, `${label}.mime_type`, 128);
|
|
864
|
+
const durationSeconds = source.duration_seconds === undefined ? null : integer(source.duration_seconds, `${label}.duration_seconds`, 1, 86_400);
|
|
865
|
+
const replyTo = optionalOpaque(source.reply_to, `${label}.reply_to`, 2_048);
|
|
866
|
+
const mentions = source.mentions === undefined
|
|
867
|
+
? Object.freeze([])
|
|
868
|
+
: Object.freeze(stringArray(source.mentions, `${label}.mentions`, 25, 2_048)
|
|
869
|
+
.map((item, index) => canonicalMatrixUserId(item, `${label}.mentions[${index}]`)));
|
|
870
|
+
const noPreview = source.no_preview === undefined ? false : bool(source.no_preview, `${label}.no_preview`);
|
|
871
|
+
if (kind === "text") {
|
|
872
|
+
if (text === null || text.length === 0) throw new Error(`${label}.text is required for text sends`);
|
|
873
|
+
if (file !== null || filename !== null || mimeType !== null || durationSeconds !== null) throw new Error(`${label} file fields are unsupported for text sends`);
|
|
874
|
+
} else {
|
|
875
|
+
if (file === null) throw new Error(`${label}.file is required for ${kind} sends`);
|
|
876
|
+
if (mentions.length > 0 || noPreview) throw new Error(`${label} mentions and no_preview are text-only`);
|
|
877
|
+
if (kind !== "file" && text !== null) throw new Error(`${label}.text is supported only for text and file sends`);
|
|
878
|
+
if (kind !== "voice" && durationSeconds !== null) throw new Error(`${label}.duration_seconds is voice-only`);
|
|
879
|
+
if (kind === "sticker" && mimeType !== null && mimeType !== "image/webp") throw new Error(`${label}.mime_type must be image/webp for stickers`);
|
|
880
|
+
if (kind === "voice" && mimeType !== null && !["audio/ogg", "audio/opus"].includes(mimeType)) throw new Error(`${label}.mime_type must be audio/ogg or audio/opus for voice notes`);
|
|
881
|
+
}
|
|
882
|
+
return Object.freeze({ ...conversationInput(source, label), kind, text, file, filename, mimeType, durationSeconds, replyTo, mentions, noPreview });
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
export function parseBeeperOperationInputForContract(
|
|
886
|
+
action: BeeperLocalOperationName,
|
|
887
|
+
contractVersion: number,
|
|
888
|
+
input: OperationInput,
|
|
889
|
+
): BeeperOperationInput {
|
|
890
|
+
if (!isBeeperLocalOperationContractVersion(action, contractVersion)) {
|
|
891
|
+
throw new Error(`${action}@${String(contractVersion)} is not an installed Beeper contract`);
|
|
892
|
+
}
|
|
893
|
+
if (action === "contacts.list") {
|
|
894
|
+
return contractVersion === 1
|
|
895
|
+
? parseBeeperContactsListInputV1(input)
|
|
896
|
+
: contractVersion === 2
|
|
897
|
+
? parseBeeperContactsListInput(input)
|
|
898
|
+
: parseBeeperContactsListInputV3(input);
|
|
899
|
+
}
|
|
900
|
+
if (action === "contacts.search") return parseBeeperContactsSearchInput(input);
|
|
901
|
+
if (action === "messaging.list") return parseBeeperMessagingListInput(input);
|
|
902
|
+
if (action === "messaging.search") return parseBeeperMessagingSearchInput(input);
|
|
903
|
+
if (action === "messaging.read") {
|
|
904
|
+
return contractVersion === 3
|
|
905
|
+
? parseBeeperMessagingReadInput(input)
|
|
906
|
+
: parseBeeperMessagingReadInputV2(input);
|
|
907
|
+
}
|
|
908
|
+
if (action === "messaging.content.search") {
|
|
909
|
+
const parsed = parseMessageSearchInput(input);
|
|
910
|
+
if (
|
|
911
|
+
contractVersion === 1
|
|
912
|
+
&& (parsed.beforeCursor !== null || parsed.afterCursor !== null)
|
|
913
|
+
) throw new Error("Beeper message search cursor input requires direct-read contract v2");
|
|
914
|
+
return parsed;
|
|
915
|
+
}
|
|
916
|
+
const label = `${action} input`;
|
|
917
|
+
const source = record(input, label);
|
|
918
|
+
if (action === "accounts.list") {
|
|
919
|
+
exactKeys(source, [], [], label);
|
|
920
|
+
return Object.freeze({});
|
|
921
|
+
}
|
|
922
|
+
if (action === "accounts.read") {
|
|
923
|
+
exactKeys(source, ["account_id"], [], label);
|
|
924
|
+
return Object.freeze({ accountId: accountId(source, label) });
|
|
925
|
+
}
|
|
926
|
+
if (action === "bridges.list") {
|
|
927
|
+
exactKeys(source, [], ["provider", "available", "limit"], label);
|
|
928
|
+
return Object.freeze({
|
|
929
|
+
provider: contractVersion === 1
|
|
930
|
+
? optionalEnum(source.provider, `${label}.provider`, [
|
|
931
|
+
"local", "cloud", "self-hosted",
|
|
932
|
+
] as const)
|
|
933
|
+
: optionalEnum(source.provider, `${label}.provider`, [
|
|
934
|
+
"local", "cloud", "self-hosted", "platform-sdk",
|
|
935
|
+
] as const),
|
|
936
|
+
available: optionalBool(source.available, `${label}.available`),
|
|
937
|
+
limit: source.limit === undefined ? 128 : integer(source.limit, `${label}.limit`, 1, 128),
|
|
938
|
+
});
|
|
939
|
+
}
|
|
940
|
+
if (action === "bridges.read") {
|
|
941
|
+
exactKeys(source, ["bridge_id"], [], label);
|
|
942
|
+
const bridgeId = boundedOpaque(source.bridge_id, `${label}.bridge_id`, 512);
|
|
943
|
+
if (!/^[a-z0-9][a-z0-9._-]{0,127}$/u.test(bridgeId)) {
|
|
944
|
+
throw new Error(`${label}.bridge_id must be one canonical bridge ID`);
|
|
945
|
+
}
|
|
946
|
+
return Object.freeze({ bridgeId });
|
|
947
|
+
}
|
|
948
|
+
if (action === "contacts.read") {
|
|
949
|
+
exactKeys(source, ["account_id", "contact_id"], [], label);
|
|
950
|
+
return Object.freeze({
|
|
951
|
+
accountId: accountId(source, label),
|
|
952
|
+
contactId: boundedOpaque(source.contact_id, `${label}.contact_id`, 2_048),
|
|
953
|
+
});
|
|
954
|
+
}
|
|
955
|
+
if (action === "conversations.read") {
|
|
956
|
+
exactKeys(source, ["account_id", "conversation_id"], ["max_participants"], label);
|
|
957
|
+
return Object.freeze({
|
|
958
|
+
...conversationInput(source, label),
|
|
959
|
+
maxParticipants: source.max_participants === undefined
|
|
960
|
+
? 200
|
|
961
|
+
: integer(source.max_participants, `${label}.max_participants`, 1, 500),
|
|
962
|
+
});
|
|
963
|
+
}
|
|
964
|
+
if (action === "messaging.message.read") {
|
|
965
|
+
exactKeys(source, ["account_id", "conversation_id", "message_id"], [], label);
|
|
966
|
+
return Object.freeze({ ...conversationInput(source, label), messageId: messageId(source, label) });
|
|
967
|
+
}
|
|
968
|
+
if (action === "messaging.context.read") {
|
|
969
|
+
exactKeys(source, ["account_id", "conversation_id", "message_id"], ["before", "after"], label);
|
|
970
|
+
return Object.freeze({
|
|
971
|
+
...conversationInput(source, label),
|
|
972
|
+
messageId: messageId(source, label),
|
|
973
|
+
before: source.before === undefined ? 10 : integer(source.before, `${label}.before`, 0, 100),
|
|
974
|
+
after: source.after === undefined ? 10 : integer(source.after, `${label}.after`, 0, 100),
|
|
975
|
+
});
|
|
976
|
+
}
|
|
977
|
+
if (action === "messaging.send") return parseSendInput(input);
|
|
978
|
+
if (action === "reactions.set") {
|
|
979
|
+
exactKeys(source, ["account_id", "conversation_id", "message_id", "reaction", "enabled"], [], label);
|
|
980
|
+
return Object.freeze({
|
|
981
|
+
...conversationInput(source, label),
|
|
982
|
+
messageId: messageId(source, label),
|
|
983
|
+
reaction: boundedText(source.reaction, `${label}.reaction`, 256),
|
|
984
|
+
enabled: bool(source.enabled, `${label}.enabled`),
|
|
985
|
+
});
|
|
986
|
+
}
|
|
987
|
+
if (action === "messaging.edit") {
|
|
988
|
+
exactKeys(source, ["account_id", "conversation_id", "message_id", "text"], [], label);
|
|
989
|
+
return Object.freeze({
|
|
990
|
+
...conversationInput(source, label),
|
|
991
|
+
messageId: messageId(source, label),
|
|
992
|
+
text: boundedText(source.text, `${label}.text`, 65_536),
|
|
993
|
+
});
|
|
994
|
+
}
|
|
995
|
+
if (action === "conversations.start") {
|
|
996
|
+
exactKeys(source, ["account_id", "user_id"], [], label);
|
|
997
|
+
return Object.freeze({
|
|
998
|
+
accountId: accountId(source, label),
|
|
999
|
+
userId: canonicalMatrixUserId(source.user_id, `${label}.user_id`),
|
|
1000
|
+
});
|
|
1001
|
+
}
|
|
1002
|
+
if (["conversations.archive.set", "conversations.pin.set", "conversations.mute.set"].includes(action)) {
|
|
1003
|
+
exactKeys(source, ["account_id", "conversation_id", "enabled"], [], label);
|
|
1004
|
+
return Object.freeze({
|
|
1005
|
+
...conversationInput(source, label),
|
|
1006
|
+
enabled: bool(source.enabled, `${label}.enabled`),
|
|
1007
|
+
});
|
|
1008
|
+
}
|
|
1009
|
+
if (action === "conversations.read-state.set") {
|
|
1010
|
+
exactKeys(source, ["account_id", "conversation_id", "unread"], ["message_id"], label);
|
|
1011
|
+
return Object.freeze({
|
|
1012
|
+
...conversationInput(source, label),
|
|
1013
|
+
unread: bool(source.unread, `${label}.unread`),
|
|
1014
|
+
messageId: optionalOpaque(source.message_id, `${label}.message_id`, 2_048),
|
|
1015
|
+
});
|
|
1016
|
+
}
|
|
1017
|
+
if (action === "conversations.priority.set") {
|
|
1018
|
+
exactKeys(source, ["account_id", "conversation_id", "level"], [], label);
|
|
1019
|
+
return Object.freeze({
|
|
1020
|
+
...conversationInput(source, label),
|
|
1021
|
+
level: exactEnum(source.level, `${label}.level`, ["inbox", "low"] as const),
|
|
1022
|
+
});
|
|
1023
|
+
}
|
|
1024
|
+
if (action === "conversations.notify") {
|
|
1025
|
+
exactKeys(source, ["account_id", "conversation_id"], [], label);
|
|
1026
|
+
return conversationInput(source, label);
|
|
1027
|
+
}
|
|
1028
|
+
if (action === "conversations.title.set") {
|
|
1029
|
+
exactKeys(source, ["account_id", "conversation_id", "title"], [], label);
|
|
1030
|
+
return Object.freeze({
|
|
1031
|
+
...conversationInput(source, label),
|
|
1032
|
+
value: boundedText(source.title, `${label}.title`, 1_024),
|
|
1033
|
+
});
|
|
1034
|
+
}
|
|
1035
|
+
if (action === "conversations.description.set") {
|
|
1036
|
+
exactKeys(source, ["account_id", "conversation_id", "clear"], ["description"], label);
|
|
1037
|
+
const clear = bool(source.clear, `${label}.clear`);
|
|
1038
|
+
const value = optionalText(source.description, `${label}.description`, 65_536);
|
|
1039
|
+
if (clear === (value !== null)) throw new Error(`${label} requires exactly one of clear or description`);
|
|
1040
|
+
if (value !== null && value.length === 0) {
|
|
1041
|
+
throw new Error(`${label}.description must be nonempty bounded text`);
|
|
1042
|
+
}
|
|
1043
|
+
return Object.freeze({ ...conversationInput(source, label), value });
|
|
1044
|
+
}
|
|
1045
|
+
if (action === "conversations.avatar.set") {
|
|
1046
|
+
exactKeys(source, ["account_id", "conversation_id", "clear"], ["avatar"], label);
|
|
1047
|
+
const clear = bool(source.clear, `${label}.clear`);
|
|
1048
|
+
const file = optionalFile(source.avatar, `${label}.avatar`);
|
|
1049
|
+
if (clear === (file !== null)) throw new Error(`${label} requires exactly one of clear or avatar`);
|
|
1050
|
+
return Object.freeze({ ...conversationInput(source, label), file });
|
|
1051
|
+
}
|
|
1052
|
+
if (action === "conversations.draft.set") {
|
|
1053
|
+
exactKeys(source, ["account_id", "conversation_id", "clear"], ["text", "attachment", "filename", "mime_type"], label);
|
|
1054
|
+
const clear = bool(source.clear, `${label}.clear`);
|
|
1055
|
+
const text = optionalText(source.text, `${label}.text`, 65_536);
|
|
1056
|
+
const file = optionalFile(source.attachment, `${label}.attachment`);
|
|
1057
|
+
const filename = optionalOpaque(source.filename, `${label}.filename`, 512);
|
|
1058
|
+
const mimeType = optionalOpaque(source.mime_type, `${label}.mime_type`, 128);
|
|
1059
|
+
if (clear && (text !== null || file !== null || filename !== null || mimeType !== null)) throw new Error(`${label}.clear cannot include draft content`);
|
|
1060
|
+
if (!clear && text === null) throw new Error(`${label} requires text when setting a draft`);
|
|
1061
|
+
if (file === null && (filename !== null || mimeType !== null)) throw new Error(`${label} attachment metadata requires attachment`);
|
|
1062
|
+
return Object.freeze({ ...conversationInput(source, label), text, file, filename, mimeType, clear });
|
|
1063
|
+
}
|
|
1064
|
+
if (action === "conversations.disappearing.set") {
|
|
1065
|
+
exactKeys(source, ["account_id", "conversation_id", "seconds"], [], label);
|
|
1066
|
+
return Object.freeze({
|
|
1067
|
+
...conversationInput(source, label),
|
|
1068
|
+
seconds: integer(source.seconds, `${label}.seconds`, 0, 31_536_000),
|
|
1069
|
+
});
|
|
1070
|
+
}
|
|
1071
|
+
if (action === "conversations.reminder.set") {
|
|
1072
|
+
exactKeys(source, ["account_id", "conversation_id", "clear"], ["when", "dismiss_on_message"], label);
|
|
1073
|
+
const clear = bool(source.clear, `${label}.clear`);
|
|
1074
|
+
const when = optionalTimestamp(source.when, `${label}.when`);
|
|
1075
|
+
if (clear === (when !== null)) throw new Error(`${label} requires exactly one of clear or when`);
|
|
1076
|
+
const dismissOnMessage = source.dismiss_on_message === undefined
|
|
1077
|
+
? false
|
|
1078
|
+
: bool(source.dismiss_on_message, `${label}.dismiss_on_message`);
|
|
1079
|
+
if (clear && dismissOnMessage) throw new Error(`${label}.dismiss_on_message requires when`);
|
|
1080
|
+
return Object.freeze({ ...conversationInput(source, label), when, dismissOnMessage });
|
|
1081
|
+
}
|
|
1082
|
+
if (action === "conversations.focus") {
|
|
1083
|
+
exactKeys(source, ["account_id", "conversation_id"], ["message_id", "draft", "attachment"], label);
|
|
1084
|
+
return Object.freeze({
|
|
1085
|
+
...conversationInput(source, label),
|
|
1086
|
+
messageId: optionalOpaque(source.message_id, `${label}.message_id`, 2_048),
|
|
1087
|
+
draft: optionalText(source.draft, `${label}.draft`, 65_536),
|
|
1088
|
+
attachment: optionalFile(source.attachment, `${label}.attachment`),
|
|
1089
|
+
});
|
|
1090
|
+
}
|
|
1091
|
+
exactKeys(source, ["account_id", "conversation_id", "state"], ["duration_seconds"], label);
|
|
1092
|
+
const state = exactEnum(source.state, `${label}.state`, ["typing", "paused"] as const);
|
|
1093
|
+
const durationSeconds = source.duration_seconds === undefined
|
|
1094
|
+
? null
|
|
1095
|
+
: integer(source.duration_seconds, `${label}.duration_seconds`, 1, 30);
|
|
1096
|
+
if (state === "paused" && durationSeconds !== null) throw new Error(`${label}.duration_seconds applies only to typing`);
|
|
1097
|
+
return Object.freeze({ ...conversationInput(source, label), state, durationSeconds });
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
export function parseBeeperOperationInput(
|
|
1101
|
+
action: BeeperLocalOperationName,
|
|
1102
|
+
input: OperationInput,
|
|
1103
|
+
): BeeperOperationInput {
|
|
1104
|
+
return parseBeeperOperationInputForContract(
|
|
1105
|
+
action,
|
|
1106
|
+
BEEPER_LOCAL_OPERATION_CONTRACT_VERSIONS[action],
|
|
1107
|
+
input,
|
|
1108
|
+
);
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
function timeoutArgument(timeoutMs: number): string {
|
|
1112
|
+
return `${Math.max(1, Math.min(3_600, Math.ceil(timeoutMs / 1_000)))}s`;
|
|
1113
|
+
}
|
|
1114
|
+
function readArguments(timeoutMs: number): readonly string[] {
|
|
1115
|
+
return Object.freeze(["--read-only", "--json", "--full", "--quiet", "--target", BEEPER_DESKTOP_TARGET, "--timeout", timeoutArgument(timeoutMs)]);
|
|
1116
|
+
}
|
|
1117
|
+
function writeArguments(timeoutMs: number): readonly string[] {
|
|
1118
|
+
return Object.freeze(["--json", "--full", "--quiet", "--yes", "--target", BEEPER_DESKTOP_TARGET, "--timeout", timeoutArgument(timeoutMs)]);
|
|
1119
|
+
}
|
|
1120
|
+
function booleanFlag(name: string, value: boolean | null): readonly string[] {
|
|
1121
|
+
return value === null ? [] : [value ? `--${name}` : `--no-${name}`];
|
|
1122
|
+
}
|
|
1123
|
+
function command(action: BeeperCommand["action"], argv: readonly string[], mutation: boolean): BeeperCommand {
|
|
1124
|
+
return Object.freeze({ action, argv: Object.freeze(argv), mutation });
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
export function planBeeperAccountsListCommand(timeoutMs: number): BeeperReadCommand {
|
|
1128
|
+
return command("accounts.list", ["accounts", "list", ...readArguments(timeoutMs)], false);
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
/** Exact pinned-tool preflight; it is never exposed as a caller operation. */
|
|
1132
|
+
export function planBeeperVersionCommand(): BeeperReadCommand {
|
|
1133
|
+
return command(
|
|
1134
|
+
"version",
|
|
1135
|
+
["version", "--read-only", "--json", "--quiet"],
|
|
1136
|
+
false,
|
|
1137
|
+
);
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
/** Private realm preflight; it is never exposed as a caller operation. */
|
|
1141
|
+
export function planBeeperTargetStatusCommand(timeoutMs: number): BeeperReadCommand {
|
|
1142
|
+
return command(
|
|
1143
|
+
"target-status",
|
|
1144
|
+
["targets", "status", BEEPER_DESKTOP_TARGET, ...readArguments(timeoutMs)],
|
|
1145
|
+
false,
|
|
1146
|
+
);
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
export function planBeeperOperationCommand(
|
|
1150
|
+
action: BeeperLocalOperationName,
|
|
1151
|
+
input: BeeperOperationInput,
|
|
1152
|
+
timeoutMs: number,
|
|
1153
|
+
resolvedFilePath?: string,
|
|
1154
|
+
): BeeperCommand {
|
|
1155
|
+
const read = readArguments(timeoutMs);
|
|
1156
|
+
const write = writeArguments(timeoutMs);
|
|
1157
|
+
if (action === "accounts.list") return command(action, ["accounts", "list", ...read], false);
|
|
1158
|
+
if (action === "accounts.read") return command(action, ["accounts", "show", (input as AccountInput).accountId, ...read], false);
|
|
1159
|
+
if (action === "bridges.list") {
|
|
1160
|
+
const value = input as BridgeListInput;
|
|
1161
|
+
return command(action, [
|
|
1162
|
+
"bridges", "list",
|
|
1163
|
+
...(value.provider === null || value.provider === "platform-sdk"
|
|
1164
|
+
? []
|
|
1165
|
+
: ["--provider", value.provider]),
|
|
1166
|
+
...booleanFlag("available", value.available),
|
|
1167
|
+
...read,
|
|
1168
|
+
], false);
|
|
1169
|
+
}
|
|
1170
|
+
if (action === "bridges.read") return command(action, ["bridges", "show", (input as BridgeInput).bridgeId, ...read], false);
|
|
1171
|
+
if (action === "contacts.list") {
|
|
1172
|
+
// Official CLI v0.6.2 `contacts list` accepts --limit but collectPage does
|
|
1173
|
+
// not pass that bound into GET /v1/accounts/{accountID}/contacts/list and
|
|
1174
|
+
// does not walk hasMore/oldestCursor. The first Desktop page stays at 50.
|
|
1175
|
+
// contacts.list@3 uses the Desktop loopback path instead.
|
|
1176
|
+
const value = input as BeeperContactsListInput | BeeperContactsListInputV1;
|
|
1177
|
+
return command(action, [
|
|
1178
|
+
"contacts", "list",
|
|
1179
|
+
"--limit", String(value.limit),
|
|
1180
|
+
...(value.accountId === null ? [] : ["--account", value.accountId]),
|
|
1181
|
+
...(!("query" in value) || value.query === null ? [] : ["--query", value.query]),
|
|
1182
|
+
...read,
|
|
1183
|
+
], false);
|
|
1184
|
+
}
|
|
1185
|
+
if (action === "contacts.search") {
|
|
1186
|
+
const value = input as BeeperContactsSearchInput;
|
|
1187
|
+
return command(action, [
|
|
1188
|
+
"contacts", "search", value.query,
|
|
1189
|
+
...(value.accountId === null ? [] : ["--account", value.accountId]),
|
|
1190
|
+
...read,
|
|
1191
|
+
], false);
|
|
1192
|
+
}
|
|
1193
|
+
if (action === "contacts.read") {
|
|
1194
|
+
const value = input as ContactInput;
|
|
1195
|
+
return command(action, ["contacts", "show", value.contactId, "--account", value.accountId, ...read], false);
|
|
1196
|
+
}
|
|
1197
|
+
if (action === "messaging.list") {
|
|
1198
|
+
const value = input as BeeperMessagingListInput;
|
|
1199
|
+
return command(action, [
|
|
1200
|
+
"chats", "list", "--limit", String(value.limit),
|
|
1201
|
+
...(value.accountId === null ? [] : ["--account", value.accountId]),
|
|
1202
|
+
...booleanFlag("archived", value.archived),
|
|
1203
|
+
...booleanFlag("pinned", value.pinned),
|
|
1204
|
+
...booleanFlag("muted", value.muted),
|
|
1205
|
+
...booleanFlag("unread", value.unread),
|
|
1206
|
+
...booleanFlag("low-priority", value.lowPriority),
|
|
1207
|
+
...read,
|
|
1208
|
+
], false);
|
|
1209
|
+
}
|
|
1210
|
+
if (action === "messaging.search") {
|
|
1211
|
+
const value = input as BeeperMessagingSearchInput;
|
|
1212
|
+
return command(action, [
|
|
1213
|
+
"chats", "search", value.query, "--limit", String(value.limit),
|
|
1214
|
+
...(value.accountId === null ? [] : ["--account", value.accountId]),
|
|
1215
|
+
...read,
|
|
1216
|
+
], false);
|
|
1217
|
+
}
|
|
1218
|
+
if (action === "conversations.read") {
|
|
1219
|
+
const value = input as ConversationReadInput;
|
|
1220
|
+
return command(action, [
|
|
1221
|
+
"chats", "show", "--chat", value.conversationId,
|
|
1222
|
+
"--max-participants", String(value.maxParticipants), ...read,
|
|
1223
|
+
], false);
|
|
1224
|
+
}
|
|
1225
|
+
if (action === "messaging.read") {
|
|
1226
|
+
const value = input as BeeperMessagingReadInput | BeeperMessagingReadInputV2;
|
|
1227
|
+
if ("sender" in value) {
|
|
1228
|
+
throw new Error(
|
|
1229
|
+
"messaging.read contract v3 is direct-only and cannot be planned through the Beeper CLI",
|
|
1230
|
+
);
|
|
1231
|
+
}
|
|
1232
|
+
return command(action, [
|
|
1233
|
+
"messages", "list", "--chat", value.conversationId,
|
|
1234
|
+
"--limit", String(value.limit),
|
|
1235
|
+
...(value.beforeCursor === null ? [] : ["--before-cursor", value.beforeCursor]),
|
|
1236
|
+
...(value.afterCursor === null ? [] : ["--after-cursor", value.afterCursor]),
|
|
1237
|
+
...read,
|
|
1238
|
+
], false);
|
|
1239
|
+
}
|
|
1240
|
+
if (action === "messaging.content.search") {
|
|
1241
|
+
const value = input as BeeperMessageContentSearchInput;
|
|
1242
|
+
return command(action, [
|
|
1243
|
+
"messages", "search",
|
|
1244
|
+
...(value.query === null ? [] : [value.query]),
|
|
1245
|
+
...(value.accountId === null ? [] : ["--account", value.accountId]),
|
|
1246
|
+
...(value.conversationId === null ? [] : ["--chat", value.conversationId]),
|
|
1247
|
+
...(value.chatType === null ? [] : ["--chat-type", value.chatType]),
|
|
1248
|
+
...(value.after === null ? [] : ["--after", value.after]),
|
|
1249
|
+
...(value.before === null ? [] : ["--before", value.before]),
|
|
1250
|
+
...booleanFlag("exclude-low-priority", value.excludeLowPriority),
|
|
1251
|
+
...booleanFlag("include-muted", value.includeMuted),
|
|
1252
|
+
...value.media.flatMap((item) => ["--media", item]),
|
|
1253
|
+
...(value.sender === null ? [] : ["--sender", value.sender]),
|
|
1254
|
+
"--limit", String(value.limit), ...read,
|
|
1255
|
+
], false);
|
|
1256
|
+
}
|
|
1257
|
+
if (action === "messaging.message.read") {
|
|
1258
|
+
const value = input as MessageInput;
|
|
1259
|
+
return command(action, [
|
|
1260
|
+
"messages", "show", "--chat", value.conversationId,
|
|
1261
|
+
"--id", value.messageId, ...read,
|
|
1262
|
+
], false);
|
|
1263
|
+
}
|
|
1264
|
+
if (action === "messaging.context.read") {
|
|
1265
|
+
const value = input as MessageContextInput;
|
|
1266
|
+
return command(action, [
|
|
1267
|
+
"messages", "context", "--chat", value.conversationId,
|
|
1268
|
+
"--id", value.messageId, "--before", String(value.before),
|
|
1269
|
+
"--after", String(value.after), ...read,
|
|
1270
|
+
], false);
|
|
1271
|
+
}
|
|
1272
|
+
if (action === "messaging.send") {
|
|
1273
|
+
const value = input as SendInput;
|
|
1274
|
+
const base = ["send", value.kind, "--to", value.conversationId];
|
|
1275
|
+
if (value.kind === "text") base.push("--message", value.text!);
|
|
1276
|
+
else {
|
|
1277
|
+
if (resolvedFilePath === undefined || !isAbsolute(resolvedFilePath)) {
|
|
1278
|
+
throw new Error(`${action} requires one resolved absolute plan-bound file`);
|
|
1279
|
+
}
|
|
1280
|
+
base.push("--file", resolvedFilePath);
|
|
1281
|
+
if (value.kind === "file" && value.text !== null) base.push("--caption", value.text);
|
|
1282
|
+
if (value.filename !== null) base.push("--filename", value.filename);
|
|
1283
|
+
if (value.mimeType !== null) base.push("--mime", value.mimeType);
|
|
1284
|
+
if (value.durationSeconds !== null) base.push("--duration", String(value.durationSeconds));
|
|
1285
|
+
}
|
|
1286
|
+
if (value.replyTo !== null) base.push("--reply-to", value.replyTo);
|
|
1287
|
+
for (const mention of value.mentions) base.push("--mention", mention);
|
|
1288
|
+
if (value.noPreview) base.push("--no-preview");
|
|
1289
|
+
base.push(...write);
|
|
1290
|
+
return command(action, base, true);
|
|
1291
|
+
}
|
|
1292
|
+
if (action === "reactions.set") {
|
|
1293
|
+
const value = input as ReactionInput;
|
|
1294
|
+
return command(action, [
|
|
1295
|
+
"send", value.enabled ? "react" : "unreact",
|
|
1296
|
+
"--to", value.conversationId, "--id", value.messageId,
|
|
1297
|
+
"--reaction", value.reaction, ...write,
|
|
1298
|
+
], true);
|
|
1299
|
+
}
|
|
1300
|
+
if (action === "messaging.edit") {
|
|
1301
|
+
const value = input as EditInput;
|
|
1302
|
+
return command(action, [
|
|
1303
|
+
"messages", "edit", "--chat", value.conversationId,
|
|
1304
|
+
"--id", value.messageId, "--message", value.text, ...write,
|
|
1305
|
+
], true);
|
|
1306
|
+
}
|
|
1307
|
+
if (action === "conversations.start") {
|
|
1308
|
+
const value = input as StartInput;
|
|
1309
|
+
return command(action, [
|
|
1310
|
+
"chats", "start", value.userId, "--account", value.accountId,
|
|
1311
|
+
...write,
|
|
1312
|
+
], true);
|
|
1313
|
+
}
|
|
1314
|
+
const conversation = input as ConversationInput;
|
|
1315
|
+
if (action === "conversations.archive.set" || action === "conversations.pin.set" || action === "conversations.mute.set") {
|
|
1316
|
+
const verb = action === "conversations.archive.set"
|
|
1317
|
+
? "archive"
|
|
1318
|
+
: action === "conversations.pin.set" ? "pin" : "mute";
|
|
1319
|
+
const enabled = (input as BooleanStateInput).enabled;
|
|
1320
|
+
return command(action, [
|
|
1321
|
+
"chats", enabled ? verb : `un${verb}`,
|
|
1322
|
+
"--chat", conversation.conversationId, ...write,
|
|
1323
|
+
], true);
|
|
1324
|
+
}
|
|
1325
|
+
if (action === "conversations.read-state.set") {
|
|
1326
|
+
const value = input as ReadStateInput;
|
|
1327
|
+
return command(action, [
|
|
1328
|
+
"chats", value.unread ? "mark-unread" : "mark-read",
|
|
1329
|
+
"--chat", value.conversationId,
|
|
1330
|
+
...(value.messageId === null ? [] : ["--message", value.messageId]), ...write,
|
|
1331
|
+
], true);
|
|
1332
|
+
}
|
|
1333
|
+
if (action === "conversations.priority.set") {
|
|
1334
|
+
const value = input as PriorityInput;
|
|
1335
|
+
return command(action, [
|
|
1336
|
+
"chats", "priority", "--chat", value.conversationId,
|
|
1337
|
+
"--level", value.level, ...write,
|
|
1338
|
+
], true);
|
|
1339
|
+
}
|
|
1340
|
+
if (action === "conversations.notify") {
|
|
1341
|
+
return command(action, [
|
|
1342
|
+
"chats", "notify-anyway", "--chat", conversation.conversationId, ...write,
|
|
1343
|
+
], true);
|
|
1344
|
+
}
|
|
1345
|
+
if (action === "conversations.title.set") {
|
|
1346
|
+
return command(action, [
|
|
1347
|
+
"chats", "rename", "--chat", conversation.conversationId,
|
|
1348
|
+
"--title", (input as TextStateInput).value!, ...write,
|
|
1349
|
+
], true);
|
|
1350
|
+
}
|
|
1351
|
+
if (action === "conversations.description.set") {
|
|
1352
|
+
const value = input as TextStateInput;
|
|
1353
|
+
return command(action, [
|
|
1354
|
+
"chats", "description", "--chat", value.conversationId,
|
|
1355
|
+
...(value.value === null ? ["--clear"] : ["--description", value.value]),
|
|
1356
|
+
...write,
|
|
1357
|
+
], true);
|
|
1358
|
+
}
|
|
1359
|
+
if (action === "conversations.avatar.set") {
|
|
1360
|
+
const value = input as FileStateInput;
|
|
1361
|
+
if (value.file !== null && (resolvedFilePath === undefined || !isAbsolute(resolvedFilePath))) {
|
|
1362
|
+
throw new Error(`${action} requires one resolved absolute plan-bound file`);
|
|
1363
|
+
}
|
|
1364
|
+
return command(action, [
|
|
1365
|
+
"chats", "avatar", "--chat", value.conversationId,
|
|
1366
|
+
...(value.file === null ? ["--clear"] : ["--file", resolvedFilePath!]),
|
|
1367
|
+
...write,
|
|
1368
|
+
], true);
|
|
1369
|
+
}
|
|
1370
|
+
if (action === "conversations.draft.set") {
|
|
1371
|
+
const value = input as DraftInput;
|
|
1372
|
+
if (value.file !== null && (resolvedFilePath === undefined || !isAbsolute(resolvedFilePath))) {
|
|
1373
|
+
throw new Error(`${action} requires one resolved absolute plan-bound file`);
|
|
1374
|
+
}
|
|
1375
|
+
return command(action, [
|
|
1376
|
+
"chats", "draft", "--chat", value.conversationId,
|
|
1377
|
+
...(value.clear ? ["--clear"] : []),
|
|
1378
|
+
...(value.text === null ? [] : ["--text", value.text]),
|
|
1379
|
+
...(value.file === null ? [] : ["--file", resolvedFilePath!]),
|
|
1380
|
+
...(value.filename === null ? [] : ["--filename", value.filename]),
|
|
1381
|
+
...(value.mimeType === null ? [] : ["--mime", value.mimeType]), ...write,
|
|
1382
|
+
], true);
|
|
1383
|
+
}
|
|
1384
|
+
if (action === "conversations.disappearing.set") {
|
|
1385
|
+
const value = input as DisappearingInput;
|
|
1386
|
+
return command(action, [
|
|
1387
|
+
"chats", "disappear", "--chat", value.conversationId,
|
|
1388
|
+
"--seconds", value.seconds === 0 ? "off" : String(value.seconds), ...write,
|
|
1389
|
+
], true);
|
|
1390
|
+
}
|
|
1391
|
+
if (action === "conversations.reminder.set") {
|
|
1392
|
+
const value = input as ReminderInput;
|
|
1393
|
+
return command(action, [
|
|
1394
|
+
"chats", value.when === null ? "unremind" : "remind",
|
|
1395
|
+
"--chat", value.conversationId,
|
|
1396
|
+
...(value.when === null ? [] : ["--when", value.when]),
|
|
1397
|
+
...(value.dismissOnMessage ? ["--dismiss-on-message"] : []), ...write,
|
|
1398
|
+
], true);
|
|
1399
|
+
}
|
|
1400
|
+
if (action === "conversations.focus") {
|
|
1401
|
+
const value = input as FocusInput;
|
|
1402
|
+
if (value.attachment !== null && (resolvedFilePath === undefined || !isAbsolute(resolvedFilePath))) {
|
|
1403
|
+
throw new Error(`${action} requires one resolved absolute plan-bound file`);
|
|
1404
|
+
}
|
|
1405
|
+
return command(action, [
|
|
1406
|
+
"chats", "focus", "--chat", value.conversationId,
|
|
1407
|
+
...(value.messageId === null ? [] : ["--message", value.messageId]),
|
|
1408
|
+
...(value.draft === null ? [] : ["--draft", value.draft]),
|
|
1409
|
+
...(value.attachment === null ? [] : ["--attachment", resolvedFilePath!]),
|
|
1410
|
+
...write,
|
|
1411
|
+
], true);
|
|
1412
|
+
}
|
|
1413
|
+
const value = input as PresenceInput;
|
|
1414
|
+
return command(action, [
|
|
1415
|
+
"presence", "--chat", value.conversationId, "--state", value.state,
|
|
1416
|
+
...write,
|
|
1417
|
+
], true);
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
/**
|
|
1421
|
+
* A duration is two explicit provider dispatches. Never pass upstream
|
|
1422
|
+
* `--duration`, which would hide the paused post inside one child process.
|
|
1423
|
+
*/
|
|
1424
|
+
export function planBeeperPresenceCommands(
|
|
1425
|
+
input: BeeperOperationInput,
|
|
1426
|
+
timeoutMs: number,
|
|
1427
|
+
): readonly BeeperCommand[] {
|
|
1428
|
+
const value = input as PresenceInput;
|
|
1429
|
+
const first = planBeeperOperationCommand("presence.set", value, timeoutMs);
|
|
1430
|
+
if (value.durationSeconds === null) return Object.freeze([first]);
|
|
1431
|
+
const paused = Object.freeze({ ...value, state: "paused" as const, durationSeconds: null });
|
|
1432
|
+
return Object.freeze([
|
|
1433
|
+
first,
|
|
1434
|
+
planBeeperOperationCommand("presence.set", paused, timeoutMs),
|
|
1435
|
+
]);
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
export function planBeeperReadCommand(
|
|
1439
|
+
action: BeeperLocalOperationName,
|
|
1440
|
+
input: BeeperOperationInput,
|
|
1441
|
+
timeoutMs: number,
|
|
1442
|
+
): BeeperReadCommand {
|
|
1443
|
+
const planned = planBeeperOperationCommand(action, input, timeoutMs);
|
|
1444
|
+
if (planned.mutation) throw new Error(`${action} is not a Beeper read operation`);
|
|
1445
|
+
return planned;
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
export function planBeeperMessageLikeMeExportCommand(
|
|
1449
|
+
options: BeeperMessageLikeMeExportCommandOptions,
|
|
1450
|
+
timeoutMs: number,
|
|
1451
|
+
): readonly string[] {
|
|
1452
|
+
if (!isAbsolute(options.outputDirectory)) throw new Error("Beeper export output directory must be absolute");
|
|
1453
|
+
const maxParticipants = integer(options.maxParticipants, "Beeper export maxParticipants", 1, 2_000);
|
|
1454
|
+
const limitChats = integer(options.limitChats, "Beeper export limitChats", 1, 100_000);
|
|
1455
|
+
const limitMessages = integer(options.limitMessages, "Beeper export limitMessages", 1, 1_000_000);
|
|
1456
|
+
const timeoutSeconds = Math.max(1, Math.min(6 * 60 * 60, Math.ceil(timeoutMs / 1_000)));
|
|
1457
|
+
return Object.freeze([
|
|
1458
|
+
"export", "--out", options.outputDirectory, "--no-attachments",
|
|
1459
|
+
"--max-participants", String(maxParticipants), "--limit-chats", String(limitChats),
|
|
1460
|
+
"--limit-messages", String(limitMessages), "--read-only", "--quiet",
|
|
1461
|
+
"--target", BEEPER_DESKTOP_TARGET, "--timeout", `${timeoutSeconds}s`,
|
|
1462
|
+
]);
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
export function isBeeperLocalOperation(value: string): value is BeeperLocalOperationName {
|
|
1466
|
+
return BEEPER_LOCAL_OPERATION_NAMES.includes(value as BeeperLocalOperationName);
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
export type BeeperCliCoverage =
|
|
1470
|
+
| Readonly<{ state: "supported"; operation: BeeperLocalOperationName }>
|
|
1471
|
+
| Readonly<{
|
|
1472
|
+
state: "internal-preflight";
|
|
1473
|
+
purpose: "desktop-target-realm-proof" | "pinned-tool-version-proof";
|
|
1474
|
+
}>
|
|
1475
|
+
| Readonly<{
|
|
1476
|
+
state: "unavailable";
|
|
1477
|
+
reason:
|
|
1478
|
+
| "installation-lifecycle"
|
|
1479
|
+
| "target-lifecycle"
|
|
1480
|
+
| "authentication-and-verification"
|
|
1481
|
+
| "account-lifecycle-r4"
|
|
1482
|
+
| "destructive-message-deletion-r4"
|
|
1483
|
+
| "caller-path-media-or-export"
|
|
1484
|
+
| "unbounded-event-stream"
|
|
1485
|
+
| "raw-api-or-rpc"
|
|
1486
|
+
| "cli-extension-or-configuration"
|
|
1487
|
+
| "cli-maintenance-or-documentation";
|
|
1488
|
+
}>;
|
|
1489
|
+
|
|
1490
|
+
const supported = (operation: BeeperLocalOperationName): BeeperCliCoverage =>
|
|
1491
|
+
Object.freeze({ state: "supported", operation });
|
|
1492
|
+
const internalPreflight = (
|
|
1493
|
+
purpose: Extract<BeeperCliCoverage, { state: "internal-preflight" }>[
|
|
1494
|
+
"purpose"
|
|
1495
|
+
],
|
|
1496
|
+
): BeeperCliCoverage => Object.freeze({
|
|
1497
|
+
state: "internal-preflight",
|
|
1498
|
+
purpose,
|
|
1499
|
+
});
|
|
1500
|
+
const unavailable = (
|
|
1501
|
+
reason: Extract<BeeperCliCoverage, { state: "unavailable" }>["reason"],
|
|
1502
|
+
): BeeperCliCoverage => Object.freeze({ state: "unavailable", reason });
|
|
1503
|
+
|
|
1504
|
+
/** Exact command inventory emitted by official Beeper CLI v0.6.2 `man --json`. */
|
|
1505
|
+
export const BEEPER_CLI_COMMAND_COVERAGE = Object.freeze({
|
|
1506
|
+
"setup": unavailable("installation-lifecycle"),
|
|
1507
|
+
"install desktop": unavailable("installation-lifecycle"),
|
|
1508
|
+
"install server": unavailable("installation-lifecycle"),
|
|
1509
|
+
"targets list": unavailable("target-lifecycle"),
|
|
1510
|
+
"bridges list": supported("bridges.list"),
|
|
1511
|
+
"bridges show": supported("bridges.read"),
|
|
1512
|
+
"targets add desktop": unavailable("target-lifecycle"),
|
|
1513
|
+
"targets add server": unavailable("target-lifecycle"),
|
|
1514
|
+
"targets add remote": unavailable("target-lifecycle"),
|
|
1515
|
+
"targets use": unavailable("target-lifecycle"),
|
|
1516
|
+
"targets show": unavailable("target-lifecycle"),
|
|
1517
|
+
"targets status": internalPreflight("desktop-target-realm-proof"),
|
|
1518
|
+
"targets start": unavailable("target-lifecycle"),
|
|
1519
|
+
"targets stop": unavailable("target-lifecycle"),
|
|
1520
|
+
"targets restart": unavailable("target-lifecycle"),
|
|
1521
|
+
"targets logs": unavailable("target-lifecycle"),
|
|
1522
|
+
"targets enable": unavailable("target-lifecycle"),
|
|
1523
|
+
"targets disable": unavailable("target-lifecycle"),
|
|
1524
|
+
"targets remove": unavailable("target-lifecycle"),
|
|
1525
|
+
"targets tunnel": unavailable("target-lifecycle"),
|
|
1526
|
+
"auth status": unavailable("authentication-and-verification"),
|
|
1527
|
+
"auth logout": unavailable("authentication-and-verification"),
|
|
1528
|
+
"auth email start": unavailable("authentication-and-verification"),
|
|
1529
|
+
"auth email response": unavailable("authentication-and-verification"),
|
|
1530
|
+
"verify": unavailable("authentication-and-verification"),
|
|
1531
|
+
"verify status": unavailable("authentication-and-verification"),
|
|
1532
|
+
"verify approve": unavailable("authentication-and-verification"),
|
|
1533
|
+
"verify recovery-key": unavailable("authentication-and-verification"),
|
|
1534
|
+
"verify reset-recovery-key": unavailable("authentication-and-verification"),
|
|
1535
|
+
"verify cancel": unavailable("authentication-and-verification"),
|
|
1536
|
+
"verify list": unavailable("authentication-and-verification"),
|
|
1537
|
+
"verify start": unavailable("authentication-and-verification"),
|
|
1538
|
+
"verify show": unavailable("authentication-and-verification"),
|
|
1539
|
+
"verify sas": unavailable("authentication-and-verification"),
|
|
1540
|
+
"verify sas-confirm": unavailable("authentication-and-verification"),
|
|
1541
|
+
"verify qr-scan": unavailable("authentication-and-verification"),
|
|
1542
|
+
"verify qr-confirm": unavailable("authentication-and-verification"),
|
|
1543
|
+
"accounts list": supported("accounts.list"),
|
|
1544
|
+
"accounts add": unavailable("account-lifecycle-r4"),
|
|
1545
|
+
"accounts show": supported("accounts.read"),
|
|
1546
|
+
"accounts remove": unavailable("account-lifecycle-r4"),
|
|
1547
|
+
"accounts use": unavailable("account-lifecycle-r4"),
|
|
1548
|
+
"chats list": supported("messaging.list"),
|
|
1549
|
+
"chats search": supported("messaging.search"),
|
|
1550
|
+
"chats show": supported("conversations.read"),
|
|
1551
|
+
"chats start": supported("conversations.start"),
|
|
1552
|
+
"chats archive": supported("conversations.archive.set"),
|
|
1553
|
+
"chats unarchive": supported("conversations.archive.set"),
|
|
1554
|
+
"chats pin": supported("conversations.pin.set"),
|
|
1555
|
+
"chats unpin": supported("conversations.pin.set"),
|
|
1556
|
+
"chats mute": supported("conversations.mute.set"),
|
|
1557
|
+
"chats unmute": supported("conversations.mute.set"),
|
|
1558
|
+
"chats mark-read": supported("conversations.read-state.set"),
|
|
1559
|
+
"chats mark-unread": supported("conversations.read-state.set"),
|
|
1560
|
+
"chats priority": supported("conversations.priority.set"),
|
|
1561
|
+
"chats notify-anyway": supported("conversations.notify"),
|
|
1562
|
+
"chats rename": supported("conversations.title.set"),
|
|
1563
|
+
"chats description": supported("conversations.description.set"),
|
|
1564
|
+
"chats avatar": supported("conversations.avatar.set"),
|
|
1565
|
+
"chats draft": supported("conversations.draft.set"),
|
|
1566
|
+
"chats disappear": supported("conversations.disappearing.set"),
|
|
1567
|
+
"chats remind": supported("conversations.reminder.set"),
|
|
1568
|
+
"chats unremind": supported("conversations.reminder.set"),
|
|
1569
|
+
"chats focus": supported("conversations.focus"),
|
|
1570
|
+
"messages list": supported("messaging.read"),
|
|
1571
|
+
"messages search": supported("messaging.content.search"),
|
|
1572
|
+
"messages show": supported("messaging.message.read"),
|
|
1573
|
+
"messages context": supported("messaging.context.read"),
|
|
1574
|
+
"messages edit": supported("messaging.edit"),
|
|
1575
|
+
"messages delete": unavailable("destructive-message-deletion-r4"),
|
|
1576
|
+
"messages export": unavailable("caller-path-media-or-export"),
|
|
1577
|
+
"send text": supported("messaging.send"),
|
|
1578
|
+
"send file": supported("messaging.send"),
|
|
1579
|
+
"send react": supported("reactions.set"),
|
|
1580
|
+
"send sticker": supported("messaging.send"),
|
|
1581
|
+
"send unreact": supported("reactions.set"),
|
|
1582
|
+
"send voice": supported("messaging.send"),
|
|
1583
|
+
"presence": supported("presence.set"),
|
|
1584
|
+
"contacts list": supported("contacts.list"),
|
|
1585
|
+
"contacts search": supported("contacts.search"),
|
|
1586
|
+
"contacts show": supported("contacts.read"),
|
|
1587
|
+
"media download": unavailable("caller-path-media-or-export"),
|
|
1588
|
+
"export": unavailable("caller-path-media-or-export"),
|
|
1589
|
+
"watch": unavailable("unbounded-event-stream"),
|
|
1590
|
+
"rpc": unavailable("raw-api-or-rpc"),
|
|
1591
|
+
"man": unavailable("cli-maintenance-or-documentation"),
|
|
1592
|
+
"doctor": unavailable("target-lifecycle"),
|
|
1593
|
+
"status": unavailable("target-lifecycle"),
|
|
1594
|
+
"docs": unavailable("cli-maintenance-or-documentation"),
|
|
1595
|
+
"version": internalPreflight("pinned-tool-version-proof"),
|
|
1596
|
+
"completion": unavailable("cli-maintenance-or-documentation"),
|
|
1597
|
+
"plugins": unavailable("cli-extension-or-configuration"),
|
|
1598
|
+
"plugins available": unavailable("cli-extension-or-configuration"),
|
|
1599
|
+
"update": unavailable("cli-extension-or-configuration"),
|
|
1600
|
+
"config get": unavailable("cli-extension-or-configuration"),
|
|
1601
|
+
"config set": unavailable("cli-extension-or-configuration"),
|
|
1602
|
+
"config path": unavailable("cli-extension-or-configuration"),
|
|
1603
|
+
"config reset": unavailable("cli-extension-or-configuration"),
|
|
1604
|
+
"api get": unavailable("raw-api-or-rpc"),
|
|
1605
|
+
"api post": unavailable("raw-api-or-rpc"),
|
|
1606
|
+
"api request": unavailable("raw-api-or-rpc"),
|
|
1607
|
+
} satisfies Readonly<Record<string, BeeperCliCoverage>>);
|
|
1608
|
+
|
|
1609
|
+
/** SHA-256 of JSON.stringify(the exact ordered v0.6.2 `man --json` command list). */
|
|
1610
|
+
export const BEEPER_CLI_COMMAND_LIST_SHA256 =
|
|
1611
|
+
"5f86df11801a7d288e617000b591f6803fb5b9ba6aa0ba122913105b59041187" as const;
|
|
1612
|
+
|
|
1613
|
+
type BeeperCliV062ArgumentProfile = readonly [
|
|
1614
|
+
name: string,
|
|
1615
|
+
required: boolean,
|
|
1616
|
+
enumValues?: readonly string[],
|
|
1617
|
+
];
|
|
1618
|
+
|
|
1619
|
+
type BeeperCliV062FlagProfile = readonly [
|
|
1620
|
+
name: string,
|
|
1621
|
+
aliases: readonly string[],
|
|
1622
|
+
valueType: "string" | "number" | "boolean",
|
|
1623
|
+
required: boolean,
|
|
1624
|
+
multiple: boolean,
|
|
1625
|
+
enumValues: readonly string[],
|
|
1626
|
+
defaultValue: string | number | boolean | null,
|
|
1627
|
+
];
|
|
1628
|
+
|
|
1629
|
+
type BeeperCliV062CommandProfile = readonly [
|
|
1630
|
+
command: string,
|
|
1631
|
+
mutates: boolean,
|
|
1632
|
+
output: "data" | "list" | "manual" | "send-result" | "stream" | "success",
|
|
1633
|
+
arguments: readonly BeeperCliV062ArgumentProfile[],
|
|
1634
|
+
flags: readonly BeeperCliV062FlagProfile[],
|
|
1635
|
+
];
|
|
1636
|
+
|
|
1637
|
+
/**
|
|
1638
|
+
* Exact normalized arguments, command flags, defaults, mutation bit, and
|
|
1639
|
+
* output category in the 101 entries of the official v0.6.2 generated manual.
|
|
1640
|
+
* Global flags are represented once in BEEPER_CLI_V062_GLOBAL_FLAGS below.
|
|
1641
|
+
*/
|
|
1642
|
+
const BEEPER_CLI_V062_COMMAND_PROFILES = Object.freeze([
|
|
1643
|
+
["setup",true,"success",[],[["--channel",[],"string",false,false,["stable","nightly"],"stable"],["--desktop",[],"boolean",false,false,[],null],["--email",[],"string",false,false,[],null],["--install",[],"boolean",false,false,[],null],["--local",[],"boolean",false,false,[],null],["--oauth",[],"boolean",false,false,[],null],["--remote",[],"string",false,false,[],null],["--server",[],"boolean",false,false,[],null],["--server-env",[],"string",false,false,["production","staging"],"production"],["--username",[],"string",false,false,[],null]]],
|
|
1644
|
+
["install desktop",true,"success",[],[["--channel",[],"string",false,false,["stable","nightly"],"stable"]]],
|
|
1645
|
+
["install server",true,"success",[],[["--channel",[],"string",false,false,["stable","nightly"],"stable"],["--server-env",[],"string",false,false,["production","staging"],"production"]]],
|
|
1646
|
+
["targets list",false,"list",[],[]],
|
|
1647
|
+
["bridges list",false,"list",[],[["--available",[],"boolean",false,false,[],null],["--provider",[],"string",false,false,["local","cloud","self-hosted"],null]]],
|
|
1648
|
+
["bridges show",false,"data",[["bridge",true]],[]],
|
|
1649
|
+
["targets add desktop",true,"success",[["name",false]],[["--default",[],"boolean",false,false,[],null],["--port",[],"number",false,false,[],null],["--server-env",[],"string",false,false,["production","staging"],"production"]]],
|
|
1650
|
+
["targets add server",true,"success",[["name",false]],[["--default",[],"boolean",false,false,[],null],["--port",[],"number",false,false,[],null],["--server-env",[],"string",false,false,["production","staging"],"production"]]],
|
|
1651
|
+
["targets add remote",true,"success",[["name",true],["url",true]],[["--default",[],"boolean",false,false,[],null]]],
|
|
1652
|
+
["targets use",true,"success",[["name",true]],[]],
|
|
1653
|
+
["targets show",false,"data",[["name",false]],[]],
|
|
1654
|
+
["targets status",false,"data",[["name",false]],[]],
|
|
1655
|
+
["targets start",true,"success",[["name",false]],[]],
|
|
1656
|
+
["targets stop",true,"success",[["name",false]],[]],
|
|
1657
|
+
["targets restart",true,"success",[["name",false]],[]],
|
|
1658
|
+
["targets logs",false,"data",[["name",false]],[["--all",[],"boolean",false,false,[],null],["--files",[],"number",false,false,[],5],["--lines",[],"number",false,false,[],200]]],
|
|
1659
|
+
["targets enable",true,"success",[["name",false]],[]],
|
|
1660
|
+
["targets disable",true,"success",[["name",false]],[]],
|
|
1661
|
+
["targets remove",true,"success",[["name",true]],[]],
|
|
1662
|
+
["targets tunnel",false,"data",[["name",false]],[["--install",[],"boolean",false,false,[],false],["--cloudflared-path",[],"string",false,false,[],null],["--retries",[],"number",false,false,[],5],["--url-only",[],"boolean",false,false,[],false]]],
|
|
1663
|
+
["auth status",false,"data",[],[]],
|
|
1664
|
+
["auth logout",true,"success",[],[]],
|
|
1665
|
+
["auth email start",true,"success",[],[["--email",[],"string",true,false,[],null]]],
|
|
1666
|
+
["auth email response",false,"data",[],[["--code",[],"string",true,false,[],null],["--setup-request-id",[],"string",true,false,[],null],["--username",[],"string",false,false,[],null],["--yes",[],"boolean",false,false,[],false]]],
|
|
1667
|
+
["verify",false,"data",[],[["--user",[],"string",false,false,[],null]]],
|
|
1668
|
+
["verify status",false,"data",[],[]],
|
|
1669
|
+
["verify approve",true,"success",[],[["--id",[],"string",false,false,[],null]]],
|
|
1670
|
+
["verify recovery-key",true,"success",[],[["--key",[],"string",true,false,[],null]]],
|
|
1671
|
+
["verify reset-recovery-key",true,"success",[],[]],
|
|
1672
|
+
["verify cancel",true,"success",[],[["--id",[],"string",false,false,[],null]]],
|
|
1673
|
+
["verify list",false,"list",[],[]],
|
|
1674
|
+
["verify start",true,"success",[],[["--user",[],"string",false,false,[],null]]],
|
|
1675
|
+
["verify show",false,"data",[],[]],
|
|
1676
|
+
["verify sas",true,"success",[],[["--id",[],"string",false,false,[],null]]],
|
|
1677
|
+
["verify sas-confirm",true,"success",[],[["--id",[],"string",false,false,[],null]]],
|
|
1678
|
+
["verify qr-scan",true,"success",[],[["--id",[],"string",false,false,[],null],["--payload",[],"string",true,false,[],null]]],
|
|
1679
|
+
["verify qr-confirm",true,"success",[],[["--id",[],"string",false,false,[],null]]],
|
|
1680
|
+
["accounts list",false,"list",[],[["--account",[],"string",false,true,[],null],["--ids",[],"boolean",false,false,[],null]]],
|
|
1681
|
+
["accounts add",true,"success",[["bridge",false]],[["--cookie",[],"string",false,true,[],null],["--field",[],"string",false,true,[],null],["--flow",[],"string",false,false,[],null],["--guided",[],"boolean",false,false,[],true],["--login-id",[],"string",false,false,[],null],["--non-interactive",[],"boolean",false,false,[],null],["--webview",[],"boolean",false,false,[],null],["--webview-backend",[],"string",false,false,["auto","chrome","webkit"],"chrome"],["--webview-timeout",[],"number",false,false,[],120]]],
|
|
1682
|
+
["accounts show",false,"data",[["account",true]],[]],
|
|
1683
|
+
["accounts remove",true,"success",[["account",true]],[]],
|
|
1684
|
+
["accounts use",true,"success",[["account",true]],[]],
|
|
1685
|
+
["chats list",false,"list",[],[["--account",[],"string",false,true,[],null],["--archived",[],"boolean",false,false,[],null],["--ids",[],"boolean",false,false,[],null],["--limit",[],"number",false,false,[],20],["--low-priority",[],"boolean",false,false,[],null],["--muted",[],"boolean",false,false,[],null],["--pinned",[],"boolean",false,false,[],null],["--unread",[],"boolean",false,false,[],null]]],
|
|
1686
|
+
["chats search",false,"list",[["query",true]],[["--account",[],"string",false,true,[],null],["--ids",[],"boolean",false,false,[],null],["--limit",[],"number",false,false,[],20]]],
|
|
1687
|
+
["chats show",false,"data",[],[["--chat",[],"string",true,false,[],null],["--max-participants",[],"number",false,false,[],null],["--pick",[],"number",false,false,[],null]]],
|
|
1688
|
+
["chats start",true,"success",[["user",true]],[["--account",[],"string",false,false,[],null],["--title",[],"string",false,false,[],null]]],
|
|
1689
|
+
["chats archive",true,"success",[],[["--chat",[],"string",true,false,[],null],["--pick",[],"number",false,false,[],null]]],
|
|
1690
|
+
["chats unarchive",true,"success",[],[["--chat",[],"string",true,false,[],null],["--pick",[],"number",false,false,[],null]]],
|
|
1691
|
+
["chats pin",true,"success",[],[["--chat",[],"string",true,false,[],null],["--pick",[],"number",false,false,[],null]]],
|
|
1692
|
+
["chats unpin",true,"success",[],[["--chat",[],"string",true,false,[],null],["--pick",[],"number",false,false,[],null]]],
|
|
1693
|
+
["chats mute",true,"success",[],[["--chat",[],"string",true,false,[],null],["--pick",[],"number",false,false,[],null]]],
|
|
1694
|
+
["chats unmute",true,"success",[],[["--chat",[],"string",true,false,[],null],["--pick",[],"number",false,false,[],null]]],
|
|
1695
|
+
["chats mark-read",true,"success",[],[["--chat",[],"string",true,false,[],null],["--message",[],"string",false,false,[],null],["--pick",[],"number",false,false,[],null]]],
|
|
1696
|
+
["chats mark-unread",true,"success",[],[["--chat",[],"string",true,false,[],null],["--message",[],"string",false,false,[],null],["--pick",[],"number",false,false,[],null]]],
|
|
1697
|
+
["chats priority",true,"success",[],[["--chat",[],"string",true,false,[],null],["--level",[],"string",true,false,["inbox","low"],null],["--pick",[],"number",false,false,[],null]]],
|
|
1698
|
+
["chats notify-anyway",true,"success",[],[["--chat",[],"string",true,false,[],null],["--pick",[],"number",false,false,[],null]]],
|
|
1699
|
+
["chats rename",true,"success",[],[["--chat",[],"string",true,false,[],null],["--pick",[],"number",false,false,[],null],["--title",[],"string",true,false,[],null]]],
|
|
1700
|
+
["chats description",true,"success",[],[["--chat",[],"string",true,false,[],null],["--clear",[],"boolean",false,false,[],null],["--description",[],"string",false,false,[],null],["--pick",[],"number",false,false,[],null]]],
|
|
1701
|
+
["chats avatar",true,"success",[],[["--chat",[],"string",true,false,[],null],["--clear",[],"boolean",false,false,[],null],["--file",[],"string",false,false,[],null],["--pick",[],"number",false,false,[],null]]],
|
|
1702
|
+
["chats draft",true,"success",[],[["--chat",[],"string",true,false,[],null],["--clear",[],"boolean",false,false,[],null],["--file",[],"string",false,false,[],null],["--filename",[],"string",false,false,[],null],["--mime",[],"string",false,false,[],null],["--pick",[],"number",false,false,[],null],["--text",[],"string",false,false,[],null]]],
|
|
1703
|
+
["chats disappear",true,"success",[],[["--chat",[],"string",true,false,[],null],["--pick",[],"number",false,false,[],null],["--seconds",[],"string",true,false,[],null]]],
|
|
1704
|
+
["chats remind",true,"success",[],[["--chat",[],"string",true,false,[],null],["--dismiss-on-message",[],"boolean",false,false,[],null],["--pick",[],"number",false,false,[],null],["--when",[],"string",true,false,[],null]]],
|
|
1705
|
+
["chats unremind",true,"success",[],[["--chat",[],"string",true,false,[],null],["--pick",[],"number",false,false,[],null]]],
|
|
1706
|
+
["chats focus",true,"success",[],[["--attachment",[],"string",false,false,[],null],["--chat",[],"string",true,false,[],null],["--draft",[],"string",false,false,[],null],["--message",[],"string",false,false,[],null],["--pick",[],"number",false,false,[],null]]],
|
|
1707
|
+
["messages list",false,"list",[],[["--after-cursor",[],"string",false,false,[],null],["--asc",[],"boolean",false,false,[],null],["--before-cursor",[],"string",false,false,[],null],["--chat",[],"string",true,false,[],null],["--ids",[],"boolean",false,false,[],null],["--limit",[],"number",false,false,[],50],["--pick",[],"number",false,false,[],null],["--sender",[],"string",false,false,[],null]]],
|
|
1708
|
+
["messages search",false,"list",[["query",false]],[["--account",[],"string",false,true,[],null],["--after",[],"string",false,false,[],null],["--before",[],"string",false,false,[],null],["--chat",[],"string",false,true,[],null],["--chat-type",[],"string",false,false,["group","single"],null],["--exclude-low-priority",[],"boolean",false,false,[],true],["--ids",[],"boolean",false,false,[],null],["--include-muted",[],"boolean",false,false,[],true],["--limit",[],"number",false,false,[],50],["--media",[],"string",false,true,["any","video","image","link","file"],null],["--sender",[],"string",false,false,[],null]]],
|
|
1709
|
+
["messages show",false,"data",[],[["--chat",[],"string",true,false,[],null],["--id",[],"string",true,false,[],null],["--pick",[],"number",false,false,[],null]]],
|
|
1710
|
+
["messages context",false,"data",[],[["--after",[],"number",false,false,[],10],["--before",[],"number",false,false,[],10],["--chat",[],"string",true,false,[],null],["--id",[],"string",true,false,[],null],["--pick",[],"number",false,false,[],null]]],
|
|
1711
|
+
["messages edit",true,"success",[],[["--chat",[],"string",true,false,[],null],["--id",[],"string",true,false,[],null],["--message",[],"string",true,false,[],null],["--pick",[],"number",false,false,[],null]]],
|
|
1712
|
+
["messages delete",true,"success",[],[["--chat",[],"string",true,false,[],null],["--for-everyone",[],"boolean",false,false,[],null],["--id",[],"string",true,false,[],null],["--pick",[],"number",false,false,[],null]]],
|
|
1713
|
+
["messages export",false,"data",[],[["--after",[],"string",false,false,[],null],["--after-cursor",[],"string",false,false,[],null],["--asc",[],"boolean",false,false,[],null],["--before",[],"string",false,false,[],null],["--before-cursor",[],"string",false,false,[],null],["--chat",[],"string",true,false,[],null],["--limit",[],"number",false,false,[],null],["--output",["-o"],"string",false,false,[],"-"],["--pick",[],"number",false,false,[],null]]],
|
|
1714
|
+
["send text",true,"send-result",[],[["--mention",[],"string",false,true,[],null],["--message",[],"string",true,false,[],null],["--no-preview",[],"boolean",false,false,[],null],["--pick",[],"number",false,false,[],null],["--reply-to",[],"string",false,false,[],null],["--to",[],"string",true,false,[],null],["--wait",[],"boolean",false,false,[],null],["--wait-timeout",[],"number",false,false,[],30000]]],
|
|
1715
|
+
["send file",true,"send-result",[],[["--caption",[],"string",false,false,[],null],["--file",[],"string",true,false,[],null],["--filename",[],"string",false,false,[],null],["--mime",[],"string",false,false,[],null],["--pick",[],"number",false,false,[],null],["--reply-to",[],"string",false,false,[],null],["--to",[],"string",true,false,[],null],["--wait",[],"boolean",false,false,[],null],["--wait-timeout",[],"number",false,false,[],30000]]],
|
|
1716
|
+
["send react",true,"send-result",[],[["--id",[],"string",true,false,[],null],["--pick",[],"number",false,false,[],null],["--reaction",[],"string",true,false,[],null],["--to",[],"string",true,false,[],null],["--transaction",[],"string",false,false,[],null]]],
|
|
1717
|
+
["send sticker",true,"send-result",[],[["--file",[],"string",true,false,[],null],["--filename",[],"string",false,false,[],null],["--mime",[],"string",false,false,[],"image/webp"],["--pick",[],"number",false,false,[],null],["--reply-to",[],"string",false,false,[],null],["--to",[],"string",true,false,[],null],["--wait",[],"boolean",false,false,[],null],["--wait-timeout",[],"number",false,false,[],30000]]],
|
|
1718
|
+
["send unreact",true,"send-result",[],[["--id",[],"string",true,false,[],null],["--pick",[],"number",false,false,[],null],["--reaction",[],"string",true,false,[],null],["--to",[],"string",true,false,[],null],["--transaction",[],"string",false,false,[],null]]],
|
|
1719
|
+
["send voice",true,"send-result",[],[["--duration",[],"number",false,false,[],null],["--file",[],"string",true,false,[],null],["--filename",[],"string",false,false,[],null],["--mime",[],"string",false,false,[],"audio/ogg"],["--pick",[],"number",false,false,[],null],["--reply-to",[],"string",false,false,[],null],["--to",[],"string",true,false,[],null],["--wait",[],"boolean",false,false,[],null],["--wait-timeout",[],"number",false,false,[],30000]]],
|
|
1720
|
+
["presence",false,"data",[],[["--chat",[],"string",true,false,[],null],["--duration",[],"number",false,false,[],null],["--pick",[],"number",false,false,[],null],["--state",[],"string",false,false,["typing","paused"],"typing"]]],
|
|
1721
|
+
["contacts list",false,"list",[],[["--account",[],"string",false,true,[],null],["--ids",[],"boolean",false,false,[],null],["--limit",[],"number",false,false,[],50],["--query",[],"string",false,false,[],null]]],
|
|
1722
|
+
["contacts search",false,"list",[["query",true]],[["--account",[],"string",false,true,[],null]]],
|
|
1723
|
+
["contacts show",false,"data",[["id",true]],[["--account",[],"string",false,true,[],null]]],
|
|
1724
|
+
["media download",false,"data",[["url",true]],[["--out",["-o"],"string",false,false,[],"."]]],
|
|
1725
|
+
["export",false,"data",[],[["--account",[],"string",false,true,[],null],["--chat",[],"string",false,true,[],null],["--force",[],"boolean",false,false,[],null],["--limit-chats",[],"number",false,false,[],null],["--limit-messages",[],"number",false,false,[],null],["--max-participants",[],"number",false,false,[],500],["--no-attachments",[],"boolean",false,false,[],null],["--out",["-o"],"string",false,false,[],"beeper-export"],["--pick",[],"number",false,false,[],null],["--quiet",[],"boolean",false,false,[],false]]],
|
|
1726
|
+
["watch",false,"stream",[],[["--chat",["-c"],"string",false,true,[],null],["--exclude-type",[],"string",false,true,["chat.upserted","chat.deleted","message.upserted","message.deleted"],null],["--include-type",[],"string",false,true,["chat.upserted","chat.deleted","message.upserted","message.deleted"],null],["--json",[],"boolean",false,false,[],false],["--webhook",[],"string",false,false,[],null],["--webhook-queue",[],"number",false,false,[],64],["--webhook-secret",[],"string",false,false,[],null]]],
|
|
1727
|
+
["rpc",false,"stream",[],[]],
|
|
1728
|
+
["man",false,"manual",[],[]],
|
|
1729
|
+
["doctor",false,"data",[],[]],
|
|
1730
|
+
["status",false,"data",[],[]],
|
|
1731
|
+
["docs",false,"data",[],[]],
|
|
1732
|
+
["version",false,"data",[],[]],
|
|
1733
|
+
["completion",false,"data",[["shell",false]],[["--refresh-cache",["-r"],"boolean",false,false,[],null],["--semantic",[],"boolean",false,false,[],null]]],
|
|
1734
|
+
["plugins",false,"data",[],[]],
|
|
1735
|
+
["plugins available",false,"data",[],[]],
|
|
1736
|
+
["update",true,"success",[],[["--check",[],"boolean",false,false,[],null],["--cli",[],"boolean",false,false,[],null],["--desktop",[],"boolean",false,false,[],null],["--server",[],"boolean",false,false,[],null]]],
|
|
1737
|
+
["config get",false,"data",[["key",false,["baseURL","auth","defaultTarget","defaultAccount"]]],[]],
|
|
1738
|
+
["config set",true,"success",[["key",true,["defaultTarget","defaultAccount"]],["value",true]],[]],
|
|
1739
|
+
["config path",false,"data",[],[]],
|
|
1740
|
+
["config reset",true,"success",[],[]],
|
|
1741
|
+
["api get",false,"data",[["path",true]],[["--json",[],"boolean",false,false,[],true],["--no-auth",[],"boolean",false,false,[],false]]],
|
|
1742
|
+
["api post",false,"data",[["path",true]],[["--body",[],"string",false,false,[],"{}"],["--json",[],"boolean",false,false,[],true],["--no-auth",[],"boolean",false,false,[],false]]],
|
|
1743
|
+
["api request",false,"data",[["method",true,["GET","POST","PUT","PATCH","DELETE"]],["path",true]],[["--body",[],"string",false,false,[],null],["--json",[],"boolean",false,false,[],true],["--no-auth",[],"boolean",false,false,[],false]]],
|
|
1744
|
+
] as const satisfies readonly BeeperCliV062CommandProfile[]);
|
|
1745
|
+
|
|
1746
|
+
const BEEPER_CLI_V062_PRIVATE_COMMAND_PROFILE = Object.freeze([
|
|
1747
|
+
"_complete",
|
|
1748
|
+
false,
|
|
1749
|
+
"list",
|
|
1750
|
+
[["kind", true, ["chat", "account", "contact", "target"]]],
|
|
1751
|
+
[
|
|
1752
|
+
["--query", [], "string", false, false, [], null],
|
|
1753
|
+
["--target", [], "string", false, false, [], null],
|
|
1754
|
+
["--limit", [], "number", false, false, [], 25],
|
|
1755
|
+
["--timeout-ms", [], "number", false, false, [], 1_500],
|
|
1756
|
+
],
|
|
1757
|
+
] as const satisfies BeeperCliV062CommandProfile);
|
|
1758
|
+
|
|
1759
|
+
const commandFlagCoordinate = (command: string, flag: string): string =>
|
|
1760
|
+
`${command}\u0000${flag}`;
|
|
1761
|
+
|
|
1762
|
+
const BEEPER_CLI_V062_ALLOW_NO_FLAGS = new Set<string>([
|
|
1763
|
+
commandFlagCoordinate("accounts add", "--guided"),
|
|
1764
|
+
commandFlagCoordinate("api get", "--json"),
|
|
1765
|
+
commandFlagCoordinate("api post", "--json"),
|
|
1766
|
+
commandFlagCoordinate("api request", "--json"),
|
|
1767
|
+
commandFlagCoordinate("bridges list", "--available"),
|
|
1768
|
+
...["--archived", "--low-priority", "--muted", "--pinned", "--unread"]
|
|
1769
|
+
.map((flag) => commandFlagCoordinate("chats list", flag)),
|
|
1770
|
+
commandFlagCoordinate("messages search", "--exclude-low-priority"),
|
|
1771
|
+
commandFlagCoordinate("messages search", "--include-muted"),
|
|
1772
|
+
]);
|
|
1773
|
+
|
|
1774
|
+
const BEEPER_CLI_V062_EXPLICIT_FALSE_BOOLEAN_FLAGS = new Set<string>([
|
|
1775
|
+
...["--desktop", "--install", "--local", "--oauth", "--server"]
|
|
1776
|
+
.map((flag) => commandFlagCoordinate("setup", flag)),
|
|
1777
|
+
...["targets add desktop", "targets add remote", "targets add server"]
|
|
1778
|
+
.map((command) => commandFlagCoordinate(command, "--default")),
|
|
1779
|
+
commandFlagCoordinate("targets logs", "--all"),
|
|
1780
|
+
commandFlagCoordinate("accounts list", "--ids"),
|
|
1781
|
+
commandFlagCoordinate("accounts add", "--non-interactive"),
|
|
1782
|
+
commandFlagCoordinate("accounts add", "--webview"),
|
|
1783
|
+
commandFlagCoordinate("chats list", "--ids"),
|
|
1784
|
+
commandFlagCoordinate("chats search", "--ids"),
|
|
1785
|
+
commandFlagCoordinate("chats description", "--clear"),
|
|
1786
|
+
commandFlagCoordinate("chats avatar", "--clear"),
|
|
1787
|
+
commandFlagCoordinate("chats draft", "--clear"),
|
|
1788
|
+
commandFlagCoordinate("chats remind", "--dismiss-on-message"),
|
|
1789
|
+
commandFlagCoordinate("messages list", "--asc"),
|
|
1790
|
+
commandFlagCoordinate("messages list", "--ids"),
|
|
1791
|
+
commandFlagCoordinate("messages search", "--ids"),
|
|
1792
|
+
commandFlagCoordinate("messages delete", "--for-everyone"),
|
|
1793
|
+
commandFlagCoordinate("messages export", "--asc"),
|
|
1794
|
+
commandFlagCoordinate("send text", "--no-preview"),
|
|
1795
|
+
...["send text", "send file", "send sticker", "send voice"]
|
|
1796
|
+
.map((command) => commandFlagCoordinate(command, "--wait")),
|
|
1797
|
+
commandFlagCoordinate("contacts list", "--ids"),
|
|
1798
|
+
commandFlagCoordinate("export", "--force"),
|
|
1799
|
+
commandFlagCoordinate("export", "--no-attachments"),
|
|
1800
|
+
...["--refresh-cache", "--semantic"]
|
|
1801
|
+
.map((flag) => commandFlagCoordinate("completion", flag)),
|
|
1802
|
+
...["--check", "--cli", "--desktop", "--server"]
|
|
1803
|
+
.map((flag) => commandFlagCoordinate("update", flag)),
|
|
1804
|
+
]);
|
|
1805
|
+
|
|
1806
|
+
function surfaceDecision(
|
|
1807
|
+
disposition: LocalCliSurfaceDecisionV1["disposition"],
|
|
1808
|
+
rationale: string,
|
|
1809
|
+
operation: string | null = null,
|
|
1810
|
+
replacement: string | null = null,
|
|
1811
|
+
fixedValue: string | number | boolean | null = null,
|
|
1812
|
+
): LocalCliSurfaceDecisionV1 {
|
|
1813
|
+
return Object.freeze({ disposition, rationale, operation, replacement, fixedValue });
|
|
1814
|
+
}
|
|
1815
|
+
|
|
1816
|
+
// This reviewed surface includes historical Wrench wording in signed digest
|
|
1817
|
+
// inputs. Preserve it verbatim across the Ghostget branding migration.
|
|
1818
|
+
const BEEPER_CLI_V062_GLOBAL_FLAGS = Object.freeze([
|
|
1819
|
+
Object.freeze({
|
|
1820
|
+
name: "--base-url", aliases: Object.freeze([]), source: "global" as const,
|
|
1821
|
+
valueType: "string" as const, allowNo: false, required: false, multiple: false,
|
|
1822
|
+
enum: Object.freeze([]), default: Object.freeze({ kind: "none" as const }),
|
|
1823
|
+
decision: surfaceDecision(
|
|
1824
|
+
"fixed",
|
|
1825
|
+
"Wrench resolves and verifies the fixed Desktop loopback endpoint; callers cannot supply an endpoint.",
|
|
1826
|
+
null,
|
|
1827
|
+
null,
|
|
1828
|
+
"verified-desktop-loopback-endpoint",
|
|
1829
|
+
),
|
|
1830
|
+
}),
|
|
1831
|
+
Object.freeze({
|
|
1832
|
+
name: "--target", aliases: Object.freeze(["-t"]), source: "global" as const,
|
|
1833
|
+
valueType: "string" as const, allowNo: false, required: false, multiple: false,
|
|
1834
|
+
enum: Object.freeze([]), default: Object.freeze({ kind: "derived" as const, description: "configured CLI target" }),
|
|
1835
|
+
decision: surfaceDecision("fixed", "The provider is permanently bound to the Desktop target.", null, null, "desktop"),
|
|
1836
|
+
}),
|
|
1837
|
+
Object.freeze({
|
|
1838
|
+
name: "--debug", aliases: Object.freeze([]), source: "global" as const,
|
|
1839
|
+
valueType: "boolean" as const, allowNo: false, required: false, multiple: false,
|
|
1840
|
+
enum: Object.freeze([]), default: Object.freeze({ kind: "literal" as const, value: false, authority: "tagged-source" as const }),
|
|
1841
|
+
decision: surfaceDecision("unsupported", "Raw SDK debug output can contain provider internals and is never returned."),
|
|
1842
|
+
}),
|
|
1843
|
+
Object.freeze({
|
|
1844
|
+
name: "--events", aliases: Object.freeze([]), source: "global" as const,
|
|
1845
|
+
valueType: "boolean" as const, allowNo: false, required: false, multiple: false,
|
|
1846
|
+
enum: Object.freeze([]), default: Object.freeze({ kind: "literal" as const, value: false, authority: "tagged-source" as const }),
|
|
1847
|
+
decision: surfaceDecision("replaced", "Wrench emits its own bounded operation lifecycle instead of raw CLI NDJSON.", null, "Wrench operation events"),
|
|
1848
|
+
}),
|
|
1849
|
+
Object.freeze({
|
|
1850
|
+
name: "--full", aliases: Object.freeze([]), source: "global" as const,
|
|
1851
|
+
valueType: "boolean" as const, allowNo: false, required: false, multiple: false,
|
|
1852
|
+
enum: Object.freeze([]), default: Object.freeze({ kind: "literal" as const, value: false, authority: "tagged-source" as const }),
|
|
1853
|
+
decision: surfaceDecision(
|
|
1854
|
+
"fixed",
|
|
1855
|
+
"Wrench fixes --full true and independently enforces its output bound; the upstream core currently ignores the parsed value.",
|
|
1856
|
+
null,
|
|
1857
|
+
null,
|
|
1858
|
+
true,
|
|
1859
|
+
),
|
|
1860
|
+
}),
|
|
1861
|
+
Object.freeze({
|
|
1862
|
+
name: "--json", aliases: Object.freeze([]), source: "global" as const,
|
|
1863
|
+
valueType: "boolean" as const, allowNo: false, required: false, multiple: false,
|
|
1864
|
+
enum: Object.freeze([]), default: Object.freeze({ kind: "literal" as const, value: false, authority: "tagged-source" as const }),
|
|
1865
|
+
decision: surfaceDecision("fixed", "The provider always requests machine-readable output.", null, null, true),
|
|
1866
|
+
}),
|
|
1867
|
+
Object.freeze({
|
|
1868
|
+
name: "--quiet", aliases: Object.freeze(["-q"]), source: "global" as const,
|
|
1869
|
+
valueType: "boolean" as const, allowNo: false, required: false, multiple: false,
|
|
1870
|
+
enum: Object.freeze([]), default: Object.freeze({ kind: "literal" as const, value: false, authority: "tagged-source" as const }),
|
|
1871
|
+
decision: surfaceDecision("fixed", "The provider always suppresses interactive presentation.", null, null, true),
|
|
1872
|
+
}),
|
|
1873
|
+
Object.freeze({
|
|
1874
|
+
name: "--read-only", aliases: Object.freeze([]), source: "global" as const,
|
|
1875
|
+
valueType: "boolean" as const, allowNo: false, required: false, multiple: false,
|
|
1876
|
+
enum: Object.freeze([]), default: Object.freeze({ kind: "literal" as const, value: false, authority: "tagged-source" as const }),
|
|
1877
|
+
decision: surfaceDecision("absorbed", "Wrench fixes this flag for reads and uses kernel preview and confirmation for writes."),
|
|
1878
|
+
}),
|
|
1879
|
+
Object.freeze({
|
|
1880
|
+
name: "--timeout", aliases: Object.freeze([]), source: "global" as const,
|
|
1881
|
+
valueType: "string" as const, allowNo: false, required: false, multiple: false,
|
|
1882
|
+
enum: Object.freeze([]), default: Object.freeze({ kind: "none" as const }),
|
|
1883
|
+
decision: surfaceDecision(
|
|
1884
|
+
"fixed",
|
|
1885
|
+
"The upstream core currently ignores --timeout; Wrench's outer process deadline is authoritative and callers cannot inject duration syntax.",
|
|
1886
|
+
null,
|
|
1887
|
+
null,
|
|
1888
|
+
"outer-process-deadline",
|
|
1889
|
+
),
|
|
1890
|
+
}),
|
|
1891
|
+
Object.freeze({
|
|
1892
|
+
name: "--yes", aliases: Object.freeze(["-y"]), source: "global" as const,
|
|
1893
|
+
valueType: "boolean" as const, allowNo: false, required: false, multiple: false,
|
|
1894
|
+
enum: Object.freeze([]), default: Object.freeze({ kind: "literal" as const, value: false, authority: "tagged-source" as const }),
|
|
1895
|
+
decision: surfaceDecision("absorbed", "The kernel owns explicit preview and confirmation before fixed noninteractive dispatch."),
|
|
1896
|
+
}),
|
|
1897
|
+
] satisfies readonly LocalCliSurfaceFlagV1[]);
|
|
1898
|
+
|
|
1899
|
+
const INPUT_DEPENDENT_EFFECT_COMMANDS = new Set<string>([
|
|
1900
|
+
"api request",
|
|
1901
|
+
"completion",
|
|
1902
|
+
"messages export",
|
|
1903
|
+
"media download",
|
|
1904
|
+
"plugins",
|
|
1905
|
+
"rpc",
|
|
1906
|
+
"update",
|
|
1907
|
+
"watch",
|
|
1908
|
+
]);
|
|
1909
|
+
|
|
1910
|
+
const REPORTED_READS_WITH_REVIEWED_WRITES = new Set<string>([
|
|
1911
|
+
"auth email response",
|
|
1912
|
+
"api post",
|
|
1913
|
+
"export",
|
|
1914
|
+
"presence",
|
|
1915
|
+
"targets tunnel",
|
|
1916
|
+
"verify",
|
|
1917
|
+
]);
|
|
1918
|
+
|
|
1919
|
+
function reviewedEffect(
|
|
1920
|
+
command: string,
|
|
1921
|
+
upstreamReportedMutates: boolean,
|
|
1922
|
+
): LocalCliSurfaceCommandDefinitionV1["reviewedEffect"] {
|
|
1923
|
+
if (INPUT_DEPENDENT_EFFECT_COMMANDS.has(command)) return "input-dependent";
|
|
1924
|
+
return upstreamReportedMutates || REPORTED_READS_WITH_REVIEWED_WRITES.has(command)
|
|
1925
|
+
? "write"
|
|
1926
|
+
: "read";
|
|
1927
|
+
}
|
|
1928
|
+
|
|
1929
|
+
function surfaceCommandDecision(command: string): LocalCliSurfaceDecisionV1 {
|
|
1930
|
+
if (command === "_complete") {
|
|
1931
|
+
return surfaceDecision(
|
|
1932
|
+
"unsupported",
|
|
1933
|
+
"Private source completion internals can resolve fuzzy live identities and are outside provider authority.",
|
|
1934
|
+
null,
|
|
1935
|
+
"Wrench capability metadata and exact semantic IDs",
|
|
1936
|
+
);
|
|
1937
|
+
}
|
|
1938
|
+
if (command === "accounts add" || command === "accounts remove") {
|
|
1939
|
+
return surfaceDecision("R4", "Account lifecycle is operator-only administration and has no routine provider operation.");
|
|
1940
|
+
}
|
|
1941
|
+
if (command === "messages delete") {
|
|
1942
|
+
return surfaceDecision(
|
|
1943
|
+
"R4",
|
|
1944
|
+
"Deletion stays inert: upstream can silently fall back from delete-for-everyone to local deletion and only returns success/void with no provable effect.",
|
|
1945
|
+
null,
|
|
1946
|
+
"No dispatch without exact external confirmation and effect proof",
|
|
1947
|
+
);
|
|
1948
|
+
}
|
|
1949
|
+
if (command === "accounts use") {
|
|
1950
|
+
return surfaceDecision(
|
|
1951
|
+
"absorbed",
|
|
1952
|
+
"Mutable default-account selection is replaced by an exact account_id on every account-aware operation.",
|
|
1953
|
+
null,
|
|
1954
|
+
"Explicit account_id",
|
|
1955
|
+
);
|
|
1956
|
+
}
|
|
1957
|
+
if (command === "export") {
|
|
1958
|
+
return surfaceDecision(
|
|
1959
|
+
"internal",
|
|
1960
|
+
"Only the existing bounded private archive path may invoke top-level export with Wrench-owned output, limits, and no attachments.",
|
|
1961
|
+
null,
|
|
1962
|
+
"Internal bounded private export",
|
|
1963
|
+
);
|
|
1964
|
+
}
|
|
1965
|
+
const legacy = BEEPER_CLI_COMMAND_COVERAGE[
|
|
1966
|
+
command as keyof typeof BEEPER_CLI_COMMAND_COVERAGE
|
|
1967
|
+
];
|
|
1968
|
+
if (legacy === undefined) throw new Error(`Beeper surface command ${command} lacks a disposition`);
|
|
1969
|
+
if (legacy.state === "supported") {
|
|
1970
|
+
return surfaceDecision(
|
|
1971
|
+
"supported",
|
|
1972
|
+
`A bounded semantic ${legacy.operation} operation covers this command without raw argv authority.`,
|
|
1973
|
+
legacy.operation,
|
|
1974
|
+
);
|
|
1975
|
+
}
|
|
1976
|
+
if (legacy.state === "internal-preflight") {
|
|
1977
|
+
return surfaceDecision(
|
|
1978
|
+
"internal",
|
|
1979
|
+
`This command is restricted to the ${legacy.purpose} runtime preflight.`,
|
|
1980
|
+
);
|
|
1981
|
+
}
|
|
1982
|
+
const special = command === "messages export"
|
|
1983
|
+
? "A bounded private messages artifact is not yet exposed; caller paths and buffered stdout remain forbidden."
|
|
1984
|
+
: command === "media download"
|
|
1985
|
+
? "No media operation exists until an opaque prior-message handle can be consumed by a genuinely bounded worker."
|
|
1986
|
+
: command === "watch"
|
|
1987
|
+
? "No event operation exists until finite supervision proves count, duration, byte, and termination bounds; webhooks remain forbidden."
|
|
1988
|
+
: command === "targets tunnel"
|
|
1989
|
+
? "The floating Cloudflare JIT plugin and public tunnel are permanently outside provider authority."
|
|
1990
|
+
: `This ${legacy.reason} command group remains outside the semantic provider authority.`;
|
|
1991
|
+
return surfaceDecision("unsupported", special);
|
|
1992
|
+
}
|
|
1993
|
+
|
|
1994
|
+
function surfaceItemDecision(
|
|
1995
|
+
command: string,
|
|
1996
|
+
item: string,
|
|
1997
|
+
commandDecision: LocalCliSurfaceDecisionV1,
|
|
1998
|
+
): LocalCliSurfaceDecisionV1 {
|
|
1999
|
+
if (command === "export" && commandDecision.disposition === "internal") {
|
|
2000
|
+
if (item === "--no-attachments") {
|
|
2001
|
+
return surfaceDecision(
|
|
2002
|
+
"fixed",
|
|
2003
|
+
"The internal private export always disables attachments.",
|
|
2004
|
+
null,
|
|
2005
|
+
"Wrench-owned bounded private export",
|
|
2006
|
+
true,
|
|
2007
|
+
);
|
|
2008
|
+
}
|
|
2009
|
+
if (item === "--force") {
|
|
2010
|
+
return surfaceDecision(
|
|
2011
|
+
"fixed",
|
|
2012
|
+
"The internal private export never overwrites an existing caller path.",
|
|
2013
|
+
null,
|
|
2014
|
+
"Fresh Wrench-owned export root",
|
|
2015
|
+
false,
|
|
2016
|
+
);
|
|
2017
|
+
}
|
|
2018
|
+
if (item === "--out") {
|
|
2019
|
+
return surfaceDecision(
|
|
2020
|
+
"fixed",
|
|
2021
|
+
"The output root is allocated and owned by Wrench; callers cannot inject a path.",
|
|
2022
|
+
null,
|
|
2023
|
+
"Wrench-owned private export root",
|
|
2024
|
+
"wrench-owned-private-export-root",
|
|
2025
|
+
);
|
|
2026
|
+
}
|
|
2027
|
+
if (item === "--quiet") {
|
|
2028
|
+
return surfaceDecision(
|
|
2029
|
+
"fixed",
|
|
2030
|
+
"The internal export suppresses interactive presentation.",
|
|
2031
|
+
null,
|
|
2032
|
+
"Bounded canonical manifest",
|
|
2033
|
+
true,
|
|
2034
|
+
);
|
|
2035
|
+
}
|
|
2036
|
+
if (["--limit-chats", "--limit-messages", "--max-participants"].includes(item)) {
|
|
2037
|
+
return surfaceDecision(
|
|
2038
|
+
"absorbed",
|
|
2039
|
+
`${item} is supplied only by the bounded private export planner.`,
|
|
2040
|
+
null,
|
|
2041
|
+
"Validated Wrench capture bound",
|
|
2042
|
+
);
|
|
2043
|
+
}
|
|
2044
|
+
return surfaceDecision(
|
|
2045
|
+
"internal",
|
|
2046
|
+
`${item} is unavailable to provider callers and omitted by the fixed private export plan.`,
|
|
2047
|
+
null,
|
|
2048
|
+
"Internal bounded private export",
|
|
2049
|
+
);
|
|
2050
|
+
}
|
|
2051
|
+
if (commandDecision.disposition !== "supported") {
|
|
2052
|
+
return surfaceDecision(
|
|
2053
|
+
commandDecision.disposition,
|
|
2054
|
+
`${item} inherits the command disposition: ${commandDecision.rationale}`,
|
|
2055
|
+
commandDecision.operation,
|
|
2056
|
+
commandDecision.replacement,
|
|
2057
|
+
);
|
|
2058
|
+
}
|
|
2059
|
+
const operation = commandDecision.operation;
|
|
2060
|
+
if (item === "--pick") {
|
|
2061
|
+
return surfaceDecision("replaced", "Fuzzy selection and result picking are forbidden.", operation, "Exact provider ID input");
|
|
2062
|
+
}
|
|
2063
|
+
if (item === "--ids") {
|
|
2064
|
+
return surfaceDecision("absorbed", "Normalized results always include stable provider IDs.", operation);
|
|
2065
|
+
}
|
|
2066
|
+
if (item === "--asc") {
|
|
2067
|
+
return surfaceDecision("absorbed", "The operation uses one canonical page order and never passes --asc before deriving continuation.", operation, "Canonical operation order");
|
|
2068
|
+
}
|
|
2069
|
+
if (item === "--wait" || item === "--wait-timeout") {
|
|
2070
|
+
return surfaceDecision("replaced", "Mutation dispatch never waits inside the send command.", operation, "Future read-only messaging.delivery.await over an accepted pending send");
|
|
2071
|
+
}
|
|
2072
|
+
if (item === "--transaction") {
|
|
2073
|
+
return surfaceDecision("absorbed", "Wrench owns the reaction transaction identity through the confirmed plan and dispatch fence.", operation);
|
|
2074
|
+
}
|
|
2075
|
+
if (command === "chats start" && item === "--title") {
|
|
2076
|
+
return surfaceDecision("replaced", "Conversation creation first returns an exact ID; title mutation is separate.", operation, "conversations.title.set");
|
|
2077
|
+
}
|
|
2078
|
+
if (command === "presence" && item === "--duration") {
|
|
2079
|
+
return surfaceDecision("absorbed", "Duration becomes two explicit bounded Wrench dispatches instead of a hidden child-process write.", operation);
|
|
2080
|
+
}
|
|
2081
|
+
if (item === "--file" || item === "--attachment") {
|
|
2082
|
+
return surfaceDecision("replaced", "Caller filesystem paths are forbidden.", operation, "Wrench plan-bound file capability");
|
|
2083
|
+
}
|
|
2084
|
+
if (item === "--account" && command === "accounts list") {
|
|
2085
|
+
return surfaceDecision("replaced", "The list returns the fixed account realm; exact lookup is separate.", operation, "accounts.read with account_id");
|
|
2086
|
+
}
|
|
2087
|
+
if (item === "--account") {
|
|
2088
|
+
return surfaceDecision("replaced", "Fuzzy and multi-account selectors are forbidden.", operation, "One exact account_id");
|
|
2089
|
+
}
|
|
2090
|
+
if (item === "--chat" || item === "--to") {
|
|
2091
|
+
return surfaceDecision("replaced", "Numeric, title, fuzzy, and multi-chat selectors are forbidden.", operation, "One exact full conversation_id");
|
|
2092
|
+
}
|
|
2093
|
+
if (["bridge", "account", "user", "id"].includes(item)) {
|
|
2094
|
+
return surfaceDecision("replaced", "Selector shorthand is replaced by an exact provider identity.", operation, "Exact semantic ID input");
|
|
2095
|
+
}
|
|
2096
|
+
if (command === "messages search" && item === "--exclude-low-priority") {
|
|
2097
|
+
return surfaceDecision(
|
|
2098
|
+
"supported",
|
|
2099
|
+
"The semantic default is true, matching the generated Desktop API/OpenAPI server default used when the CLI omits the flag.",
|
|
2100
|
+
operation,
|
|
2101
|
+
);
|
|
2102
|
+
}
|
|
2103
|
+
if (command === "bridges list" && item === "--provider") {
|
|
2104
|
+
return surfaceDecision(
|
|
2105
|
+
"supported",
|
|
2106
|
+
"The three upstream values pass exactly; platform-sdk is a Wrench-local filter over an unfiltered bridge catalog and is never passed upstream.",
|
|
2107
|
+
operation,
|
|
2108
|
+
);
|
|
2109
|
+
}
|
|
2110
|
+
return surfaceDecision("supported", `${item} is exposed through bounded typed input for ${operation}.`, operation);
|
|
2111
|
+
}
|
|
2112
|
+
|
|
2113
|
+
function surfaceDefault(
|
|
2114
|
+
command: string,
|
|
2115
|
+
name: string,
|
|
2116
|
+
value: BeeperCliV062FlagProfile[6],
|
|
2117
|
+
): LocalCliSurfaceDefaultV1 {
|
|
2118
|
+
const sourceAuthority = command === "targets tunnel"
|
|
2119
|
+
? "jit-plugin-source"
|
|
2120
|
+
: "tagged-source";
|
|
2121
|
+
if (BEEPER_CLI_V062_EXPLICIT_FALSE_BOOLEAN_FLAGS.has(
|
|
2122
|
+
commandFlagCoordinate(command, name),
|
|
2123
|
+
)) return Object.freeze({
|
|
2124
|
+
kind: "literal",
|
|
2125
|
+
value: false,
|
|
2126
|
+
authority: sourceAuthority,
|
|
2127
|
+
});
|
|
2128
|
+
return value === null
|
|
2129
|
+
? Object.freeze({ kind: "none" })
|
|
2130
|
+
: Object.freeze({
|
|
2131
|
+
kind: "literal",
|
|
2132
|
+
value,
|
|
2133
|
+
authority: command === "messages search" && name === "--exclude-low-priority"
|
|
2134
|
+
? "sdk-openapi"
|
|
2135
|
+
: sourceAuthority,
|
|
2136
|
+
});
|
|
2137
|
+
}
|
|
2138
|
+
|
|
2139
|
+
function commandReconciliation(
|
|
2140
|
+
command: string,
|
|
2141
|
+
decision: LocalCliSurfaceDecisionV1,
|
|
2142
|
+
): LocalCliSurfaceReconciliationV1 {
|
|
2143
|
+
if (decision.disposition !== "supported" || decision.operation === null) {
|
|
2144
|
+
return Object.freeze({
|
|
2145
|
+
availability: "none",
|
|
2146
|
+
namespace: null,
|
|
2147
|
+
predicate: null,
|
|
2148
|
+
rationale: "No provider mutation dispatch is authorized by this disposition.",
|
|
2149
|
+
});
|
|
2150
|
+
}
|
|
2151
|
+
const policy = BEEPER_LOCAL_OPERATIONS[decision.operation as BeeperLocalOperationName];
|
|
2152
|
+
if (policy.effect === "read") {
|
|
2153
|
+
return Object.freeze({
|
|
2154
|
+
availability: "none",
|
|
2155
|
+
namespace: null,
|
|
2156
|
+
predicate: null,
|
|
2157
|
+
rationale: "Read operations do not require mutation reconciliation.",
|
|
2158
|
+
});
|
|
2159
|
+
}
|
|
2160
|
+
if (["presence.set", "conversations.notify", "conversations.focus"].includes(decision.operation)) {
|
|
2161
|
+
return Object.freeze({
|
|
2162
|
+
availability: "none",
|
|
2163
|
+
namespace: null,
|
|
2164
|
+
predicate: null,
|
|
2165
|
+
rationale: "This visible effect has no exact provider readback and is never blindly retried after dispatch uncertainty.",
|
|
2166
|
+
});
|
|
2167
|
+
}
|
|
2168
|
+
if (command === "chats avatar") {
|
|
2169
|
+
return Object.freeze({
|
|
2170
|
+
availability: "input-dependent",
|
|
2171
|
+
namespace: "semantic-operation",
|
|
2172
|
+
predicate: Object.freeze({
|
|
2173
|
+
op: "not",
|
|
2174
|
+
predicate: Object.freeze({ op: "present", field: "avatar" }),
|
|
2175
|
+
}),
|
|
2176
|
+
rationale: "Only clear/no-file avatar state has an exact readback; uploaded file identity is irreconcilable.",
|
|
2177
|
+
});
|
|
2178
|
+
}
|
|
2179
|
+
if (command === "chats draft") {
|
|
2180
|
+
return Object.freeze({
|
|
2181
|
+
availability: "input-dependent",
|
|
2182
|
+
namespace: "semantic-operation",
|
|
2183
|
+
predicate: Object.freeze({
|
|
2184
|
+
op: "not",
|
|
2185
|
+
predicate: Object.freeze({ op: "present", field: "attachment" }),
|
|
2186
|
+
}),
|
|
2187
|
+
rationale: "Only drafts without an attachment have an exact readback; attachment identity is irreconcilable.",
|
|
2188
|
+
});
|
|
2189
|
+
}
|
|
2190
|
+
if (command === "chats mark-read" || command === "chats mark-unread") {
|
|
2191
|
+
return Object.freeze({
|
|
2192
|
+
availability: "input-dependent",
|
|
2193
|
+
namespace: "semantic-operation",
|
|
2194
|
+
predicate: Object.freeze({
|
|
2195
|
+
op: "not",
|
|
2196
|
+
predicate: Object.freeze({ op: "present", field: "message_id" }),
|
|
2197
|
+
}),
|
|
2198
|
+
rationale: "Only the conversation-level marker has an exact readback; a caller-selected message boundary is irreconcilable.",
|
|
2199
|
+
});
|
|
2200
|
+
}
|
|
2201
|
+
return Object.freeze({
|
|
2202
|
+
availability: "always",
|
|
2203
|
+
namespace: null,
|
|
2204
|
+
predicate: null,
|
|
2205
|
+
rationale: "The operation contract defines an exact accepted target or desired-state readback and forbids blind retry.",
|
|
2206
|
+
});
|
|
2207
|
+
}
|
|
2208
|
+
|
|
2209
|
+
function commandOutput(
|
|
2210
|
+
command: string,
|
|
2211
|
+
output: BeeperCliV062CommandProfile[2],
|
|
2212
|
+
decision: LocalCliSurfaceDecisionV1,
|
|
2213
|
+
): LocalCliSurfaceCommandDefinitionV1["output"] {
|
|
2214
|
+
if (command === "export") {
|
|
2215
|
+
return Object.freeze({
|
|
2216
|
+
shape: "Private canonical archive shards under a Wrench-owned root.",
|
|
2217
|
+
completeness: "internal",
|
|
2218
|
+
maxBytes: 4 * 1024 * 1024 * 1024,
|
|
2219
|
+
privateArtifact: true,
|
|
2220
|
+
truncation: "The internal caller fixes chat, message, participant, timeout, and no-attachment bounds.",
|
|
2221
|
+
});
|
|
2222
|
+
}
|
|
2223
|
+
if (decision.disposition !== "supported") {
|
|
2224
|
+
return Object.freeze({
|
|
2225
|
+
shape: `Upstream ${output} output is not returned through a provider operation.`,
|
|
2226
|
+
completeness: "unavailable",
|
|
2227
|
+
maxBytes: null,
|
|
2228
|
+
privateArtifact: false,
|
|
2229
|
+
truncation: null,
|
|
2230
|
+
});
|
|
2231
|
+
}
|
|
2232
|
+
const policy = BEEPER_LOCAL_OPERATIONS[decision.operation as BeeperLocalOperationName];
|
|
2233
|
+
const search = decision.operation?.endsWith("search") === true;
|
|
2234
|
+
const blendedContactQuery = command === "contacts list";
|
|
2235
|
+
return Object.freeze({
|
|
2236
|
+
shape: policy.effect === "read"
|
|
2237
|
+
? "Bounded normalized provider projection with explicit completeness metadata."
|
|
2238
|
+
: "Bounded normalized mutation receipt and exact reconciliation evidence when available.",
|
|
2239
|
+
completeness: policy.effect === "write"
|
|
2240
|
+
? "input-dependent"
|
|
2241
|
+
: search ? "candidate-window" : blendedContactQuery ? "input-dependent" : "bounded",
|
|
2242
|
+
maxBytes: 10 * 1024 * 1024,
|
|
2243
|
+
privateArtifact: false,
|
|
2244
|
+
truncation: policy.effect === "read"
|
|
2245
|
+
? blendedContactQuery
|
|
2246
|
+
? "Desktop loopback contact pages include continuation metadata. Official CLI v0.6.2 contracts remain a first-page window with no continuation."
|
|
2247
|
+
: "Provider limits and continuation availability are explicit in the normalized output."
|
|
2248
|
+
: "No upstream body, path, token, or unbounded diagnostic output is exposed.",
|
|
2249
|
+
});
|
|
2250
|
+
}
|
|
2251
|
+
|
|
2252
|
+
function commandInputRules(command: string) {
|
|
2253
|
+
if (command === "messages list" || command === "messages export") {
|
|
2254
|
+
const namespace = command === "messages list"
|
|
2255
|
+
? "semantic-operation" as const
|
|
2256
|
+
: "upstream-command" as const;
|
|
2257
|
+
return Object.freeze([
|
|
2258
|
+
Object.freeze({
|
|
2259
|
+
namespace,
|
|
2260
|
+
when: Object.freeze({ op: "present" as const, field: "before_cursor" }),
|
|
2261
|
+
require: Object.freeze([]),
|
|
2262
|
+
requireAny: Object.freeze([]),
|
|
2263
|
+
exactlyOne: Object.freeze([]),
|
|
2264
|
+
forbid: Object.freeze(["after_cursor"]),
|
|
2265
|
+
rationale: "A page has only one continuation direction.",
|
|
2266
|
+
}),
|
|
2267
|
+
Object.freeze({
|
|
2268
|
+
namespace,
|
|
2269
|
+
when: Object.freeze({ op: "present" as const, field: "after_cursor" }),
|
|
2270
|
+
require: Object.freeze([]),
|
|
2271
|
+
requireAny: Object.freeze([]),
|
|
2272
|
+
exactlyOne: Object.freeze([]),
|
|
2273
|
+
forbid: Object.freeze(["before_cursor"]),
|
|
2274
|
+
rationale: "A page has only one continuation direction.",
|
|
2275
|
+
}),
|
|
2276
|
+
]);
|
|
2277
|
+
}
|
|
2278
|
+
if (command === "chats description") return Object.freeze([
|
|
2279
|
+
Object.freeze({
|
|
2280
|
+
namespace: "semantic-operation" as const,
|
|
2281
|
+
when: Object.freeze({ op: "eq" as const, field: "clear", value: true }),
|
|
2282
|
+
require: Object.freeze([]), requireAny: Object.freeze([]), exactlyOne: Object.freeze([]),
|
|
2283
|
+
forbid: Object.freeze(["description"]),
|
|
2284
|
+
rationale: "Clear description cannot also provide replacement text.",
|
|
2285
|
+
}),
|
|
2286
|
+
Object.freeze({
|
|
2287
|
+
namespace: "semantic-operation" as const,
|
|
2288
|
+
when: Object.freeze({ op: "eq" as const, field: "clear", value: false }),
|
|
2289
|
+
require: Object.freeze(["description"]), requireAny: Object.freeze([]), exactlyOne: Object.freeze([]), forbid: Object.freeze([]),
|
|
2290
|
+
rationale: "A non-clear description mutation requires replacement text.",
|
|
2291
|
+
}),
|
|
2292
|
+
]);
|
|
2293
|
+
if (command === "chats avatar") return Object.freeze([
|
|
2294
|
+
Object.freeze({
|
|
2295
|
+
namespace: "semantic-operation" as const,
|
|
2296
|
+
when: Object.freeze({ op: "eq" as const, field: "clear", value: true }),
|
|
2297
|
+
require: Object.freeze([]), requireAny: Object.freeze([]), exactlyOne: Object.freeze([]),
|
|
2298
|
+
forbid: Object.freeze(["avatar"]),
|
|
2299
|
+
rationale: "Clear avatar cannot also provide an upload capability.",
|
|
2300
|
+
}),
|
|
2301
|
+
Object.freeze({
|
|
2302
|
+
namespace: "semantic-operation" as const,
|
|
2303
|
+
when: Object.freeze({ op: "eq" as const, field: "clear", value: false }),
|
|
2304
|
+
require: Object.freeze(["avatar"]), requireAny: Object.freeze([]), exactlyOne: Object.freeze([]), forbid: Object.freeze([]),
|
|
2305
|
+
rationale: "A non-clear avatar mutation requires one plan-bound upload capability.",
|
|
2306
|
+
}),
|
|
2307
|
+
]);
|
|
2308
|
+
if (command === "chats draft") return Object.freeze([
|
|
2309
|
+
Object.freeze({
|
|
2310
|
+
namespace: "semantic-operation" as const,
|
|
2311
|
+
when: Object.freeze({ op: "eq" as const, field: "clear", value: true }),
|
|
2312
|
+
require: Object.freeze([]),
|
|
2313
|
+
requireAny: Object.freeze([]),
|
|
2314
|
+
exactlyOne: Object.freeze([]),
|
|
2315
|
+
forbid: Object.freeze(["text", "attachment", "filename", "mime_type"]),
|
|
2316
|
+
rationale: "Clear draft cannot carry replacement content or attachment metadata.",
|
|
2317
|
+
}),
|
|
2318
|
+
Object.freeze({
|
|
2319
|
+
namespace: "semantic-operation" as const,
|
|
2320
|
+
when: Object.freeze({ op: "eq" as const, field: "clear", value: false }),
|
|
2321
|
+
require: Object.freeze(["text"]),
|
|
2322
|
+
requireAny: Object.freeze([]),
|
|
2323
|
+
exactlyOne: Object.freeze([]),
|
|
2324
|
+
forbid: Object.freeze([]),
|
|
2325
|
+
rationale: "A non-clear draft requires text; one plan-bound attachment is optional.",
|
|
2326
|
+
}),
|
|
2327
|
+
...["filename", "mime_type"].map((field) => Object.freeze({
|
|
2328
|
+
namespace: "semantic-operation" as const,
|
|
2329
|
+
when: Object.freeze({ op: "present" as const, field }),
|
|
2330
|
+
require: Object.freeze(["attachment"]),
|
|
2331
|
+
requireAny: Object.freeze([]),
|
|
2332
|
+
exactlyOne: Object.freeze([]),
|
|
2333
|
+
forbid: Object.freeze([]),
|
|
2334
|
+
rationale: `${field} is meaningful only with one attachment.`,
|
|
2335
|
+
})),
|
|
2336
|
+
]);
|
|
2337
|
+
if (command === "messages search") return Object.freeze([
|
|
2338
|
+
Object.freeze({
|
|
2339
|
+
namespace: "semantic-operation" as const,
|
|
2340
|
+
when: Object.freeze({ op: "true" as const }),
|
|
2341
|
+
require: Object.freeze([]),
|
|
2342
|
+
requireAny: Object.freeze(["query", "account_id", "conversation_id", "chat_type", "after", "before", "media", "sender"]),
|
|
2343
|
+
exactlyOne: Object.freeze([]),
|
|
2344
|
+
forbid: Object.freeze([]),
|
|
2345
|
+
rationale: "Search requires text or a substantive bounded filter; presentation booleans alone are insufficient.",
|
|
2346
|
+
}),
|
|
2347
|
+
]);
|
|
2348
|
+
return Object.freeze([]);
|
|
2349
|
+
}
|
|
2350
|
+
|
|
2351
|
+
function commandPathSemanticInputs(
|
|
2352
|
+
command: string,
|
|
2353
|
+
): LocalCliSurfaceCommandDefinitionV1["pathSemanticInputs"] {
|
|
2354
|
+
const fixed = {
|
|
2355
|
+
"chats archive": Object.freeze({ enabled: true }),
|
|
2356
|
+
"chats unarchive": Object.freeze({ enabled: false }),
|
|
2357
|
+
"chats pin": Object.freeze({ enabled: true }),
|
|
2358
|
+
"chats unpin": Object.freeze({ enabled: false }),
|
|
2359
|
+
"chats mute": Object.freeze({ enabled: true }),
|
|
2360
|
+
"chats unmute": Object.freeze({ enabled: false }),
|
|
2361
|
+
"chats mark-read": Object.freeze({ unread: false }),
|
|
2362
|
+
"chats mark-unread": Object.freeze({ unread: true }),
|
|
2363
|
+
"chats remind": Object.freeze({ clear: false }),
|
|
2364
|
+
"chats unremind": Object.freeze({ clear: true }),
|
|
2365
|
+
"send text": Object.freeze({ kind: "text" }),
|
|
2366
|
+
"send file": Object.freeze({ kind: "file" }),
|
|
2367
|
+
"send react": Object.freeze({ enabled: true }),
|
|
2368
|
+
"send sticker": Object.freeze({ kind: "sticker" }),
|
|
2369
|
+
"send unreact": Object.freeze({ enabled: false }),
|
|
2370
|
+
"send voice": Object.freeze({ kind: "voice" }),
|
|
2371
|
+
} as const satisfies Readonly<Record<
|
|
2372
|
+
string,
|
|
2373
|
+
LocalCliSurfaceCommandDefinitionV1["pathSemanticInputs"]
|
|
2374
|
+
>>;
|
|
2375
|
+
return fixed[command as keyof typeof fixed] ?? Object.freeze({});
|
|
2376
|
+
}
|
|
2377
|
+
|
|
2378
|
+
function commandDefinition(
|
|
2379
|
+
profile: BeeperCliV062CommandProfile,
|
|
2380
|
+
exposure: "public-manual" | "source-only-private" = "public-manual",
|
|
2381
|
+
): LocalCliSurfaceCommandDefinitionV1 {
|
|
2382
|
+
const [command, upstreamReportedMutates, output, argumentProfiles, flagProfiles] = profile;
|
|
2383
|
+
const decision = surfaceCommandDecision(command);
|
|
2384
|
+
const provenance = exposure === "source-only-private"
|
|
2385
|
+
? "source-only-private" as const
|
|
2386
|
+
: command === "targets tunnel" ? "jit-plugin" as const : "built-in-canonical" as const;
|
|
2387
|
+
const arguments_: readonly LocalCliSurfaceArgumentV1[] = Object.freeze(
|
|
2388
|
+
argumentProfiles.map(([name, required, enumValues = []], position) => Object.freeze({
|
|
2389
|
+
name,
|
|
2390
|
+
position,
|
|
2391
|
+
required,
|
|
2392
|
+
multiple: false,
|
|
2393
|
+
valueType: "string" as const,
|
|
2394
|
+
enum: Object.freeze([...enumValues]),
|
|
2395
|
+
default: Object.freeze({ kind: "none" as const }),
|
|
2396
|
+
decision: surfaceItemDecision(command, name, decision),
|
|
2397
|
+
})),
|
|
2398
|
+
);
|
|
2399
|
+
const flags: readonly LocalCliSurfaceFlagV1[] = Object.freeze(flagProfiles.map(([
|
|
2400
|
+
name, aliases, valueType, required, multiple, enumValues, defaultValue,
|
|
2401
|
+
]) => Object.freeze({
|
|
2402
|
+
name,
|
|
2403
|
+
aliases: Object.freeze([...aliases]),
|
|
2404
|
+
source: "command" as const,
|
|
2405
|
+
valueType,
|
|
2406
|
+
allowNo: BEEPER_CLI_V062_ALLOW_NO_FLAGS.has(commandFlagCoordinate(command, name)),
|
|
2407
|
+
required,
|
|
2408
|
+
multiple,
|
|
2409
|
+
enum: Object.freeze([...enumValues]),
|
|
2410
|
+
default: surfaceDefault(command, name, defaultValue),
|
|
2411
|
+
decision: surfaceItemDecision(command, name, decision),
|
|
2412
|
+
})));
|
|
2413
|
+
return Object.freeze({
|
|
2414
|
+
path: Object.freeze(command.split(" ")),
|
|
2415
|
+
provenance,
|
|
2416
|
+
profileAuthority: command === "targets tunnel" ? "jit-plugin-source" : "tagged-source",
|
|
2417
|
+
package: command === "targets tunnel" ? "@beeper/cli-plugin-cloudflare" : "@beeper/cli",
|
|
2418
|
+
version: command === "targets tunnel" ? "^0.6.0" : "0.6.1",
|
|
2419
|
+
versionKind: command === "targets tunnel" ? "range" : "exact",
|
|
2420
|
+
registered: exposure === "public-manual" && command !== "targets tunnel",
|
|
2421
|
+
publicManual: exposure === "public-manual",
|
|
2422
|
+
generatedCanonical: exposure === "public-manual" && command !== "targets tunnel",
|
|
2423
|
+
upstreamReportedMutates,
|
|
2424
|
+
reviewedEffect: reviewedEffect(command, upstreamReportedMutates),
|
|
2425
|
+
arguments: arguments_,
|
|
2426
|
+
flags,
|
|
2427
|
+
decision,
|
|
2428
|
+
pathSemanticInputs: commandPathSemanticInputs(command),
|
|
2429
|
+
output: commandOutput(command, output, decision),
|
|
2430
|
+
conditionalInputs: commandInputRules(command),
|
|
2431
|
+
reconciliation: commandReconciliation(command, decision),
|
|
2432
|
+
});
|
|
2433
|
+
}
|
|
2434
|
+
|
|
2435
|
+
const additionalDecision = (disposition: LocalCliSurfaceDecisionV1["disposition"], rationale: string, replacement: string | null = null) =>
|
|
2436
|
+
surfaceDecision(disposition, rationale, null, replacement);
|
|
2437
|
+
|
|
2438
|
+
function oclifPluginEntry(
|
|
2439
|
+
path: string,
|
|
2440
|
+
canonicalTarget: string | null = null,
|
|
2441
|
+
): LocalCliSurfaceAdditionalEntryV1 {
|
|
2442
|
+
return Object.freeze({
|
|
2443
|
+
path: Object.freeze(path.split(" ")),
|
|
2444
|
+
provenance: "dynamic-plugin",
|
|
2445
|
+
profileAuthority: "framework-runtime",
|
|
2446
|
+
canonicalTarget: canonicalTarget === null
|
|
2447
|
+
? null
|
|
2448
|
+
: Object.freeze(canonicalTarget.split(" ")),
|
|
2449
|
+
package: "@oclif/plugin-plugins",
|
|
2450
|
+
version: null,
|
|
2451
|
+
versionKind: null,
|
|
2452
|
+
registered: true,
|
|
2453
|
+
publicManual: false,
|
|
2454
|
+
rationale: canonicalTarget === null
|
|
2455
|
+
? "Callable oclif plugin-management command omitted from the generated command map."
|
|
2456
|
+
: "Callable oclif plugin-management alias omitted from the generated command map.",
|
|
2457
|
+
decision: additionalDecision(
|
|
2458
|
+
"unsupported",
|
|
2459
|
+
"Plugin inspection and lifecycle authority are permanently outside the provider.",
|
|
2460
|
+
),
|
|
2461
|
+
});
|
|
2462
|
+
}
|
|
2463
|
+
|
|
2464
|
+
const BEEPER_CLI_V062_ADDITIONAL_ENTRIES: readonly LocalCliSurfaceAdditionalEntryV1[] = Object.freeze([
|
|
2465
|
+
...[
|
|
2466
|
+
["accounts", "accounts list", "accounts.list"],
|
|
2467
|
+
["accounts chats", "chats list", "messaging.list"],
|
|
2468
|
+
["bridges", "bridges list", "bridges.list"],
|
|
2469
|
+
["chats", "chats list", "messaging.list"],
|
|
2470
|
+
["contacts", "contacts list", "contacts.list"],
|
|
2471
|
+
["targets", "targets list", null],
|
|
2472
|
+
].map(([path, target, operation]) => Object.freeze({
|
|
2473
|
+
path: Object.freeze(path!.split(" ")),
|
|
2474
|
+
provenance: "built-in-alias" as const,
|
|
2475
|
+
profileAuthority: "tagged-source" as const,
|
|
2476
|
+
canonicalTarget: Object.freeze(target!.split(" ")),
|
|
2477
|
+
package: "@beeper/cli",
|
|
2478
|
+
version: "0.6.1",
|
|
2479
|
+
versionKind: "exact" as const,
|
|
2480
|
+
registered: true,
|
|
2481
|
+
publicManual: false,
|
|
2482
|
+
rationale: "Generated registration alias; never accepted as raw caller syntax.",
|
|
2483
|
+
decision: operation === null
|
|
2484
|
+
? additionalDecision("unsupported", "Target lifecycle aliases remain outside provider authority.")
|
|
2485
|
+
: surfaceDecision("absorbed", "The canonical semantic operation absorbs this generated alias.", operation),
|
|
2486
|
+
})),
|
|
2487
|
+
Object.freeze({
|
|
2488
|
+
path: Object.freeze(["autocomplete"]), provenance: "built-in-hidden" as const,
|
|
2489
|
+
profileAuthority: "tagged-source" as const,
|
|
2490
|
+
canonicalTarget: null, package: "@beeper/cli", version: "0.6.1", versionKind: "exact" as const, registered: true,
|
|
2491
|
+
publicManual: false, rationale: "Hidden generated canonical command omitted from the public manual.",
|
|
2492
|
+
decision: additionalDecision("internal", "Wrench capability metadata replaces CLI autocomplete."),
|
|
2493
|
+
}),
|
|
2494
|
+
Object.freeze({
|
|
2495
|
+
path: Object.freeze(["help"]), provenance: "built-in-hidden" as const,
|
|
2496
|
+
profileAuthority: "framework-runtime" as const,
|
|
2497
|
+
canonicalTarget: null, package: "@oclif/core", version: null, versionKind: null, registered: true,
|
|
2498
|
+
publicManual: false, rationale: "Callable framework help behavior is outside the generated source command map.",
|
|
2499
|
+
decision: additionalDecision("internal", "Wrench capability metadata replaces CLI help."),
|
|
2500
|
+
}),
|
|
2501
|
+
oclifPluginEntry("plugins inspect"),
|
|
2502
|
+
oclifPluginEntry("plugins install"),
|
|
2503
|
+
oclifPluginEntry("plugins add", "plugins install"),
|
|
2504
|
+
oclifPluginEntry("plugins link"),
|
|
2505
|
+
oclifPluginEntry("plugins reset"),
|
|
2506
|
+
oclifPluginEntry("plugins uninstall"),
|
|
2507
|
+
oclifPluginEntry("plugins unlink", "plugins uninstall"),
|
|
2508
|
+
oclifPluginEntry("plugins remove", "plugins uninstall"),
|
|
2509
|
+
oclifPluginEntry("plugins update"),
|
|
2510
|
+
Object.freeze({
|
|
2511
|
+
path: Object.freeze(["<dynamic-plugin-command>"]), provenance: "dynamic-plugin" as const,
|
|
2512
|
+
profileAuthority: "framework-runtime" as const,
|
|
2513
|
+
canonicalTarget: null, package: null, version: null, versionKind: null, registered: false,
|
|
2514
|
+
publicManual: false, rationale: "Installed oclif plugins may add unpinned commands dynamically.",
|
|
2515
|
+
decision: additionalDecision("unsupported", "Dynamic plugin commands are permanently outside provider authority."),
|
|
2516
|
+
}),
|
|
2517
|
+
Object.freeze({
|
|
2518
|
+
path: Object.freeze(["messages", "react"]), provenance: "documented-only" as const,
|
|
2519
|
+
profileAuthority: "documentation" as const,
|
|
2520
|
+
canonicalTarget: null, package: null, version: null, versionKind: null, registered: false,
|
|
2521
|
+
publicManual: false, rationale: "A stale documentation alias is absent from the tagged source registration.",
|
|
2522
|
+
decision: additionalDecision("replaced", "Use reactions.set through the canonical send react mapping.", "reactions.set"),
|
|
2523
|
+
}),
|
|
2524
|
+
Object.freeze({
|
|
2525
|
+
path: Object.freeze(["messages", "unreact"]), provenance: "documented-only" as const,
|
|
2526
|
+
profileAuthority: "documentation" as const,
|
|
2527
|
+
canonicalTarget: null, package: null, version: null, versionKind: null, registered: false,
|
|
2528
|
+
publicManual: false, rationale: "A stale documentation alias is absent from the tagged source registration.",
|
|
2529
|
+
decision: additionalDecision("replaced", "Use reactions.set through the canonical send unreact mapping.", "reactions.set"),
|
|
2530
|
+
}),
|
|
2531
|
+
]);
|
|
2532
|
+
|
|
2533
|
+
const BEEPER_CLI_V062_COMMANDS = Object.freeze(
|
|
2534
|
+
[
|
|
2535
|
+
...BEEPER_CLI_V062_COMMAND_PROFILES.map((profile) => commandDefinition(profile)),
|
|
2536
|
+
commandDefinition(BEEPER_CLI_V062_PRIVATE_COMMAND_PROFILE, "source-only-private"),
|
|
2537
|
+
],
|
|
2538
|
+
);
|
|
2539
|
+
|
|
2540
|
+
if (
|
|
2541
|
+
BEEPER_CLI_V062_COMMANDS.length !== 102
|
|
2542
|
+
|| BEEPER_CLI_V062_COMMANDS.filter((command) => command.publicManual).length !== 101
|
|
2543
|
+
|| BEEPER_CLI_V062_COMMANDS.filter((command) => command.generatedCanonical).length !== 100
|
|
2544
|
+
|| BEEPER_CLI_V062_COMMANDS.filter((command) => command.registered).length + 7 !== 107
|
|
2545
|
+
) throw new Error("Beeper v0.6.2 surface cardinality drifted from reviewed provenance");
|
|
2546
|
+
|
|
2547
|
+
export const BEEPER_CLI_V062_SURFACE_CONTRACT = defineLocalCliSurfaceContractV1({
|
|
2548
|
+
schemaVersion: 1,
|
|
2549
|
+
format: "wrench.local-cli-surface",
|
|
2550
|
+
surface: "beeper",
|
|
2551
|
+
executable: {
|
|
2552
|
+
id: BEEPER_CLI_PIN.id,
|
|
2553
|
+
implementation: BEEPER_CLI_PIN.implementation,
|
|
2554
|
+
releaseVersion: BEEPER_CLI_PIN.version,
|
|
2555
|
+
releaseDate: "2026-05-18",
|
|
2556
|
+
releaseTag: "v0.6.2",
|
|
2557
|
+
releaseCommit: BEEPER_CLI_PIN.commit,
|
|
2558
|
+
releaseManifestSha256: BEEPER_CLI_PIN.releaseManifestSha256,
|
|
2559
|
+
runtimeReportedName: "@beeper/cli",
|
|
2560
|
+
runtimeReportedVersion: "0.6.2",
|
|
2561
|
+
artifacts: Object.freeze(BEEPER_CLI_PIN.artifacts.map((artifact) => Object.freeze({
|
|
2562
|
+
platform: artifact.platform,
|
|
2563
|
+
arch: artifact.arch,
|
|
2564
|
+
archiveSha256: artifact.archiveSha256,
|
|
2565
|
+
executableSha256: artifact.executableSha256,
|
|
2566
|
+
}))),
|
|
2567
|
+
},
|
|
2568
|
+
source: {
|
|
2569
|
+
package: "@beeper/cli",
|
|
2570
|
+
packagePath: "packages/cli/package.json",
|
|
2571
|
+
packageDeclaredVersion: "0.6.1",
|
|
2572
|
+
versionDiscrepancy: "Official v0.6.2 binaries.json and the exact executable report 0.6.2, while package.json at tag a416af06023449a87312dc11e54643fd9dc94b8c declares 0.6.1; executable runtime identity remains authoritative.",
|
|
2573
|
+
generatedManualSha256: "18a11300ae7fe321ace0c9c5bbdfd062f114c91add7d64e256b78c2e89e328a9",
|
|
2574
|
+
generatedManualIncludesFlagsAndDefaults: false,
|
|
2575
|
+
generatedManualEntries: 101,
|
|
2576
|
+
generatedCanonicalEntries: 101,
|
|
2577
|
+
registeredKeys: 107,
|
|
2578
|
+
},
|
|
2579
|
+
sdk: BEEPER_DESKTOP_API_PIN,
|
|
2580
|
+
runtime: {
|
|
2581
|
+
providerPluginId: "beeper-linked-device",
|
|
2582
|
+
providerPluginVersion: "2.4.0",
|
|
2583
|
+
adapterId: "beeper-local",
|
|
2584
|
+
adapterVersion: "2.4.0",
|
|
2585
|
+
operationContractVersions: BEEPER_LOCAL_OPERATION_CONTRACT_VERSIONS,
|
|
2586
|
+
operationInputTypes: BEEPER_LOCAL_OPERATION_INPUT_TYPES,
|
|
2587
|
+
target: BEEPER_DESKTOP_TARGET,
|
|
2588
|
+
realm: "Fixed local Beeper Desktop realm with a verified loopback endpoint, exact account subject, isolated config/cache/plugins, and no caller endpoint, target, path, or environment.",
|
|
2589
|
+
compatibility: "The exact CLI executable identity is enforced and its reviewed API schema is @beeper/desktop-api 5.0.0 at b9c1714410139c2139b597338cd002d785653e85; callers cannot select a Desktop channel or protocol.",
|
|
2590
|
+
},
|
|
2591
|
+
globalFlags: BEEPER_CLI_V062_GLOBAL_FLAGS,
|
|
2592
|
+
commands: BEEPER_CLI_V062_COMMANDS,
|
|
2593
|
+
additionalEntries: BEEPER_CLI_V062_ADDITIONAL_ENTRIES,
|
|
2594
|
+
});
|
|
2595
|
+
|
|
2596
|
+
export const BEEPER_CLI_V062_UPSTREAM_SURFACE_SHA256 =
|
|
2597
|
+
"74297df1af30fe89cf1596a0670983e79cf85c0768c2f68e9bc3d386be640836" as const;
|
|
2598
|
+
export const BEEPER_CLI_V062_CLASSIFICATION_SHA256 =
|
|
2599
|
+
"bcd411af1544e5cd618cd3c04f2852a797bd804d92b7fe4cd226374b61c57d08" as const;
|
|
2600
|
+
export const BEEPER_CLI_V062_SEMANTIC_PROFILES_SHA256 =
|
|
2601
|
+
"fb7ea5f70f004dd8090c3e6e0996bfa00b0bab8ea5639203e2d1027602450ffe" as const;
|
|
2602
|
+
export const BEEPER_CLI_V062_WHOLE_SURFACE_SHA256 =
|
|
2603
|
+
"72201ac5eb3532f7c159583f19009f547d7d313e86388466b57c135bd2dc4944" as const;
|
|
2604
|
+
|
|
2605
|
+
export const BEEPER_CLI_V062_PUBLIC_MANUAL_SEMANTIC_PROFILE_SHA256 = Object.freeze({
|
|
2606
|
+
"setup": "cd432e2649e5724d70398e739a2d1c0c21557a23820aaa14562575a5fe689406",
|
|
2607
|
+
"install desktop": "478f4cc022b1d51d4016a319efea17e4523c55e3168560c99fdf7779347ae78a",
|
|
2608
|
+
"install server": "999b4aa7576c772ef4f2fa21076064885d67ab53354ab2446399562e1ad8af89",
|
|
2609
|
+
"targets list": "b1db92e025f67dda8d6bcfc642fa591ae7a2e4d3555c5aee7af5a0b94cfe94f2",
|
|
2610
|
+
"bridges list": "52c8c245a2088a781f0021232c0baaf6bf14fa9246dbfb192c6c57ef3f5d11af",
|
|
2611
|
+
"bridges show": "e23a6027d7536389501d008da503320d58b421c1f132c9a7d402b1d7b031658e",
|
|
2612
|
+
"targets add desktop": "fbfa2c790848792b519b11d45285c5ebf42abe3f46fc6b0a7bca231b9d7408e0",
|
|
2613
|
+
"targets add server": "488d26c83f2261dbe04195fb429d04019977c5cb21d092db8a4765afe68812bf",
|
|
2614
|
+
"targets add remote": "db56749d593d43b78922aab5ff1456f617d6dbdbf7f06d44761c2a5133c80da3",
|
|
2615
|
+
"targets use": "4617f7e87a68149c5a96624567ce9dc1501d4e31c86920eb64e3f9c536553eeb",
|
|
2616
|
+
"targets show": "a48dacbc13afc37f1a58c5b2178d22c5ae049bff1f2701e1e3e72a9e4088259b",
|
|
2617
|
+
"targets status": "a70e6eef1793a561914c49a8bcfa118be22b23244a24e622360ad205c9232cc2",
|
|
2618
|
+
"targets start": "4bb79ca30f3564cab02a1c5ec173ee016a04bafe263e38ddbf7aa778747d5f48",
|
|
2619
|
+
"targets stop": "65ebf81b3855a7fe5585f94b0cd6dc042ae6e5d92eb3e299c5076e9f84ee24f6",
|
|
2620
|
+
"targets restart": "dd1281aad6369e7d68185d6a949449d0b0ce8ff395f9d01570a8bf5ed78387e7",
|
|
2621
|
+
"targets logs": "808f0e13386372ef9580575b7a2c9110d540209407cb14dc778faba538a51b41",
|
|
2622
|
+
"targets enable": "20baea46e6d8b5f5f8ebc5dc0a8bfe9ff7836ba7b1991c8be2af8ee373cbbe1b",
|
|
2623
|
+
"targets disable": "0d5695503942f090b118cb445b64556713536d6deeb32ba582e8a1f6f48135fb",
|
|
2624
|
+
"targets remove": "0ba9940afc0ead36641b1539dea4f97dd920d8064067839ddb59b5bda874a2e0",
|
|
2625
|
+
"targets tunnel": "a96cdc41c42ea64777782ca96b81cbe630928df35067cdcdd903c506e7bdb5a8",
|
|
2626
|
+
"auth status": "e08a0176cdc9d9dd06c75a6082b64c8e9b5f68075b58e9c2e61fe2f99bbb6044",
|
|
2627
|
+
"auth logout": "25a8d13779a8fc86c13cf8dbd9c1cfc8d68694fed1a7ab9f872cb648dee366ad",
|
|
2628
|
+
"auth email start": "d6d355b494860b57a8ae00d3562c9bd1054c6c70b8f0bf478abff88497ef2334",
|
|
2629
|
+
"auth email response": "0842a657dbfbeeeb7744332a60ec04064e991dab501fd47f92bc5596cfd7fbfb",
|
|
2630
|
+
"verify": "b16da79fe75c014faf681d36eff05c4e81e274c8ca29d576592d45f35078503a",
|
|
2631
|
+
"verify status": "abd89afdf112ac6aeaa97b9843bc2f81e1a52f6e92e4c42ad8e7a035ae73198b",
|
|
2632
|
+
"verify approve": "f724a957287b638b9f27a6b02897260c4ece685113448ebd69e6ff793038b2a0",
|
|
2633
|
+
"verify recovery-key": "db9d69676807ba959731125bc95ffb6bef735bddb8cd5b5401cb96d8678e4dfb",
|
|
2634
|
+
"verify reset-recovery-key": "e59d2b06f2feaa8008f0e285ac4ec7d7a75a18fd66ca39ddce6d9914c28b6c98",
|
|
2635
|
+
"verify cancel": "40197355731efde46280b13a2b227f13fb9ebc33cb80712087380d8d805944f0",
|
|
2636
|
+
"verify list": "85de0df592d523d3c127f8c33adb2855fc49f43d470d2675d7854401c975ec9e",
|
|
2637
|
+
"verify start": "3c0ef55faaf49552315322b4b521290185c36774a8d87a0e2f2614392407e2b7",
|
|
2638
|
+
"verify show": "08a4a85bdfc3a900c8d9f49564fdaa3d4d37046eace6ed665c8b767867b83afc",
|
|
2639
|
+
"verify sas": "d6fd6130fd9eee6d51c193989184d2115b171993a304cf4707c0aaecafd2c3e0",
|
|
2640
|
+
"verify sas-confirm": "0bc55ff8df6068cb6e3fae1ed136dba94442bae00fc4a95c56a9f206fa3a9d38",
|
|
2641
|
+
"verify qr-scan": "d618900cb8548d4abf09d29cd577c7aedbaa660dc6978e28fababe7ea520bf25",
|
|
2642
|
+
"verify qr-confirm": "4dc005579ee36737d6c03f0f9fdba18274fbf257bc0921bf2592421ec11b3464",
|
|
2643
|
+
"accounts list": "325bb478a9fc344a1982c130c6fddecd0c34371052409df6ba05ba59248e3d38",
|
|
2644
|
+
"accounts add": "fa8b9c9e695bf6bb3bf6b122773cc633e8fa6c817760314ecd44a848a384eb17",
|
|
2645
|
+
"accounts show": "234fef2ec2a24ca7254e3d2b77f0e7179f71bcc67b4e9fc387ae82122a5d6c7a",
|
|
2646
|
+
"accounts remove": "6188c67a6ded6d6b7d2662295ead8fab65c6509614c3d39ebc301f7e7cf5eaf0",
|
|
2647
|
+
"accounts use": "bd8dc05db1633966700bf488c56ca6710be4766ea7576c11dd0fe7431d576275",
|
|
2648
|
+
"chats list": "cc71df6c537ae4878cffc5c3de743481b2daabf47459ab1a52fabcae14d65a92",
|
|
2649
|
+
"chats search": "055097bbd9b156f02a5c5177d566a1ed611477032b0f279dbfaabc5830afc56b",
|
|
2650
|
+
"chats show": "b090de1ce0486fc8217264baf46d6b312af7d0b063a457037eb14201ef4d77e7",
|
|
2651
|
+
"chats start": "7691cf0d6ac68081ce574edbc9ab2c9e5bd2ad6f82902d04d82590bc54cfc91a",
|
|
2652
|
+
"chats archive": "fcfe1fb838c27a65a27b116394911bee2bc2f641e7ae2f7c5904388efcafede1",
|
|
2653
|
+
"chats unarchive": "d371d735e983d85ae235a955e06cb0542ccb6bcd7e00155919938b948985f30f",
|
|
2654
|
+
"chats pin": "5f037961e20915f06ede7fd02b809591ff81ca139d1c5213170f8a14fb032d12",
|
|
2655
|
+
"chats unpin": "02bda6e96362e9037931fb53733ce6901a61f6025aaf01de81fc10d103995e58",
|
|
2656
|
+
"chats mute": "e56e2b2e43a037765db6bb7ad031a6e927bee1d6e4dd23ec4068488dbb7a4ba5",
|
|
2657
|
+
"chats unmute": "f39c1f039777c9771bc97897b0542f95e626c0f34c3c9c854b4da4c740e455d4",
|
|
2658
|
+
"chats mark-read": "a3b738d7aba9737d892a62b37d922a7f2023c5bf0929f9283f2de4c3fb4cdfe4",
|
|
2659
|
+
"chats mark-unread": "28520f6500ae501537822360ad25b58ff1d48df6adaddd65d9e5b56d557f9e0f",
|
|
2660
|
+
"chats priority": "759b966b88e8d1b5236a77405568ff3b659cccbbacf1dbf9dfcdd64e46817c56",
|
|
2661
|
+
"chats notify-anyway": "b527d36467f10fbad9913742846605acbb6e1024e8a07c45ba19658a49bd6ab2",
|
|
2662
|
+
"chats rename": "5e45d6f1bb18fcc0e8f13097e6b2e286ae6bc2cf925f4dba90c2e60002968e48",
|
|
2663
|
+
"chats description": "158fd28b2573c3da43160e04b8749ace7ad552d80aef5a61c912ed33931a69c7",
|
|
2664
|
+
"chats avatar": "8e16f4edfd5b780ce0ee3968416864d43f19603f17999bb4e2dd99a6b873440f",
|
|
2665
|
+
"chats draft": "bb245a9e50012cca9dbcd65dc2197b1babc6461ce1f0ab61ed53b6322fea02c6",
|
|
2666
|
+
"chats disappear": "9d2b7b70d11cb1c749007d2f1c6a2b2298a525ba88fe1790e9dacc97490996b6",
|
|
2667
|
+
"chats remind": "b5133c8f6a103234344c2203637ceea98c65c3b97f805e97e169f475bd0e35fd",
|
|
2668
|
+
"chats unremind": "0dd3dbfe98dba426c286ca08a73753a4c0a5dfff1ed1ac397e964b336d46b4db",
|
|
2669
|
+
"chats focus": "a53f3dc9bbd93e05adfee9f9f6540e417f8fd5030b1c9d1789fc495e5f625931",
|
|
2670
|
+
"messages list": "e691ea9f57b65cdfb9e2576ded7a502e24e61d9522122d9440fea386c9c22e6b",
|
|
2671
|
+
"messages search": "a19a5290708c3c43aa1e1f84fb5d3ba2664575aa32afc2784042bab2314e72a9",
|
|
2672
|
+
"messages show": "b8436a8e46bf761062850f6cc8bf5777a3ae00166631cb77e1cd21ea122be67f",
|
|
2673
|
+
"messages context": "68f0a77a8b98435c8aee0dccd603f9ee8d21c83c97b5face47ec582cd525c711",
|
|
2674
|
+
"messages edit": "95380f389feaac7cb93f27be89eed617d997ce98f9a3bc1972306d2ccf353339",
|
|
2675
|
+
"messages delete": "305e8826df70ebde061dd7099af33b5eec203453560a50a5a1c008ba175a0ecc",
|
|
2676
|
+
"messages export": "7a0c90728af3b602726d59fd2f8ac9352db4fae8ccb440515daab5b37955d2ce",
|
|
2677
|
+
"send text": "05ef4e97a2611f3e350f54fe34ec31c7f34873de1da060450c7bc7ae9308f32f",
|
|
2678
|
+
"send file": "770a6ff88d6e28d1431d2435bac54c2a76a344611e7be5a442f5d1657a005ebf",
|
|
2679
|
+
"send react": "b46e199df693cb824bf109dbd1aa5e847445549096c75e6e04c81ea75f1895b6",
|
|
2680
|
+
"send sticker": "3da1e6a17df385f098a4a9b6d7e86ab7dd1c7c6d2c0ca5bee9f5de1f38b0b741",
|
|
2681
|
+
"send unreact": "f2fe46e5854571eecbde82fc59663f99b44b4484bffda7c92b12614de709b2d3",
|
|
2682
|
+
"send voice": "966b39e192073fc581b8efece9fa744f169b51361e9a8223a84d3c9251e08c24",
|
|
2683
|
+
"presence": "2d6e067b5d572f7d2524c3e4fdf41300ee82b9af03116f17b098a6afcb9fc9b7",
|
|
2684
|
+
"contacts list": "77b752f6e0a37b68cf31cd85c554a5bdec51c20b0239a8a13ef111f7d30b9c6c",
|
|
2685
|
+
"contacts search": "8b33f02bb3011f2dd304be5f9f5ccec2303833ae739dc4e96dc3691d6354032b",
|
|
2686
|
+
"contacts show": "724c25f80b5f58bbfb4b858187d5f785e946853e6a2f355bd14f8b8bc2052c92",
|
|
2687
|
+
"media download": "47f7e05839640b88c42e719b44f3d8f9632ddb7f5abbb1ae2f4eee32341d4f88",
|
|
2688
|
+
"export": "3a8e13c94c79c7352fc204e7f73c103a55e871befe6dcf943023c832b7119d52",
|
|
2689
|
+
"watch": "7839bc4e2a9e905f883a372895fbe40aecfb055f3ed642c4e800dcf4fffb9c03",
|
|
2690
|
+
"rpc": "2399aaaab78a0129bcac2fd0e39c26be1e2fb97ce3ed0c40b631474ca1bd4d9d",
|
|
2691
|
+
"man": "5f9eb21048ecf86926e64751865c28aa76f3673fc29b41ef5f8273801377cb2a",
|
|
2692
|
+
"doctor": "18b285f3054ac9f1ee445827540cc26d1a5e367b230d2015d502233d1c5946aa",
|
|
2693
|
+
"status": "73db0a7f0fd5a33620589d8b6f6e2f9a86310371cbc265a28af6e0901373d8a7",
|
|
2694
|
+
"docs": "1d44df3d2ad09e8986ec7937c312c3ca06e90ca02693846b64a001b65edaa3bf",
|
|
2695
|
+
"version": "706d4759642840fa4cc99a80cb4e73f3020cdcf71aee8d0537341bd3f6746c79",
|
|
2696
|
+
"completion": "707719d9c820895ad1cdb0590501bc4656e966c54edb22d9bcba02358395c2d0",
|
|
2697
|
+
"plugins": "788ea2111f878f710c8e7c9ed5ac163a58d30831e5a9a67f2c1c1aa88f767f46",
|
|
2698
|
+
"plugins available": "b579adca88123da1be0da8c3df02269b629c7f287086ae072e858d79c1e49198",
|
|
2699
|
+
"update": "c7f6cf4ef3a19e4161a428b993a1527836c864adb4e812f8392c7cfbada76228",
|
|
2700
|
+
"config get": "4568bc28c22e82ed02429a59b38a47d68635a3e599570ad9a4b16d5d1bc4f9bc",
|
|
2701
|
+
"config set": "3708ca19869aa056d30c43b2c7368d13168255f73474d7ecea6a38d5d6f748ae",
|
|
2702
|
+
"config path": "7c45a2996a9d8b8da6ccc7691ec98c378d7cc3494f2929a0930b98e5e037ebbe",
|
|
2703
|
+
"config reset": "4f8f09e26043c8d7770dda894ab17289abdb41ab4881b90d3a287ca548a15374",
|
|
2704
|
+
"api get": "677eaba3c3008fd86da6920d6086a166de702c6a650b7499c54b5264300a65e5",
|
|
2705
|
+
"api post": "79a8826a3386036d10d2be46ef7eb87a0be769214e032ca909b3758916aecc5a",
|
|
2706
|
+
"api request": "9158437c08a2504cd1e7df43aa21c253052ea5f2afbefa7b7e0a01c0ce55746e",
|
|
2707
|
+
} as const satisfies Readonly<Record<keyof typeof BEEPER_CLI_COMMAND_COVERAGE, string>>);
|
|
2708
|
+
|
|
2709
|
+
export const BEEPER_CLI_V062_SOURCE_ONLY_COMPLETE_SEMANTIC_PROFILE_SHA256 =
|
|
2710
|
+
"917b94060ef7f99a07843c15d1eee58bdeb4f53797a14157ebf5e54fb72350b2" as const;
|
|
2711
|
+
|
|
2712
|
+
if (
|
|
2713
|
+
BEEPER_CLI_V062_SURFACE_CONTRACT.digests.upstreamSurfaceSha256
|
|
2714
|
+
!== BEEPER_CLI_V062_UPSTREAM_SURFACE_SHA256
|
|
2715
|
+
|| BEEPER_CLI_V062_SURFACE_CONTRACT.digests.classificationSha256
|
|
2716
|
+
!== BEEPER_CLI_V062_CLASSIFICATION_SHA256
|
|
2717
|
+
|| BEEPER_CLI_V062_SURFACE_CONTRACT.digests.semanticProfilesSha256
|
|
2718
|
+
!== BEEPER_CLI_V062_SEMANTIC_PROFILES_SHA256
|
|
2719
|
+
|| BEEPER_CLI_V062_SURFACE_CONTRACT.digests.wholeSurfaceSha256
|
|
2720
|
+
!== BEEPER_CLI_V062_WHOLE_SURFACE_SHA256
|
|
2721
|
+
) throw new Error(`Beeper v0.6.2 reviewed surface digest drifted: ${JSON.stringify(
|
|
2722
|
+
BEEPER_CLI_V062_SURFACE_CONTRACT.digests,
|
|
2723
|
+
)}`);
|
|
2724
|
+
|
|
2725
|
+
for (const command of BEEPER_CLI_V062_SURFACE_CONTRACT.commands) {
|
|
2726
|
+
const path = command.path.join(" ");
|
|
2727
|
+
const reviewed = command.publicManual
|
|
2728
|
+
? BEEPER_CLI_V062_PUBLIC_MANUAL_SEMANTIC_PROFILE_SHA256[
|
|
2729
|
+
path as keyof typeof BEEPER_CLI_V062_PUBLIC_MANUAL_SEMANTIC_PROFILE_SHA256
|
|
2730
|
+
]
|
|
2731
|
+
: path === "_complete"
|
|
2732
|
+
? BEEPER_CLI_V062_SOURCE_ONLY_COMPLETE_SEMANTIC_PROFILE_SHA256
|
|
2733
|
+
: undefined;
|
|
2734
|
+
if (reviewed !== command.semanticProfileSha256) {
|
|
2735
|
+
throw new Error(
|
|
2736
|
+
`Beeper v0.6.2 semantic profile digest drifted for ${path}: ${command.semanticProfileSha256}`,
|
|
2737
|
+
);
|
|
2738
|
+
}
|
|
2739
|
+
}
|