vybekiit 0.7.34 → 0.7.35

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.
Files changed (121) hide show
  1. package/dist/bin.js +520 -9
  2. package/dist/global-skills/eas-app-stores/SKILL.md +2 -2
  3. package/dist/global-skills/eas-hosting/SKILL.md +2 -2
  4. package/dist/global-skills/eas-observe/SKILL.md +7 -15
  5. package/dist/global-skills/eas-observe/references/queries.md +51 -10
  6. package/dist/global-skills/eas-observe/references/setup.md +60 -7
  7. package/dist/global-skills/eas-simulator/SKILL.md +2 -2
  8. package/dist/global-skills/eas-simulator/references/run-your-app.md +2 -0
  9. package/dist/global-skills/eas-update/SKILL.md +2 -2
  10. package/dist/global-skills/eas-update-insights/SKILL.md +2 -2
  11. package/dist/global-skills/eas-workflows/SKILL.md +2 -2
  12. package/dist/global-skills/expo-animation/SKILL.md +2 -2
  13. package/dist/global-skills/expo-app-clip/SKILL.md +2 -2
  14. package/dist/global-skills/expo-brownfield/SKILL.md +2 -2
  15. package/dist/global-skills/expo-data-fetching/SKILL.md +2 -2
  16. package/dist/global-skills/expo-design-system/SKILL.md +2 -2
  17. package/dist/global-skills/expo-dev-client/SKILL.md +4 -2
  18. package/dist/global-skills/expo-dom/SKILL.md +2 -2
  19. package/dist/global-skills/expo-examples/SKILL.md +2 -2
  20. package/dist/global-skills/expo-migrate-module/SKILL.md +2 -2
  21. package/dist/global-skills/expo-module/SKILL.md +2 -2
  22. package/dist/global-skills/expo-native-ui/SKILL.md +2 -2
  23. package/dist/global-skills/expo-overview/SKILL.md +9 -4
  24. package/dist/global-skills/expo-overview/references/launch-without-dev-menu.md +24 -0
  25. package/dist/global-skills/expo-project-structure/SKILL.md +2 -2
  26. package/dist/global-skills/expo-router/SKILL.md +2 -2
  27. package/dist/global-skills/expo-ui/SKILL.md +2 -2
  28. package/dist/global-skills/expo-upgrade/SKILL.md +2 -2
  29. package/dist/global-skills/expo-web-to-native/SKILL.md +2 -2
  30. package/dist/global-skills/expo-web-to-native/references/verify-on-device.md +1 -1
  31. package/dist/global-skills/firebase-app-hosting-basics/SKILL.md +16 -3
  32. package/dist/global-skills/firebase-app-hosting-basics/references/cli_commands.md +10 -12
  33. package/dist/global-skills/firebase-app-hosting-basics/references/configuration.md +2 -1
  34. package/dist/global-skills/firebase-basics/SKILL.md +6 -4
  35. package/dist/global-skills/firebase-basics/references/web_setup.md +3 -3
  36. package/dist/global-skills/instrument-feature-flags/references/adding-feature-flag-code.md +81 -333
  37. package/dist/global-skills/instrument-feature-flags/references/android.md +19 -13
  38. package/dist/global-skills/instrument-feature-flags/references/api.md +17 -13
  39. package/dist/global-skills/instrument-feature-flags/references/best-practices.md +35 -35
  40. package/dist/global-skills/instrument-feature-flags/references/django.md +120 -72
  41. package/dist/global-skills/instrument-feature-flags/references/dotnet.md +48 -102
  42. package/dist/global-skills/instrument-feature-flags/references/elixir.md +4 -10
  43. package/dist/global-skills/instrument-feature-flags/references/flask.md +55 -39
  44. package/dist/global-skills/instrument-feature-flags/references/flutter.md +19 -13
  45. package/dist/global-skills/instrument-feature-flags/references/go.md +21 -13
  46. package/dist/global-skills/instrument-feature-flags/references/ios.md +19 -13
  47. package/dist/global-skills/instrument-feature-flags/references/java.md +4 -12
  48. package/dist/global-skills/instrument-feature-flags/references/laravel.md +18 -16
  49. package/dist/global-skills/instrument-feature-flags/references/next-js.md +44 -54
  50. package/dist/global-skills/instrument-feature-flags/references/nodejs.md +21 -13
  51. package/dist/global-skills/instrument-feature-flags/references/php.md +21 -13
  52. package/dist/global-skills/instrument-feature-flags/references/python.md +22 -14
  53. package/dist/global-skills/instrument-feature-flags/references/react-native.md +18 -13
  54. package/dist/global-skills/instrument-feature-flags/references/react.md +21 -14
  55. package/dist/global-skills/instrument-feature-flags/references/ruby-on-rails.md +31 -68
  56. package/dist/global-skills/instrument-feature-flags/references/ruby.md +21 -13
  57. package/dist/global-skills/instrument-feature-flags/references/rust.md +10 -34
  58. package/dist/global-skills/instrument-feature-flags/references/usage.md +94 -114
  59. package/dist/global-skills/instrument-feature-flags/references/web.md +21 -13
  60. package/dist/global-skills/instrument-product-analytics/references/EXAMPLE-android.md +7 -7
  61. package/dist/global-skills/instrument-product-analytics/references/EXAMPLE-expo.md +2 -2
  62. package/dist/global-skills/instrument-product-analytics/references/EXAMPLE-fastapi.md +1 -1
  63. package/dist/global-skills/instrument-product-analytics/references/EXAMPLE-flask.md +1 -1
  64. package/dist/global-skills/instrument-product-analytics/references/EXAMPLE-laravel.md +2 -2
  65. package/dist/global-skills/instrument-product-analytics/references/EXAMPLE-php.md +1 -1
  66. package/dist/global-skills/instrument-product-analytics/references/EXAMPLE-python.md +1 -1
  67. package/dist/global-skills/instrument-product-analytics/references/EXAMPLE-ruby.md +1 -1
  68. package/dist/global-skills/instrument-product-analytics/references/EXAMPLE-swift.md +4 -4
  69. package/dist/global-skills/instrument-product-analytics/references/android.md +64 -109
  70. package/dist/global-skills/instrument-product-analytics/references/angular.md +32 -28
  71. package/dist/global-skills/instrument-product-analytics/references/astro.md +29 -34
  72. package/dist/global-skills/instrument-product-analytics/references/configuration.md +27 -42
  73. package/dist/global-skills/instrument-product-analytics/references/django.md +120 -72
  74. package/dist/global-skills/instrument-product-analytics/references/dotnet.md +48 -102
  75. package/dist/global-skills/instrument-product-analytics/references/elixir.md +25 -50
  76. package/dist/global-skills/instrument-product-analytics/references/flask.md +55 -39
  77. package/dist/global-skills/instrument-product-analytics/references/flutter.md +94 -96
  78. package/dist/global-skills/instrument-product-analytics/references/go.md +39 -77
  79. package/dist/global-skills/instrument-product-analytics/references/identify-users.md +24 -26
  80. package/dist/global-skills/instrument-product-analytics/references/ios.md +22 -22
  81. package/dist/global-skills/instrument-product-analytics/references/laravel.md +18 -16
  82. package/dist/global-skills/instrument-product-analytics/references/next-js.md +44 -54
  83. package/dist/global-skills/instrument-product-analytics/references/nuxt-js-3-6.md +32 -34
  84. package/dist/global-skills/instrument-product-analytics/references/nuxt-js.md +36 -36
  85. package/dist/global-skills/instrument-product-analytics/references/php.md +42 -87
  86. package/dist/global-skills/instrument-product-analytics/references/posthog-python.md +15 -12
  87. package/dist/global-skills/instrument-product-analytics/references/python.md +106 -154
  88. package/dist/global-skills/instrument-product-analytics/references/react-native.md +97 -176
  89. package/dist/global-skills/instrument-product-analytics/references/react-router-v6.md +29 -40
  90. package/dist/global-skills/instrument-product-analytics/references/react-router-v7-data-mode.md +29 -38
  91. package/dist/global-skills/instrument-product-analytics/references/react-router-v7-declarative-mode.md +29 -40
  92. package/dist/global-skills/instrument-product-analytics/references/react-router-v7-framework-mode.md +32 -37
  93. package/dist/global-skills/instrument-product-analytics/references/ruby-on-rails.md +31 -68
  94. package/dist/global-skills/instrument-product-analytics/references/ruby.md +40 -96
  95. package/dist/global-skills/instrument-product-analytics/references/svelte.md +34 -34
  96. package/dist/global-skills/instrument-product-analytics/references/tanstack-start.md +19 -19
  97. package/dist/global-skills/instrument-product-analytics/references/usage.md +94 -114
  98. package/dist/global-skills/instrument-product-analytics/references/vue-js.md +33 -45
  99. package/dist/global-skills/mongodb-search-and-ai/SKILL.md +40 -22
  100. package/dist/global-skills/mongodb-search-and-ai/references/automated-embedding.md +8 -2
  101. package/dist/global-skills/mongodb-search-and-ai/references/quick-start.md +725 -0
  102. package/dist/global-skills/mongodb-search-and-ai/scripts/quickstart_complete.py +386 -0
  103. package/dist/global-skills/neon/references/claimable-neon.md +2 -0
  104. package/dist/global-skills/neon-realtime/references/queries.md +2 -2
  105. package/dist/global-skills/resend/SKILL.md +1 -1
  106. package/dist/global-skills/resend/references/inboxes.md +49 -21
  107. package/dist/global-skills/resend/references/webhooks.md +1 -1
  108. package/dist/global-skills/stripe-best-practices/SKILL.md +1 -1
  109. package/dist/global-skills/stripe-best-practices/references/connect.md +1 -1
  110. package/dist/global-skills/stripe-best-practices/references/security.md +9 -9
  111. package/dist/global-skills/vybekiitReferences/backend/AGENTS.md +1 -1
  112. package/dist/global-skills/vybekiitReferences/backend/agent/goal-routes.json +303 -18
  113. package/dist/global-skills/vybekiitReferences/extension/AGENTS.md +1 -1
  114. package/dist/global-skills/vybekiitReferences/extension/agent/goal-routes.json +352 -18
  115. package/dist/global-skills/vybekiitReferences/mobile/AGENTS.md +1 -1
  116. package/dist/global-skills/vybekiitReferences/mobile/agent/goal-routes.json +413 -22
  117. package/dist/global-skills/vybekiitReferences/spa/AGENTS.md +1 -1
  118. package/dist/global-skills/vybekiitReferences/spa/agent/goal-routes.json +391 -21
  119. package/dist/global-skills/vybekiitReferences/web/AGENTS.md +14 -2
  120. package/dist/global-skills/vybekiitReferences/web/agent/goal-routes.json +490 -32
  121. package/package.json +8 -8
@@ -0,0 +1,386 @@
1
+ #!/usr/bin/env python3
2
+ # /// script
3
+ # dependencies = [
4
+ # "pymongo>=4.6.0",
5
+ # ]
6
+ # ///
7
+
8
+ # MongoDB Search Quickstart — Complete Script
9
+ # ─────────────────────────────────────────────────────────────
10
+ # Written for the Atlas `sample_mflix.movies` sample collection. The
11
+ # schema is hard-coded throughout: the `plot`, `genres`, and `title`
12
+ # fields, the index definitions, and the sample query text.
13
+ #
14
+ # The connection string comes from the environment: set MONGODB_URI, or
15
+ # MDB_MCP_CONNECTION_STRING, which the MongoDB MCP server already uses.
16
+ #
17
+ # To run against your own data, change the two values below AND
18
+ # update the index definitions, `$project` stages, and query strings
19
+ # to match your own field names.
20
+
21
+ DB_NAME = "sample_mflix"
22
+ COLLECTION_NAME = "movies"
23
+
24
+ # Index names. The walkthrough uses these defaults, but creates `<name>_2` when
25
+ # the default is already taken by an incompatible definition. If that happened,
26
+ # set the matching constant to the name it created, so this script reuses that
27
+ # index instead of stopping on the conflicting one.
28
+ SEMANTIC_INDEX_NAME = "quickstart_semantic"
29
+ TEXT_INDEX_NAME = "quickstart_text"
30
+ AUTOCOMPLETE_INDEX_NAME = "quickstart_autocomplete"
31
+
32
+ INDEX_WAIT_TIMEOUT_SECONDS = 600
33
+
34
+ # ─────────────────────────────────────────────────────────────
35
+
36
+ import os
37
+ import time
38
+ from pymongo import MongoClient
39
+ from pymongo.operations import SearchIndexModel
40
+
41
+ CONNECTION_STRING = (os.environ.get("MONGODB_URI")
42
+ or os.environ.get("MDB_MCP_CONNECTION_STRING"))
43
+
44
+ if not CONNECTION_STRING:
45
+ raise SystemExit("\n ERROR: no connection string. Set MONGODB_URI (or"
46
+ " MDB_MCP_CONNECTION_STRING) to your cluster's SRV"
47
+ " string, then rerun.")
48
+
49
+ client = MongoClient(CONNECTION_STRING)
50
+ db = client[DB_NAME]
51
+ collection = db[COLLECTION_NAME]
52
+
53
+ def fail(message):
54
+ print(f"\n ERROR: {message}")
55
+ client.close()
56
+ raise SystemExit(1)
57
+
58
+ def wait_for_index(name):
59
+ print(f" Waiting for index '{name}' to be ready...", end="", flush=True)
60
+ deadline = time.monotonic() + INDEX_WAIT_TIMEOUT_SECONDS
61
+ while True:
62
+ indexes = list(collection.list_search_indexes(name))
63
+ if not indexes:
64
+ fail(f"Index '{name}' does not exist.")
65
+ status = indexes[0].get("status")
66
+ if status == "READY":
67
+ print(" ready.")
68
+ return
69
+ if status == "STALE":
70
+ fail(f"Index '{name}' is stale because embedding sync is paused. "
71
+ "Free disk space, wait for READY, then rerun.")
72
+ if status == "FAILED":
73
+ fail(f"Index '{name}' failed to build. Check the tier index cap"
74
+ " (3 on Free, 10 on Flex) and the index definition.")
75
+ if time.monotonic() > deadline:
76
+ fail(f"Index '{name}' is still '{status}' after"
77
+ f" {INDEX_WAIT_TIMEOUT_SECONDS}s. Check its status in Atlas,"
78
+ " then rerun.")
79
+ print(".", end="", flush=True)
80
+ time.sleep(5)
81
+
82
+ def covers(expected, actual):
83
+ # True when `actual` contains everything `expected` asks for. Atlas adds
84
+ # its own defaults to a stored definition, so an exact match is too strict.
85
+ if isinstance(expected, dict):
86
+ # A field can be stored as a list of types (a multi-type field), which
87
+ # still satisfies a single expected type.
88
+ if isinstance(actual, list):
89
+ return any(covers(expected, candidate) for candidate in actual)
90
+ return (isinstance(actual, dict)
91
+ and all(k in actual and covers(v, actual[k])
92
+ for k, v in expected.items()))
93
+ if isinstance(expected, list):
94
+ return (isinstance(actual, list)
95
+ and all(any(covers(item, candidate) for candidate in actual)
96
+ for item in expected))
97
+ return expected == actual
98
+
99
+ def check_definition(name, expected):
100
+ indexes = list(collection.list_search_indexes(name))
101
+ if not indexes:
102
+ fail(f"Index '{name}' does not exist.")
103
+ actual = indexes[0].get("latestDefinition", {})
104
+ if not covers(expected, actual):
105
+ fail(f"An index named '{name}' already exists, but its definition does"
106
+ " not match the one this script needs, so the queries below would"
107
+ " fail. An index created by another route (for example the MCP"
108
+ " walkthrough) can omit fields this script queries. Drop"
109
+ f" '{name}' in Atlas and rerun, or point the matching"
110
+ " *_INDEX_NAME constant at the top of this script at a name that"
111
+ f" is free or already correct.\n Existing definition: {actual}")
112
+
113
+ def ensure_index(model, name, definition):
114
+ try:
115
+ collection.create_search_index(model)
116
+ except Exception as e:
117
+ if "already exists" not in str(e):
118
+ fail(f"Could not create index '{name}': {e}")
119
+ print(" Index already exists — checking its definition.")
120
+ check_definition(name, definition)
121
+ wait_for_index(name)
122
+
123
+ def print_results(results, fields=["title", "plot"]):
124
+ for i, doc in enumerate(results, 1):
125
+ print(f"\n {i}. {doc.get('title', 'N/A')}")
126
+ if "score" in doc:
127
+ print(f" Score: {round(doc['score'], 3)}")
128
+ if "plot" in fields and doc.get("plot"):
129
+ print(f" Plot: {doc['plot'][:120]}...")
130
+ if "genres" in doc:
131
+ print(f" Genres: {doc.get('genres', [])}")
132
+
133
+ # ─────────────────────────────────────────────────────────────
134
+ # 1. CREATE AUTO EMBEDDING INDEX (Semantic Search)
135
+ # ─────────────────────────────────────────────────────────────
136
+ print("\n── SEMANTIC SEARCH (Auto Embedding) ──────────────────")
137
+ print("Creating autoEmbed index on 'plot' field...")
138
+
139
+ semantic_definition = {
140
+ "fields": [
141
+ {
142
+ "type": "autoEmbed",
143
+ "modality": "text",
144
+ "path": "plot",
145
+ "model": "voyage-4"
146
+ },
147
+ {
148
+ "type": "filter",
149
+ "path": "genres"
150
+ }
151
+ ]
152
+ }
153
+
154
+ ensure_index(
155
+ SearchIndexModel(
156
+ definition=semantic_definition,
157
+ name=SEMANTIC_INDEX_NAME,
158
+ type="vectorSearch"
159
+ ),
160
+ SEMANTIC_INDEX_NAME,
161
+ semantic_definition
162
+ )
163
+
164
+ # Run semantic query
165
+ print("\nQuery: 'a story about growing up'")
166
+ results = list(collection.aggregate([
167
+ {
168
+ "$vectorSearch": {
169
+ "index": SEMANTIC_INDEX_NAME,
170
+ "path": "plot",
171
+ "query": "a story about growing up",
172
+ "numCandidates": 100,
173
+ "limit": 3
174
+ }
175
+ },
176
+ {
177
+ "$project": {
178
+ "_id": 0,
179
+ "title": 1,
180
+ "plot": 1,
181
+ "score": { "$meta": "vectorSearchScore" }
182
+ }
183
+ }
184
+ ]))
185
+ print_results(results)
186
+
187
+ # Run filtered semantic query
188
+ print("\nFiltered query: 'a story about growing up' + Drama only")
189
+ results = list(collection.aggregate([
190
+ {
191
+ "$vectorSearch": {
192
+ "index": SEMANTIC_INDEX_NAME,
193
+ "path": "plot",
194
+ "query": "a story about growing up",
195
+ "filter": { "genres": { "$eq": "Drama" } },
196
+ "numCandidates": 150,
197
+ "limit": 3
198
+ }
199
+ },
200
+ {
201
+ "$project": {
202
+ "_id": 0,
203
+ "title": 1,
204
+ "genres": 1,
205
+ "score": { "$meta": "vectorSearchScore" }
206
+ }
207
+ }
208
+ ]))
209
+ print_results(results, fields=["title", "genres"])
210
+
211
+ # ─────────────────────────────────────────────────────────────
212
+ # 2. CREATE TEXT SEARCH INDEX (Keyword Search)
213
+ # ─────────────────────────────────────────────────────────────
214
+ print("\n── KEYWORD SEARCH (Atlas Search) ─────────────────────")
215
+ print("Creating text search index on 'title', 'plot', 'genres'...")
216
+
217
+ text_definition = {
218
+ "mappings": {
219
+ "dynamic": False,
220
+ "fields": {
221
+ "title": { "type": "string" },
222
+ "plot": { "type": "string" },
223
+ "genres": { "type": "token" }
224
+ }
225
+ }
226
+ }
227
+
228
+ ensure_index(
229
+ SearchIndexModel(
230
+ definition=text_definition,
231
+ name=TEXT_INDEX_NAME
232
+ ),
233
+ TEXT_INDEX_NAME,
234
+ text_definition
235
+ )
236
+
237
+ # Run keyword query
238
+ print("\nKeyword query: 'space adventure'")
239
+ results = list(collection.aggregate([
240
+ {
241
+ "$search": {
242
+ "index": TEXT_INDEX_NAME,
243
+ "text": {
244
+ "query": "space adventure",
245
+ "path": ["title", "plot"]
246
+ }
247
+ }
248
+ },
249
+ {
250
+ "$project": {
251
+ "_id": 0,
252
+ "title": 1,
253
+ "plot": 1,
254
+ "score": { "$meta": "searchScore" }
255
+ }
256
+ },
257
+ { "$limit": 3 }
258
+ ]))
259
+ print_results(results)
260
+
261
+ # Run fuzzy query
262
+ print("\nFuzzy query: 'sapce adventre' (intentional typos)")
263
+ results = list(collection.aggregate([
264
+ {
265
+ "$search": {
266
+ "index": TEXT_INDEX_NAME,
267
+ "text": {
268
+ "query": "sapce adventre",
269
+ "path": ["title", "plot"],
270
+ "fuzzy": { "maxEdits": 1 }
271
+ }
272
+ }
273
+ },
274
+ {
275
+ "$project": {
276
+ "_id": 0,
277
+ "title": 1,
278
+ "score": { "$meta": "searchScore" }
279
+ }
280
+ },
281
+ { "$limit": 3 }
282
+ ]))
283
+ print_results(results, fields=["title"])
284
+
285
+ # Autocomplete needs 'title' typed as autocomplete, which the text index does
286
+ # not provide. Use a dedicated index, matching the walkthrough's MCP route.
287
+ print("\nCreating autocomplete index on 'title'...")
288
+
289
+ autocomplete_definition = {
290
+ "mappings": {
291
+ "dynamic": False,
292
+ "fields": {
293
+ "title": { "type": "autocomplete", "tokenization": "edgeGram" }
294
+ }
295
+ }
296
+ }
297
+
298
+ ensure_index(
299
+ SearchIndexModel(
300
+ definition=autocomplete_definition,
301
+ name=AUTOCOMPLETE_INDEX_NAME
302
+ ),
303
+ AUTOCOMPLETE_INDEX_NAME,
304
+ autocomplete_definition
305
+ )
306
+
307
+ # Run autocomplete
308
+ print("\nAutocomplete query: 'inc'")
309
+ results = list(collection.aggregate([
310
+ {
311
+ "$search": {
312
+ "index": AUTOCOMPLETE_INDEX_NAME,
313
+ "autocomplete": { "query": "inc", "path": "title" }
314
+ }
315
+ },
316
+ { "$project": { "_id": 0, "title": 1 } },
317
+ { "$limit": 5 }
318
+ ]))
319
+ for doc in results:
320
+ print(f" - {doc['title']}")
321
+
322
+ # ─────────────────────────────────────────────────────────────
323
+ # 3. HYBRID SEARCH ($rankFusion)
324
+ # ─────────────────────────────────────────────────────────────
325
+ print("\n── HYBRID SEARCH ($rankFusion) ───────────────────────")
326
+ print("Query: 'a story about growing up' (semantic 70% + keyword 30%)")
327
+
328
+ try:
329
+ results = list(collection.aggregate([
330
+ {
331
+ "$rankFusion": {
332
+ "input": {
333
+ "pipelines": {
334
+ "semanticPipeline": [
335
+ {
336
+ "$vectorSearch": {
337
+ "index": SEMANTIC_INDEX_NAME,
338
+ "path": "plot",
339
+ "query": "a story about growing up",
340
+ "numCandidates": 100,
341
+ "limit": 20
342
+ }
343
+ }
344
+ ],
345
+ "keywordPipeline": [
346
+ {
347
+ "$search": {
348
+ "index": TEXT_INDEX_NAME,
349
+ "text": {
350
+ "query": "coming of age",
351
+ "path": "plot"
352
+ }
353
+ }
354
+ },
355
+ { "$limit": 20 }
356
+ ]
357
+ }
358
+ },
359
+ "combination": {
360
+ "weights": {
361
+ "semanticPipeline": 0.7,
362
+ "keywordPipeline": 0.3
363
+ }
364
+ }
365
+ }
366
+ },
367
+ { "$project": { "_id": 0, "title": 1, "plot": 1 } },
368
+ { "$limit": 5 }
369
+ ]))
370
+ print_results(results)
371
+ except Exception as e:
372
+ message = str(e)
373
+ if "Unrecognized pipeline stage name: '$rankFusion'" in message:
374
+ print(" $rankFusion requires MongoDB 8.0+. This cluster does not"
375
+ " support it, so hybrid search was skipped.")
376
+ else:
377
+ fail(f"Hybrid search query failed: {message}")
378
+
379
+ print("\n── Done! ─────────────────────────────────────────────")
380
+ print("This script targets the sample_mflix.movies schema. To use your")
381
+ print("own data, set MONGODB_URI (or MDB_MCP_CONNECTION_STRING), then")
382
+ print("change DB_NAME and COLLECTION_NAME at the top and adjust the")
383
+ print("index definitions, $project stages, and query strings to match")
384
+ print("your own field names.")
385
+
386
+ client.close()
@@ -38,6 +38,8 @@ export default defineConfig({
38
38
  neon claim create --service auth --service data-api --env-pull
39
39
  ```
40
40
 
41
+ The project is created in `aws-us-east-2` unless you pass `--region-id` (for example `neon claim create --region-id aws-eu-central-1`). Pick the region closest to where the app runs. A project's region cannot change after create.
42
+
41
43
  `neon claim create` reads `neon.ts` when it is present. It writes provisioned vars to an existing `.env`, otherwise `.env.local`, and gitignores that file. If `.env` or `.env.local` already has a `DATABASE_URL` (or other Neon-managed keys), pass `--file <path>` or `--no-env-pull`. The identity assertion is the pre-claim credential.
42
44
 
43
45
  Before claim, Postgres is always granted; Auth and the Data API are granted when requested. Functions, Object Storage, and AI Gateway come back with `granted: false` and `reason: "requires_claim"`. The CLI prints those as `denied_capabilities`. Report what you were given. Do not retry or strip them.
@@ -1,4 +1,4 @@
1
- <!-- Generated by scripts/sync-skill-docs.mjs from website@thruflo/realtime-2-docs:24a2fd708 (docs/realtime/guides/queries). Do not edit: change the docs and run npm run sync:docs. sha256:d93bfa0e827dc447fc6108960e0dc3941c2126c9cfe5dc7ca3df6e662485f9f4 -->
1
+ <!-- Generated by scripts/sync-skill-docs.mjs from website@thruflo/realtime-nextjs-drizzle-kysely:78f8f132f (docs/realtime/guides/queries). Do not edit: change the docs and run npm run sync:docs. sha256:c4d6f1dc7acdcb310b0fc39744dd73fccf51b4aaa88abf7d8684b925d06535c1 -->
2
2
  Canonical page: https://neon.com/docs/realtime/guides/queries.md
3
3
 
4
4
  > Summary: Define live queries in your backend with raw SQL or Drizzle, fill in their parameters from the authenticated request, and seal them so clients can subscribe without being able to read or change them.
@@ -306,7 +306,7 @@ This is where **sealing** comes in. Sealing signs and encrypts the exact query,
306
306
 
307
307
  **Warning: Keep your Realtime secret on the server**
308
308
 
309
- Anyone with `NEON_REALTIME_SECRET` can seal any query. Treat it like your `DATABASE_URL`, and never ship it in a browser bundle. In a Vite app, that means never giving it a `VITE_` prefix.
309
+ Anyone with `NEON_REALTIME_SECRET` can seal any query. Treat it like your `DATABASE_URL`, and never ship it in a browser bundle. In a Vite app, that means never giving it a `VITE_` prefix. To replace it, run [`neon realtime rotate-secret`](https://neon.com/docs/cli/realtime.md#rotate-secret).
310
310
 
311
311
  To seal a query you pass your query instance, e.g.:
312
312
 
@@ -4,7 +4,7 @@ description: Use when working with the Resend email API — sending transactiona
4
4
  license: MIT
5
5
  metadata:
6
6
  author: resend
7
- version: "3.15.0"
7
+ version: "3.16.0"
8
8
  homepage: https://resend.com/agent-skills
9
9
  source: https://github.com/resend/resend-skills
10
10
  openclaw:
@@ -2,7 +2,7 @@
2
2
 
3
3
  An inbox sends and receives email at one address on your domain. Received and sent emails group into threads. Each thread is in one folder and can carry labels. Drafts hold unsent emails, and agent settings tell an AI agent how to handle the threads of the inbox.
4
4
 
5
- Inboxes need Node.js `resend` 6.33.0 or later, or Python `resend` 2.50.0 or later.
5
+ Inboxes need Node.js `resend` 6.34.1 or later, or Python `resend` 2.50.0 or later.
6
6
 
7
7
  If an agent acts on the content of received emails, also install the `agent-email-inbox` skill. It covers the security levels for untrusted input.
8
8
 
@@ -26,6 +26,9 @@ If an agent acts on the content of received emails, also install the `agent-emai
26
26
  | Get thread | `resend.inboxes.threads.get({ inboxId, threadId })` | `resend.Inboxes.Threads.get(inbox_id, thread_id)` |
27
27
  | Update thread | `resend.inboxes.threads.update({ inboxId, threadId, ...params })` | `resend.Inboxes.Threads.update(params)`, `inbox_id` and `id` inside params |
28
28
  | Delete thread | `resend.inboxes.threads.remove({ inboxId, threadId })` | `resend.Inboxes.Threads.remove(inbox_id, thread_id)` |
29
+ | Add thread labels | `resend.inboxes.threads.labels.add({ inboxId, threadId, labels })` | `resend.Inboxes.Threads.Labels.add(params)`, `inbox_id`, `thread_id` and `labels` inside params |
30
+ | Set thread labels | `resend.inboxes.threads.labels.set({ inboxId, threadId, labels })` | `resend.Inboxes.Threads.Labels.set(params)`, `inbox_id`, `thread_id` and `labels` inside params |
31
+ | Remove thread label | `resend.inboxes.threads.labels.remove({ inboxId, threadId, labelId })` | `resend.Inboxes.Threads.Labels.remove(inbox_id, thread_id, label_id)` |
29
32
  | List thread emails | `resend.inboxes.threads.emails.list({ inboxId, threadId, ...params })` | `resend.Inboxes.Threads.Emails.list(inbox_id, thread_id, params?)` |
30
33
  | Get thread email | `resend.inboxes.threads.emails.get({ inboxId, threadId, emailId })` | `resend.Inboxes.Threads.Emails.get(inbox_id, thread_id, email_id)` |
31
34
  | Reply to thread email | `resend.inboxes.threads.emails.reply({ inboxId, threadId, emailId, ...params })` | `resend.Inboxes.Threads.Emails.reply(params)`, `inbox_id`, `thread_id` and `email_id` inside params |
@@ -39,6 +42,8 @@ If an agent acts on the content of received emails, also install the `agent-emai
39
42
 
40
43
  Use `.remove()`, not `.delete()`, in the Node.js SDK.
41
44
 
45
+ Every inbox endpoint takes the inbox ID or the inbox email address in the path. In Node.js, pass `emailAddress` in place of `inboxId`, for example `{ emailAddress: 'support@acme.com', threadId }`. `get`, `update` and `remove` take either one as the first argument. In Python, `inbox_id` takes either one.
46
+
42
47
  ## Inboxes
43
48
 
44
49
  ```typescript
@@ -72,8 +77,9 @@ const { data: deleted } = await resend.inboxes.remove('b3e2b2b6-3f0e-4c8e-9ad3-2
72
77
  - Create returns only `{ object, id }`. Call `get` for the other fields.
73
78
  - Without `forwarding`, the domain must have receiving enabled, DKIM and SPF verified, and the inbound MX record verified or pending. With `forwarding: true`, the domain only needs DKIM and SPF, and Resend returns a `receiving_address` to forward mail to.
74
79
  - Create returns 422 for a domain that does not meet these rules, 409 for an address already in use, 403 when the team is at the inbox limit of its plan, and 451 for a blocked account.
75
- - `get` accepts the inbox ID or its email address, without case. Every other inbox endpoint accepts only the inbox ID.
76
- - List items carry fewer fields than `get`.
80
+ - The email address in the path matches without case. An unknown ID or address returns 404 `not_found`.
81
+ - The local part of `emailAddress` can contain only letters, numbers, `.`, `_`, `%` and `-`. A `+` returns 422.
82
+ - List items carry fewer fields than `get`. `created_at`, `updated_at` and `last_received` are ISO 8601 strings.
77
83
  - `fromName` is a plain name, not `Name <email>`. It cannot contain a colon, `<`, `>`, or line breaks. An empty string removes it.
78
84
  - Update needs at least one of `name` or `fromName`.
79
85
 
@@ -91,14 +97,16 @@ const { data: updated } = await resend.inboxes.agent.update({
91
97
  tone: 'concise_professional',
92
98
  actions: ['draft_reply', 'add_labels'],
93
99
  });
100
+ // updated: { object: 'inbox_agent', id }
94
101
  ```
95
102
 
96
103
  **Key gotchas:**
97
104
  - An inbox without an agent returns `instructions: null`, `tone: null` and `actions: []`. Update creates the agent when the inbox has none.
98
105
  - Omitted fields keep their value. Send `null` to clear `instructions` or `tone`.
99
- - `actions` replaces the whole set, without duplicates. Send `[]` to disable all actions. The values are `draft_reply`, `forward_thread`, `add_labels`, `assign_thread`, `mark_as_spam`, `archive_thread` and `delete_thread`.
106
+ - `actions` replaces the whole set, without duplicates. Send `[]` to disable all actions. The values are `draft_reply`, `forward_thread`, `add_labels`, `assign_thread`, `mark_as_spam`, `archive` and `move_to_trash`.
100
107
  - `instructions` is at most 4000 characters.
101
108
  - `tone` is one of `concise_professional`, `concise_casual`, `expanded_professional` and `expanded_casual`.
109
+ - Update returns only `{ object, id }`. Call `get` for the settings.
102
110
 
103
111
  ## Labels
104
112
 
@@ -129,7 +137,7 @@ await resend.inboxes.labels.remove({ inboxId, labelId: label.id });
129
137
  - `color` is a `#RRGGBB` hex string. The API returns it in uppercase. Without `color`, the API picks a random color.
130
138
  - An inbox has at most 100 labels. Names are unique per inbox, compared without case. A duplicate name or a full inbox returns 422.
131
139
  - The list is not paginated. It returns every label, oldest first.
132
- - To label a thread, call `threads.update` with `addLabelIds`. `removeLabelIds` takes it off the thread and keeps the label. Only `labels.remove` deletes it.
140
+ - To label a thread, call `threads.labels.add`. `threads.labels.remove` takes the label off the thread and keeps it on the inbox. Only `labels.remove` deletes it.
133
141
  - Deleting a label removes it from every thread. It does not send `inbox.thread.labels.updated` for those threads.
134
142
 
135
143
  ## Threads
@@ -157,20 +165,24 @@ const { data: results } = await resend.inboxes.threads.search({
157
165
  startDate: '2026-09-01',
158
166
  endDate: '2026-09-30',
159
167
  });
160
- // results.data[i]: the thread fields plus matched_email_id and highlights
168
+ // results.data[i]: the thread fields plus matched_email_id
161
169
 
162
170
  const { data: thread } = await resend.inboxes.threads.get({ inboxId, threadId });
163
171
  // thread: { object, id, subject, folder, labels, read }
164
172
 
165
- // Mark read, move to a folder, add and remove labels
173
+ // Mark read and move to a folder
166
174
  const { data: changed } = await resend.inboxes.threads.update({
167
175
  inboxId,
168
176
  threadId,
169
177
  read: true,
170
178
  folder: 'archive',
171
- addLabelIds: [labelId],
172
179
  });
173
- await resend.inboxes.threads.update({ inboxId, threadId, removeLabelIds: [labelId] });
180
+
181
+ // Add labels, replace all labels, or remove one label
182
+ const { data: added } = await resend.inboxes.threads.labels.add({ inboxId, threadId, labels: [labelId] });
183
+ // added: { object: 'list', data: [{ id, name, color }] }, every label now on the thread
184
+ await resend.inboxes.threads.labels.set({ inboxId, threadId, labels: [labelId] });
185
+ await resend.inboxes.threads.labels.remove({ inboxId, threadId, labelId });
174
186
 
175
187
  // Move the thread to trash
176
188
  const { data: trashed } = await resend.inboxes.threads.remove({ inboxId, threadId });
@@ -183,10 +195,11 @@ const { data: trashed } = await resend.inboxes.threads.remove({ inboxId, threadI
183
195
  - `after` and `before` take a thread ID, not a date. Pass only one of them. A cursor thread that is not in the selected folders returns 404.
184
196
  - Search can be up to about a minute behind new emails. All filters must match. `query`, `from`, `to`, `cc`, `bcc`, `hasAttachment`, `startDate` and `endDate` must all match the same email in the thread. When you set any of them, that email must itself be in one of `folders`.
185
197
  - In `query`, every word must match. Wrap words in double quotes for an exact phrase. Start a word with `-` to exclude it.
186
- - A search result has `matched_email_id`, the email that matched, and `highlights`, the matched text with each match in `**`. The thread email endpoints accept `matched_email_id` as `emailId`.
187
- - Update needs at least one of `read`, `folder`, `addLabelIds` or `removeLabelIds`. Each label array takes 1 to 10 IDs. A label ID cannot be in both arrays, or the request returns 422.
188
- - `removeLabelIds` takes the label off the thread. The label itself stays on the inbox.
189
- - Update applies `read`, `folder`, added labels, then removed labels. It is not atomic. A 404 for a label leaves the earlier changes applied.
198
+ - A search result has `matched_email_id`, the `id` of the email that matched. Pass it as `emailId` to the thread email endpoints.
199
+ - Update needs at least one of `read` or `folder`.
200
+ - `threads.labels.add` takes 1 to 10 label IDs and keeps the labels already on the thread. `threads.labels.set` takes 0 to 100 IDs and replaces every label. Send `[]` to clear them.
201
+ - `threads.labels.remove` takes one label off the thread. The label itself stays on the inbox.
202
+ - All three return every label on the thread after the change. An unknown label ID returns 404.
190
203
  - `remove` moves the thread to `trash` and deletes it after 30 days. To restore it, update `folder` to `inbox`, `archive` or `spam`.
191
204
 
192
205
  ## Thread Emails
@@ -233,7 +246,8 @@ const { data: forwarded } = await resend.inboxes.threads.emails.forward({
233
246
  - Reply sends right away, with no stored draft. For a received email, the reply goes to its `Reply-To` addresses, or to its sender. For an email the inbox sent, it goes to that email's `to` recipients. You cannot set `to` on a reply.
234
247
  - With `replyAll: true`, a reply to a received email also copies the other `To` addresses and the `cc` of that email. A reply-all to an email the inbox sent goes to its Reply-To when that is not the inbox address, else to its `to`, and copies its `cc`. The inbox address is left out, and duplicates are removed.
235
248
  - Reply needs `html` or `text` with more than whitespace. Without `subject`, the thread subject is used with a `Re:` prefix. Forward uses a `Fwd:` prefix.
236
- - Forward needs `to`. It quotes the original email and sends its attachments with your `attachments`. Together they can have up to 10 files and 25 MB of `content`. An original attachment that was sent from a URL (`path`) cannot be forwarded, and the request returns 422.
249
+ - Forward needs `to`. It quotes the original email and sends its attachments with your `attachments`. An original attachment that was sent from a URL (`path`) cannot be forwarded, and the request returns 422.
250
+ - The `content` of all attachments can total at most 25 MB. On a forward, the original attachments count toward it. A `path` file does not count, because Resend fetches it at send time. The whole email, encoded, can be at most 40 MB.
237
251
  - `attachments` and `headers` have the same shape as on `resend.emails.send()`. Resend sets `In-Reply-To`, `References` and `Reply-To`. Setting one of them in `headers`, in any letter case, returns 422. An empty header name, or a line break or null character in a name or value, also returns 422.
238
252
  - An empty file, a blocked file type, or a `path` that is not an `http` or `https` URL, or that points to `localhost`, returns 422.
239
253
  - Reply and forward return only the sent email ID.
@@ -267,7 +281,7 @@ const { data: saved } = await resend.inboxes.drafts.get({ inboxId, draftId: draf
267
281
  // saved.attachments[i]: { id, filename, content_type, size }
268
282
 
269
283
  // Replace the body and every attachment
270
- await resend.inboxes.drafts.update({
284
+ const { data: changed } = await resend.inboxes.drafts.update({
271
285
  inboxId,
272
286
  draftId: draft.id,
273
287
  html: '<p>Refund issued for order 1041.</p>',
@@ -280,6 +294,7 @@ await resend.inboxes.drafts.update({
280
294
  },
281
295
  ],
282
296
  });
297
+ // changed: { object: 'inbox_draft', id }
283
298
 
284
299
  const { data: sent } = await resend.inboxes.drafts.send(
285
300
  { inboxId, draftId: draft.id, headers: { 'X-Ticket-Id': '4821' } },
@@ -295,12 +310,15 @@ await resend.inboxes.drafts.remove({ inboxId, draftId: other.id });
295
310
 
296
311
  **Key gotchas:**
297
312
  - For a reply draft, pass `threadId` and `replyToEmailId` together. Without `to`, the API addresses it like a reply. Send `to: []` to leave it empty.
298
- - Each email can have one unsent reply draft. Creating another returns 200 with the ID of the existing draft, not 201. That request ignores every other field except `attachments`, which replaces the attachments of the existing draft.
313
+ - Each email can have one unsent reply draft. Creating another returns 200 with the ID of the existing draft, not 201. That request ignores every other field except `attachments`, which replaces the attachments of the existing draft. With the `draft_reply` action on, the inbox agent can create that draft before you do.
299
314
  - A draft needs at least one non-empty `to`, `cc`, `bcc`, `subject`, `text`, `html` or `attachments`. `to`, `cc` and `bcc` together have at most 50 recipients.
300
- - The draft stores its attachments, so each one needs `content` (base64), `filename` and `contentType`. A `path` or `contentId` returns 422. The files on a draft can total 25 MB, with no limit on the count. An empty file or a blocked file type, such as `.exe`, returns 422.
315
+ - The draft stores its attachments, so each one needs `content` (base64), `filename` and `contentType`. A `path` without `content`, or a `contentId`, returns 422. The files on a draft can total at most 25 MB. An empty file or a blocked file type, such as `.exe`, returns 422.
301
316
  - An update with `attachments` replaces every attachment on the draft. Send `[]` to remove them all. Omit the field to keep them.
302
317
  - An update that sets `text` or `html` replaces the whole body. The part you omit becomes `null`, so send both to keep both.
303
- - Send needs at least one `to` recipient, a non-empty `subject`, and `html` or `text`, or it returns 422. A reply draft created without `subject` has an empty subject with no `Re:` prefix, so set one before you send it. Update, remove or send on a draft that was already sent returns 409.
318
+ - Send needs at least one `to` recipient and `html` or `text`, or it returns 422. A new-thread draft also needs a non-empty `subject`.
319
+ - A reply draft created without `subject` has an empty subject. Send uses the thread subject with a `Re:` prefix.
320
+ - The sent email, encoded, can be at most 40 MB.
321
+ - Update, remove or send on a draft that was already sent returns 409.
304
322
  - `drafts.list` returns only unsent drafts, most recently updated first.
305
323
  - `drafts.send` takes `headers`, an object of string values, as on `resend.emails.send()`. Create and update do not take headers. The header rules of reply and forward apply.
306
324
  - Create and send take an `idempotencyKey` option. See [Idempotency Keys](#idempotency-keys).
@@ -320,6 +338,13 @@ Create draft, send draft, reply and forward accept an idempotency key, the same
320
338
  | **Same key + different payload** | Returns 409 `invalid_idempotent_request` |
321
339
  | **Same key while the first request runs** | Returns 409 `concurrent_idempotent_requests` |
322
340
 
341
+ ## API Key Permissions
342
+
343
+ - A full access key can call every inbox endpoint.
344
+ - A sending access key can call only reply, forward, create draft and send draft. Every other inbox endpoint returns 401 `restricted_api_key`.
345
+ - A sending access key limited to one domain works only on inboxes of that domain. An inbox on another domain returns 403.
346
+ - An agent that reads emails or lists threads needs a full access key.
347
+
323
348
  ## Webhook Events
324
349
 
325
350
  Inboxes send `inbox.*` events. See the event list in [webhooks.md](webhooks.md#other-events).
@@ -328,15 +353,18 @@ Inboxes send `inbox.*` events. See the event list in [webhooks.md](webhooks.md#o
328
353
  - Email and draft payloads carry the object without `html` or `text`. Call `threads.emails.get()` or `drafts.get()` for the body.
329
354
  - The `draft` object in `inbox.draft.*` payloads lists `attachments` as `{ id, filename, content_type, size }`, without the file content.
330
355
  - `inbox.deleted` carries only `inbox_id`. `inbox.draft.deleted` has no `draft` object.
331
- - `inbox.thread.labels.updated` carries one label, in `added` or `removed`.
356
+ - `inbox.created` and `inbox.updated` carry `inbox`, with the fields of `inboxes.get()`. Its timestamps are ISO 8601 strings.
357
+ - `inbox.thread.labels.updated` lists the changed labels in `added` and `removed`. One `threads.labels.set` call can fill both. A call that changes nothing sends no event.
332
358
  - Sending a draft sends both `inbox.draft.sent` and `inbox.email.sent`, in no fixed order.
333
359
 
334
360
  ## Common Mistakes
335
361
 
336
362
  | Mistake | Fix |
337
363
  |---------|-----|
338
- | Using an email address on `update`, `remove` or a sub-resource | Only `inboxes.get` accepts an address. Use the inbox ID everywhere else |
339
- | Reading fields from the create response | Create inbox, label and draft return only `{ object, id }`. Call `get` |
364
+ | Reading fields from a create or update response | Create and update of inboxes, labels and drafts, and agent update, return only `{ object, id }`. Call `get` |
365
+ | Labeling a thread with `threads.update` | Update takes only `read` and `folder`. Use `threads.labels.add`, `set` or `remove` |
366
+ | Calling `threads.labels.set` to add one label | `set` replaces every label on the thread. Use `add` |
367
+ | Reading threads with a sending access key | That key can only reply, forward, create drafts and send drafts. Use a full access key |
340
368
  | Setting `Reply-To` in `headers` on a reply, forward or draft send | Resend sets `In-Reply-To`, `References` and `Reply-To`. Leave them out of `headers` |
341
369
  | Passing `to` on a reply | Reply sets `to` from the email you reply to. Use `forward` to send to someone new |
342
370
  | Using an ID from `resend.emails.list()` as `emailId` | Use the ID from `threads.emails.list` or `matched_email_id` from a search |
@@ -73,7 +73,7 @@ The `email.received` payload contains metadata only (sender, recipient, subject,
73
73
  | `inbox.email.received` / `sent` | Inbound email added to a thread, or outbound email accepted for delivery and added to a thread |
74
74
  | `inbox.thread.folder.updated` | Thread moved between folders |
75
75
  | `inbox.thread.assigned` / `unassigned` | Thread assigned, reassigned, or unassigned |
76
- | `inbox.thread.labels.updated` | Label applied to or removed from a thread |
76
+ | `inbox.thread.labels.updated` | Labels added to or removed from a thread |
77
77
  | `inbox.draft.created` / `updated` / `sent` / `deleted` | Draft created, edited, sent, or discarded |
78
78
 
79
79
  Inbox event payloads carry no email or draft body. See [inboxes.md](inboxes.md#webhook-events).
@@ -37,7 +37,7 @@ If you don’t have a Stripe account, you can use the CLI to generate a sandbox
37
37
 
38
38
  If `stripe sandbox create` is used, don’t use MCP.
39
39
 
40
- If you try to use MCP after running sandbox creation, first make sure `stripe sandbox claim` is run. Use `stripe whoami --format json` for CLI auth checks; do not run `stripe config --list`.
40
+ If you try to use MCP after running sandbox creation, first make sure `stripe sandbox claim` is run. Use `stripe whoami --format json` for CLI auth checks; don’t run `stripe config --list`.
41
41
 
42
42
  API key default: Always recommend a [restricted API key (RAK)](https://docs.stripe.com/keys.md#manage-your-api-keys) (`rk_` prefix) over a secret key (`sk_` prefix).
43
43
 
@@ -111,7 +111,7 @@ For **separate charges and transfers**: collect fees by transferring less than t
111
111
 
112
112
  ## Webhooks
113
113
 
114
- Always use webhooks for reliable payment confirmation. Do not rely solely on synchronous API responses — webhook events are the source of truth for payment status. Verify incoming [webhook signatures](https://docs.stripe.com/webhooks.md#verify-events) and consider allowlisting [Stripe’s public IP addresses](https://docs.stripe.com/ips.md).
114
+ Always use webhooks for reliable payment confirmation. Don’t rely solely on synchronous API responses — webhook events are the source of truth for payment status. Verify incoming [webhook signatures](https://docs.stripe.com/webhooks.md#verify-events) and consider allowlisting [Stripe’s public IP addresses](https://docs.stripe.com/ips.md).
115
115
 
116
116
  ## Subscriptions with Connect (v2)
117
117