@pome-sh/cli 0.7.0 → 0.9.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.
Files changed (154) hide show
  1. package/dist/build-info.json +3 -3
  2. package/dist/src/cli/agent-identity.js +7 -2
  3. package/dist/src/cli/compile-seeds.js +51 -5
  4. package/dist/src/cli/docs-topics.js +5 -3
  5. package/dist/src/cli/main.js +21 -11
  6. package/dist/src/cli/project-config.d.ts +8 -0
  7. package/dist/src/cli/project-config.js +18 -0
  8. package/dist/src/cli/register.d.ts +15 -0
  9. package/dist/src/cli/register.js +26 -3
  10. package/dist/src/cli/session.d.ts +10 -0
  11. package/dist/src/cli/session.js +28 -2
  12. package/dist/src/fix-prompt/index.d.ts +1 -1
  13. package/dist/src/fix-prompt/index.js +2 -2
  14. package/dist/src/fix-prompt/prompt.d.ts +2 -2
  15. package/dist/src/fix-prompt/prompt.js +9 -9
  16. package/dist/src/hosted/client.d.ts +8 -2
  17. package/dist/src/hosted/client.js +108 -26
  18. package/dist/src/hosted/errors.d.ts +14 -0
  19. package/dist/src/hosted/errors.js +22 -0
  20. package/dist/src/hosted/evalResultCache.d.ts +4 -4
  21. package/dist/src/hosted/evalResultCache.js +10 -3
  22. package/dist/src/recorder/artifacts.d.ts +1 -1
  23. package/dist/src/recorder/artifacts.js +8 -1
  24. package/dist/src/runner/runTask.d.ts +14 -0
  25. package/dist/src/runner/runTaskHosted.js +9 -2
  26. package/dist/src/runner/runTrialGroup.js +4 -1
  27. package/dist/src/task/taskSchema.d.ts +52 -16
  28. package/node_modules/@pome-sh/shared-types/dist/seed-state.d.ts +27 -0
  29. package/node_modules/@pome-sh/shared-types/dist/seed-state.js +12 -0
  30. package/node_modules/@pome-sh/shared-types/dist/seed-state.js.map +1 -1
  31. package/node_modules/@pome-sh/shared-types/dist/task.d.ts +18 -0
  32. package/node_modules/@pome-sh/shared-types/package.json +1 -1
  33. package/node_modules/@pome-sh/shared-types/trace-contract.json +1 -1
  34. package/node_modules/@pome-sh/twin-gmail/CHANGELOG.md +21 -0
  35. package/node_modules/@pome-sh/twin-gmail/HOSTED.md +37 -15
  36. package/node_modules/@pome-sh/twin-gmail/LIMITS.md +10 -0
  37. package/node_modules/@pome-sh/twin-gmail/README.md +52 -11
  38. package/node_modules/@pome-sh/twin-gmail/dist/fixtures/mcp-tools-list.canonical.json +242 -7
  39. package/node_modules/@pome-sh/twin-gmail/dist/src/db.js +6 -0
  40. package/node_modules/@pome-sh/twin-gmail/dist/src/db.js.map +1 -1
  41. package/node_modules/@pome-sh/twin-gmail/dist/src/domain/drafts.d.ts +41 -0
  42. package/node_modules/@pome-sh/twin-gmail/dist/src/domain/drafts.js +138 -0
  43. package/node_modules/@pome-sh/twin-gmail/dist/src/domain/drafts.js.map +1 -0
  44. package/node_modules/@pome-sh/twin-gmail/dist/src/domain/filters.d.ts +10 -0
  45. package/node_modules/@pome-sh/twin-gmail/dist/src/{domain-search.js → domain/filters.js} +52 -36
  46. package/node_modules/@pome-sh/twin-gmail/dist/src/domain/filters.js.map +1 -0
  47. package/node_modules/@pome-sh/twin-gmail/dist/src/{domain.d.ts → domain/gmail-domain.d.ts} +45 -34
  48. package/node_modules/@pome-sh/twin-gmail/dist/src/domain/gmail-domain.js +174 -0
  49. package/node_modules/@pome-sh/twin-gmail/dist/src/domain/gmail-domain.js.map +1 -0
  50. package/node_modules/@pome-sh/twin-gmail/dist/src/domain/hydrate.d.ts +8 -0
  51. package/node_modules/@pome-sh/twin-gmail/dist/src/domain/hydrate.js +122 -0
  52. package/node_modules/@pome-sh/twin-gmail/dist/src/domain/hydrate.js.map +1 -0
  53. package/node_modules/@pome-sh/twin-gmail/dist/src/domain/index.d.ts +4 -0
  54. package/node_modules/@pome-sh/twin-gmail/dist/src/domain/index.js +5 -0
  55. package/node_modules/@pome-sh/twin-gmail/dist/src/domain/index.js.map +1 -0
  56. package/node_modules/@pome-sh/twin-gmail/dist/src/domain/labels.d.ts +40 -0
  57. package/node_modules/@pome-sh/twin-gmail/dist/src/domain/labels.js +122 -0
  58. package/node_modules/@pome-sh/twin-gmail/dist/src/domain/labels.js.map +1 -0
  59. package/node_modules/@pome-sh/twin-gmail/dist/src/domain/messages.d.ts +49 -0
  60. package/node_modules/@pome-sh/twin-gmail/dist/src/domain/messages.js +276 -0
  61. package/node_modules/@pome-sh/twin-gmail/dist/src/domain/messages.js.map +1 -0
  62. package/node_modules/@pome-sh/twin-gmail/dist/src/domain/search-sql.d.ts +19 -0
  63. package/node_modules/@pome-sh/twin-gmail/dist/src/domain/search-sql.js +179 -0
  64. package/node_modules/@pome-sh/twin-gmail/dist/src/domain/search-sql.js.map +1 -0
  65. package/node_modules/@pome-sh/twin-gmail/dist/src/domain/settings.d.ts +27 -0
  66. package/node_modules/@pome-sh/twin-gmail/dist/src/domain/settings.js +98 -0
  67. package/node_modules/@pome-sh/twin-gmail/dist/src/domain/settings.js.map +1 -0
  68. package/node_modules/@pome-sh/twin-gmail/dist/src/errors.js +2 -0
  69. package/node_modules/@pome-sh/twin-gmail/dist/src/errors.js.map +1 -1
  70. package/node_modules/@pome-sh/twin-gmail/dist/src/faults.d.ts +21 -0
  71. package/node_modules/@pome-sh/twin-gmail/dist/src/faults.js +52 -0
  72. package/node_modules/@pome-sh/twin-gmail/dist/src/faults.js.map +1 -0
  73. package/node_modules/@pome-sh/twin-gmail/dist/src/identity.d.ts +1 -6
  74. package/node_modules/@pome-sh/twin-gmail/dist/src/identity.js +0 -11
  75. package/node_modules/@pome-sh/twin-gmail/dist/src/identity.js.map +1 -1
  76. package/node_modules/@pome-sh/twin-gmail/dist/src/index.d.ts +4 -3
  77. package/node_modules/@pome-sh/twin-gmail/dist/src/index.js +3 -3
  78. package/node_modules/@pome-sh/twin-gmail/dist/src/index.js.map +1 -1
  79. package/node_modules/@pome-sh/twin-gmail/dist/src/mcp-schemas.d.ts +48 -0
  80. package/node_modules/@pome-sh/twin-gmail/dist/src/mcp-schemas.js +23 -0
  81. package/node_modules/@pome-sh/twin-gmail/dist/src/mcp-schemas.js.map +1 -1
  82. package/node_modules/@pome-sh/twin-gmail/dist/src/mcp.d.ts +1 -1
  83. package/node_modules/@pome-sh/twin-gmail/dist/src/mcp.js +39 -1
  84. package/node_modules/@pome-sh/twin-gmail/dist/src/mcp.js.map +1 -1
  85. package/node_modules/@pome-sh/twin-gmail/dist/src/rest-routes-drafts.js +4 -5
  86. package/node_modules/@pome-sh/twin-gmail/dist/src/rest-routes-drafts.js.map +1 -1
  87. package/node_modules/@pome-sh/twin-gmail/dist/src/rest-routes-kit.d.ts +2 -3
  88. package/node_modules/@pome-sh/twin-gmail/dist/src/rest-routes-kit.js +4 -4
  89. package/node_modules/@pome-sh/twin-gmail/dist/src/rest-routes-kit.js.map +1 -1
  90. package/node_modules/@pome-sh/twin-gmail/dist/src/rest-routes-messages.js +7 -8
  91. package/node_modules/@pome-sh/twin-gmail/dist/src/rest-routes-messages.js.map +1 -1
  92. package/node_modules/@pome-sh/twin-gmail/dist/src/rest-routes-resources.js +20 -21
  93. package/node_modules/@pome-sh/twin-gmail/dist/src/rest-routes-resources.js.map +1 -1
  94. package/node_modules/@pome-sh/twin-gmail/dist/src/rest-routes.d.ts +1 -1
  95. package/node_modules/@pome-sh/twin-gmail/dist/src/rest-serializers.d.ts +2 -4
  96. package/node_modules/@pome-sh/twin-gmail/dist/src/rest-serializers.js +4 -6
  97. package/node_modules/@pome-sh/twin-gmail/dist/src/rest-serializers.js.map +1 -1
  98. package/node_modules/@pome-sh/twin-gmail/dist/src/search-match.d.ts +4 -0
  99. package/node_modules/@pome-sh/twin-gmail/dist/src/search-match.js +179 -0
  100. package/node_modules/@pome-sh/twin-gmail/dist/src/search-match.js.map +1 -0
  101. package/node_modules/@pome-sh/twin-gmail/dist/src/search-parse.d.ts +61 -0
  102. package/node_modules/@pome-sh/twin-gmail/dist/src/search-parse.js +318 -0
  103. package/node_modules/@pome-sh/twin-gmail/dist/src/search-parse.js.map +1 -0
  104. package/node_modules/@pome-sh/twin-gmail/dist/src/search.d.ts +3 -61
  105. package/node_modules/@pome-sh/twin-gmail/dist/src/search.js +2 -503
  106. package/node_modules/@pome-sh/twin-gmail/dist/src/search.js.map +1 -1
  107. package/node_modules/@pome-sh/twin-gmail/dist/src/seed.d.ts +13 -4
  108. package/node_modules/@pome-sh/twin-gmail/dist/src/seed.js +3 -1
  109. package/node_modules/@pome-sh/twin-gmail/dist/src/seed.js.map +1 -1
  110. package/node_modules/@pome-sh/twin-gmail/dist/src/server.js +1 -1
  111. package/node_modules/@pome-sh/twin-gmail/dist/src/server.js.map +1 -1
  112. package/node_modules/@pome-sh/twin-gmail/dist/src/storage.js +1 -1
  113. package/node_modules/@pome-sh/twin-gmail/dist/src/storage.js.map +1 -1
  114. package/node_modules/@pome-sh/twin-gmail/dist/src/twin.d.ts +2 -3
  115. package/node_modules/@pome-sh/twin-gmail/dist/src/twin.js +1 -4
  116. package/node_modules/@pome-sh/twin-gmail/dist/src/twin.js.map +1 -1
  117. package/node_modules/@pome-sh/twin-gmail/dist/src/types.d.ts +1 -0
  118. package/node_modules/@pome-sh/twin-gmail/fidelity.inventory.json +42 -435
  119. package/node_modules/@pome-sh/twin-gmail/fixtures/README.md +4 -4
  120. package/node_modules/@pome-sh/twin-gmail/fixtures/mcp-tools-list.canonical.json +242 -7
  121. package/node_modules/@pome-sh/twin-gmail/fixtures/mcp-tools-list.meta.json +3 -3
  122. package/node_modules/@pome-sh/twin-gmail/package.json +3 -2
  123. package/node_modules/hono/dist/cjs/client/utils.js +1 -1
  124. package/node_modules/hono/dist/cjs/helper/streaming/sse.js +5 -4
  125. package/node_modules/hono/dist/cjs/middleware/cache/index.js +1 -1
  126. package/node_modules/hono/dist/cjs/middleware/compress/index.js +2 -1
  127. package/node_modules/hono/dist/cjs/middleware/method-override/index.js +5 -3
  128. package/node_modules/hono/dist/cjs/middleware/secure-headers/secure-headers.js +10 -4
  129. package/node_modules/hono/dist/cjs/request.js +8 -3
  130. package/node_modules/hono/dist/cjs/utils/accept.js +1 -1
  131. package/node_modules/hono/dist/cjs/utils/body.js +6 -0
  132. package/node_modules/hono/dist/cjs/utils/url.js +1 -1
  133. package/node_modules/hono/dist/client/utils.js +1 -1
  134. package/node_modules/hono/dist/helper/streaming/sse.js +5 -4
  135. package/node_modules/hono/dist/middleware/cache/index.js +1 -1
  136. package/node_modules/hono/dist/middleware/compress/index.js +2 -1
  137. package/node_modules/hono/dist/middleware/method-override/index.js +5 -3
  138. package/node_modules/hono/dist/middleware/secure-headers/secure-headers.js +10 -4
  139. package/node_modules/hono/dist/request.js +8 -3
  140. package/node_modules/hono/dist/types/adapter/aws-lambda/types.d.ts +9 -0
  141. package/node_modules/hono/dist/types/helper/websocket/index.d.ts +1 -1
  142. package/node_modules/hono/dist/types/middleware/combine/index.d.ts +1 -1
  143. package/node_modules/hono/dist/utils/accept.js +1 -1
  144. package/node_modules/hono/dist/utils/body.js +6 -0
  145. package/node_modules/hono/dist/utils/url.js +1 -1
  146. package/node_modules/hono/package.json +3 -3
  147. package/package.json +3 -3
  148. package/node_modules/@pome-sh/twin-gmail/dist/src/domain-search.d.ts +0 -5
  149. package/node_modules/@pome-sh/twin-gmail/dist/src/domain-search.js.map +0 -1
  150. package/node_modules/@pome-sh/twin-gmail/dist/src/domain.js +0 -407
  151. package/node_modules/@pome-sh/twin-gmail/dist/src/domain.js.map +0 -1
  152. package/node_modules/@pome-sh/twin-gmail/dist/src/rest-store.d.ts +0 -75
  153. package/node_modules/@pome-sh/twin-gmail/dist/src/rest-store.js +0 -282
  154. package/node_modules/@pome-sh/twin-gmail/dist/src/rest-store.js.map +0 -1
@@ -1,4 +1,4 @@
1
- # Gmail twin fixtures (Gate 0 oracles)
1
+ # Gmail twin fixtures (Gate 0/1 oracles)
2
2
 
3
3
  Immutable upstream captures for `@pome-sh/twin-gmail`. Normal tests must not require Google credentials.
4
4
 
@@ -15,8 +15,8 @@ Immutable upstream captures for `@pome-sh/twin-gmail`. Normal tests must not req
15
15
  | File | Purpose |
16
16
  | --- | --- |
17
17
  | `mcp-tools-list.raw.json` | Live unauthenticated `tools/list` (13 tools as returned) |
18
- | `mcp-tools-list.canonical.json` | Launch 10-tool listing oracle (schemas from live capture) |
19
- | `mcp-tools-list.meta.json` | Endpoint, protocol version, SHA-256, exclusions |
18
+ | `mcp-tools-list.canonical.json` | Gate-1 launch 13-tool listing oracle (schemas from live capture) |
19
+ | `mcp-tools-list.meta.json` | Endpoint, protocol version, SHA-256, Gate-1 promotions |
20
20
  | `mcp-initialize.raw.json` / `.meta.json` | Live `initialize` (protocolVersion) |
21
21
  | `mcp-tools-call-unauth-error.raw.json` | Live unauthenticated `tools/call` error envelope |
22
22
  | `mcp-tools-call.representative.json` | Schema-derived representative success call shapes |
@@ -25,4 +25,4 @@ Immutable upstream captures for `@pome-sh/twin-gmail`. Normal tests must not req
25
25
 
26
26
  - `tools/list` and `initialize` were captured live without OAuth on 2026-07-20 against `https://gmailmcp.googleapis.com/mcp/v1`.
27
27
  - Authenticated `tools/call` success was **not** available; representative success fixtures are reconstructed from live `outputSchema` + public docs and are marked as such.
28
- - Live listing currently includes three tools outside the launch set (`get_message`, `apply_sensitive_thread_label`, `apply_sensitive_message_label`). The twin launch freeze is the documented 10-tool set.
28
+ - Gate 1 expands the OSS launch set to the full live 13-tool listing, promoting `get_message`, `apply_sensitive_thread_label`, and `apply_sensitive_message_label` that Gate 0 treated as preview drift.
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "meta": {
3
- "source": "live_unauthenticated_tools_list_filtered_to_launch_set",
3
+ "source": "live_unauthenticated_tools_list_gate1_launch_set",
4
4
  "endpoint": "https://gmailmcp.googleapis.com/mcp/v1",
5
5
  "method": "tools/list",
6
6
  "protocol": "JSON-RPC 2.0 over HTTP (accept: application/json, text/event-stream)",
7
7
  "protocolVersion": "2025-03-26",
8
8
  "captureDate": "2026-07-20",
9
9
  "rawFileSha256": "6ef35bad568967015c101f89e995148c4eb2131b3a6dd244f7237058abae8e7a",
10
- "launchToolCount": 10,
10
+ "launchToolCount": 13,
11
11
  "liveToolCount": 13,
12
12
  "liveToolOrder": [
13
13
  "create_draft",
@@ -28,28 +28,43 @@
28
28
  "create_draft",
29
29
  "list_drafts",
30
30
  "get_thread",
31
+ "get_message",
31
32
  "search_threads",
32
33
  "label_thread",
33
34
  "unlabel_thread",
35
+ "apply_sensitive_thread_label",
34
36
  "list_labels",
35
37
  "label_message",
36
38
  "unlabel_message",
39
+ "apply_sensitive_message_label",
37
40
  "create_label"
38
41
  ],
39
- "excludedFromLaunch": [
42
+ "gate0LaunchToolOrder": [
43
+ "create_draft",
44
+ "list_drafts",
45
+ "get_thread",
46
+ "search_threads",
47
+ "label_thread",
48
+ "unlabel_thread",
49
+ "list_labels",
50
+ "label_message",
51
+ "unlabel_message",
52
+ "create_label"
53
+ ],
54
+ "gate1PromotedFromPreview": [
40
55
  "get_message",
41
56
  "apply_sensitive_thread_label",
42
57
  "apply_sensitive_message_label"
43
58
  ],
44
59
  "notes": [
45
60
  "Raw live tools/list returned 13 tools without auth (Developer Preview).",
46
- "Gate 0 launch set is exactly the 10 tools named in the Gmail twin plan / public tool reference.",
47
- "Schemas/annotations/descriptions for launch tools are copied verbatim from the live capture.",
48
- "Excluded live tools (get_message, apply_sensitive_thread_label, apply_sensitive_message_label) are preview drift vs the documented 10-tool launch set — not served by the twin while claiming official listing parity.",
61
+ "Gate 1 expands the OSS launch set from the Gate 0 ten-tool freeze to the full live 13-tool listing.",
62
+ "Schemas/annotations/descriptions for all launch tools are copied verbatim from the live capture.",
63
+ "Promoted Gate-1 tools: get_message, apply_sensitive_thread_label, apply_sensitive_message_label.",
49
64
  "Authenticated tools/call success envelopes were unavailable; see mcp-tools-call.representative.json and mcp-tools-call-unauth-error.raw.json.",
50
65
  "initialize returned protocolVersion=2025-03-26 (see mcp-initialize.raw.json)."
51
66
  ],
52
- "canonicalSha256": "8771e967b1c8d5c364de2a05c890aaddd27fb9eba45fde68e18d767b1dfc7ac3"
67
+ "canonicalSha256": "56b5e86181c13ead63fdc8ea0d4bedc6860c1a3fdb17372fdca76591e48a4eed"
53
68
  },
54
69
  "jsonrpc": "2.0",
55
70
  "id": 1,
@@ -463,6 +478,138 @@
463
478
  "type": "object"
464
479
  }
465
480
  },
481
+ {
482
+ "annotations": {
483
+ "destructiveHint": false,
484
+ "idempotentHint": true,
485
+ "openWorldHint": false,
486
+ "readOnlyHint": true,
487
+ "title": "Retrieves a specific email message from the authenticated user's Gmail account."
488
+ },
489
+ "description": "Retrieves a specific email message from the authenticated user's Gmail account by its unique message ID.\n\nUse this tool to inspect a single, individual email when you already know its message ID. If the user wants to read a specific email in detail, check the exact wording of a message, or examine attachment metadata for a single email, this is the right tool. It is not suitable for retrieving entire conversations or viewing back-and-forth discussion threads; use the 'get_thread' tool instead.\nKey indicators include if the user asks for the full content of a specific message ID returned by a previous search, or if the query asks to inspect a specific individual email rather than an entire thread.\nExample user prompts are: \"Get the full text of message ID 18f123456789abcd.\", \"Read the latest message in that thread from Alice.\", and \"What are the attachment names in the email I just received from HR?\" \n\nThe optional `messageFormat` parameter controls the format of the message returned. By default (or with `FULL_CONTENT`), it returns the full content of the message. Use `MINIMAL` to include only subject and snippet (excluding body). Use `METADATA_ONLY` to include only basic metadata (message ID, thread ID, labels, timestamp, and size estimate). \n",
490
+ "inputSchema": {
491
+ "description": "Request message for GetMessage RPC.",
492
+ "properties": {
493
+ "messageFormat": {
494
+ "description": "Optional. Specifies the format of the message returned. Defaults to FULL_CONTENT.",
495
+ "enum": [
496
+ "MESSAGE_FORMAT_UNSPECIFIED",
497
+ "MINIMAL",
498
+ "FULL_CONTENT",
499
+ "METADATA_ONLY"
500
+ ],
501
+ "type": "string",
502
+ "x-google-enum-descriptions": [
503
+ "Defaults to FULL_CONTENT.",
504
+ "Returns message snippets and key headers (Subject, From, To, Cc, Date).",
505
+ "Returns all information in \"MINIMAL\" plus the full body content of each message.",
506
+ "Metadata only: does not include subject, snippet, body, attachment filenames."
507
+ ]
508
+ },
509
+ "messageId": {
510
+ "description": "Required. The unique identifier of the message to fetch.",
511
+ "type": "string"
512
+ }
513
+ },
514
+ "required": [
515
+ "messageId"
516
+ ],
517
+ "type": "object"
518
+ },
519
+ "name": "get_message",
520
+ "outputSchema": {
521
+ "$defs": {
522
+ "AttachmentMetadata": {
523
+ "description": "Represents an attachment associated with a message.",
524
+ "properties": {
525
+ "filename": {
526
+ "description": "The filename of the attachment.",
527
+ "type": "string"
528
+ },
529
+ "id": {
530
+ "description": "Output only. The ID of the attachment.",
531
+ "readOnly": true,
532
+ "type": "string"
533
+ },
534
+ "mimeType": {
535
+ "description": "The MIME type of the attachment.",
536
+ "type": "string"
537
+ }
538
+ },
539
+ "type": "object"
540
+ }
541
+ },
542
+ "description": "Message within a thread.",
543
+ "properties": {
544
+ "attachmentIds": {
545
+ "description": "Output only. The attachment ids, only populated if MessageFormat was FULL_CONTENT.",
546
+ "items": {
547
+ "type": "string"
548
+ },
549
+ "readOnly": true,
550
+ "type": "array"
551
+ },
552
+ "attachments": {
553
+ "description": "Output only. The attachments, only populated if MessageFormat was FULL_CONTENT.",
554
+ "items": {
555
+ "$ref": "#/$defs/AttachmentMetadata"
556
+ },
557
+ "readOnly": true,
558
+ "type": "array"
559
+ },
560
+ "ccRecipients": {
561
+ "description": "CC recipient email addresses.",
562
+ "items": {
563
+ "type": "string"
564
+ },
565
+ "type": "array"
566
+ },
567
+ "date": {
568
+ "description": "Date of the message in ISO 8601 format (YYYY-MM-DD).",
569
+ "type": "string"
570
+ },
571
+ "htmlBody": {
572
+ "description": "The HTML content of the email, only populated if MessageFormat was FULL_CONTENT.",
573
+ "type": "string"
574
+ },
575
+ "id": {
576
+ "description": "The unique identifier of the message.",
577
+ "type": "string"
578
+ },
579
+ "labelIds": {
580
+ "description": "The ids of the labels attached to the message. Includes ids of user labels and standard system labels limited to \"INBOX\", \"SPAM\", \"TRASH\", \"UNREAD\", \"STARRED\", \"IMPORTANT\", \"SENT\", \"DRAFT\", \"CHAT\".",
581
+ "items": {
582
+ "type": "string"
583
+ },
584
+ "type": "array"
585
+ },
586
+ "plaintextBody": {
587
+ "description": "Full body content, only populated if MessageFormat was FULL_CONTENT.",
588
+ "type": "string"
589
+ },
590
+ "sender": {
591
+ "description": "Sender email address.",
592
+ "type": "string"
593
+ },
594
+ "snippet": {
595
+ "description": "Snippet of the message body.",
596
+ "type": "string"
597
+ },
598
+ "subject": {
599
+ "description": "The message subject extracted from headers:",
600
+ "type": "string"
601
+ },
602
+ "toRecipients": {
603
+ "description": "To recipient email addresses.",
604
+ "items": {
605
+ "type": "string"
606
+ },
607
+ "type": "array"
608
+ }
609
+ },
610
+ "type": "object"
611
+ }
612
+ },
466
613
  {
467
614
  "annotations": {
468
615
  "destructiveHint": false,
@@ -716,6 +863,50 @@
716
863
  "type": "object"
717
864
  }
718
865
  },
866
+ {
867
+ "annotations": {
868
+ "destructiveHint": true,
869
+ "idempotentHint": true,
870
+ "openWorldHint": false,
871
+ "readOnlyHint": false,
872
+ "title": "Adds a sensitive label (Trash or Spam) to a thread."
873
+ },
874
+ "description": "Adds a sensitive label (Trash or Spam) to an entire thread in the authenticated user's Gmail account. This operation affects all messages currently in the thread and any future messages added to it.\n\nUse this tool to trash or mark a thread as spam. If unsure of the thread ID, use the `search_threads` tool first.\n",
875
+ "inputSchema": {
876
+ "description": "Request message for ApplySensitiveThreadLabel RPC.",
877
+ "properties": {
878
+ "labelOption": {
879
+ "description": "Required. The sensitive label option to add.",
880
+ "enum": [
881
+ "LABEL_OPTION_UNSPECIFIED",
882
+ "TRASH",
883
+ "SPAM"
884
+ ],
885
+ "type": "string",
886
+ "x-google-enum-descriptions": [
887
+ "Unspecified label option.",
888
+ "Trash label.",
889
+ "Spam label."
890
+ ]
891
+ },
892
+ "threadId": {
893
+ "description": "Required. The ID of the thread to add the label to.",
894
+ "type": "string"
895
+ }
896
+ },
897
+ "required": [
898
+ "threadId",
899
+ "labelOption"
900
+ ],
901
+ "type": "object"
902
+ },
903
+ "name": "apply_sensitive_thread_label",
904
+ "outputSchema": {
905
+ "description": "Response message for ApplySensitiveThreadLabel RPC.",
906
+ "properties": {},
907
+ "type": "object"
908
+ }
909
+ },
719
910
  {
720
911
  "annotations": {
721
912
  "destructiveHint": false,
@@ -877,6 +1068,50 @@
877
1068
  "type": "object"
878
1069
  }
879
1070
  },
1071
+ {
1072
+ "annotations": {
1073
+ "destructiveHint": true,
1074
+ "idempotentHint": true,
1075
+ "openWorldHint": false,
1076
+ "readOnlyHint": false,
1077
+ "title": "Adds a sensitive label (Trash or Spam) to a message."
1078
+ },
1079
+ "description": "Adds a sensitive label (Trash or Spam) to a specific message in the authenticated user's Gmail account.\n\nUse this tool to trash or mark a message as spam. To find the message ID, use tools like `search_threads` or `get_thread`.\n",
1080
+ "inputSchema": {
1081
+ "description": "Request message for ApplySensitiveMessageLabel RPC.",
1082
+ "properties": {
1083
+ "labelOption": {
1084
+ "description": "Required. The sensitive label option to add.",
1085
+ "enum": [
1086
+ "LABEL_OPTION_UNSPECIFIED",
1087
+ "TRASH",
1088
+ "SPAM"
1089
+ ],
1090
+ "type": "string",
1091
+ "x-google-enum-descriptions": [
1092
+ "Unspecified label option.",
1093
+ "Trash label.",
1094
+ "Spam label."
1095
+ ]
1096
+ },
1097
+ "messageId": {
1098
+ "description": "Required. The ID of the message to add the label to.",
1099
+ "type": "string"
1100
+ }
1101
+ },
1102
+ "required": [
1103
+ "messageId",
1104
+ "labelOption"
1105
+ ],
1106
+ "type": "object"
1107
+ },
1108
+ "name": "apply_sensitive_message_label",
1109
+ "outputSchema": {
1110
+ "description": "Response message for ApplySensitiveMessageLabel RPC.",
1111
+ "properties": {},
1112
+ "type": "object"
1113
+ }
1114
+ },
880
1115
  {
881
1116
  "annotations": {
882
1117
  "destructiveHint": false,
@@ -6,7 +6,7 @@
6
6
  "captureDate": "2026-07-20",
7
7
  "httpStatus": 200,
8
8
  "sha256": "6ef35bad568967015c101f89e995148c4eb2131b3a6dd244f7237058abae8e7a",
9
- "canonicalSha256": "8771e967b1c8d5c364de2a05c890aaddd27fb9eba45fde68e18d767b1dfc7ac3",
9
+ "canonicalSha256": "56b5e86181c13ead63fdc8ea0d4bedc6860c1a3fdb17372fdca76591e48a4eed",
10
10
  "request": {
11
11
  "headers": {
12
12
  "content-type": "application/json",
@@ -20,8 +20,8 @@
20
20
  },
21
21
  "auth": "none (unauthenticated capture succeeded for tools/list and initialize)",
22
22
  "liveToolCount": 13,
23
- "launchToolCount": 10,
24
- "excludedFromLaunch": [
23
+ "launchToolCount": 13,
24
+ "gate1PromotedFromPreview": [
25
25
  "get_message",
26
26
  "apply_sensitive_thread_label",
27
27
  "apply_sensitive_message_label"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pome-sh/twin-gmail",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "Deterministic Gmail-shaped twin for agent testing — SQLite-backed domain core.",
5
5
  "private": false,
6
6
  "type": "module",
@@ -47,8 +47,9 @@
47
47
  "access": "public"
48
48
  },
49
49
  "dependencies": {
50
+ "@hono/node-server": "^2.0.10",
50
51
  "@pome-sh/sdk": "0.5.1",
51
- "hono": "^4.12.27",
52
+ "hono": "^4.12.31",
52
53
  "zod": "^4.1.13"
53
54
  },
54
55
  "keywords": [
@@ -36,7 +36,7 @@ const mergePath = (base, path) => {
36
36
  };
37
37
  const replaceUrlParam = (urlString, params) => {
38
38
  for (const [k, v] of Object.entries(params)) {
39
- const reg = new RegExp("/:" + k + "(?:{[^/]+})?\\??");
39
+ const reg = new RegExp("/:" + k + "(?:{[^/]+})?\\??(?=/|$)");
40
40
  urlString = urlString.replace(reg, v ? `/${v}` : "");
41
41
  }
42
42
  return urlString;
@@ -33,16 +33,17 @@ class SSEStreamingApi extends import_stream.StreamingApi {
33
33
  const dataLines = data.split(/\r\n|\r|\n/).map((line) => {
34
34
  return `data: ${line}`;
35
35
  }).join("\n");
36
- for (const key of ["event", "id", "retry"]) {
37
- if (message[key] && /[\r\n]/.test(message[key])) {
36
+ for (const key of ["event", "id"]) {
37
+ const value = message[key];
38
+ if (value && /[\r\n]/.test(value)) {
38
39
  throw new Error(`${key} must not contain "\\r" or "\\n"`);
39
40
  }
40
41
  }
41
42
  const sseData = [
42
43
  message.event && `event: ${message.event}`,
43
44
  dataLines,
44
- message.id && `id: ${message.id}`,
45
- message.retry && `retry: ${message.retry}`
45
+ message.id !== void 0 && `id: ${message.id}`,
46
+ message.retry !== void 0 && `retry: ${message.retry}`
46
47
  ].filter(Boolean).join("\n") + "\n\n";
47
48
  await this.write(sseData);
48
49
  }
@@ -55,7 +55,7 @@ const cache = (options) => {
55
55
  );
56
56
  const addHeader = (c, responseVary) => {
57
57
  if (cacheControlDirectives) {
58
- const existingDirectives = c.res.headers.get("Cache-Control")?.split(",").map((d) => d.trim().split("=", 1)[0]) ?? [];
58
+ const existingDirectives = c.res.headers.get("Cache-Control")?.split(",").map((d) => d.trim().split("=", 1)[0].toLowerCase()) ?? [];
59
59
  for (const directive of cacheControlDirectives) {
60
60
  let [name, value] = directive.trim().split("=", 2);
61
61
  name = name.toLowerCase();
@@ -57,7 +57,8 @@ const compress = (options) => {
57
57
  return async function compress2(ctx, next) {
58
58
  await next();
59
59
  const contentLength = ctx.res.headers.get("Content-Length");
60
- if (ctx.res.headers.has("Content-Encoding") || // already encoded
60
+ if (ctx.res.status === 206 || // partial content, Content-Range refers to the uncompressed bytes
61
+ ctx.res.headers.has("Content-Encoding") || // already encoded
61
62
  ctx.res.headers.has("Transfer-Encoding") || // already encoded or chunked
62
63
  ctx.req.method === "HEAD" || // HEAD request
63
64
  contentLength && Number(contentLength) < threshold || // content-length below threshold
@@ -80,11 +80,13 @@ const methodOverride = (options) => async function methodOverride2(c, next) {
80
80
  if (method) {
81
81
  const url = new URL(c.req.url);
82
82
  url.searchParams.delete(queryName);
83
- const request = new Request(url.toString(), {
83
+ const requestInit = {
84
84
  body: c.req.raw.body,
85
85
  headers: c.req.raw.headers,
86
- method
87
- });
86
+ method,
87
+ duplex: c.req.raw.body ? "half" : void 0
88
+ };
89
+ const request = new Request(url.toString(), requestInit);
88
90
  return app.fetch(request, c.env, getExecutionCtx(c));
89
91
  }
90
92
  }
@@ -71,14 +71,20 @@ const secureHeaders = (customOptions) => {
71
71
  const headersToSet = getFilteredHeaders(options);
72
72
  const callbacks = [];
73
73
  if (options.contentSecurityPolicy) {
74
- const [callback, value] = getCSPDirectives(options.contentSecurityPolicy);
74
+ const [callback, value] = getCSPDirectives(
75
+ options.contentSecurityPolicy,
76
+ "Content-Security-Policy"
77
+ );
75
78
  if (callback) {
76
79
  callbacks.push(callback);
77
80
  }
78
81
  headersToSet.push(["Content-Security-Policy", value]);
79
82
  }
80
83
  if (options.contentSecurityPolicyReportOnly) {
81
- const [callback, value] = getCSPDirectives(options.contentSecurityPolicyReportOnly);
84
+ const [callback, value] = getCSPDirectives(
85
+ options.contentSecurityPolicyReportOnly,
86
+ "Content-Security-Policy-Report-Only"
87
+ );
82
88
  if (callback) {
83
89
  callbacks.push(callback);
84
90
  }
@@ -111,7 +117,7 @@ function getFilteredHeaders(options) {
111
117
  return typeof overrideValue === "string" ? [defaultValue[0], overrideValue] : defaultValue;
112
118
  });
113
119
  }
114
- function getCSPDirectives(contentSecurityPolicy) {
120
+ function getCSPDirectives(contentSecurityPolicy, headerName) {
115
121
  const callbacks = [];
116
122
  const resultValues = [];
117
123
  for (const [directive, value] of Object.entries(contentSecurityPolicy)) {
@@ -136,7 +142,7 @@ function getCSPDirectives(contentSecurityPolicy) {
136
142
  resultValues.pop();
137
143
  return callbacks.length === 0 ? [void 0, resultValues.join("")] : [
138
144
  (ctx, headersToSet) => headersToSet.map((values) => {
139
- if (values[0] === "Content-Security-Policy" || values[0] === "Content-Security-Policy-Report-Only") {
145
+ if (values[0] === headerName) {
140
146
  const clone = values[1].slice();
141
147
  callbacks.forEach((cb) => {
142
148
  cb(ctx, clone);
@@ -98,7 +98,7 @@ class HonoRequest {
98
98
  if (name) {
99
99
  return this.raw.headers.get(name) ?? void 0;
100
100
  }
101
- const headerData = {};
101
+ const headerData = /* @__PURE__ */ Object.create(null);
102
102
  this.raw.headers.forEach((value, key) => {
103
103
  headerData[key] = value;
104
104
  });
@@ -316,11 +316,16 @@ const cloneRawRequest = async (req) => {
316
316
  message: "Cannot clone request: body was already consumed and not cached. Please use HonoRequest methods (e.g., req.json(), req.text()) instead of consuming req.raw directly."
317
317
  });
318
318
  }
319
+ const body = await req[cacheKey]();
320
+ const headers = req.header();
321
+ if (body instanceof FormData) {
322
+ delete headers["content-type"];
323
+ }
319
324
  const requestInit = {
320
- body: await req[cacheKey](),
325
+ body,
321
326
  cache: req.raw.cache,
322
327
  credentials: req.raw.credentials,
323
- headers: req.header(),
328
+ headers,
324
329
  integrity: req.raw.integrity,
325
330
  keepalive: req.raw.keepalive,
326
331
  method: req.method,
@@ -146,7 +146,7 @@ const getNextParam = (acceptHeader, startIndex) => {
146
146
  const getNextAcceptValue = (acceptHeader, startIndex) => {
147
147
  const accept = {
148
148
  type: "",
149
- params: {},
149
+ params: /* @__PURE__ */ Object.create(null),
150
150
  q: 1
151
151
  };
152
152
  startIndex = consumeWhitespace(acceptHeader, startIndex);
@@ -33,6 +33,12 @@ const parseBody = async (request, options = /* @__PURE__ */ Object.create(null))
33
33
  return {};
34
34
  };
35
35
  async function parseFormData(request, options) {
36
+ if (!isRawRequest(request) && request.bodyCache.formData) {
37
+ return convertFormDataToBodyData(
38
+ await request.bodyCache.formData,
39
+ options
40
+ );
41
+ }
36
42
  const headers = isRawRequest(request) ? request.headers : request.raw.headers;
37
43
  const arrayBuffer = await request.arrayBuffer();
38
44
  const formDataPromise = (0, import_buffer.bufferToFormData)(arrayBuffer, headers.get("Content-Type") || "");
@@ -192,7 +192,7 @@ const _getQueryParam = (url, key, multiple) => {
192
192
  return void 0;
193
193
  }
194
194
  }
195
- const results = {};
195
+ const results = /* @__PURE__ */ Object.create(null);
196
196
  encoded ??= /[%+]/.test(url);
197
197
  let keyIndex = url.indexOf("?", 8);
198
198
  while (keyIndex !== -1) {
@@ -8,7 +8,7 @@ var mergePath = (base, path) => {
8
8
  };
9
9
  var replaceUrlParam = (urlString, params) => {
10
10
  for (const [k, v] of Object.entries(params)) {
11
- const reg = new RegExp("/:" + k + "(?:{[^/]+})?\\??");
11
+ const reg = new RegExp("/:" + k + "(?:{[^/]+})?\\??(?=/|$)");
12
12
  urlString = urlString.replace(reg, v ? `/${v}` : "");
13
13
  }
14
14
  return urlString;
@@ -11,16 +11,17 @@ var SSEStreamingApi = class extends StreamingApi {
11
11
  const dataLines = data.split(/\r\n|\r|\n/).map((line) => {
12
12
  return `data: ${line}`;
13
13
  }).join("\n");
14
- for (const key of ["event", "id", "retry"]) {
15
- if (message[key] && /[\r\n]/.test(message[key])) {
14
+ for (const key of ["event", "id"]) {
15
+ const value = message[key];
16
+ if (value && /[\r\n]/.test(value)) {
16
17
  throw new Error(`${key} must not contain "\\r" or "\\n"`);
17
18
  }
18
19
  }
19
20
  const sseData = [
20
21
  message.event && `event: ${message.event}`,
21
22
  dataLines,
22
- message.id && `id: ${message.id}`,
23
- message.retry && `retry: ${message.retry}`
23
+ message.id !== void 0 && `id: ${message.id}`,
24
+ message.retry !== void 0 && `retry: ${message.retry}`
24
25
  ].filter(Boolean).join("\n") + "\n\n";
25
26
  await this.write(sseData);
26
27
  }
@@ -34,7 +34,7 @@ var cache = (options) => {
34
34
  );
35
35
  const addHeader = (c, responseVary) => {
36
36
  if (cacheControlDirectives) {
37
- const existingDirectives = c.res.headers.get("Cache-Control")?.split(",").map((d) => d.trim().split("=", 1)[0]) ?? [];
37
+ const existingDirectives = c.res.headers.get("Cache-Control")?.split(",").map((d) => d.trim().split("=", 1)[0].toLowerCase()) ?? [];
38
38
  for (const directive of cacheControlDirectives) {
39
39
  let [name, value] = directive.trim().split("=", 2);
40
40
  name = name.toLowerCase();
@@ -35,7 +35,8 @@ var compress = (options) => {
35
35
  return async function compress2(ctx, next) {
36
36
  await next();
37
37
  const contentLength = ctx.res.headers.get("Content-Length");
38
- if (ctx.res.headers.has("Content-Encoding") || // already encoded
38
+ if (ctx.res.status === 206 || // partial content, Content-Range refers to the uncompressed bytes
39
+ ctx.res.headers.has("Content-Encoding") || // already encoded
39
40
  ctx.res.headers.has("Transfer-Encoding") || // already encoded or chunked
40
41
  ctx.req.method === "HEAD" || // HEAD request
41
42
  contentLength && Number(contentLength) < threshold || // content-length below threshold
@@ -59,11 +59,13 @@ var methodOverride = (options) => async function methodOverride2(c, next) {
59
59
  if (method) {
60
60
  const url = new URL(c.req.url);
61
61
  url.searchParams.delete(queryName);
62
- const request = new Request(url.toString(), {
62
+ const requestInit = {
63
63
  body: c.req.raw.body,
64
64
  headers: c.req.raw.headers,
65
- method
66
- });
65
+ method,
66
+ duplex: c.req.raw.body ? "half" : void 0
67
+ };
68
+ const request = new Request(url.toString(), requestInit);
67
69
  return app.fetch(request, c.env, getExecutionCtx(c));
68
70
  }
69
71
  }