mercury-agent 0.4.5
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/LICENSE +22 -0
- package/README.md +438 -0
- package/container/Dockerfile +127 -0
- package/container/Dockerfile.base +109 -0
- package/container/Dockerfile.power +17 -0
- package/container/agent-package.json +8 -0
- package/container/build.sh +54 -0
- package/docs/TODOS.md +147 -0
- package/docs/auth/dashboard.md +28 -0
- package/docs/auth/overview.md +109 -0
- package/docs/auth/whatsapp.md +173 -0
- package/docs/configuration.md +54 -0
- package/docs/container-lifecycle.md +349 -0
- package/docs/context-architecture.md +87 -0
- package/docs/deployment.md +199 -0
- package/docs/extensions.md +375 -0
- package/docs/graceful-shutdown.md +62 -0
- package/docs/kb-distillation.md +77 -0
- package/docs/media/overview.md +140 -0
- package/docs/media/whatsapp.md +171 -0
- package/docs/memory.md +137 -0
- package/docs/permissions.md +217 -0
- package/docs/pipeline.md +228 -0
- package/docs/prd-chat-memory.md +76 -0
- package/docs/prd-config-load.md +82 -0
- package/docs/rate-limiting.md +166 -0
- package/docs/scheduler.md +288 -0
- package/docs/setup-discord.md +100 -0
- package/docs/setup-slack.md +119 -0
- package/docs/setup-whatsapp.md +94 -0
- package/docs/subagents.md +166 -0
- package/docs/web-search.md +62 -0
- package/examples/extensions/README.md +12 -0
- package/examples/extensions/charts/index.ts +13 -0
- package/examples/extensions/charts/skill/SKILL.md +98 -0
- package/examples/extensions/gws/README.md +52 -0
- package/examples/extensions/gws/index.ts +106 -0
- package/examples/extensions/gws/skill/SKILL.md +57 -0
- package/examples/extensions/gws/skill/references/calendar.md +101 -0
- package/examples/extensions/gws/skill/references/docs.md +65 -0
- package/examples/extensions/gws/skill/references/drive.md +79 -0
- package/examples/extensions/gws/skill/references/gmail.md +85 -0
- package/examples/extensions/gws/skill/references/sheets.md +60 -0
- package/examples/extensions/napkin/index.ts +821 -0
- package/examples/extensions/napkin/prompts/consolidation-monthly.md +73 -0
- package/examples/extensions/napkin/prompts/consolidation-weekly.md +67 -0
- package/examples/extensions/napkin/prompts/kb-distillation.md +176 -0
- package/examples/extensions/napkin/skill/SKILL.md +728 -0
- package/examples/extensions/pdf/index.ts +23 -0
- package/examples/extensions/pdf/skill/LICENSE.txt +30 -0
- package/examples/extensions/pdf/skill/SKILL.md +314 -0
- package/examples/extensions/pdf/skill/forms.md +294 -0
- package/examples/extensions/pdf/skill/reference.md +612 -0
- package/examples/extensions/pdf/skill/scripts/check_bounding_boxes.py +65 -0
- package/examples/extensions/pdf/skill/scripts/check_fillable_fields.py +11 -0
- package/examples/extensions/pdf/skill/scripts/convert_pdf_to_images.py +33 -0
- package/examples/extensions/pdf/skill/scripts/create_validation_image.py +37 -0
- package/examples/extensions/pdf/skill/scripts/extract_form_field_info.py +122 -0
- package/examples/extensions/pdf/skill/scripts/extract_form_structure.py +115 -0
- package/examples/extensions/pdf/skill/scripts/fill_fillable_fields.py +98 -0
- package/examples/extensions/pdf/skill/scripts/fill_pdf_form_with_annotations.py +107 -0
- package/examples/extensions/permission-guard/index.ts +65 -0
- package/examples/extensions/pinchtab/index.ts +199 -0
- package/examples/extensions/pinchtab/lib/session-injector.ts +144 -0
- package/examples/extensions/pinchtab/skill/SKILL.md +224 -0
- package/examples/extensions/pinchtab/skill/TRUST.md +69 -0
- package/examples/extensions/pinchtab/skill/references/api.md +297 -0
- package/examples/extensions/pinchtab/skill/references/env.md +45 -0
- package/examples/extensions/pinchtab/skill/references/profiles.md +107 -0
- package/examples/extensions/tradestation/host/refresh.ts +102 -0
- package/examples/extensions/tradestation/index.ts +153 -0
- package/examples/extensions/tradestation/skill/SKILL.md +67 -0
- package/examples/extensions/tradestation/skill/scripts/ts-cli.ts +111 -0
- package/examples/extensions/voice-synth/index.ts +94 -0
- package/examples/extensions/voice-synth/skill/SKILL.md +38 -0
- package/examples/extensions/voice-transcribe/index.ts +381 -0
- package/examples/extensions/voice-transcribe/requirements.txt +8 -0
- package/examples/extensions/voice-transcribe/scripts/transcribe.py +179 -0
- package/examples/extensions/voice-transcribe/skill/SKILL.md +53 -0
- package/examples/extensions/web-search/index.ts +22 -0
- package/examples/extensions/web-search/skill/SKILL.md +114 -0
- package/examples/extensions/web-search/skill/references/apartments.md +178 -0
- package/examples/extensions/web-search/skill/references/car-purchase.md +132 -0
- package/examples/extensions/web-search/skill/references/car-rental.md +113 -0
- package/examples/extensions/web-search/skill/references/flights.md +133 -0
- package/examples/extensions/web-search/skill/references/hotels.md +148 -0
- package/examples/extensions/yahoo-mail/cli/bun.lock +66 -0
- package/examples/extensions/yahoo-mail/cli/package.json +13 -0
- package/examples/extensions/yahoo-mail/cli/ymail.mjs +353 -0
- package/examples/extensions/yahoo-mail/index.ts +57 -0
- package/examples/extensions/yahoo-mail/skill/SKILL.md +78 -0
- package/package.json +106 -0
- package/resources/agents/explore.md +50 -0
- package/resources/agents/worker.md +24 -0
- package/resources/builtin-extensions.txt +3 -0
- package/resources/connection-env-vars.json +25 -0
- package/resources/extensions/.gitkeep +0 -0
- package/resources/pi-extensions/subagent/agents.ts +126 -0
- package/resources/pi-extensions/subagent/index.ts +964 -0
- package/resources/profiles/coding/AGENTS.md +43 -0
- package/resources/profiles/coding/mercury-profile.yaml +15 -0
- package/resources/profiles/general/AGENTS.md +31 -0
- package/resources/profiles/general/mercury-profile.yaml +15 -0
- package/resources/profiles/research/AGENTS.md +40 -0
- package/resources/profiles/research/mercury-profile.yaml +15 -0
- package/resources/skills/config/SKILL.md +25 -0
- package/resources/skills/context/SKILL.md +33 -0
- package/resources/skills/conversation-recap/SKILL.md +19 -0
- package/resources/skills/media/SKILL.md +27 -0
- package/resources/skills/mutes/SKILL.md +31 -0
- package/resources/skills/permissions/SKILL.md +19 -0
- package/resources/skills/preferences/SKILL.md +31 -0
- package/resources/skills/recall/SKILL.md +24 -0
- package/resources/skills/roles/SKILL.md +18 -0
- package/resources/skills/spaces/SKILL.md +18 -0
- package/resources/skills/tasks/SKILL.md +45 -0
- package/resources/templates/AGENTS.md +157 -0
- package/resources/templates/env.template +34 -0
- package/resources/templates/mercury.example.yaml +75 -0
- package/src/adapters/discord-native.ts +534 -0
- package/src/adapters/discord.ts +38 -0
- package/src/adapters/setup.ts +89 -0
- package/src/adapters/slack.ts +9 -0
- package/src/adapters/whatsapp-media.ts +337 -0
- package/src/adapters/whatsapp.ts +629 -0
- package/src/agent/api-socket.ts +127 -0
- package/src/agent/container-entry.ts +967 -0
- package/src/agent/container-error.ts +49 -0
- package/src/agent/container-runner.ts +1272 -0
- package/src/agent/model-capabilities-core.ts +23 -0
- package/src/agent/model-capabilities.ts +231 -0
- package/src/agent/pi-failure-class.ts +83 -0
- package/src/agent/pi-jsonl-parser.ts +306 -0
- package/src/agent/preferences-prompt.ts +20 -0
- package/src/agent/user-error-messages.ts +78 -0
- package/src/bridges/discord.ts +171 -0
- package/src/bridges/slack.ts +177 -0
- package/src/bridges/teams.ts +160 -0
- package/src/bridges/telegram.ts +571 -0
- package/src/bridges/whatsapp.ts +290 -0
- package/src/chat-shim.ts +259 -0
- package/src/cli/mercury.ts +2508 -0
- package/src/cli/mrctl-http.ts +27 -0
- package/src/cli/mrctl.ts +611 -0
- package/src/cli/whatsapp-auth.ts +260 -0
- package/src/config-file.ts +397 -0
- package/src/config-model-chain.ts +30 -0
- package/src/config.ts +316 -0
- package/src/core/api-types.ts +58 -0
- package/src/core/api.ts +105 -0
- package/src/core/commands.ts +76 -0
- package/src/core/conversation.ts +47 -0
- package/src/core/handler.ts +206 -0
- package/src/core/media.ts +200 -0
- package/src/core/mute-duration.ts +22 -0
- package/src/core/outbox.ts +76 -0
- package/src/core/permissions.ts +192 -0
- package/src/core/profiles.ts +245 -0
- package/src/core/rate-limiter.ts +127 -0
- package/src/core/router.ts +191 -0
- package/src/core/routes/chat.ts +172 -0
- package/src/core/routes/config-builtin.ts +107 -0
- package/src/core/routes/config.ts +81 -0
- package/src/core/routes/connections.ts +190 -0
- package/src/core/routes/console.ts +668 -0
- package/src/core/routes/control.ts +46 -0
- package/src/core/routes/conversations.ts +66 -0
- package/src/core/routes/dashboard.ts +2491 -0
- package/src/core/routes/extensions.ts +37 -0
- package/src/core/routes/index.ts +14 -0
- package/src/core/routes/media.ts +72 -0
- package/src/core/routes/messages.ts +37 -0
- package/src/core/routes/mutes.ts +89 -0
- package/src/core/routes/prefs.ts +95 -0
- package/src/core/routes/roles.ts +125 -0
- package/src/core/routes/spaces.ts +60 -0
- package/src/core/routes/storage.ts +126 -0
- package/src/core/routes/tasks.ts +189 -0
- package/src/core/routes/tradestation.ts +268 -0
- package/src/core/routes/tts.ts +51 -0
- package/src/core/runtime.ts +1140 -0
- package/src/core/space-queue.ts +103 -0
- package/src/core/storage-cleanup.ts +140 -0
- package/src/core/storage-guard.ts +24 -0
- package/src/core/task-scheduler.ts +132 -0
- package/src/core/telegram-format.ts +178 -0
- package/src/core/trigger.ts +142 -0
- package/src/dashboard/index.html +729 -0
- package/src/dashboard/tokens.css +53 -0
- package/src/extensions/api.ts +252 -0
- package/src/extensions/catalog.ts +117 -0
- package/src/extensions/config-registry.ts +83 -0
- package/src/extensions/context.ts +36 -0
- package/src/extensions/hooks.ts +156 -0
- package/src/extensions/image-builder.ts +617 -0
- package/src/extensions/installer.ts +306 -0
- package/src/extensions/jobs.ts +122 -0
- package/src/extensions/loader.ts +271 -0
- package/src/extensions/permission-guard.ts +52 -0
- package/src/extensions/reserved.ts +28 -0
- package/src/extensions/skills.ts +123 -0
- package/src/extensions/types.ts +462 -0
- package/src/logger.ts +174 -0
- package/src/main.ts +586 -0
- package/src/server.ts +391 -0
- package/src/storage/db.ts +1624 -0
- package/src/storage/memory.ts +45 -0
- package/src/storage/pi-auth.ts +95 -0
- package/src/text/markdown.ts +117 -0
- package/src/text/rtl.ts +38 -0
- package/src/tradestation/host-api.ts +77 -0
- package/src/tradestation/pending-orders.ts +69 -0
- package/src/tts/azure.ts +52 -0
- package/src/tts/google.ts +128 -0
- package/src/tts/index.ts +8 -0
- package/src/tts/language.ts +20 -0
- package/src/tts/synthesize.ts +133 -0
- package/src/types.ts +295 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# Hotels Reference
|
|
2
|
+
|
|
3
|
+
## Primary site: Google Hotels
|
|
4
|
+
|
|
5
|
+
**Why Google Hotels:**
|
|
6
|
+
- Deep-link URL accepts city, dates, and guest counts
|
|
7
|
+
- Clean accessibility tree: hotel cards have price, rating, and neighbourhood in aria-labels
|
|
8
|
+
- No CAPTCHA for read-only searches
|
|
9
|
+
- Aggregates pricing across Booking.com, Expedia, Hotels.com, and direct hotel sites
|
|
10
|
+
|
|
11
|
+
**Fallback: Booking.com** — broader international coverage, especially for non-English cities. See below.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## URL Construction
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
https://www.google.com/travel/hotels/entity/{CITY_SLUG}?q=hotels+in+{CITY}&checkin={CHECKIN}&checkout={CHECKOUT}&adults={ADULTS}&children={CHILDREN_AGES}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
The simplest and most reliable approach is the natural-language search URL:
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
https://www.google.com/travel/hotels?q=hotels+in+{CITY}&checkin={CHECKIN}&checkout={CHECKOUT}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
| Placeholder | Format | Example |
|
|
28
|
+
|---|---|---|
|
|
29
|
+
| `{CITY}` | URL-encoded city name | `Bangkok` → `Bangkok` |
|
|
30
|
+
| `{CHECKIN}` | YYYY-MM-DD | `2026-11-01` |
|
|
31
|
+
| `{CHECKOUT}` | YYYY-MM-DD | `2026-11-08` |
|
|
32
|
+
| `{ADULTS}` | integer | `2` |
|
|
33
|
+
| `{CHILDREN_AGES}` | comma-separated ages | `7,3` |
|
|
34
|
+
|
|
35
|
+
**Worked example — Bangkok, 2 adults + child 7 + child 3, Nov 1–8:**
|
|
36
|
+
```
|
|
37
|
+
https://www.google.com/travel/hotels?q=hotels+in+Bangkok&checkin=2026-11-01&checkout=2026-11-08
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Guest counts and filters are best applied after load via filter refs (see below).
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Step-by-Step Workflow
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
# 1. Ensure browser is running
|
|
48
|
+
pinchtab_ensure || exit 1
|
|
49
|
+
|
|
50
|
+
# 2. Navigate
|
|
51
|
+
pinchtab nav "https://www.google.com/travel/hotels?q=hotels+in+Bangkok&checkin=2026-11-01&checkout=2026-11-08"
|
|
52
|
+
|
|
53
|
+
# 3. Wait for results to render
|
|
54
|
+
sleep 4
|
|
55
|
+
|
|
56
|
+
# 4. Extract results
|
|
57
|
+
pinchtab text
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
If results are present (hotel names, prices, ratings), extract top 5–10. Done.
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Adjusting Guest Count
|
|
65
|
+
|
|
66
|
+
If the default guest count (2 adults) doesn't match the request:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
pinchtab snap -i -c
|
|
70
|
+
# Find the "Travelers" or "Guests" selector ref
|
|
71
|
+
pinchtab click {ref}
|
|
72
|
+
sleep 2
|
|
73
|
+
pinchtab snap -i -c
|
|
74
|
+
# Find increment/decrement refs for adults and children
|
|
75
|
+
# Adjust, then close and re-read
|
|
76
|
+
pinchtab text
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Applying Filters
|
|
82
|
+
|
|
83
|
+
| Filter | What to look for in snap -i -c |
|
|
84
|
+
|---|---|
|
|
85
|
+
| Price range | "Price" filter button ref |
|
|
86
|
+
| Star rating | "Hotel class" filter button ref |
|
|
87
|
+
| Sort by price | "Sort" dropdown → "Price: low to high" option ref |
|
|
88
|
+
| Amenities | "Amenities" filter ref |
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Result Extraction
|
|
93
|
+
|
|
94
|
+
From `pinchtab text`, look for:
|
|
95
|
+
- Hotel name
|
|
96
|
+
- Nightly price (e.g. "$89/night")
|
|
97
|
+
- Total price for stay
|
|
98
|
+
- Star rating or guest score
|
|
99
|
+
- Neighbourhood / distance from centre
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Fallback: Booking.com
|
|
104
|
+
|
|
105
|
+
Use for cities where Google Hotels returns sparse results, or for more granular filtering.
|
|
106
|
+
|
|
107
|
+
```
|
|
108
|
+
https://www.booking.com/searchresults.html?ss={CITY}&checkin={CHECKIN}&checkout={CHECKOUT}&group_adults={ADULTS}&no_rooms=1&group_children={CHILDREN}&age={CHILD_AGE_1}&age={CHILD_AGE_2}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
pinchtab nav "https://www.booking.com/searchresults.html?ss=Bangkok&checkin=2026-11-01&checkout=2026-11-08&group_adults=2&no_rooms=1&group_children=2&age=7&age=3"
|
|
113
|
+
sleep 4
|
|
114
|
+
pinchtab text
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
**Known issue — cookie consent banner**: Booking.com shows a GDPR consent dialog on first load. If `pinchtab text` returns no hotel results:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
pinchtab snap -i -c
|
|
121
|
+
# Find the "Accept" or "I agree" button ref
|
|
122
|
+
pinchtab click {ref}
|
|
123
|
+
sleep 2
|
|
124
|
+
pinchtab text
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## Fallback: Airbnb (for apartment-style or vacation rental framing)
|
|
130
|
+
|
|
131
|
+
Use when the user says "Airbnb", "vacation rental", or "apartment with kitchen":
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
https://www.airbnb.com/s/{CITY}/homes?checkin={CHECKIN}&checkout={CHECKOUT}&adults={ADULTS}&children={CHILDREN}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
pinchtab nav "https://www.airbnb.com/s/Bangkok/homes?checkin=2026-11-01&checkout=2026-11-08&adults=2&children=2"
|
|
139
|
+
sleep 5 # Airbnb is React-heavy, needs extra time
|
|
140
|
+
pinchtab text
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## Token Efficiency
|
|
146
|
+
|
|
147
|
+
- `pinchtab text` (~800 tokens) is sufficient to read hotel names, prices, and ratings.
|
|
148
|
+
- Use `pinchtab snap -i -c` only when adjusting guest counts or applying filters.
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"lockfileVersion": 1,
|
|
3
|
+
"configVersion": 1,
|
|
4
|
+
"workspaces": {
|
|
5
|
+
"": {
|
|
6
|
+
"name": "ymail",
|
|
7
|
+
"dependencies": {
|
|
8
|
+
"imapflow": "^1.0.171",
|
|
9
|
+
"nodemailer": "^8.0.5",
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
"packages": {
|
|
14
|
+
"@pinojs/redact": ["@pinojs/redact@0.4.0", "", {}, "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg=="],
|
|
15
|
+
|
|
16
|
+
"@zone-eu/mailsplit": ["@zone-eu/mailsplit@5.4.9", "", { "dependencies": { "libbase64": "1.3.0", "libmime": "5.3.8", "libqp": "2.1.1" } }, "sha512-Qq7k6FzA5SmGf5HFPcr17gE7M+O1gttlmWn7tlGUlhGsbbjUaBL/4cEWIwExeCzqu5+kyZJ91mcBZbQ9zEwwYA=="],
|
|
17
|
+
|
|
18
|
+
"atomic-sleep": ["atomic-sleep@1.0.0", "", {}, "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ=="],
|
|
19
|
+
|
|
20
|
+
"encoding-japanese": ["encoding-japanese@2.2.0", "", {}, "sha512-EuJWwlHPZ1LbADuKTClvHtwbaFn4rOD+dRAbWysqEOXRc2Uui0hJInNJrsdH0c+OhJA4nrCBdSkW4DD5YxAo6A=="],
|
|
21
|
+
|
|
22
|
+
"iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="],
|
|
23
|
+
|
|
24
|
+
"imapflow": ["imapflow@1.3.3", "", { "dependencies": { "@zone-eu/mailsplit": "5.4.9", "encoding-japanese": "2.2.0", "iconv-lite": "0.7.2", "libbase64": "1.3.0", "libmime": "5.3.8", "libqp": "2.1.1", "nodemailer": "8.0.7", "pino": "10.3.1", "socks": "2.8.8" } }, "sha512-lx7nWcUDfNgITEKYYfunUDqJ3LT6ImuiA1ReqJepVEA2nqBQNUqa3ppF7Yz5CNjuDYG95pmzsCcNqRjMrwh/Vg=="],
|
|
25
|
+
|
|
26
|
+
"ip-address": ["ip-address@10.2.0", "", {}, "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA=="],
|
|
27
|
+
|
|
28
|
+
"libbase64": ["libbase64@1.3.0", "", {}, "sha512-GgOXd0Eo6phYgh0DJtjQ2tO8dc0IVINtZJeARPeiIJqge+HdsWSuaDTe8ztQ7j/cONByDZ3zeB325AHiv5O0dg=="],
|
|
29
|
+
|
|
30
|
+
"libmime": ["libmime@5.3.8", "", { "dependencies": { "encoding-japanese": "2.2.0", "iconv-lite": "0.7.2", "libbase64": "1.3.0", "libqp": "2.1.1" } }, "sha512-ZrCY+Q66mPvasAfjsQ/IgahzoBvfE1VdtGRpo1hwRB1oK3wJKxhKA3GOcd2a6j7AH5eMFccxK9fBoCpRZTf8ng=="],
|
|
31
|
+
|
|
32
|
+
"libqp": ["libqp@2.1.1", "", {}, "sha512-0Wd+GPz1O134cP62YU2GTOPNA7Qgl09XwCqM5zpBv87ERCXdfDtyKXvV7c9U22yWJh44QZqBocFnXN11K96qow=="],
|
|
33
|
+
|
|
34
|
+
"nodemailer": ["nodemailer@8.0.7", "", {}, "sha512-pkjE4mkBzQjdJT4/UmlKl3pX0rC9fZmjh7c6C9o7lv66Ac6w9WCnzPzhbPNxwZAzlF4mdq4CSWB5+FbK6FWCow=="],
|
|
35
|
+
|
|
36
|
+
"on-exit-leak-free": ["on-exit-leak-free@2.1.2", "", {}, "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA=="],
|
|
37
|
+
|
|
38
|
+
"pino": ["pino@10.3.1", "", { "dependencies": { "@pinojs/redact": "^0.4.0", "atomic-sleep": "^1.0.0", "on-exit-leak-free": "^2.1.0", "pino-abstract-transport": "^3.0.0", "pino-std-serializers": "^7.0.0", "process-warning": "^5.0.0", "quick-format-unescaped": "^4.0.3", "real-require": "^0.2.0", "safe-stable-stringify": "^2.3.1", "sonic-boom": "^4.0.1", "thread-stream": "^4.0.0" }, "bin": { "pino": "bin.js" } }, "sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg=="],
|
|
39
|
+
|
|
40
|
+
"pino-abstract-transport": ["pino-abstract-transport@3.0.0", "", { "dependencies": { "split2": "^4.0.0" } }, "sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg=="],
|
|
41
|
+
|
|
42
|
+
"pino-std-serializers": ["pino-std-serializers@7.1.0", "", {}, "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw=="],
|
|
43
|
+
|
|
44
|
+
"process-warning": ["process-warning@5.0.0", "", {}, "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA=="],
|
|
45
|
+
|
|
46
|
+
"quick-format-unescaped": ["quick-format-unescaped@4.0.4", "", {}, "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg=="],
|
|
47
|
+
|
|
48
|
+
"real-require": ["real-require@0.2.0", "", {}, "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg=="],
|
|
49
|
+
|
|
50
|
+
"safe-stable-stringify": ["safe-stable-stringify@2.5.0", "", {}, "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA=="],
|
|
51
|
+
|
|
52
|
+
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
|
|
53
|
+
|
|
54
|
+
"smart-buffer": ["smart-buffer@4.2.0", "", {}, "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="],
|
|
55
|
+
|
|
56
|
+
"socks": ["socks@2.8.8", "", { "dependencies": { "ip-address": "^10.1.1", "smart-buffer": "^4.2.0" } }, "sha512-NlGELfPrgX2f1TAAcz0WawlLn+0r3FyhhCRpFFK2CemXenPYvzMWWZINv3eDNo9ucdwme7oCHRY0Jnbs4aIkog=="],
|
|
57
|
+
|
|
58
|
+
"sonic-boom": ["sonic-boom@4.2.1", "", { "dependencies": { "atomic-sleep": "^1.0.0" } }, "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q=="],
|
|
59
|
+
|
|
60
|
+
"split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="],
|
|
61
|
+
|
|
62
|
+
"thread-stream": ["thread-stream@4.1.0", "", { "dependencies": { "real-require": "^1.0.0" } }, "sha512-Bw6h2iBDt16v6iHLChBIoVYU8CBo9GPsW8TG7h1hRVhqKhIkH6N8qkxNSmiOZTKsCLPbtWG4ViWLkU6KeKXpig=="],
|
|
63
|
+
|
|
64
|
+
"thread-stream/real-require": ["real-require@1.0.0", "", {}, "sha512-P4nbQYQfePJxRSmY+v/KINxVucm4NF3p3s7pJveMTtom52FR4YGltUQLB8idDXwDDWW+eYrWDFbuzUnjoWHF7g=="],
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { ImapFlow } from "imapflow";
|
|
4
|
+
import { createTransport } from "nodemailer";
|
|
5
|
+
|
|
6
|
+
const IMAP_HOST = "imap.mail.yahoo.com";
|
|
7
|
+
const IMAP_PORT = 993;
|
|
8
|
+
const SMTP_HOST = "smtp.mail.yahoo.com";
|
|
9
|
+
const SMTP_PORT = 465;
|
|
10
|
+
|
|
11
|
+
function env(name) {
|
|
12
|
+
const v = process.env[name];
|
|
13
|
+
if (!v) {
|
|
14
|
+
console.error(JSON.stringify({ error: `${name} not set` }));
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
return v;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function withClient(fn) {
|
|
21
|
+
const client = new ImapFlow({
|
|
22
|
+
host: IMAP_HOST,
|
|
23
|
+
port: IMAP_PORT,
|
|
24
|
+
secure: true,
|
|
25
|
+
auth: {
|
|
26
|
+
user: env("YAHOO_EMAIL"),
|
|
27
|
+
pass: env("YAHOO_APP_PASSWORD"),
|
|
28
|
+
},
|
|
29
|
+
logger: false,
|
|
30
|
+
});
|
|
31
|
+
try {
|
|
32
|
+
await client.connect();
|
|
33
|
+
return await fn(client);
|
|
34
|
+
} finally {
|
|
35
|
+
await client.logout().catch(() => {});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function formatMessage(msg) {
|
|
40
|
+
return {
|
|
41
|
+
uid: msg.uid,
|
|
42
|
+
messageId: msg.envelope?.messageId ?? null,
|
|
43
|
+
inReplyTo: msg.envelope?.inReplyTo ?? null,
|
|
44
|
+
from: msg.envelope?.from?.map((a) => `${a.name || ""} <${a.address}>`).join(", ") ?? "",
|
|
45
|
+
to: msg.envelope?.to?.map((a) => `${a.name || ""} <${a.address}>`).join(", ") ?? "",
|
|
46
|
+
subject: msg.envelope?.subject ?? "",
|
|
47
|
+
date: msg.envelope?.date?.toISOString() ?? "",
|
|
48
|
+
flags: [...(msg.flags || [])],
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function downloadTextBody(client, uid) {
|
|
53
|
+
const msg = await client.fetchOne(uid, {
|
|
54
|
+
uid: true,
|
|
55
|
+
bodyStructure: true,
|
|
56
|
+
});
|
|
57
|
+
const parts = flattenParts(msg.bodyStructure);
|
|
58
|
+
const textPart = parts.find((p) => p.type === "text/plain") || parts.find((p) => p.type === "text/html");
|
|
59
|
+
if (!textPart) return "(no text body)";
|
|
60
|
+
|
|
61
|
+
const { content } = await client.download(uid.toString(), textPart.part, { uid: true });
|
|
62
|
+
const chunks = [];
|
|
63
|
+
for await (const chunk of content) {
|
|
64
|
+
chunks.push(chunk);
|
|
65
|
+
}
|
|
66
|
+
let text = Buffer.concat(chunks).toString("utf8");
|
|
67
|
+
if (textPart.type === "text/html") {
|
|
68
|
+
text = text.replace(/<[^>]+>/g, "").replace(/ /g, " ").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
69
|
+
}
|
|
70
|
+
return text.trim().slice(0, 10_000);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function flattenParts(structure, prefix = "") {
|
|
74
|
+
const results = [];
|
|
75
|
+
if (!structure) return results;
|
|
76
|
+
|
|
77
|
+
if (structure.childNodes) {
|
|
78
|
+
for (let i = 0; i < structure.childNodes.length; i++) {
|
|
79
|
+
const partNum = prefix ? `${prefix}.${i + 1}` : `${i + 1}`;
|
|
80
|
+
results.push(...flattenParts(structure.childNodes[i], partNum));
|
|
81
|
+
}
|
|
82
|
+
} else {
|
|
83
|
+
results.push({
|
|
84
|
+
part: prefix || "1",
|
|
85
|
+
type: structure.type,
|
|
86
|
+
encoding: structure.encoding,
|
|
87
|
+
size: structure.size,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
return results;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const commands = {
|
|
94
|
+
async "list-folders"() {
|
|
95
|
+
return withClient(async (client) => {
|
|
96
|
+
const folders = await client.list();
|
|
97
|
+
const result = folders.map((f) => ({
|
|
98
|
+
path: f.path,
|
|
99
|
+
name: f.name,
|
|
100
|
+
specialUse: f.specialUse || null,
|
|
101
|
+
messages: f.status?.messages ?? null,
|
|
102
|
+
}));
|
|
103
|
+
console.log(JSON.stringify(result));
|
|
104
|
+
});
|
|
105
|
+
},
|
|
106
|
+
|
|
107
|
+
async "list-inbox"() {
|
|
108
|
+
const folder = process.argv[3] || "INBOX";
|
|
109
|
+
const limit = parseInt(process.argv[4] || "50", 10);
|
|
110
|
+
return withClient(async (client) => {
|
|
111
|
+
const lock = await client.getMailboxLock(folder);
|
|
112
|
+
try {
|
|
113
|
+
const status = await client.status(folder, { messages: true });
|
|
114
|
+
const total = status.messages || 0;
|
|
115
|
+
const start = Math.max(1, total - limit + 1);
|
|
116
|
+
const messages = [];
|
|
117
|
+
for await (const msg of client.fetch(`${start}:*`, {
|
|
118
|
+
envelope: true,
|
|
119
|
+
flags: true,
|
|
120
|
+
})) {
|
|
121
|
+
messages.push(formatMessage(msg));
|
|
122
|
+
}
|
|
123
|
+
messages.reverse();
|
|
124
|
+
console.log(JSON.stringify({ folder, total, messages }));
|
|
125
|
+
} finally {
|
|
126
|
+
lock.release();
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
},
|
|
130
|
+
|
|
131
|
+
async search() {
|
|
132
|
+
const query = process.argv[3];
|
|
133
|
+
if (!query) {
|
|
134
|
+
console.error(JSON.stringify({ error: "Usage: ymail search <query> [folder] [limit]" }));
|
|
135
|
+
process.exit(1);
|
|
136
|
+
}
|
|
137
|
+
const folder = process.argv[4] || "INBOX";
|
|
138
|
+
const limit = parseInt(process.argv[5] || "20", 10);
|
|
139
|
+
return withClient(async (client) => {
|
|
140
|
+
const lock = await client.getMailboxLock(folder);
|
|
141
|
+
try {
|
|
142
|
+
const uids = await client.search(
|
|
143
|
+
{ or: [{ subject: query }, { from: query }, { body: query }] },
|
|
144
|
+
{ uid: true },
|
|
145
|
+
);
|
|
146
|
+
const selected = uids.slice(-limit);
|
|
147
|
+
const messages = [];
|
|
148
|
+
if (selected.length > 0) {
|
|
149
|
+
const range = selected.join(",");
|
|
150
|
+
for await (const msg of client.fetch(range, {
|
|
151
|
+
envelope: true,
|
|
152
|
+
flags: true,
|
|
153
|
+
uid: true,
|
|
154
|
+
})) {
|
|
155
|
+
messages.push(formatMessage(msg));
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
messages.reverse();
|
|
159
|
+
console.log(JSON.stringify({ query, folder, total: uids.length, messages }));
|
|
160
|
+
} finally {
|
|
161
|
+
lock.release();
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
},
|
|
165
|
+
|
|
166
|
+
async read() {
|
|
167
|
+
const uid = process.argv[3];
|
|
168
|
+
if (!uid) {
|
|
169
|
+
console.error(JSON.stringify({ error: "Usage: ymail read <uid> [folder]" }));
|
|
170
|
+
process.exit(1);
|
|
171
|
+
}
|
|
172
|
+
const folder = process.argv[4] || "INBOX";
|
|
173
|
+
return withClient(async (client) => {
|
|
174
|
+
const lock = await client.getMailboxLock(folder);
|
|
175
|
+
try {
|
|
176
|
+
const msg = await client.fetchOne(uid, {
|
|
177
|
+
envelope: true,
|
|
178
|
+
flags: true,
|
|
179
|
+
uid: true,
|
|
180
|
+
});
|
|
181
|
+
const body = await downloadTextBody(client, uid);
|
|
182
|
+
console.log(
|
|
183
|
+
JSON.stringify({
|
|
184
|
+
...formatMessage(msg),
|
|
185
|
+
body,
|
|
186
|
+
})
|
|
187
|
+
);
|
|
188
|
+
} finally {
|
|
189
|
+
lock.release();
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
},
|
|
193
|
+
|
|
194
|
+
async move() {
|
|
195
|
+
const uid = process.argv[3];
|
|
196
|
+
const destination = process.argv[4];
|
|
197
|
+
if (!uid || !destination) {
|
|
198
|
+
console.error(JSON.stringify({ error: "Usage: ymail move <uid> <destination-folder> [source-folder]" }));
|
|
199
|
+
process.exit(1);
|
|
200
|
+
}
|
|
201
|
+
const source = process.argv[5] || "INBOX";
|
|
202
|
+
return withClient(async (client) => {
|
|
203
|
+
const lock = await client.getMailboxLock(source);
|
|
204
|
+
try {
|
|
205
|
+
await client.messageMove(uid, destination, { uid: true });
|
|
206
|
+
console.log(JSON.stringify({ ok: true, uid, from: source, to: destination }));
|
|
207
|
+
} finally {
|
|
208
|
+
lock.release();
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
},
|
|
212
|
+
|
|
213
|
+
async delete() {
|
|
214
|
+
const uid = process.argv[3];
|
|
215
|
+
if (!uid) {
|
|
216
|
+
console.error(JSON.stringify({ error: "Usage: ymail delete <uid> [folder]" }));
|
|
217
|
+
process.exit(1);
|
|
218
|
+
}
|
|
219
|
+
const folder = process.argv[4] || "INBOX";
|
|
220
|
+
return withClient(async (client) => {
|
|
221
|
+
const lock = await client.getMailboxLock(folder);
|
|
222
|
+
try {
|
|
223
|
+
await client.messageDelete(uid, { uid: true });
|
|
224
|
+
console.log(JSON.stringify({ ok: true, uid, folder }));
|
|
225
|
+
} finally {
|
|
226
|
+
lock.release();
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
},
|
|
230
|
+
|
|
231
|
+
async "mark-read"() {
|
|
232
|
+
const uid = process.argv[3];
|
|
233
|
+
if (!uid) {
|
|
234
|
+
console.error(JSON.stringify({ error: "Usage: ymail mark-read <uid> [folder]" }));
|
|
235
|
+
process.exit(1);
|
|
236
|
+
}
|
|
237
|
+
const folder = process.argv[4] || "INBOX";
|
|
238
|
+
return withClient(async (client) => {
|
|
239
|
+
const lock = await client.getMailboxLock(folder);
|
|
240
|
+
try {
|
|
241
|
+
await client.messageFlagsAdd(uid, ["\\Seen"], { uid: true });
|
|
242
|
+
console.log(JSON.stringify({ ok: true, uid, folder, marked: "read" }));
|
|
243
|
+
} finally {
|
|
244
|
+
lock.release();
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
},
|
|
248
|
+
|
|
249
|
+
async "mark-unread"() {
|
|
250
|
+
const uid = process.argv[3];
|
|
251
|
+
if (!uid) {
|
|
252
|
+
console.error(JSON.stringify({ error: "Usage: ymail mark-unread <uid> [folder]" }));
|
|
253
|
+
process.exit(1);
|
|
254
|
+
}
|
|
255
|
+
const folder = process.argv[4] || "INBOX";
|
|
256
|
+
return withClient(async (client) => {
|
|
257
|
+
const lock = await client.getMailboxLock(folder);
|
|
258
|
+
try {
|
|
259
|
+
await client.messageFlagsRemove(uid, ["\\Seen"], { uid: true });
|
|
260
|
+
console.log(JSON.stringify({ ok: true, uid, folder, marked: "unread" }));
|
|
261
|
+
} finally {
|
|
262
|
+
lock.release();
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
},
|
|
266
|
+
|
|
267
|
+
async send() {
|
|
268
|
+
const to = process.argv[3];
|
|
269
|
+
const subject = process.argv[4];
|
|
270
|
+
const body = process.argv[5];
|
|
271
|
+
if (!to || !subject || !body) {
|
|
272
|
+
console.error(JSON.stringify({ error: "Usage: ymail send <to> <subject> <body>" }));
|
|
273
|
+
process.exit(1);
|
|
274
|
+
}
|
|
275
|
+
const from = env("YAHOO_EMAIL");
|
|
276
|
+
const transport = createTransport({
|
|
277
|
+
host: SMTP_HOST,
|
|
278
|
+
port: SMTP_PORT,
|
|
279
|
+
secure: true,
|
|
280
|
+
auth: { user: from, pass: env("YAHOO_APP_PASSWORD") },
|
|
281
|
+
});
|
|
282
|
+
const info = await transport.sendMail({ from, to, subject, text: body });
|
|
283
|
+
console.log(JSON.stringify({ ok: true, messageId: info.messageId, to, subject }));
|
|
284
|
+
},
|
|
285
|
+
|
|
286
|
+
async reply() {
|
|
287
|
+
const uid = process.argv[3];
|
|
288
|
+
const body = process.argv[4];
|
|
289
|
+
if (!uid || !body) {
|
|
290
|
+
console.error(JSON.stringify({ error: "Usage: ymail reply <uid> <body> [folder]" }));
|
|
291
|
+
process.exit(1);
|
|
292
|
+
}
|
|
293
|
+
const folder = process.argv[5] || "INBOX";
|
|
294
|
+
const from = env("YAHOO_EMAIL");
|
|
295
|
+
|
|
296
|
+
const original = await withClient(async (client) => {
|
|
297
|
+
const lock = await client.getMailboxLock(folder);
|
|
298
|
+
try {
|
|
299
|
+
return await client.fetchOne(uid, { envelope: true, uid: true });
|
|
300
|
+
} finally {
|
|
301
|
+
lock.release();
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
const replyTo = original.envelope?.replyTo?.[0]?.address
|
|
306
|
+
|| original.envelope?.from?.[0]?.address;
|
|
307
|
+
if (!replyTo) {
|
|
308
|
+
console.error(JSON.stringify({ error: "Cannot determine reply address from original message" }));
|
|
309
|
+
process.exit(1);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const originalSubject = original.envelope?.subject ?? "";
|
|
313
|
+
const subject = originalSubject.startsWith("Re:") ? originalSubject : `Re: ${originalSubject}`;
|
|
314
|
+
const messageId = original.envelope?.messageId;
|
|
315
|
+
|
|
316
|
+
const headers = {};
|
|
317
|
+
if (messageId) {
|
|
318
|
+
headers["In-Reply-To"] = messageId;
|
|
319
|
+
headers["References"] = messageId;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const transport = createTransport({
|
|
323
|
+
host: SMTP_HOST,
|
|
324
|
+
port: SMTP_PORT,
|
|
325
|
+
secure: true,
|
|
326
|
+
auth: { user: from, pass: env("YAHOO_APP_PASSWORD") },
|
|
327
|
+
});
|
|
328
|
+
const info = await transport.sendMail({
|
|
329
|
+
from,
|
|
330
|
+
to: replyTo,
|
|
331
|
+
subject,
|
|
332
|
+
text: body,
|
|
333
|
+
headers,
|
|
334
|
+
});
|
|
335
|
+
console.log(JSON.stringify({ ok: true, messageId: info.messageId, to: replyTo, subject }));
|
|
336
|
+
},
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
const cmd = process.argv[2];
|
|
340
|
+
if (!cmd || !commands[cmd]) {
|
|
341
|
+
console.error(
|
|
342
|
+
JSON.stringify({
|
|
343
|
+
error: `Unknown command: ${cmd}`,
|
|
344
|
+
available: Object.keys(commands),
|
|
345
|
+
})
|
|
346
|
+
);
|
|
347
|
+
process.exit(1);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
commands[cmd]().catch((err) => {
|
|
351
|
+
console.error(JSON.stringify({ error: err.message }));
|
|
352
|
+
process.exit(1);
|
|
353
|
+
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
type ConnectionCategory =
|
|
2
|
+
| "email"
|
|
3
|
+
| "drive"
|
|
4
|
+
| "calendar"
|
|
5
|
+
| "finance"
|
|
6
|
+
| "messaging"
|
|
7
|
+
| "docs"
|
|
8
|
+
| "workspace"
|
|
9
|
+
| "other";
|
|
10
|
+
|
|
11
|
+
type ConnectionAuthType =
|
|
12
|
+
| "oauth2"
|
|
13
|
+
| "apikey"
|
|
14
|
+
| "app-password"
|
|
15
|
+
| "credentials-file"
|
|
16
|
+
| "custom";
|
|
17
|
+
|
|
18
|
+
type MercuryExt = {
|
|
19
|
+
cli(opts: { name: string; install: string; bin?: string }): void;
|
|
20
|
+
permission(opts: { defaultRoles: string[] }): void;
|
|
21
|
+
env(def: { from: string; as?: string }): void;
|
|
22
|
+
skill(relativePath: string): void;
|
|
23
|
+
connection(def: {
|
|
24
|
+
displayName: string;
|
|
25
|
+
iconUrl?: string;
|
|
26
|
+
category: ConnectionCategory;
|
|
27
|
+
authType: ConnectionAuthType;
|
|
28
|
+
credentialEnvVar?: string;
|
|
29
|
+
scopes?: string[];
|
|
30
|
+
}): void;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
import manifest from "../../../resources/connection-env-vars.json";
|
|
34
|
+
|
|
35
|
+
const yahooEnv = manifest["yahoo-mail"];
|
|
36
|
+
|
|
37
|
+
export default function (mercury: MercuryExt) {
|
|
38
|
+
mercury.cli({
|
|
39
|
+
name: "ymail",
|
|
40
|
+
install: "npm install -g imapflow nodemailer",
|
|
41
|
+
bin: "./cli/ymail.mjs",
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
mercury.permission({ defaultRoles: ["admin"] });
|
|
45
|
+
|
|
46
|
+
mercury.env({ from: yahooEnv.env.email });
|
|
47
|
+
mercury.env({ from: yahooEnv.env.appPassword });
|
|
48
|
+
|
|
49
|
+
mercury.connection({
|
|
50
|
+
displayName: "Yahoo Mail",
|
|
51
|
+
category: "email",
|
|
52
|
+
authType: "app-password",
|
|
53
|
+
credentialEnvVar: yahooEnv.credentialEnvVar,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
mercury.skill("./skill");
|
|
57
|
+
}
|