loudmouth-ai 0.1.0 → 0.2.0
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/README.md +148 -77
- package/dist/build-info.json +3 -3
- package/extensions/package.json +6 -0
- package/package.json +1 -1
- package/skills/autopilot/SKILL.md +179 -0
- package/skills/goals/SKILL.md +189 -0
- package/skills/wordpress/SKILL.md +232 -0
- package/extensions/bluebubbles/clawdbot.plugin.json +0 -11
- package/extensions/bluebubbles/index.ts +0 -20
- package/extensions/bluebubbles/package.json +0 -33
- package/extensions/bluebubbles/src/accounts.ts +0 -80
- package/extensions/bluebubbles/src/actions.test.ts +0 -651
- package/extensions/bluebubbles/src/actions.ts +0 -403
- package/extensions/bluebubbles/src/attachments.test.ts +0 -346
- package/extensions/bluebubbles/src/attachments.ts +0 -282
- package/extensions/bluebubbles/src/channel.ts +0 -399
- package/extensions/bluebubbles/src/chat.test.ts +0 -462
- package/extensions/bluebubbles/src/chat.ts +0 -354
- package/extensions/bluebubbles/src/config-schema.ts +0 -51
- package/extensions/bluebubbles/src/media-send.ts +0 -168
- package/extensions/bluebubbles/src/monitor.test.ts +0 -2140
- package/extensions/bluebubbles/src/monitor.ts +0 -2101
- package/extensions/bluebubbles/src/onboarding.ts +0 -340
- package/extensions/bluebubbles/src/probe.ts +0 -127
- package/extensions/bluebubbles/src/reactions.test.ts +0 -393
- package/extensions/bluebubbles/src/reactions.ts +0 -183
- package/extensions/bluebubbles/src/runtime.ts +0 -14
- package/extensions/bluebubbles/src/send.test.ts +0 -809
- package/extensions/bluebubbles/src/send.ts +0 -418
- package/extensions/bluebubbles/src/targets.test.ts +0 -184
- package/extensions/bluebubbles/src/targets.ts +0 -323
- package/extensions/bluebubbles/src/types.ts +0 -127
- package/extensions/copilot-proxy/README.md +0 -24
- package/extensions/copilot-proxy/clawdbot.plugin.json +0 -11
- package/extensions/copilot-proxy/index.ts +0 -142
- package/extensions/copilot-proxy/package.json +0 -11
- package/extensions/google-antigravity-auth/README.md +0 -24
- package/extensions/google-antigravity-auth/clawdbot.plugin.json +0 -11
- package/extensions/google-antigravity-auth/index.ts +0 -437
- package/extensions/google-antigravity-auth/package.json +0 -11
- package/extensions/google-gemini-cli-auth/README.md +0 -35
- package/extensions/google-gemini-cli-auth/clawdbot.plugin.json +0 -11
- package/extensions/google-gemini-cli-auth/index.ts +0 -91
- package/extensions/google-gemini-cli-auth/oauth.test.ts +0 -228
- package/extensions/google-gemini-cli-auth/oauth.ts +0 -580
- package/extensions/google-gemini-cli-auth/package.json +0 -11
- package/extensions/googlechat/clawdbot.plugin.json +0 -11
- package/extensions/googlechat/index.ts +0 -20
- package/extensions/googlechat/package.json +0 -39
- package/extensions/googlechat/src/accounts.ts +0 -133
- package/extensions/googlechat/src/actions.ts +0 -162
- package/extensions/googlechat/src/api.test.ts +0 -62
- package/extensions/googlechat/src/api.ts +0 -259
- package/extensions/googlechat/src/auth.ts +0 -113
- package/extensions/googlechat/src/channel.ts +0 -580
- package/extensions/googlechat/src/monitor.test.ts +0 -27
- package/extensions/googlechat/src/monitor.ts +0 -900
- package/extensions/googlechat/src/onboarding.ts +0 -278
- package/extensions/googlechat/src/runtime.ts +0 -14
- package/extensions/googlechat/src/targets.test.ts +0 -35
- package/extensions/googlechat/src/targets.ts +0 -55
- package/extensions/googlechat/src/types.config.ts +0 -3
- package/extensions/googlechat/src/types.ts +0 -73
- package/extensions/imessage/clawdbot.plugin.json +0 -11
- package/extensions/imessage/index.ts +0 -18
- package/extensions/imessage/package.json +0 -11
- package/extensions/imessage/src/channel.ts +0 -294
- package/extensions/imessage/src/runtime.ts +0 -14
- package/extensions/line/clawdbot.plugin.json +0 -11
- package/extensions/line/index.ts +0 -20
- package/extensions/line/package.json +0 -29
- package/extensions/line/src/card-command.ts +0 -338
- package/extensions/line/src/channel.logout.test.ts +0 -96
- package/extensions/line/src/channel.sendPayload.test.ts +0 -308
- package/extensions/line/src/channel.ts +0 -773
- package/extensions/line/src/runtime.ts +0 -14
- package/extensions/matrix/CHANGELOG.md +0 -54
- package/extensions/matrix/clawdbot.plugin.json +0 -11
- package/extensions/matrix/index.ts +0 -18
- package/extensions/matrix/package.json +0 -36
- package/extensions/matrix/src/actions.ts +0 -185
- package/extensions/matrix/src/channel.directory.test.ts +0 -56
- package/extensions/matrix/src/channel.ts +0 -417
- package/extensions/matrix/src/config-schema.ts +0 -62
- package/extensions/matrix/src/directory-live.ts +0 -175
- package/extensions/matrix/src/group-mentions.ts +0 -61
- package/extensions/matrix/src/matrix/accounts.test.ts +0 -83
- package/extensions/matrix/src/matrix/accounts.ts +0 -63
- package/extensions/matrix/src/matrix/actions/client.ts +0 -53
- package/extensions/matrix/src/matrix/actions/messages.ts +0 -120
- package/extensions/matrix/src/matrix/actions/pins.ts +0 -70
- package/extensions/matrix/src/matrix/actions/reactions.ts +0 -84
- package/extensions/matrix/src/matrix/actions/room.ts +0 -88
- package/extensions/matrix/src/matrix/actions/summary.ts +0 -77
- package/extensions/matrix/src/matrix/actions/types.ts +0 -84
- package/extensions/matrix/src/matrix/actions.ts +0 -15
- package/extensions/matrix/src/matrix/active-client.ts +0 -11
- package/extensions/matrix/src/matrix/client/config.ts +0 -165
- package/extensions/matrix/src/matrix/client/create-client.ts +0 -127
- package/extensions/matrix/src/matrix/client/logging.ts +0 -35
- package/extensions/matrix/src/matrix/client/runtime.ts +0 -4
- package/extensions/matrix/src/matrix/client/shared.ts +0 -169
- package/extensions/matrix/src/matrix/client/storage.ts +0 -131
- package/extensions/matrix/src/matrix/client/types.ts +0 -34
- package/extensions/matrix/src/matrix/client.test.ts +0 -57
- package/extensions/matrix/src/matrix/client.ts +0 -9
- package/extensions/matrix/src/matrix/credentials.ts +0 -103
- package/extensions/matrix/src/matrix/deps.ts +0 -57
- package/extensions/matrix/src/matrix/format.test.ts +0 -34
- package/extensions/matrix/src/matrix/format.ts +0 -22
- package/extensions/matrix/src/matrix/index.ts +0 -11
- package/extensions/matrix/src/matrix/monitor/allowlist.ts +0 -58
- package/extensions/matrix/src/matrix/monitor/auto-join.ts +0 -68
- package/extensions/matrix/src/matrix/monitor/direct.ts +0 -105
- package/extensions/matrix/src/matrix/monitor/events.ts +0 -103
- package/extensions/matrix/src/matrix/monitor/handler.ts +0 -645
- package/extensions/matrix/src/matrix/monitor/index.ts +0 -279
- package/extensions/matrix/src/matrix/monitor/location.ts +0 -83
- package/extensions/matrix/src/matrix/monitor/media.test.ts +0 -103
- package/extensions/matrix/src/matrix/monitor/media.ts +0 -113
- package/extensions/matrix/src/matrix/monitor/mentions.ts +0 -31
- package/extensions/matrix/src/matrix/monitor/replies.ts +0 -96
- package/extensions/matrix/src/matrix/monitor/room-info.ts +0 -58
- package/extensions/matrix/src/matrix/monitor/rooms.ts +0 -43
- package/extensions/matrix/src/matrix/monitor/threads.ts +0 -64
- package/extensions/matrix/src/matrix/monitor/types.ts +0 -39
- package/extensions/matrix/src/matrix/poll-types.test.ts +0 -22
- package/extensions/matrix/src/matrix/poll-types.ts +0 -157
- package/extensions/matrix/src/matrix/probe.ts +0 -70
- package/extensions/matrix/src/matrix/send/client.ts +0 -63
- package/extensions/matrix/src/matrix/send/formatting.ts +0 -92
- package/extensions/matrix/src/matrix/send/media.ts +0 -220
- package/extensions/matrix/src/matrix/send/targets.test.ts +0 -102
- package/extensions/matrix/src/matrix/send/targets.ts +0 -144
- package/extensions/matrix/src/matrix/send/types.ts +0 -109
- package/extensions/matrix/src/matrix/send.test.ts +0 -172
- package/extensions/matrix/src/matrix/send.ts +0 -255
- package/extensions/matrix/src/onboarding.ts +0 -432
- package/extensions/matrix/src/outbound.ts +0 -53
- package/extensions/matrix/src/resolve-targets.ts +0 -89
- package/extensions/matrix/src/runtime.ts +0 -14
- package/extensions/matrix/src/tool-actions.ts +0 -160
- package/extensions/matrix/src/types.ts +0 -95
- package/extensions/mattermost/clawdbot.plugin.json +0 -11
- package/extensions/mattermost/index.ts +0 -18
- package/extensions/mattermost/package.json +0 -25
- package/extensions/mattermost/src/channel.test.ts +0 -43
- package/extensions/mattermost/src/channel.ts +0 -339
- package/extensions/mattermost/src/config-schema.ts +0 -56
- package/extensions/mattermost/src/group-mentions.ts +0 -14
- package/extensions/mattermost/src/mattermost/accounts.ts +0 -115
- package/extensions/mattermost/src/mattermost/client.ts +0 -208
- package/extensions/mattermost/src/mattermost/index.ts +0 -9
- package/extensions/mattermost/src/mattermost/monitor-helpers.ts +0 -150
- package/extensions/mattermost/src/mattermost/monitor.ts +0 -921
- package/extensions/mattermost/src/mattermost/probe.ts +0 -70
- package/extensions/mattermost/src/mattermost/send.ts +0 -217
- package/extensions/mattermost/src/normalize.ts +0 -38
- package/extensions/mattermost/src/onboarding-helpers.ts +0 -42
- package/extensions/mattermost/src/onboarding.ts +0 -187
- package/extensions/mattermost/src/runtime.ts +0 -14
- package/extensions/mattermost/src/types.ts +0 -50
- package/extensions/msteams/CHANGELOG.md +0 -51
- package/extensions/msteams/clawdbot.plugin.json +0 -11
- package/extensions/msteams/index.ts +0 -18
- package/extensions/msteams/package.json +0 -36
- package/extensions/msteams/src/attachments/download.ts +0 -206
- package/extensions/msteams/src/attachments/graph.ts +0 -319
- package/extensions/msteams/src/attachments/html.ts +0 -76
- package/extensions/msteams/src/attachments/payload.ts +0 -22
- package/extensions/msteams/src/attachments/shared.ts +0 -235
- package/extensions/msteams/src/attachments/types.ts +0 -37
- package/extensions/msteams/src/attachments.test.ts +0 -424
- package/extensions/msteams/src/attachments.ts +0 -18
- package/extensions/msteams/src/channel.directory.test.ts +0 -46
- package/extensions/msteams/src/channel.ts +0 -436
- package/extensions/msteams/src/conversation-store-fs.test.ts +0 -88
- package/extensions/msteams/src/conversation-store-fs.ts +0 -155
- package/extensions/msteams/src/conversation-store-memory.ts +0 -45
- package/extensions/msteams/src/conversation-store.ts +0 -41
- package/extensions/msteams/src/directory-live.ts +0 -179
- package/extensions/msteams/src/errors.test.ts +0 -46
- package/extensions/msteams/src/errors.ts +0 -158
- package/extensions/msteams/src/file-consent-helpers.test.ts +0 -234
- package/extensions/msteams/src/file-consent-helpers.ts +0 -73
- package/extensions/msteams/src/file-consent.ts +0 -122
- package/extensions/msteams/src/graph-chat.ts +0 -52
- package/extensions/msteams/src/graph-upload.ts +0 -445
- package/extensions/msteams/src/inbound.test.ts +0 -67
- package/extensions/msteams/src/inbound.ts +0 -38
- package/extensions/msteams/src/index.ts +0 -4
- package/extensions/msteams/src/media-helpers.test.ts +0 -186
- package/extensions/msteams/src/media-helpers.ts +0 -77
- package/extensions/msteams/src/messenger.test.ts +0 -245
- package/extensions/msteams/src/messenger.ts +0 -460
- package/extensions/msteams/src/monitor-handler/inbound-media.ts +0 -123
- package/extensions/msteams/src/monitor-handler/message-handler.ts +0 -629
- package/extensions/msteams/src/monitor-handler.ts +0 -166
- package/extensions/msteams/src/monitor-types.ts +0 -5
- package/extensions/msteams/src/monitor.ts +0 -290
- package/extensions/msteams/src/onboarding.ts +0 -432
- package/extensions/msteams/src/outbound.ts +0 -47
- package/extensions/msteams/src/pending-uploads.ts +0 -87
- package/extensions/msteams/src/policy.test.ts +0 -210
- package/extensions/msteams/src/policy.ts +0 -202
- package/extensions/msteams/src/polls-store-memory.ts +0 -30
- package/extensions/msteams/src/polls-store.test.ts +0 -40
- package/extensions/msteams/src/polls.test.ts +0 -72
- package/extensions/msteams/src/polls.ts +0 -299
- package/extensions/msteams/src/probe.test.ts +0 -57
- package/extensions/msteams/src/probe.ts +0 -99
- package/extensions/msteams/src/reply-dispatcher.ts +0 -128
- package/extensions/msteams/src/resolve-allowlist.ts +0 -277
- package/extensions/msteams/src/runtime.ts +0 -14
- package/extensions/msteams/src/sdk-types.ts +0 -19
- package/extensions/msteams/src/sdk.ts +0 -33
- package/extensions/msteams/src/send-context.ts +0 -156
- package/extensions/msteams/src/send.ts +0 -489
- package/extensions/msteams/src/sent-message-cache.test.ts +0 -16
- package/extensions/msteams/src/sent-message-cache.ts +0 -41
- package/extensions/msteams/src/storage.ts +0 -22
- package/extensions/msteams/src/store-fs.ts +0 -80
- package/extensions/msteams/src/token.ts +0 -19
- package/extensions/nextcloud-talk/clawdbot.plugin.json +0 -11
- package/extensions/nextcloud-talk/index.ts +0 -18
- package/extensions/nextcloud-talk/package.json +0 -30
- package/extensions/nextcloud-talk/src/accounts.ts +0 -154
- package/extensions/nextcloud-talk/src/channel.ts +0 -404
- package/extensions/nextcloud-talk/src/config-schema.ts +0 -78
- package/extensions/nextcloud-talk/src/format.ts +0 -79
- package/extensions/nextcloud-talk/src/inbound.ts +0 -336
- package/extensions/nextcloud-talk/src/monitor.ts +0 -246
- package/extensions/nextcloud-talk/src/normalize.ts +0 -31
- package/extensions/nextcloud-talk/src/onboarding.ts +0 -341
- package/extensions/nextcloud-talk/src/policy.ts +0 -175
- package/extensions/nextcloud-talk/src/room-info.ts +0 -111
- package/extensions/nextcloud-talk/src/runtime.ts +0 -14
- package/extensions/nextcloud-talk/src/send.ts +0 -206
- package/extensions/nextcloud-talk/src/signature.ts +0 -67
- package/extensions/nextcloud-talk/src/types.ts +0 -179
- package/extensions/nostr/CHANGELOG.md +0 -46
- package/extensions/nostr/README.md +0 -136
- package/extensions/nostr/clawdbot.plugin.json +0 -11
- package/extensions/nostr/index.ts +0 -69
- package/extensions/nostr/package.json +0 -31
- package/extensions/nostr/src/channel.test.ts +0 -141
- package/extensions/nostr/src/channel.ts +0 -342
- package/extensions/nostr/src/config-schema.ts +0 -90
- package/extensions/nostr/src/metrics.ts +0 -464
- package/extensions/nostr/src/nostr-bus.fuzz.test.ts +0 -544
- package/extensions/nostr/src/nostr-bus.integration.test.ts +0 -452
- package/extensions/nostr/src/nostr-bus.test.ts +0 -199
- package/extensions/nostr/src/nostr-bus.ts +0 -741
- package/extensions/nostr/src/nostr-profile-http.test.ts +0 -378
- package/extensions/nostr/src/nostr-profile-http.ts +0 -500
- package/extensions/nostr/src/nostr-profile-import.test.ts +0 -120
- package/extensions/nostr/src/nostr-profile-import.ts +0 -259
- package/extensions/nostr/src/nostr-profile.fuzz.test.ts +0 -479
- package/extensions/nostr/src/nostr-profile.test.ts +0 -410
- package/extensions/nostr/src/nostr-profile.ts +0 -242
- package/extensions/nostr/src/nostr-state-store.test.ts +0 -128
- package/extensions/nostr/src/nostr-state-store.ts +0 -226
- package/extensions/nostr/src/runtime.ts +0 -14
- package/extensions/nostr/src/seen-tracker.ts +0 -271
- package/extensions/nostr/src/types.test.ts +0 -161
- package/extensions/nostr/src/types.ts +0 -99
- package/extensions/nostr/test/setup.ts +0 -5
- package/extensions/open-prose/README.md +0 -25
- package/extensions/open-prose/clawdbot.plugin.json +0 -11
- package/extensions/open-prose/index.ts +0 -5
- package/extensions/open-prose/package.json +0 -11
- package/extensions/open-prose/skills/prose/LICENSE +0 -21
- package/extensions/open-prose/skills/prose/SKILL.md +0 -318
- package/extensions/open-prose/skills/prose/alt-borges.md +0 -141
- package/extensions/open-prose/skills/prose/alts/arabian-nights.md +0 -358
- package/extensions/open-prose/skills/prose/alts/borges.md +0 -360
- package/extensions/open-prose/skills/prose/alts/folk.md +0 -322
- package/extensions/open-prose/skills/prose/alts/homer.md +0 -346
- package/extensions/open-prose/skills/prose/alts/kafka.md +0 -373
- package/extensions/open-prose/skills/prose/compiler.md +0 -2967
- package/extensions/open-prose/skills/prose/examples/01-hello-world.prose +0 -4
- package/extensions/open-prose/skills/prose/examples/02-research-and-summarize.prose +0 -6
- package/extensions/open-prose/skills/prose/examples/03-code-review.prose +0 -17
- package/extensions/open-prose/skills/prose/examples/04-write-and-refine.prose +0 -14
- package/extensions/open-prose/skills/prose/examples/05-debug-issue.prose +0 -20
- package/extensions/open-prose/skills/prose/examples/06-explain-codebase.prose +0 -17
- package/extensions/open-prose/skills/prose/examples/07-refactor.prose +0 -20
- package/extensions/open-prose/skills/prose/examples/08-blog-post.prose +0 -20
- package/extensions/open-prose/skills/prose/examples/09-research-with-agents.prose +0 -25
- package/extensions/open-prose/skills/prose/examples/10-code-review-agents.prose +0 -32
- package/extensions/open-prose/skills/prose/examples/11-skills-and-imports.prose +0 -27
- package/extensions/open-prose/skills/prose/examples/12-secure-agent-permissions.prose +0 -43
- package/extensions/open-prose/skills/prose/examples/13-variables-and-context.prose +0 -51
- package/extensions/open-prose/skills/prose/examples/14-composition-blocks.prose +0 -48
- package/extensions/open-prose/skills/prose/examples/15-inline-sequences.prose +0 -23
- package/extensions/open-prose/skills/prose/examples/16-parallel-reviews.prose +0 -19
- package/extensions/open-prose/skills/prose/examples/17-parallel-research.prose +0 -19
- package/extensions/open-prose/skills/prose/examples/18-mixed-parallel-sequential.prose +0 -36
- package/extensions/open-prose/skills/prose/examples/19-advanced-parallel.prose +0 -71
- package/extensions/open-prose/skills/prose/examples/20-fixed-loops.prose +0 -20
- package/extensions/open-prose/skills/prose/examples/21-pipeline-operations.prose +0 -35
- package/extensions/open-prose/skills/prose/examples/22-error-handling.prose +0 -51
- package/extensions/open-prose/skills/prose/examples/23-retry-with-backoff.prose +0 -63
- package/extensions/open-prose/skills/prose/examples/24-choice-blocks.prose +0 -86
- package/extensions/open-prose/skills/prose/examples/25-conditionals.prose +0 -114
- package/extensions/open-prose/skills/prose/examples/26-parameterized-blocks.prose +0 -100
- package/extensions/open-prose/skills/prose/examples/27-string-interpolation.prose +0 -105
- package/extensions/open-prose/skills/prose/examples/28-automated-pr-review.prose +0 -37
- package/extensions/open-prose/skills/prose/examples/28-gas-town.prose +0 -1572
- package/extensions/open-prose/skills/prose/examples/29-captains-chair.prose +0 -218
- package/extensions/open-prose/skills/prose/examples/30-captains-chair-simple.prose +0 -42
- package/extensions/open-prose/skills/prose/examples/31-captains-chair-with-memory.prose +0 -145
- package/extensions/open-prose/skills/prose/examples/33-pr-review-autofix.prose +0 -168
- package/extensions/open-prose/skills/prose/examples/34-content-pipeline.prose +0 -204
- package/extensions/open-prose/skills/prose/examples/35-feature-factory.prose +0 -296
- package/extensions/open-prose/skills/prose/examples/36-bug-hunter.prose +0 -237
- package/extensions/open-prose/skills/prose/examples/37-the-forge.prose +0 -1474
- package/extensions/open-prose/skills/prose/examples/38-skill-scan.prose +0 -455
- package/extensions/open-prose/skills/prose/examples/39-architect-by-simulation.prose +0 -277
- package/extensions/open-prose/skills/prose/examples/40-rlm-self-refine.prose +0 -32
- package/extensions/open-prose/skills/prose/examples/41-rlm-divide-conquer.prose +0 -38
- package/extensions/open-prose/skills/prose/examples/42-rlm-filter-recurse.prose +0 -46
- package/extensions/open-prose/skills/prose/examples/43-rlm-pairwise.prose +0 -50
- package/extensions/open-prose/skills/prose/examples/44-run-endpoint-ux-test.prose +0 -261
- package/extensions/open-prose/skills/prose/examples/45-plugin-release.prose +0 -159
- package/extensions/open-prose/skills/prose/examples/45-run-endpoint-ux-test-with-remediation.prose +0 -637
- package/extensions/open-prose/skills/prose/examples/46-run-endpoint-ux-test-fast.prose +0 -148
- package/extensions/open-prose/skills/prose/examples/46-workflow-crystallizer.prose +0 -225
- package/extensions/open-prose/skills/prose/examples/47-language-self-improvement.prose +0 -356
- package/extensions/open-prose/skills/prose/examples/48-habit-miner.prose +0 -445
- package/extensions/open-prose/skills/prose/examples/49-prose-run-retrospective.prose +0 -210
- package/extensions/open-prose/skills/prose/examples/README.md +0 -391
- package/extensions/open-prose/skills/prose/examples/roadmap/README.md +0 -22
- package/extensions/open-prose/skills/prose/examples/roadmap/iterative-refinement.prose +0 -20
- package/extensions/open-prose/skills/prose/examples/roadmap/parallel-review.prose +0 -18
- package/extensions/open-prose/skills/prose/examples/roadmap/simple-pipeline.prose +0 -17
- package/extensions/open-prose/skills/prose/examples/roadmap/syntax/open-prose-syntax.prose +0 -223
- package/extensions/open-prose/skills/prose/guidance/antipatterns.md +0 -951
- package/extensions/open-prose/skills/prose/guidance/patterns.md +0 -700
- package/extensions/open-prose/skills/prose/guidance/system-prompt.md +0 -180
- package/extensions/open-prose/skills/prose/help.md +0 -143
- package/extensions/open-prose/skills/prose/lib/README.md +0 -105
- package/extensions/open-prose/skills/prose/lib/calibrator.prose +0 -215
- package/extensions/open-prose/skills/prose/lib/cost-analyzer.prose +0 -174
- package/extensions/open-prose/skills/prose/lib/error-forensics.prose +0 -250
- package/extensions/open-prose/skills/prose/lib/inspector.prose +0 -196
- package/extensions/open-prose/skills/prose/lib/profiler.prose +0 -460
- package/extensions/open-prose/skills/prose/lib/program-improver.prose +0 -275
- package/extensions/open-prose/skills/prose/lib/project-memory.prose +0 -118
- package/extensions/open-prose/skills/prose/lib/user-memory.prose +0 -93
- package/extensions/open-prose/skills/prose/lib/vm-improver.prose +0 -243
- package/extensions/open-prose/skills/prose/primitives/session.md +0 -587
- package/extensions/open-prose/skills/prose/prose.md +0 -1235
- package/extensions/open-prose/skills/prose/state/filesystem.md +0 -478
- package/extensions/open-prose/skills/prose/state/in-context.md +0 -380
- package/extensions/open-prose/skills/prose/state/postgres.md +0 -875
- package/extensions/open-prose/skills/prose/state/sqlite.md +0 -572
- package/extensions/qwen-portal-auth/README.md +0 -24
- package/extensions/qwen-portal-auth/clawdbot.plugin.json +0 -11
- package/extensions/qwen-portal-auth/index.ts +0 -127
- package/extensions/qwen-portal-auth/oauth.ts +0 -190
- package/extensions/signal/clawdbot.plugin.json +0 -11
- package/extensions/signal/index.ts +0 -18
- package/extensions/signal/package.json +0 -11
- package/extensions/signal/src/channel.ts +0 -312
- package/extensions/signal/src/runtime.ts +0 -14
- package/extensions/telegram/clawdbot.plugin.json +0 -11
- package/extensions/telegram/index.ts +0 -18
- package/extensions/telegram/package.json +0 -11
- package/extensions/telegram/src/channel.ts +0 -478
- package/extensions/telegram/src/runtime.ts +0 -14
- package/extensions/tlon/README.md +0 -5
- package/extensions/tlon/clawdbot.plugin.json +0 -11
- package/extensions/tlon/index.ts +0 -18
- package/extensions/tlon/package.json +0 -30
- package/extensions/tlon/src/channel.ts +0 -379
- package/extensions/tlon/src/config-schema.test.ts +0 -32
- package/extensions/tlon/src/config-schema.ts +0 -43
- package/extensions/tlon/src/monitor/discovery.ts +0 -71
- package/extensions/tlon/src/monitor/history.ts +0 -87
- package/extensions/tlon/src/monitor/index.ts +0 -501
- package/extensions/tlon/src/monitor/processed-messages.test.ts +0 -24
- package/extensions/tlon/src/monitor/processed-messages.ts +0 -38
- package/extensions/tlon/src/monitor/utils.ts +0 -83
- package/extensions/tlon/src/onboarding.ts +0 -213
- package/extensions/tlon/src/runtime.ts +0 -14
- package/extensions/tlon/src/targets.ts +0 -79
- package/extensions/tlon/src/types.ts +0 -85
- package/extensions/tlon/src/urbit/auth.ts +0 -18
- package/extensions/tlon/src/urbit/http-api.ts +0 -36
- package/extensions/tlon/src/urbit/send.test.ts +0 -38
- package/extensions/tlon/src/urbit/send.ts +0 -127
- package/extensions/tlon/src/urbit/sse-client.test.ts +0 -41
- package/extensions/tlon/src/urbit/sse-client.ts +0 -367
- package/extensions/twitch/CHANGELOG.md +0 -21
- package/extensions/twitch/README.md +0 -89
- package/extensions/twitch/clawdbot.plugin.json +0 -9
- package/extensions/twitch/index.ts +0 -20
- package/extensions/twitch/package.json +0 -20
- package/extensions/twitch/src/access-control.test.ts +0 -489
- package/extensions/twitch/src/access-control.ts +0 -154
- package/extensions/twitch/src/actions.ts +0 -173
- package/extensions/twitch/src/client-manager-registry.ts +0 -115
- package/extensions/twitch/src/config-schema.ts +0 -82
- package/extensions/twitch/src/config.test.ts +0 -88
- package/extensions/twitch/src/config.ts +0 -116
- package/extensions/twitch/src/monitor.ts +0 -257
- package/extensions/twitch/src/onboarding.test.ts +0 -311
- package/extensions/twitch/src/onboarding.ts +0 -411
- package/extensions/twitch/src/outbound.test.ts +0 -373
- package/extensions/twitch/src/outbound.ts +0 -186
- package/extensions/twitch/src/plugin.test.ts +0 -39
- package/extensions/twitch/src/plugin.ts +0 -274
- package/extensions/twitch/src/probe.test.ts +0 -198
- package/extensions/twitch/src/probe.ts +0 -118
- package/extensions/twitch/src/resolver.ts +0 -137
- package/extensions/twitch/src/runtime.ts +0 -14
- package/extensions/twitch/src/send.test.ts +0 -289
- package/extensions/twitch/src/send.ts +0 -136
- package/extensions/twitch/src/status.test.ts +0 -270
- package/extensions/twitch/src/status.ts +0 -176
- package/extensions/twitch/src/token.test.ts +0 -171
- package/extensions/twitch/src/token.ts +0 -87
- package/extensions/twitch/src/twitch-client.test.ts +0 -574
- package/extensions/twitch/src/twitch-client.ts +0 -277
- package/extensions/twitch/src/types.ts +0 -141
- package/extensions/twitch/src/utils/markdown.ts +0 -92
- package/extensions/twitch/src/utils/twitch.ts +0 -78
- package/extensions/twitch/test/setup.ts +0 -7
- package/extensions/voice-call/CHANGELOG.md +0 -72
- package/extensions/voice-call/README.md +0 -134
- package/extensions/voice-call/clawdbot.plugin.json +0 -601
- package/extensions/voice-call/index.ts +0 -497
- package/extensions/voice-call/package.json +0 -16
- package/extensions/voice-call/src/cli.ts +0 -300
- package/extensions/voice-call/src/config.test.ts +0 -204
- package/extensions/voice-call/src/config.ts +0 -493
- package/extensions/voice-call/src/core-bridge.ts +0 -196
- package/extensions/voice-call/src/manager/context.ts +0 -21
- package/extensions/voice-call/src/manager/events.ts +0 -177
- package/extensions/voice-call/src/manager/lookup.ts +0 -33
- package/extensions/voice-call/src/manager/outbound.ts +0 -248
- package/extensions/voice-call/src/manager/state.ts +0 -50
- package/extensions/voice-call/src/manager/store.ts +0 -88
- package/extensions/voice-call/src/manager/timers.ts +0 -86
- package/extensions/voice-call/src/manager/twiml.ts +0 -9
- package/extensions/voice-call/src/manager.test.ts +0 -108
- package/extensions/voice-call/src/manager.ts +0 -876
- package/extensions/voice-call/src/media-stream.test.ts +0 -97
- package/extensions/voice-call/src/media-stream.ts +0 -393
- package/extensions/voice-call/src/providers/base.ts +0 -67
- package/extensions/voice-call/src/providers/index.ts +0 -10
- package/extensions/voice-call/src/providers/mock.ts +0 -168
- package/extensions/voice-call/src/providers/plivo.test.ts +0 -28
- package/extensions/voice-call/src/providers/plivo.ts +0 -504
- package/extensions/voice-call/src/providers/stt-openai-realtime.ts +0 -311
- package/extensions/voice-call/src/providers/telnyx.ts +0 -364
- package/extensions/voice-call/src/providers/tts-openai.ts +0 -264
- package/extensions/voice-call/src/providers/twilio/api.ts +0 -45
- package/extensions/voice-call/src/providers/twilio/webhook.ts +0 -29
- package/extensions/voice-call/src/providers/twilio.test.ts +0 -64
- package/extensions/voice-call/src/providers/twilio.ts +0 -595
- package/extensions/voice-call/src/response-generator.ts +0 -171
- package/extensions/voice-call/src/runtime.ts +0 -205
- package/extensions/voice-call/src/telephony-audio.ts +0 -88
- package/extensions/voice-call/src/telephony-tts.ts +0 -95
- package/extensions/voice-call/src/tunnel.ts +0 -331
- package/extensions/voice-call/src/types.ts +0 -272
- package/extensions/voice-call/src/utils.ts +0 -12
- package/extensions/voice-call/src/voice-mapping.ts +0 -65
- package/extensions/voice-call/src/webhook-security.test.ts +0 -233
- package/extensions/voice-call/src/webhook-security.ts +0 -446
- package/extensions/voice-call/src/webhook.ts +0 -490
- package/extensions/whatsapp/clawdbot.plugin.json +0 -11
- package/extensions/whatsapp/index.ts +0 -18
- package/extensions/whatsapp/package.json +0 -11
- package/extensions/whatsapp/src/channel.ts +0 -500
- package/extensions/whatsapp/src/runtime.ts +0 -14
- package/extensions/zalo/CHANGELOG.md +0 -55
- package/extensions/zalo/README.md +0 -50
- package/extensions/zalo/clawdbot.plugin.json +0 -11
- package/extensions/zalo/index.ts +0 -20
- package/extensions/zalo/package.json +0 -33
- package/extensions/zalo/src/accounts.ts +0 -71
- package/extensions/zalo/src/actions.ts +0 -62
- package/extensions/zalo/src/api.ts +0 -206
- package/extensions/zalo/src/channel.directory.test.ts +0 -35
- package/extensions/zalo/src/channel.ts +0 -394
- package/extensions/zalo/src/config-schema.ts +0 -24
- package/extensions/zalo/src/monitor.ts +0 -760
- package/extensions/zalo/src/monitor.webhook.test.ts +0 -70
- package/extensions/zalo/src/onboarding.ts +0 -405
- package/extensions/zalo/src/probe.ts +0 -46
- package/extensions/zalo/src/proxy.ts +0 -18
- package/extensions/zalo/src/runtime.ts +0 -14
- package/extensions/zalo/src/send.ts +0 -117
- package/extensions/zalo/src/status-issues.ts +0 -50
- package/extensions/zalo/src/token.ts +0 -55
- package/extensions/zalo/src/types.ts +0 -42
- package/extensions/zalouser/CHANGELOG.md +0 -33
- package/extensions/zalouser/README.md +0 -221
- package/extensions/zalouser/clawdbot.plugin.json +0 -11
- package/extensions/zalouser/index.ts +0 -32
- package/extensions/zalouser/package.json +0 -33
- package/extensions/zalouser/src/accounts.ts +0 -117
- package/extensions/zalouser/src/channel.test.ts +0 -17
- package/extensions/zalouser/src/channel.ts +0 -641
- package/extensions/zalouser/src/config-schema.ts +0 -27
- package/extensions/zalouser/src/monitor.ts +0 -574
- package/extensions/zalouser/src/onboarding.ts +0 -488
- package/extensions/zalouser/src/probe.ts +0 -28
- package/extensions/zalouser/src/runtime.ts +0 -14
- package/extensions/zalouser/src/send.ts +0 -150
- package/extensions/zalouser/src/status-issues.test.ts +0 -58
- package/extensions/zalouser/src/status-issues.ts +0 -81
- package/extensions/zalouser/src/tool.ts +0 -156
- package/extensions/zalouser/src/types.ts +0 -102
- package/extensions/zalouser/src/zca.ts +0 -208
- package/skills/1password/SKILL.md +0 -53
- package/skills/1password/references/cli-examples.md +0 -29
- package/skills/1password/references/get-started.md +0 -17
- package/skills/apple-notes/SKILL.md +0 -50
- package/skills/apple-reminders/SKILL.md +0 -67
- package/skills/bear-notes/SKILL.md +0 -79
- package/skills/bird/SKILL.md +0 -197
- package/skills/blogwatcher/SKILL.md +0 -46
- package/skills/blucli/SKILL.md +0 -27
- package/skills/bluebubbles/SKILL.md +0 -39
- package/skills/camsnap/SKILL.md +0 -25
- package/skills/canvas/SKILL.md +0 -189
- package/skills/clawdhub/SKILL.md +0 -53
- package/skills/coding-agent/SKILL.md +0 -278
- package/skills/discord/SKILL.md +0 -475
- package/skills/eightctl/SKILL.md +0 -29
- package/skills/food-order/SKILL.md +0 -41
- package/skills/gemini/SKILL.md +0 -23
- package/skills/gifgrep/SKILL.md +0 -47
- package/skills/github/SKILL.md +0 -48
- package/skills/gog/SKILL.md +0 -92
- package/skills/goplaces/SKILL.md +0 -30
- package/skills/himalaya/SKILL.md +0 -217
- package/skills/himalaya/references/configuration.md +0 -174
- package/skills/himalaya/references/message-composition.md +0 -182
- package/skills/imsg/SKILL.md +0 -25
- package/skills/local-places/SERVER_README.md +0 -101
- package/skills/local-places/SKILL.md +0 -91
- package/skills/local-places/pyproject.toml +0 -27
- package/skills/local-places/src/local_places/__init__.py +0 -2
- package/skills/local-places/src/local_places/google_places.py +0 -314
- package/skills/local-places/src/local_places/main.py +0 -65
- package/skills/local-places/src/local_places/schemas.py +0 -107
- package/skills/mcporter/SKILL.md +0 -38
- package/skills/model-usage/SKILL.md +0 -45
- package/skills/model-usage/references/codexbar-cli.md +0 -28
- package/skills/model-usage/scripts/model_usage.py +0 -310
- package/skills/nano-banana-pro/SKILL.md +0 -30
- package/skills/nano-banana-pro/scripts/generate_image.py +0 -169
- package/skills/nano-pdf/SKILL.md +0 -20
- package/skills/notion/SKILL.md +0 -156
- package/skills/obsidian/SKILL.md +0 -55
- package/skills/openai-image-gen/SKILL.md +0 -71
- package/skills/openai-image-gen/scripts/gen.py +0 -240
- package/skills/openai-whisper/SKILL.md +0 -19
- package/skills/openai-whisper-api/SKILL.md +0 -43
- package/skills/openai-whisper-api/scripts/transcribe.sh +0 -85
- package/skills/openhue/SKILL.md +0 -30
- package/skills/oracle/SKILL.md +0 -105
- package/skills/ordercli/SKILL.md +0 -47
- package/skills/peekaboo/SKILL.md +0 -153
- package/skills/sag/SKILL.md +0 -62
- package/skills/session-logs/SKILL.md +0 -105
- package/skills/sherpa-onnx-tts/SKILL.md +0 -49
- package/skills/sherpa-onnx-tts/bin/sherpa-onnx-tts +0 -178
- package/skills/skill-creator/SKILL.md +0 -371
- package/skills/skill-creator/license.txt +0 -202
- package/skills/skill-creator/scripts/init_skill.py +0 -378
- package/skills/skill-creator/scripts/package_skill.py +0 -111
- package/skills/skill-creator/scripts/quick_validate.py +0 -101
- package/skills/slack/SKILL.md +0 -144
- package/skills/songsee/SKILL.md +0 -29
- package/skills/sonoscli/SKILL.md +0 -26
- package/skills/spotify-player/SKILL.md +0 -34
- package/skills/summarize/SKILL.md +0 -67
- package/skills/things-mac/SKILL.md +0 -61
- package/skills/tmux/SKILL.md +0 -121
- package/skills/tmux/scripts/find-sessions.sh +0 -112
- package/skills/tmux/scripts/wait-for-text.sh +0 -83
- package/skills/trello/SKILL.md +0 -84
- package/skills/video-frames/SKILL.md +0 -29
- package/skills/video-frames/scripts/frame.sh +0 -81
- package/skills/voice-call/SKILL.md +0 -35
- package/skills/wacli/SKILL.md +0 -42
- package/skills/weather/SKILL.md +0 -49
|
@@ -1,741 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
SimplePool,
|
|
3
|
-
finalizeEvent,
|
|
4
|
-
getPublicKey,
|
|
5
|
-
verifyEvent,
|
|
6
|
-
nip19,
|
|
7
|
-
type Event,
|
|
8
|
-
} from "nostr-tools";
|
|
9
|
-
import { decrypt, encrypt } from "nostr-tools/nip04";
|
|
10
|
-
|
|
11
|
-
import {
|
|
12
|
-
readNostrBusState,
|
|
13
|
-
writeNostrBusState,
|
|
14
|
-
computeSinceTimestamp,
|
|
15
|
-
readNostrProfileState,
|
|
16
|
-
writeNostrProfileState,
|
|
17
|
-
} from "./nostr-state-store.js";
|
|
18
|
-
import {
|
|
19
|
-
publishProfile as publishProfileFn,
|
|
20
|
-
type ProfilePublishResult,
|
|
21
|
-
} from "./nostr-profile.js";
|
|
22
|
-
import type { NostrProfile } from "./config-schema.js";
|
|
23
|
-
import { createSeenTracker, type SeenTracker } from "./seen-tracker.js";
|
|
24
|
-
import {
|
|
25
|
-
createMetrics,
|
|
26
|
-
createNoopMetrics,
|
|
27
|
-
type NostrMetrics,
|
|
28
|
-
type MetricsSnapshot,
|
|
29
|
-
type MetricEvent,
|
|
30
|
-
} from "./metrics.js";
|
|
31
|
-
|
|
32
|
-
export const DEFAULT_RELAYS = ["wss://relay.damus.io", "wss://nos.lol"];
|
|
33
|
-
|
|
34
|
-
// ============================================================================
|
|
35
|
-
// Constants
|
|
36
|
-
// ============================================================================
|
|
37
|
-
|
|
38
|
-
const STARTUP_LOOKBACK_SEC = 120; // tolerate relay lag / clock skew
|
|
39
|
-
const MAX_PERSISTED_EVENT_IDS = 5000;
|
|
40
|
-
const STATE_PERSIST_DEBOUNCE_MS = 5000; // Debounce state writes
|
|
41
|
-
|
|
42
|
-
// Reconnect configuration (exponential backoff with jitter)
|
|
43
|
-
const RECONNECT_BASE_MS = 1000; // 1 second base
|
|
44
|
-
const RECONNECT_MAX_MS = 60000; // 60 seconds max
|
|
45
|
-
const RECONNECT_JITTER = 0.3; // ±30% jitter
|
|
46
|
-
|
|
47
|
-
// Circuit breaker configuration
|
|
48
|
-
const CIRCUIT_BREAKER_THRESHOLD = 5; // failures before opening
|
|
49
|
-
const CIRCUIT_BREAKER_RESET_MS = 30000; // 30 seconds before half-open
|
|
50
|
-
|
|
51
|
-
// Health tracker configuration
|
|
52
|
-
const HEALTH_WINDOW_MS = 60000; // 1 minute window for health stats
|
|
53
|
-
|
|
54
|
-
// ============================================================================
|
|
55
|
-
// Types
|
|
56
|
-
// ============================================================================
|
|
57
|
-
|
|
58
|
-
export interface NostrBusOptions {
|
|
59
|
-
/** Private key in hex or nsec format */
|
|
60
|
-
privateKey: string;
|
|
61
|
-
/** WebSocket relay URLs (defaults to damus + nos.lol) */
|
|
62
|
-
relays?: string[];
|
|
63
|
-
/** Account ID for state persistence (optional, defaults to pubkey prefix) */
|
|
64
|
-
accountId?: string;
|
|
65
|
-
/** Called when a DM is received */
|
|
66
|
-
onMessage: (
|
|
67
|
-
pubkey: string,
|
|
68
|
-
text: string,
|
|
69
|
-
reply: (text: string) => Promise<void>
|
|
70
|
-
) => Promise<void>;
|
|
71
|
-
/** Called on errors (optional) */
|
|
72
|
-
onError?: (error: Error, context: string) => void;
|
|
73
|
-
/** Called on connection status changes (optional) */
|
|
74
|
-
onConnect?: (relay: string) => void;
|
|
75
|
-
/** Called on disconnection (optional) */
|
|
76
|
-
onDisconnect?: (relay: string) => void;
|
|
77
|
-
/** Called on EOSE (end of stored events) for initial sync (optional) */
|
|
78
|
-
onEose?: (relay: string) => void;
|
|
79
|
-
/** Called on each metric event (optional) */
|
|
80
|
-
onMetric?: (event: MetricEvent) => void;
|
|
81
|
-
/** Maximum entries in seen tracker (default: 100,000) */
|
|
82
|
-
maxSeenEntries?: number;
|
|
83
|
-
/** Seen tracker TTL in ms (default: 1 hour) */
|
|
84
|
-
seenTtlMs?: number;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
export interface NostrBusHandle {
|
|
88
|
-
/** Stop the bus and close connections */
|
|
89
|
-
close: () => void;
|
|
90
|
-
/** Get the bot's public key */
|
|
91
|
-
publicKey: string;
|
|
92
|
-
/** Send a DM to a pubkey */
|
|
93
|
-
sendDm: (toPubkey: string, text: string) => Promise<void>;
|
|
94
|
-
/** Get current metrics snapshot */
|
|
95
|
-
getMetrics: () => MetricsSnapshot;
|
|
96
|
-
/** Publish a profile (kind:0) to all relays */
|
|
97
|
-
publishProfile: (profile: NostrProfile) => Promise<ProfilePublishResult>;
|
|
98
|
-
/** Get the last profile publish state */
|
|
99
|
-
getProfileState: () => Promise<{
|
|
100
|
-
lastPublishedAt: number | null;
|
|
101
|
-
lastPublishedEventId: string | null;
|
|
102
|
-
lastPublishResults: Record<string, "ok" | "failed" | "timeout"> | null;
|
|
103
|
-
}>;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// ============================================================================
|
|
107
|
-
// Circuit Breaker
|
|
108
|
-
// ============================================================================
|
|
109
|
-
|
|
110
|
-
interface CircuitBreakerState {
|
|
111
|
-
state: "closed" | "open" | "half_open";
|
|
112
|
-
failures: number;
|
|
113
|
-
lastFailure: number;
|
|
114
|
-
lastSuccess: number;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
interface CircuitBreaker {
|
|
118
|
-
/** Check if requests should be allowed */
|
|
119
|
-
canAttempt: () => boolean;
|
|
120
|
-
/** Record a success */
|
|
121
|
-
recordSuccess: () => void;
|
|
122
|
-
/** Record a failure */
|
|
123
|
-
recordFailure: () => void;
|
|
124
|
-
/** Get current state */
|
|
125
|
-
getState: () => CircuitBreakerState["state"];
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function createCircuitBreaker(
|
|
129
|
-
relay: string,
|
|
130
|
-
metrics: NostrMetrics,
|
|
131
|
-
threshold: number = CIRCUIT_BREAKER_THRESHOLD,
|
|
132
|
-
resetMs: number = CIRCUIT_BREAKER_RESET_MS
|
|
133
|
-
): CircuitBreaker {
|
|
134
|
-
const state: CircuitBreakerState = {
|
|
135
|
-
state: "closed",
|
|
136
|
-
failures: 0,
|
|
137
|
-
lastFailure: 0,
|
|
138
|
-
lastSuccess: Date.now(),
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
return {
|
|
142
|
-
canAttempt(): boolean {
|
|
143
|
-
if (state.state === "closed") return true;
|
|
144
|
-
|
|
145
|
-
if (state.state === "open") {
|
|
146
|
-
// Check if enough time has passed to try half-open
|
|
147
|
-
if (Date.now() - state.lastFailure >= resetMs) {
|
|
148
|
-
state.state = "half_open";
|
|
149
|
-
metrics.emit("relay.circuit_breaker.half_open", 1, { relay });
|
|
150
|
-
return true;
|
|
151
|
-
}
|
|
152
|
-
return false;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// half_open: allow one attempt
|
|
156
|
-
return true;
|
|
157
|
-
},
|
|
158
|
-
|
|
159
|
-
recordSuccess(): void {
|
|
160
|
-
if (state.state === "half_open") {
|
|
161
|
-
state.state = "closed";
|
|
162
|
-
state.failures = 0;
|
|
163
|
-
metrics.emit("relay.circuit_breaker.close", 1, { relay });
|
|
164
|
-
} else if (state.state === "closed") {
|
|
165
|
-
state.failures = 0;
|
|
166
|
-
}
|
|
167
|
-
state.lastSuccess = Date.now();
|
|
168
|
-
},
|
|
169
|
-
|
|
170
|
-
recordFailure(): void {
|
|
171
|
-
state.failures++;
|
|
172
|
-
state.lastFailure = Date.now();
|
|
173
|
-
|
|
174
|
-
if (state.state === "half_open") {
|
|
175
|
-
state.state = "open";
|
|
176
|
-
metrics.emit("relay.circuit_breaker.open", 1, { relay });
|
|
177
|
-
} else if (state.state === "closed" && state.failures >= threshold) {
|
|
178
|
-
state.state = "open";
|
|
179
|
-
metrics.emit("relay.circuit_breaker.open", 1, { relay });
|
|
180
|
-
}
|
|
181
|
-
},
|
|
182
|
-
|
|
183
|
-
getState(): CircuitBreakerState["state"] {
|
|
184
|
-
return state.state;
|
|
185
|
-
},
|
|
186
|
-
};
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// ============================================================================
|
|
190
|
-
// Relay Health Tracker
|
|
191
|
-
// ============================================================================
|
|
192
|
-
|
|
193
|
-
interface RelayHealthStats {
|
|
194
|
-
successCount: number;
|
|
195
|
-
failureCount: number;
|
|
196
|
-
latencySum: number;
|
|
197
|
-
latencyCount: number;
|
|
198
|
-
lastSuccess: number;
|
|
199
|
-
lastFailure: number;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
interface RelayHealthTracker {
|
|
203
|
-
/** Record a successful operation */
|
|
204
|
-
recordSuccess: (relay: string, latencyMs: number) => void;
|
|
205
|
-
/** Record a failed operation */
|
|
206
|
-
recordFailure: (relay: string) => void;
|
|
207
|
-
/** Get health score (0-1, higher is better) */
|
|
208
|
-
getScore: (relay: string) => number;
|
|
209
|
-
/** Get relays sorted by health (best first) */
|
|
210
|
-
getSortedRelays: (relays: string[]) => string[];
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
function createRelayHealthTracker(): RelayHealthTracker {
|
|
214
|
-
const stats = new Map<string, RelayHealthStats>();
|
|
215
|
-
|
|
216
|
-
function getOrCreate(relay: string): RelayHealthStats {
|
|
217
|
-
let s = stats.get(relay);
|
|
218
|
-
if (!s) {
|
|
219
|
-
s = {
|
|
220
|
-
successCount: 0,
|
|
221
|
-
failureCount: 0,
|
|
222
|
-
latencySum: 0,
|
|
223
|
-
latencyCount: 0,
|
|
224
|
-
lastSuccess: 0,
|
|
225
|
-
lastFailure: 0,
|
|
226
|
-
};
|
|
227
|
-
stats.set(relay, s);
|
|
228
|
-
}
|
|
229
|
-
return s;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
return {
|
|
233
|
-
recordSuccess(relay: string, latencyMs: number): void {
|
|
234
|
-
const s = getOrCreate(relay);
|
|
235
|
-
s.successCount++;
|
|
236
|
-
s.latencySum += latencyMs;
|
|
237
|
-
s.latencyCount++;
|
|
238
|
-
s.lastSuccess = Date.now();
|
|
239
|
-
},
|
|
240
|
-
|
|
241
|
-
recordFailure(relay: string): void {
|
|
242
|
-
const s = getOrCreate(relay);
|
|
243
|
-
s.failureCount++;
|
|
244
|
-
s.lastFailure = Date.now();
|
|
245
|
-
},
|
|
246
|
-
|
|
247
|
-
getScore(relay: string): number {
|
|
248
|
-
const s = stats.get(relay);
|
|
249
|
-
if (!s) return 0.5; // Unknown relay gets neutral score
|
|
250
|
-
|
|
251
|
-
const total = s.successCount + s.failureCount;
|
|
252
|
-
if (total === 0) return 0.5;
|
|
253
|
-
|
|
254
|
-
// Success rate (0-1)
|
|
255
|
-
const successRate = s.successCount / total;
|
|
256
|
-
|
|
257
|
-
// Recency bonus (prefer recently successful relays)
|
|
258
|
-
const now = Date.now();
|
|
259
|
-
const recencyBonus =
|
|
260
|
-
s.lastSuccess > s.lastFailure
|
|
261
|
-
? Math.max(0, 1 - (now - s.lastSuccess) / HEALTH_WINDOW_MS) * 0.2
|
|
262
|
-
: 0;
|
|
263
|
-
|
|
264
|
-
// Latency penalty (lower is better)
|
|
265
|
-
const avgLatency =
|
|
266
|
-
s.latencyCount > 0 ? s.latencySum / s.latencyCount : 1000;
|
|
267
|
-
const latencyPenalty = Math.min(0.2, avgLatency / 10000);
|
|
268
|
-
|
|
269
|
-
return Math.max(0, Math.min(1, successRate + recencyBonus - latencyPenalty));
|
|
270
|
-
},
|
|
271
|
-
|
|
272
|
-
getSortedRelays(relays: string[]): string[] {
|
|
273
|
-
return [...relays].sort((a, b) => this.getScore(b) - this.getScore(a));
|
|
274
|
-
},
|
|
275
|
-
};
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
// ============================================================================
|
|
279
|
-
// Reconnect with Exponential Backoff + Jitter
|
|
280
|
-
// ============================================================================
|
|
281
|
-
|
|
282
|
-
function computeReconnectDelay(attempt: number): number {
|
|
283
|
-
// Exponential backoff: base * 2^attempt
|
|
284
|
-
const exponential = RECONNECT_BASE_MS * Math.pow(2, attempt);
|
|
285
|
-
const capped = Math.min(exponential, RECONNECT_MAX_MS);
|
|
286
|
-
|
|
287
|
-
// Add jitter: ±JITTER%
|
|
288
|
-
const jitter = capped * RECONNECT_JITTER * (Math.random() * 2 - 1);
|
|
289
|
-
return Math.max(RECONNECT_BASE_MS, capped + jitter);
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
// ============================================================================
|
|
293
|
-
// Key Validation
|
|
294
|
-
// ============================================================================
|
|
295
|
-
|
|
296
|
-
/**
|
|
297
|
-
* Validate and normalize a private key (accepts hex or nsec format)
|
|
298
|
-
*/
|
|
299
|
-
export function validatePrivateKey(key: string): Uint8Array {
|
|
300
|
-
const trimmed = key.trim();
|
|
301
|
-
|
|
302
|
-
// Handle nsec (bech32) format
|
|
303
|
-
if (trimmed.startsWith("nsec1")) {
|
|
304
|
-
const decoded = nip19.decode(trimmed);
|
|
305
|
-
if (decoded.type !== "nsec") {
|
|
306
|
-
throw new Error("Invalid nsec key: wrong type");
|
|
307
|
-
}
|
|
308
|
-
return decoded.data;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
// Handle hex format
|
|
312
|
-
if (!/^[0-9a-fA-F]{64}$/.test(trimmed)) {
|
|
313
|
-
throw new Error(
|
|
314
|
-
"Private key must be 64 hex characters or nsec bech32 format"
|
|
315
|
-
);
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
// Convert hex string to Uint8Array
|
|
319
|
-
const bytes = new Uint8Array(32);
|
|
320
|
-
for (let i = 0; i < 32; i++) {
|
|
321
|
-
bytes[i] = parseInt(trimmed.slice(i * 2, i * 2 + 2), 16);
|
|
322
|
-
}
|
|
323
|
-
return bytes;
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
/**
|
|
327
|
-
* Get public key from private key (hex or nsec format)
|
|
328
|
-
*/
|
|
329
|
-
export function getPublicKeyFromPrivate(privateKey: string): string {
|
|
330
|
-
const sk = validatePrivateKey(privateKey);
|
|
331
|
-
return getPublicKey(sk);
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
// ============================================================================
|
|
335
|
-
// Main Bus
|
|
336
|
-
// ============================================================================
|
|
337
|
-
|
|
338
|
-
/**
|
|
339
|
-
* Start the Nostr DM bus - subscribes to NIP-04 encrypted DMs
|
|
340
|
-
*/
|
|
341
|
-
export async function startNostrBus(
|
|
342
|
-
options: NostrBusOptions
|
|
343
|
-
): Promise<NostrBusHandle> {
|
|
344
|
-
const {
|
|
345
|
-
privateKey,
|
|
346
|
-
relays = DEFAULT_RELAYS,
|
|
347
|
-
onMessage,
|
|
348
|
-
onError,
|
|
349
|
-
onEose,
|
|
350
|
-
onMetric,
|
|
351
|
-
maxSeenEntries = 100_000,
|
|
352
|
-
seenTtlMs = 60 * 60 * 1000,
|
|
353
|
-
} = options;
|
|
354
|
-
|
|
355
|
-
const sk = validatePrivateKey(privateKey);
|
|
356
|
-
const pk = getPublicKey(sk);
|
|
357
|
-
const pool = new SimplePool();
|
|
358
|
-
const accountId = options.accountId ?? pk.slice(0, 16);
|
|
359
|
-
const gatewayStartedAt = Math.floor(Date.now() / 1000);
|
|
360
|
-
|
|
361
|
-
// Initialize metrics
|
|
362
|
-
const metrics = onMetric ? createMetrics(onMetric) : createNoopMetrics();
|
|
363
|
-
|
|
364
|
-
// Initialize seen tracker with LRU
|
|
365
|
-
const seen: SeenTracker = createSeenTracker({
|
|
366
|
-
maxEntries: maxSeenEntries,
|
|
367
|
-
ttlMs: seenTtlMs,
|
|
368
|
-
});
|
|
369
|
-
|
|
370
|
-
// Initialize circuit breakers and health tracker
|
|
371
|
-
const circuitBreakers = new Map<string, CircuitBreaker>();
|
|
372
|
-
const healthTracker = createRelayHealthTracker();
|
|
373
|
-
|
|
374
|
-
for (const relay of relays) {
|
|
375
|
-
circuitBreakers.set(relay, createCircuitBreaker(relay, metrics));
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
// Read persisted state and compute `since` timestamp (with small overlap)
|
|
379
|
-
const state = await readNostrBusState({ accountId });
|
|
380
|
-
const baseSince = computeSinceTimestamp(state, gatewayStartedAt);
|
|
381
|
-
const since = Math.max(0, baseSince - STARTUP_LOOKBACK_SEC);
|
|
382
|
-
|
|
383
|
-
// Seed in-memory dedupe with recent IDs from disk (prevents restart replay)
|
|
384
|
-
if (state?.recentEventIds?.length) {
|
|
385
|
-
seen.seed(state.recentEventIds);
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
// Persist startup timestamp
|
|
389
|
-
await writeNostrBusState({
|
|
390
|
-
accountId,
|
|
391
|
-
lastProcessedAt: state?.lastProcessedAt ?? gatewayStartedAt,
|
|
392
|
-
gatewayStartedAt,
|
|
393
|
-
recentEventIds: state?.recentEventIds ?? [],
|
|
394
|
-
});
|
|
395
|
-
|
|
396
|
-
// Debounced state persistence
|
|
397
|
-
let pendingWrite: ReturnType<typeof setTimeout> | undefined;
|
|
398
|
-
let lastProcessedAt = state?.lastProcessedAt ?? gatewayStartedAt;
|
|
399
|
-
let recentEventIds = (state?.recentEventIds ?? []).slice(
|
|
400
|
-
-MAX_PERSISTED_EVENT_IDS
|
|
401
|
-
);
|
|
402
|
-
|
|
403
|
-
function scheduleStatePersist(eventCreatedAt: number, eventId: string): void {
|
|
404
|
-
lastProcessedAt = Math.max(lastProcessedAt, eventCreatedAt);
|
|
405
|
-
recentEventIds.push(eventId);
|
|
406
|
-
if (recentEventIds.length > MAX_PERSISTED_EVENT_IDS) {
|
|
407
|
-
recentEventIds = recentEventIds.slice(-MAX_PERSISTED_EVENT_IDS);
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
if (pendingWrite) clearTimeout(pendingWrite);
|
|
411
|
-
pendingWrite = setTimeout(() => {
|
|
412
|
-
writeNostrBusState({
|
|
413
|
-
accountId,
|
|
414
|
-
lastProcessedAt,
|
|
415
|
-
gatewayStartedAt,
|
|
416
|
-
recentEventIds,
|
|
417
|
-
}).catch((err) => onError?.(err as Error, "persist state"));
|
|
418
|
-
}, STATE_PERSIST_DEBOUNCE_MS);
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
const inflight = new Set<string>();
|
|
422
|
-
|
|
423
|
-
// Event handler
|
|
424
|
-
async function handleEvent(event: Event): Promise<void> {
|
|
425
|
-
try {
|
|
426
|
-
metrics.emit("event.received");
|
|
427
|
-
|
|
428
|
-
// Fast dedupe check (handles relay reconnections)
|
|
429
|
-
if (seen.peek(event.id) || inflight.has(event.id)) {
|
|
430
|
-
metrics.emit("event.duplicate");
|
|
431
|
-
return;
|
|
432
|
-
}
|
|
433
|
-
inflight.add(event.id);
|
|
434
|
-
|
|
435
|
-
// Self-message loop prevention: skip our own messages
|
|
436
|
-
if (event.pubkey === pk) {
|
|
437
|
-
metrics.emit("event.rejected.self_message");
|
|
438
|
-
return;
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
// Skip events older than our `since` (relay may ignore filter)
|
|
442
|
-
if (event.created_at < since) {
|
|
443
|
-
metrics.emit("event.rejected.stale");
|
|
444
|
-
return;
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
// Fast p-tag check BEFORE crypto (no allocation, cheaper)
|
|
448
|
-
let targetsUs = false;
|
|
449
|
-
for (const t of event.tags) {
|
|
450
|
-
if (t[0] === "p" && t[1] === pk) {
|
|
451
|
-
targetsUs = true;
|
|
452
|
-
break;
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
if (!targetsUs) {
|
|
456
|
-
metrics.emit("event.rejected.wrong_kind");
|
|
457
|
-
return;
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
// Verify signature (must pass before we trust the event)
|
|
461
|
-
if (!verifyEvent(event)) {
|
|
462
|
-
metrics.emit("event.rejected.invalid_signature");
|
|
463
|
-
onError?.(new Error("Invalid signature"), `event ${event.id}`);
|
|
464
|
-
return;
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
// Mark seen AFTER verify (don't cache invalid IDs)
|
|
468
|
-
seen.add(event.id);
|
|
469
|
-
metrics.emit("memory.seen_tracker_size", seen.size());
|
|
470
|
-
|
|
471
|
-
// Decrypt the message
|
|
472
|
-
let plaintext: string;
|
|
473
|
-
try {
|
|
474
|
-
plaintext = await decrypt(sk, event.pubkey, event.content);
|
|
475
|
-
metrics.emit("decrypt.success");
|
|
476
|
-
} catch (err) {
|
|
477
|
-
metrics.emit("decrypt.failure");
|
|
478
|
-
metrics.emit("event.rejected.decrypt_failed");
|
|
479
|
-
onError?.(err as Error, `decrypt from ${event.pubkey}`);
|
|
480
|
-
return;
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
// Create reply function (try relays by health score)
|
|
484
|
-
const replyTo = async (text: string): Promise<void> => {
|
|
485
|
-
await sendEncryptedDm(
|
|
486
|
-
pool,
|
|
487
|
-
sk,
|
|
488
|
-
event.pubkey,
|
|
489
|
-
text,
|
|
490
|
-
relays,
|
|
491
|
-
metrics,
|
|
492
|
-
circuitBreakers,
|
|
493
|
-
healthTracker,
|
|
494
|
-
onError
|
|
495
|
-
);
|
|
496
|
-
};
|
|
497
|
-
|
|
498
|
-
// Call the message handler
|
|
499
|
-
await onMessage(event.pubkey, plaintext, replyTo);
|
|
500
|
-
|
|
501
|
-
// Mark as processed
|
|
502
|
-
metrics.emit("event.processed");
|
|
503
|
-
|
|
504
|
-
// Persist progress (debounced)
|
|
505
|
-
scheduleStatePersist(event.created_at, event.id);
|
|
506
|
-
} catch (err) {
|
|
507
|
-
onError?.(err as Error, `event ${event.id}`);
|
|
508
|
-
} finally {
|
|
509
|
-
inflight.delete(event.id);
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
const sub = pool.subscribeMany(
|
|
514
|
-
relays,
|
|
515
|
-
[{ kinds: [4], "#p": [pk], since }],
|
|
516
|
-
{
|
|
517
|
-
onevent: handleEvent,
|
|
518
|
-
oneose: () => {
|
|
519
|
-
// EOSE handler - called when all stored events have been received
|
|
520
|
-
for (const relay of relays) {
|
|
521
|
-
metrics.emit("relay.message.eose", 1, { relay });
|
|
522
|
-
}
|
|
523
|
-
onEose?.(relays.join(", "));
|
|
524
|
-
},
|
|
525
|
-
onclose: (reason) => {
|
|
526
|
-
// Handle subscription close
|
|
527
|
-
for (const relay of relays) {
|
|
528
|
-
metrics.emit("relay.message.closed", 1, { relay });
|
|
529
|
-
options.onDisconnect?.(relay);
|
|
530
|
-
}
|
|
531
|
-
onError?.(
|
|
532
|
-
new Error(`Subscription closed: ${reason}`),
|
|
533
|
-
"subscription"
|
|
534
|
-
);
|
|
535
|
-
},
|
|
536
|
-
}
|
|
537
|
-
);
|
|
538
|
-
|
|
539
|
-
// Public sendDm function
|
|
540
|
-
const sendDm = async (toPubkey: string, text: string): Promise<void> => {
|
|
541
|
-
await sendEncryptedDm(
|
|
542
|
-
pool,
|
|
543
|
-
sk,
|
|
544
|
-
toPubkey,
|
|
545
|
-
text,
|
|
546
|
-
relays,
|
|
547
|
-
metrics,
|
|
548
|
-
circuitBreakers,
|
|
549
|
-
healthTracker,
|
|
550
|
-
onError
|
|
551
|
-
);
|
|
552
|
-
};
|
|
553
|
-
|
|
554
|
-
// Profile publishing function
|
|
555
|
-
const publishProfile = async (profile: NostrProfile): Promise<ProfilePublishResult> => {
|
|
556
|
-
// Read last published timestamp for monotonic ordering
|
|
557
|
-
const profileState = await readNostrProfileState({ accountId });
|
|
558
|
-
const lastPublishedAt = profileState?.lastPublishedAt ?? undefined;
|
|
559
|
-
|
|
560
|
-
// Publish the profile
|
|
561
|
-
const result = await publishProfileFn(pool, sk, relays, profile, lastPublishedAt);
|
|
562
|
-
|
|
563
|
-
// Convert results to state format
|
|
564
|
-
const publishResults: Record<string, "ok" | "failed" | "timeout"> = {};
|
|
565
|
-
for (const relay of result.successes) {
|
|
566
|
-
publishResults[relay] = "ok";
|
|
567
|
-
}
|
|
568
|
-
for (const { relay, error } of result.failures) {
|
|
569
|
-
publishResults[relay] = error === "timeout" ? "timeout" : "failed";
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
// Persist the publish state
|
|
573
|
-
await writeNostrProfileState({
|
|
574
|
-
accountId,
|
|
575
|
-
lastPublishedAt: result.createdAt,
|
|
576
|
-
lastPublishedEventId: result.eventId,
|
|
577
|
-
lastPublishResults: publishResults,
|
|
578
|
-
});
|
|
579
|
-
|
|
580
|
-
return result;
|
|
581
|
-
};
|
|
582
|
-
|
|
583
|
-
// Get profile state function
|
|
584
|
-
const getProfileState = async () => {
|
|
585
|
-
const state = await readNostrProfileState({ accountId });
|
|
586
|
-
return {
|
|
587
|
-
lastPublishedAt: state?.lastPublishedAt ?? null,
|
|
588
|
-
lastPublishedEventId: state?.lastPublishedEventId ?? null,
|
|
589
|
-
lastPublishResults: state?.lastPublishResults ?? null,
|
|
590
|
-
};
|
|
591
|
-
};
|
|
592
|
-
|
|
593
|
-
return {
|
|
594
|
-
close: () => {
|
|
595
|
-
sub.close();
|
|
596
|
-
seen.stop();
|
|
597
|
-
// Flush pending state write synchronously on close
|
|
598
|
-
if (pendingWrite) {
|
|
599
|
-
clearTimeout(pendingWrite);
|
|
600
|
-
writeNostrBusState({
|
|
601
|
-
accountId,
|
|
602
|
-
lastProcessedAt,
|
|
603
|
-
gatewayStartedAt,
|
|
604
|
-
recentEventIds,
|
|
605
|
-
}).catch((err) => onError?.(err as Error, "persist state on close"));
|
|
606
|
-
}
|
|
607
|
-
},
|
|
608
|
-
publicKey: pk,
|
|
609
|
-
sendDm,
|
|
610
|
-
getMetrics: () => metrics.getSnapshot(),
|
|
611
|
-
publishProfile,
|
|
612
|
-
getProfileState,
|
|
613
|
-
};
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
// ============================================================================
|
|
617
|
-
// Send DM with Circuit Breaker + Health Scoring
|
|
618
|
-
// ============================================================================
|
|
619
|
-
|
|
620
|
-
/**
|
|
621
|
-
* Send an encrypted DM to a pubkey
|
|
622
|
-
*/
|
|
623
|
-
async function sendEncryptedDm(
|
|
624
|
-
pool: SimplePool,
|
|
625
|
-
sk: Uint8Array,
|
|
626
|
-
toPubkey: string,
|
|
627
|
-
text: string,
|
|
628
|
-
relays: string[],
|
|
629
|
-
metrics: NostrMetrics,
|
|
630
|
-
circuitBreakers: Map<string, CircuitBreaker>,
|
|
631
|
-
healthTracker: RelayHealthTracker,
|
|
632
|
-
onError?: (error: Error, context: string) => void
|
|
633
|
-
): Promise<void> {
|
|
634
|
-
const ciphertext = await encrypt(sk, toPubkey, text);
|
|
635
|
-
const reply = finalizeEvent(
|
|
636
|
-
{
|
|
637
|
-
kind: 4,
|
|
638
|
-
content: ciphertext,
|
|
639
|
-
tags: [["p", toPubkey]],
|
|
640
|
-
created_at: Math.floor(Date.now() / 1000),
|
|
641
|
-
},
|
|
642
|
-
sk
|
|
643
|
-
);
|
|
644
|
-
|
|
645
|
-
// Sort relays by health score (best first)
|
|
646
|
-
const sortedRelays = healthTracker.getSortedRelays(relays);
|
|
647
|
-
|
|
648
|
-
// Try relays in order of health, respecting circuit breakers
|
|
649
|
-
let lastError: Error | undefined;
|
|
650
|
-
for (const relay of sortedRelays) {
|
|
651
|
-
const cb = circuitBreakers.get(relay);
|
|
652
|
-
|
|
653
|
-
// Skip if circuit breaker is open
|
|
654
|
-
if (cb && !cb.canAttempt()) {
|
|
655
|
-
continue;
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
const startTime = Date.now();
|
|
659
|
-
try {
|
|
660
|
-
await pool.publish([relay], reply);
|
|
661
|
-
const latency = Date.now() - startTime;
|
|
662
|
-
|
|
663
|
-
// Record success
|
|
664
|
-
cb?.recordSuccess();
|
|
665
|
-
healthTracker.recordSuccess(relay, latency);
|
|
666
|
-
|
|
667
|
-
return; // Success - exit early
|
|
668
|
-
} catch (err) {
|
|
669
|
-
lastError = err as Error;
|
|
670
|
-
const latency = Date.now() - startTime;
|
|
671
|
-
|
|
672
|
-
// Record failure
|
|
673
|
-
cb?.recordFailure();
|
|
674
|
-
healthTracker.recordFailure(relay);
|
|
675
|
-
metrics.emit("relay.error", 1, { relay, latency });
|
|
676
|
-
|
|
677
|
-
onError?.(lastError, `publish to ${relay}`);
|
|
678
|
-
}
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
throw new Error(`Failed to publish to any relay: ${lastError?.message}`);
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
// ============================================================================
|
|
685
|
-
// Pubkey Utilities
|
|
686
|
-
// ============================================================================
|
|
687
|
-
|
|
688
|
-
/**
|
|
689
|
-
* Check if a string looks like a valid Nostr pubkey (hex or npub)
|
|
690
|
-
*/
|
|
691
|
-
export function isValidPubkey(input: string): boolean {
|
|
692
|
-
if (typeof input !== "string") return false;
|
|
693
|
-
const trimmed = input.trim();
|
|
694
|
-
|
|
695
|
-
// npub format
|
|
696
|
-
if (trimmed.startsWith("npub1")) {
|
|
697
|
-
try {
|
|
698
|
-
const decoded = nip19.decode(trimmed);
|
|
699
|
-
return decoded.type === "npub";
|
|
700
|
-
} catch {
|
|
701
|
-
return false;
|
|
702
|
-
}
|
|
703
|
-
}
|
|
704
|
-
|
|
705
|
-
// Hex format
|
|
706
|
-
return /^[0-9a-fA-F]{64}$/.test(trimmed);
|
|
707
|
-
}
|
|
708
|
-
|
|
709
|
-
/**
|
|
710
|
-
* Normalize a pubkey to hex format (accepts npub or hex)
|
|
711
|
-
*/
|
|
712
|
-
export function normalizePubkey(input: string): string {
|
|
713
|
-
const trimmed = input.trim();
|
|
714
|
-
|
|
715
|
-
// npub format - decode to hex
|
|
716
|
-
if (trimmed.startsWith("npub1")) {
|
|
717
|
-
const decoded = nip19.decode(trimmed);
|
|
718
|
-
if (decoded.type !== "npub") {
|
|
719
|
-
throw new Error("Invalid npub key");
|
|
720
|
-
}
|
|
721
|
-
// Convert Uint8Array to hex string
|
|
722
|
-
return Array.from(decoded.data)
|
|
723
|
-
.map((b) => b.toString(16).padStart(2, "0"))
|
|
724
|
-
.join("");
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
// Already hex - validate and return lowercase
|
|
728
|
-
if (!/^[0-9a-fA-F]{64}$/.test(trimmed)) {
|
|
729
|
-
throw new Error("Pubkey must be 64 hex characters or npub format");
|
|
730
|
-
}
|
|
731
|
-
return trimmed.toLowerCase();
|
|
732
|
-
}
|
|
733
|
-
|
|
734
|
-
/**
|
|
735
|
-
* Convert a hex pubkey to npub format
|
|
736
|
-
*/
|
|
737
|
-
export function pubkeyToNpub(hexPubkey: string): string {
|
|
738
|
-
const normalized = normalizePubkey(hexPubkey);
|
|
739
|
-
// npubEncode expects a hex string, not Uint8Array
|
|
740
|
-
return nip19.npubEncode(normalized);
|
|
741
|
-
}
|