@lanes-sh/link 0.6.10 → 0.7.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 (202) hide show
  1. package/.gcloudignore +25 -15
  2. package/README.md +62 -45
  3. package/instructions/skills/lanes-link/SKILL.md +25 -0
  4. package/package.json +3 -2
  5. package/src/audit/conformance.ts +1 -1
  6. package/src/cli/argv.ts +14 -1
  7. package/src/cli/commands/connect/acquire.ts +100 -0
  8. package/src/cli/commands/connect/custom/index.ts +1 -1
  9. package/src/cli/commands/connect/declare.ts +19 -0
  10. package/src/cli/commands/connect/index.ts +54 -56
  11. package/src/cli/commands/connect/requirements.ts +23 -1
  12. package/src/cli/commands/connect/setup.ts +1 -1
  13. package/src/cli/commands/connect/variables.ts +220 -0
  14. package/src/cli/commands/knowledge/index.ts +22 -4
  15. package/src/cli/commands/knowledge/migrate.ts +61 -43
  16. package/src/cli/commands/operate/tools.ts +1 -1
  17. package/src/cli/commands/owner/entities.ts +244 -0
  18. package/src/cli/commands/owner/shared.ts +14 -0
  19. package/src/cli/commands/owner/vault.ts +2 -2
  20. package/src/cli/commands/owner.ts +13 -3
  21. package/src/cli/commands/setup.ts +12 -0
  22. package/src/cli/config-edit.ts +16 -6
  23. package/src/cli/config-repair.ts +10 -0
  24. package/src/cli/dispatch-owner.ts +30 -1
  25. package/src/cli/main.ts +2 -1
  26. package/src/cli/runtime/open.ts +14 -7
  27. package/src/cli/runtime/registry.ts +6 -1
  28. package/src/cli/selection.ts +11 -0
  29. package/src/cli/usage.ts +13 -2
  30. package/src/connectivity/auth/strategy/index.ts +1 -1
  31. package/src/connectivity/capability.ts +1 -1
  32. package/src/connectivity/context.ts +1 -1
  33. package/src/connectivity/index.ts +1 -1
  34. package/src/connectivity/mail/attachments.ts +42 -73
  35. package/src/connectivity/mail/content-type.ts +71 -0
  36. package/src/connectivity/mail/index.ts +1 -1
  37. package/src/connectivity/manifest/provider.ts +50 -0
  38. package/src/connectivity/manifest/variables.ts +173 -0
  39. package/src/connectivity/transports/factory.ts +74 -1
  40. package/src/connectivity/transports/http/index.ts +8 -2
  41. package/src/connectivity/transports/http/keys.ts +83 -0
  42. package/src/connectivity/transports/imap/attachment.ts +53 -10
  43. package/src/connectivity/transports/imap/capabilities.ts +28 -0
  44. package/src/connectivity/transports/imap/download.ts +111 -0
  45. package/src/connectivity/transports/imap/index.ts +13 -0
  46. package/src/connectivity/transports/imap/operations.ts +1 -0
  47. package/src/deployments/adapters/s3.ts +1 -1
  48. package/src/deployments/gcp/provision.ts +1 -1
  49. package/src/deployments/knowledge.ts +44 -18
  50. package/src/deployments/prepare.ts +1 -1
  51. package/src/deployments/serving.ts +1 -1
  52. package/src/dispatch/dispatch.ts +17 -10
  53. package/src/dispatch/staging.ts +76 -1
  54. package/src/profile/knowledge.ts +19 -11
  55. package/src/profile/schema.ts +2 -2
  56. package/src/providers/README.md +189 -0
  57. package/src/providers/airtable/index.ts +10 -0
  58. package/src/providers/algolia/index.ts +10 -0
  59. package/src/providers/amplitude/index.ts +10 -0
  60. package/src/providers/apify/index.ts +10 -0
  61. package/src/providers/asana/index.ts +10 -0
  62. package/src/providers/assets/provider.ts +11 -4
  63. package/src/providers/atlassian/index.ts +19 -0
  64. package/src/providers/attio/index.ts +10 -0
  65. package/src/providers/betterstack/index.ts +10 -0
  66. package/src/providers/box/index.ts +64 -0
  67. package/src/providers/brightdata/index.ts +10 -0
  68. package/src/providers/buildkite/index.ts +10 -0
  69. package/src/providers/bunq/index.ts +2 -2
  70. package/src/providers/calendly/index.ts +10 -0
  71. package/src/providers/canva/index.ts +10 -0
  72. package/src/providers/circleci/index.ts +10 -0
  73. package/src/providers/clickup/index.ts +10 -0
  74. package/src/providers/close/index.ts +10 -0
  75. package/src/providers/cloudflare_bindings/index.ts +14 -0
  76. package/src/providers/cloudflare_observability/index.ts +10 -0
  77. package/src/providers/contentful/index.ts +10 -0
  78. package/src/providers/custom/load.ts +1 -1
  79. package/src/providers/datadog/index.ts +10 -0
  80. package/src/providers/discord/index.ts +1 -1
  81. package/src/providers/discord/specs/discord.v10.json +0 -88
  82. package/src/providers/discord/specs/vendor.ts +1 -1
  83. package/src/providers/dropbox/index.ts +10 -0
  84. package/src/providers/entities/catalogue.ts +366 -0
  85. package/src/providers/entities/find.ts +279 -0
  86. package/src/providers/entities/provider.ts +334 -0
  87. package/src/providers/entities/render.ts +142 -0
  88. package/src/providers/entities/store.ts +276 -0
  89. package/src/providers/entities/writes.ts +279 -0
  90. package/src/providers/example/provider.ts +1 -1
  91. package/src/providers/expensify/index.ts +10 -0
  92. package/src/providers/fastmail/calendar/index.ts +21 -0
  93. package/src/providers/fastmail/contacts/index.ts +17 -0
  94. package/src/providers/fastmail/index.ts +4 -0
  95. package/src/providers/fastmail/mail/index.ts +38 -0
  96. package/src/providers/fastmail/shared/setup.ts +52 -0
  97. package/src/providers/figma/index.ts +10 -0
  98. package/src/providers/fireflies/index.ts +10 -0
  99. package/src/providers/flagsmith/index.ts +10 -0
  100. package/src/providers/gamma/index.ts +10 -0
  101. package/src/providers/github/index.ts +2 -2
  102. package/src/providers/google/gmail/attachment.ts +16 -1
  103. package/src/providers/google/gmail-imap/index.ts +7 -2
  104. package/src/providers/google/shared/oauth.ts +1 -1
  105. package/src/providers/google/shared/service-account.ts +1 -1
  106. package/src/providers/google/shared/setup.ts +1 -1
  107. package/src/providers/google/specs/calendar.v3.json +0 -32
  108. package/src/providers/google/specs/docs.v1.json +0 -76
  109. package/src/providers/google/specs/drive.v3.json +0 -32
  110. package/src/providers/google/specs/gmail.v1.json +0 -76
  111. package/src/providers/google/specs/people.v1.json +0 -77
  112. package/src/providers/google/specs/sheets.v4.json +0 -76
  113. package/src/providers/google/specs/tasks.v1.json +0 -76
  114. package/src/providers/grafana/index.ts +10 -0
  115. package/src/providers/heroku/index.ts +10 -0
  116. package/src/providers/hubspot/index.ts +75 -0
  117. package/src/providers/hygraph/index.ts +10 -0
  118. package/src/providers/icloud/mail/index.ts +5 -0
  119. package/src/providers/icloud/shared/setup.ts +1 -1
  120. package/src/providers/index.ts +255 -0
  121. package/src/providers/insightly/index.ts +10 -0
  122. package/src/providers/jam/index.ts +10 -0
  123. package/src/providers/klaviyo/index.ts +10 -0
  124. package/src/providers/mailbox/index.ts +87 -0
  125. package/src/providers/memory/provider.ts +3 -8
  126. package/src/providers/mercury/index.ts +10 -0
  127. package/src/providers/microsoft/calendar/index.ts +49 -0
  128. package/src/providers/microsoft/contacts/index.ts +50 -0
  129. package/src/providers/microsoft/drive/index.ts +62 -0
  130. package/src/providers/microsoft/index.ts +12 -0
  131. package/src/providers/microsoft/mail/index.ts +76 -0
  132. package/src/providers/microsoft/shared/oauth.ts +58 -0
  133. package/src/providers/microsoft/shared/scopes.ts +56 -0
  134. package/src/providers/microsoft/shared/setup.ts +45 -0
  135. package/src/providers/microsoft/specs/microsoft-todo.v1.json +755 -0
  136. package/src/providers/microsoft/specs/onedrive.v1.json +993 -0
  137. package/src/providers/microsoft/specs/outlook-calendar.v1.json +1743 -0
  138. package/src/providers/microsoft/specs/outlook-contacts.v1.json +430 -0
  139. package/src/providers/microsoft/specs/outlook-mail.v1.json +901 -0
  140. package/src/providers/microsoft/specs/vendor.ts +232 -0
  141. package/src/providers/microsoft/todo/index.ts +54 -0
  142. package/src/providers/miro/index.ts +10 -0
  143. package/src/providers/mixpanel/index.ts +10 -0
  144. package/src/providers/monday/index.ts +10 -0
  145. package/src/providers/mux/index.ts +10 -0
  146. package/src/providers/navan/index.ts +10 -0
  147. package/src/providers/neon/index.ts +10 -0
  148. package/src/providers/netlify/index.ts +10 -0
  149. package/src/providers/nextcloud/calendar/index.ts +28 -0
  150. package/src/providers/nextcloud/contacts/index.ts +18 -0
  151. package/src/providers/nextcloud/index.ts +3 -0
  152. package/src/providers/nextcloud/shared/setup.ts +59 -0
  153. package/src/providers/owner.ts +15 -3
  154. package/src/providers/paddle/index.ts +10 -0
  155. package/src/providers/paypal/index.ts +10 -0
  156. package/src/providers/posthog/index.ts +10 -0
  157. package/src/providers/prisma/index.ts +10 -0
  158. package/src/providers/ramp/index.ts +10 -0
  159. package/src/providers/recurly/index.ts +10 -0
  160. package/src/providers/reddit/index.ts +1 -1
  161. package/src/providers/remote/index.ts +10 -0
  162. package/src/providers/render/index.ts +35 -0
  163. package/src/providers/replicate/index.ts +10 -0
  164. package/src/providers/resend/index.ts +10 -0
  165. package/src/providers/riverside/index.ts +10 -0
  166. package/src/providers/rootly/index.ts +10 -0
  167. package/src/providers/rudderstack/index.ts +10 -0
  168. package/src/providers/salesloft/index.ts +10 -0
  169. package/src/providers/sanity/index.ts +10 -0
  170. package/src/providers/scopes.ts +2 -0
  171. package/src/providers/sentry/index.ts +10 -0
  172. package/src/providers/setup/plan.ts +11 -0
  173. package/src/providers/setup/provider.ts +12 -0
  174. package/src/providers/shared/openapi.ts +70 -0
  175. package/src/providers/shared/slug.ts +28 -0
  176. package/src/providers/shared/vendor-operations.ts +43 -1
  177. package/src/providers/shared/vendor-spec.ts +98 -17
  178. package/src/providers/shortcut/index.ts +10 -0
  179. package/src/providers/slack/index.ts +2 -2
  180. package/src/providers/square/index.ts +10 -0
  181. package/src/providers/storyblok/index.ts +10 -0
  182. package/src/providers/stripe/index.ts +10 -0
  183. package/src/providers/supabase/index.ts +10 -0
  184. package/src/providers/tasks/store.ts +2 -7
  185. package/src/providers/tavily/index.ts +10 -0
  186. package/src/providers/todoist/index.ts +10 -0
  187. package/src/providers/untested.ts +105 -0
  188. package/src/providers/vault/provider.ts +1 -1
  189. package/src/providers/vercel/index.ts +10 -0
  190. package/src/providers/vimeo/index.ts +10 -0
  191. package/src/providers/webflow/index.ts +10 -0
  192. package/src/providers/whimsical/index.ts +10 -0
  193. package/src/providers/wix/index.ts +10 -0
  194. package/src/providers/workable/index.ts +10 -0
  195. package/src/providers/yahoo_mail/index.ts +73 -0
  196. package/src/providers/zapier/index.ts +10 -0
  197. package/src/providers/zoho_mail/index.ts +79 -0
  198. package/src/secrets/document.ts +1 -1
  199. package/src/server/attachments.ts +129 -5
  200. package/src/server/index.ts +3 -3
  201. package/src/server/mcp/instructions.ts +88 -18
  202. package/src/server/stdio.ts +1 -1
@@ -4,7 +4,7 @@ import { defineLocalProvider, keepKeys, type ProviderContext } from '#connectivi
4
4
  /**
5
5
  * The example provider — the reference every other provider is written
6
6
  * against, and small enough to reproduce verbatim in
7
- * `docs/detailed/creating-a-provider.md`.
7
+ * `https://lanes.sh/docs/link/creating-a-provider`.
8
8
  *
9
9
  * It is also load-bearing beyond being a sample. It declares no auth
10
10
  * requirement and touches no third-party service, which makes it an *owner
@@ -0,0 +1,10 @@
1
+ import { defineProvider } from '#connectivity';
2
+
3
+ /** Expensify registers us at connect time — nothing for an operator to set up. */
4
+ export const expensify = defineProvider({
5
+ id: 'expensify',
6
+ name: 'Expensify',
7
+ description: 'Expenses, reports, and receipts, via Expensify\'s official MCP server.',
8
+ connector: { kind: 'mcp', endpoint: 'https://mcp.expensify.com/mcp/' },
9
+ auth: { kind: 'oauth', registration: 'dynamic' },
10
+ });
@@ -0,0 +1,21 @@
1
+ import { defineProvider } from '#connectivity';
2
+ import { FASTMAIL_APP, fastmailSetup } from '../shared/setup.ts';
3
+
4
+ export const fastmailCalendar = defineProvider({
5
+ id: 'fastmail_calendar',
6
+ name: 'Fastmail Calendar',
7
+ description: 'Read and create events in Fastmail calendars over CalDAV.',
8
+ connector: { kind: 'dav', base_url: 'https://caldav.fastmail.com', service: 'caldav' },
9
+ auth: { kind: 'basic', app: FASTMAIL_APP },
10
+ identity: { kind: 'connector' },
11
+ setup: fastmailSetup('Fastmail Calendar'),
12
+ redact: {
13
+ list_events: ['calendar', 'start', 'end', 'limit'],
14
+ get_event: ['calendar', 'uid'],
15
+ // The uid and calendar say *which* event changed; the summary and location
16
+ // are the content of it.
17
+ update_event: ['calendar', 'uid'],
18
+ delete_event: ['calendar', 'uid'],
19
+ create_event: ['calendar'],
20
+ },
21
+ });
@@ -0,0 +1,17 @@
1
+ import { defineProvider } from '#connectivity';
2
+ import { FASTMAIL_APP, fastmailSetup } from '../shared/setup.ts';
3
+
4
+ export const fastmailContacts = defineProvider({
5
+ id: 'fastmail_contacts',
6
+ name: 'Fastmail Contacts',
7
+ description: 'Search and read contacts in a Fastmail address book over CardDAV.',
8
+ connector: { kind: 'dav', base_url: 'https://carddav.fastmail.com', service: 'carddav' },
9
+ auth: { kind: 'basic', app: FASTMAIL_APP },
10
+ identity: { kind: 'connector' },
11
+ setup: fastmailSetup('Fastmail Contacts'),
12
+ redact: {
13
+ // Not `query`: a name is content.
14
+ search_contacts: ['addressbook', 'limit'],
15
+ create_contact: ['addressbook'],
16
+ },
17
+ });
@@ -0,0 +1,4 @@
1
+ /** Fastmail's three providers, sharing one app password. */
2
+ export { fastmailCalendar } from './calendar/index.ts';
3
+ export { fastmailContacts } from './contacts/index.ts';
4
+ export { fastmailMail } from './mail/index.ts';
@@ -0,0 +1,38 @@
1
+ import { defineProvider } from '#connectivity';
2
+ import { FASTMAIL_APP, fastmailSetup } from '../shared/setup.ts';
3
+
4
+ export const fastmailMail = defineProvider({
5
+ // `fastmail_mail`, not `fastmail`: the bare name is the credential app the
6
+ // three share, so `lanes link connect fastmail` fans out to all of them and
7
+ // asks for the password once. Same arrangement as iCloud.
8
+ id: 'fastmail_mail',
9
+ name: 'Fastmail Mail',
10
+ description:
11
+ 'Read, search, and send mail in a Fastmail mailbox over IMAP and SMTP, with an app password that does not expire.',
12
+ connector: {
13
+ kind: 'imap',
14
+ host: 'imap.fastmail.com',
15
+ port: 993,
16
+ smtp: { host: 'smtp.fastmail.com', port: 587, starttls: true },
17
+ },
18
+ auth: { kind: 'basic', app: FASTMAIL_APP },
19
+ identity: { kind: 'connector' },
20
+ setup: fastmailSetup('Fastmail'),
21
+ redact: {
22
+ // Never the search terms — a query is content, and "who did I email about
23
+ // the diagnosis" is the whole message.
24
+ search_messages: ['mailbox', 'limit', 'unseen', 'flagged'],
25
+ // Identifiers only, and all of them: which mailbox, which message, which
26
+ // attachment. What the file turned out to be — name, size, type, digest —
27
+ // is recorded by the handler through `audit.annotate`, the same way the send
28
+ // path records what it attached.
29
+ get_attachment: ['mailbox', 'uid', 'message_id', 'attachment_id'],
30
+ get_message: ['mailbox', 'uid', 'include_body'],
31
+ mark_messages: ['mailbox', 'add_flags', 'remove_flags'],
32
+ move_messages: ['mailbox', 'destination', 'destination_flag'],
33
+ // Nothing. The recipients and the body are the message, and `attachments`
34
+ // may literally contain a file — what was attached is recorded by the send
35
+ // path itself through `audit.annotate`.
36
+ send_message: [],
37
+ },
38
+ });
@@ -0,0 +1,52 @@
1
+ export const FASTMAIL_APP = 'fastmail';
2
+
3
+ /**
4
+ * Fastmail: one account, three providers, one app password.
5
+ *
6
+ * The same shape as iCloud and for the same reason — mail is IMAP, calendars
7
+ * are CalDAV, and a manifest has one connector — but arrived at from the
8
+ * opposite direction. Apple has no other route; Fastmail has a JMAP API and
9
+ * simply does not need one here, because a single app password can be issued
10
+ * with access to mail, calendars and contacts together, and the three transports
11
+ * this repository already speaks reach all of it.
12
+ *
13
+ * `app: fastmail` resolves all three to one `fastmail/<account>` credential, so
14
+ * the password is typed once. Fastmail lets you narrow an app password to one
15
+ * protocol; if you do, connect the providers you scoped it for and no others —
16
+ * a password scoped to mail authenticates against CalDAV and then finds nothing.
17
+ */
18
+ export const fastmailSetup = (product: string) => ({
19
+ summary:
20
+ `${product} uses an app password rather than your Fastmail password, which Fastmail refuses ` +
21
+ `for IMAP, CalDAV and CardDAV. You are asked once: one app password can cover Mail, ` +
22
+ `Calendars and Contacts together, because Fastmail issues it per app rather than per service.`,
23
+ docs_url: 'https://www.fastmail.help/hc/en-us/articles/360058752854',
24
+ steps: [
25
+ 'Sign in at https://app.fastmail.com and open Settings → Privacy & Security → Integrations.',
26
+ 'Under "App passwords", choose "New app password". Name it "Lanes Link" — the name is the only way to revoke this one later without cutting off your other devices.',
27
+ 'For access, pick the set that matches what you are connecting. "Mail (IMAP/SMTP)" covers the mail provider; "Contacts (CardDAV)" and "Calendars (CalDAV)" cover the other two. Choosing all three is what lets you type the password once.',
28
+ 'Copy the password Fastmail shows. It is shown once, and it is not your account password.',
29
+ 'You are asked for your address next — the full one you sign in with, which may be at fastmail.com or at your own domain.',
30
+ ],
31
+ troubleshooting:
32
+ 'For Fastmail this is almost always the account password used where an app password belongs, or an app ' +
33
+ 'password scoped to a protocol other than the one being connected — one scoped to Mail alone will not ' +
34
+ 'authenticate against CalDAV. Create one at https://app.fastmail.com under Settings → Privacy & Security ' +
35
+ '→ Integrations and re-run: lanes link connect fastmail --replace.',
36
+ prompts: [
37
+ {
38
+ key: 'username',
39
+ label: 'Fastmail address (the full email address)',
40
+ secret: false,
41
+ scope: 'connection' as const,
42
+ field: 'username' as const,
43
+ },
44
+ {
45
+ key: 'password',
46
+ label: 'App password',
47
+ secret: true,
48
+ scope: 'connection' as const,
49
+ field: 'password' as const,
50
+ },
51
+ ],
52
+ });
@@ -0,0 +1,10 @@
1
+ import { defineProvider } from '#connectivity';
2
+
3
+ /** Figma registers us at connect time — nothing for an operator to set up. */
4
+ export const figma = defineProvider({
5
+ id: 'figma',
6
+ name: 'Figma',
7
+ description: 'Files, designs, components, and Dev Mode context, via Figma\'s official MCP server.',
8
+ connector: { kind: 'mcp', endpoint: 'https://mcp.figma.com/mcp' },
9
+ auth: { kind: 'oauth', registration: 'dynamic' },
10
+ });
@@ -0,0 +1,10 @@
1
+ import { defineProvider } from '#connectivity';
2
+
3
+ /** Fireflies registers us at connect time — nothing for an operator to set up. */
4
+ export const fireflies = defineProvider({
5
+ id: 'fireflies',
6
+ name: 'Fireflies',
7
+ description: 'Meeting transcripts, summaries, and action items, via Fireflies\'s official MCP server.',
8
+ connector: { kind: 'mcp', endpoint: 'https://api.fireflies.ai/mcp' },
9
+ auth: { kind: 'oauth', registration: 'dynamic' },
10
+ });
@@ -0,0 +1,10 @@
1
+ import { defineProvider } from '#connectivity';
2
+
3
+ /** Flagsmith registers us at connect time — nothing for an operator to set up. */
4
+ export const flagsmith = defineProvider({
5
+ id: 'flagsmith',
6
+ name: 'Flagsmith',
7
+ description: 'Feature flags, segments, and environments, via Flagsmith\'s official MCP server.',
8
+ connector: { kind: 'mcp', endpoint: 'https://mcp.flagsmith.com/mcp' },
9
+ auth: { kind: 'oauth', registration: 'dynamic' },
10
+ });
@@ -0,0 +1,10 @@
1
+ import { defineProvider } from '#connectivity';
2
+
3
+ /** Gamma registers us at connect time — nothing for an operator to set up. */
4
+ export const gamma = defineProvider({
5
+ id: 'gamma',
6
+ name: 'Gamma',
7
+ description: 'Presentations and documents, generated and read back, via Gamma\'s official MCP server.',
8
+ connector: { kind: 'mcp', endpoint: 'https://mcp.gamma.app/mcp' },
9
+ auth: { kind: 'oauth', registration: 'dynamic' },
10
+ });
@@ -16,7 +16,7 @@ import { GITHUB_REDACT } from './redact.ts';
16
16
  * The toolsets header is the whole reason `connector.headers` exists. GitHub
17
17
  * serves a different tool list per toolset and `all` is far more than an agent
18
18
  * reasons over; this asks for the ones an agent working in a repository
19
- * actually uses. It is one string to change, and `docs/detailed/setup/github.md`
19
+ * actually uses. It is one string to change, and `https://lanes.sh/docs/link/github`
20
20
  * records the read-only variant for someone who wants a narrower connection.
21
21
  */
22
22
  export const github = defineProvider({
@@ -41,7 +41,7 @@ export const github = defineProvider({
41
41
  'GitHub issues a fine-grained personal access token for this. There is no OAuth app to register: ' +
42
42
  'GitHub\'s MCP server does not support the dynamic registration Notion and Linear use, and an OAuth ' +
43
43
  'app of your own would need a fixed callback port, which this CLI does not have. You are asked once.',
44
- docs: 'docs/detailed/setup/github.md',
44
+ docs: 'https://lanes.sh/docs/link/github',
45
45
  docs_url: 'https://github.com/settings/personal-access-tokens',
46
46
  steps: [
47
47
  'Open https://github.com/settings/personal-access-tokens and choose "Generate new token".',
@@ -40,7 +40,22 @@ export function gmailAttachments(input: {
40
40
  return (await response.json()) as Record<string, unknown>;
41
41
  };
42
42
 
43
- return async ({ messageId, attachmentId }) => {
43
+ return async ({ messageId, uid, attachmentId }) => {
44
+ // Gmail's REST API has no mailbox uid — a message is named by the id
45
+ // `messages.list` reports, and a label is not a folder to EXAMINE. Saying so
46
+ // and naming the key that does work is the whole job here: the alternative
47
+ // is a caller who read "uid" in the shared schema getting a 404 from Google
48
+ // that explains nothing.
49
+ if (uid !== undefined) {
50
+ throw new Error(
51
+ 'Gmail names a message by its id rather than a mailbox uid. Use message_id, with the id ' +
52
+ 'gmail.users_messages_list reports. A uid belongs to the IMAP providers — gmail_imap, icloud_mail, fastmail_mail.',
53
+ );
54
+ }
55
+ if (messageId === undefined) {
56
+ throw new Error('Re-attaching from Gmail needs message_id.');
57
+ }
58
+
44
59
  const message = await get(`/messages/${encodeURIComponent(messageId)}?format=full`);
45
60
  const parts = attachmentParts(message['payload']);
46
61
 
@@ -10,7 +10,7 @@ import { defineProvider } from '#connectivity';
10
10
  * - The REST provider (`gmail`) authorises in a browser, and an OAuth client
11
11
  * left in "Testing" has every refresh token it issues expired after seven
12
12
  * days. Publishing the client fixes that and is the better answer where it is
13
- * available — see ADR-038 and `docs/detailed/setup/google.md`.
13
+ * available — see ADR-038 and `https://lanes.sh/docs/link/google`.
14
14
  * - The key route (`auth.assertion` on `gmail`) does not apply at all. A service
15
15
  * account has no mailbox of its own, so it can only reach one by acting as
16
16
  * somebody, and that grant is domain-wide delegation — made in a Workspace
@@ -71,7 +71,7 @@ export const gmailImap = defineProvider({
71
71
  'browser and nothing has to be re-approved later. This is the personal-account route: ' +
72
72
  'Google turned off basic authentication for Workspace accounts in March 2025, and a Workspace ' +
73
73
  'administrator can disable app passwords for the whole domain.',
74
- docs: 'docs/detailed/setup/google.md',
74
+ docs: 'https://lanes.sh/docs/link/google',
75
75
  docs_url: 'https://support.google.com/accounts/answer/185833',
76
76
  steps: [
77
77
  'Two-Step Verification has to be on. Without it Google does not offer app passwords at all, and the page below returns "the setting you are looking for is not available for your account" rather than saying why. Turn it on at https://myaccount.google.com/signinoptions/twosv.',
@@ -111,6 +111,11 @@ export const gmailImap = defineProvider({
111
111
  // Never the search terms — a query is content, and "who did I email about
112
112
  // the diagnosis" is the whole message.
113
113
  search_messages: ['mailbox', 'limit', 'unseen', 'flagged'],
114
+ // Identifiers only, and all of them: which mailbox, which message, which
115
+ // attachment. What the file turned out to be — name, size, type, digest —
116
+ // is recorded by the handler through `audit.annotate`, the same way the send
117
+ // path records what it attached.
118
+ get_attachment: ['mailbox', 'uid', 'message_id', 'attachment_id'],
114
119
  get_message: ['mailbox', 'uid', 'include_body'],
115
120
  mark_messages: ['mailbox', 'add_flags', 'remove_flags'],
116
121
  // `destination_flag` alongside `destination`: two spellings of the same
@@ -25,7 +25,7 @@ export const GOOGLE_APP = 'google';
25
25
  *
26
26
  * What this buys the operator is the whole of `setup/google.md`: no project, no
27
27
  * console, and no scope list to transcribe. What it costs is recorded in
28
- * ADR-028 and in the guarantee table in `docs/detailed/security.md` — chiefly
28
+ * ADR-028 and in the guarantee table in `https://lanes.sh/docs/link/security` — chiefly
29
29
  * that the exchange stops being local.
30
30
  *
31
31
  * What it does *not* buy, and used to claim to: escape from the seven-day
@@ -75,7 +75,7 @@ export const googleServiceAccount = (
75
75
  `route works only where a Google Workspace administrator has authorised the key to act ` +
76
76
  `as a user in their domain. On a personal Google account, use the browser instead` +
77
77
  (instead ? `, or ${instead}, which is an app password over IMAP and does not expire either.` : '.'),
78
- docs: 'docs/detailed/setup/google.md',
78
+ docs: 'https://lanes.sh/docs/link/google',
79
79
  docs_url: 'https://console.cloud.google.com/iam-admin/serviceaccounts',
80
80
  steps: [
81
81
  'Create or pick a project at https://console.cloud.google.com',
@@ -38,7 +38,7 @@ export const googleSetup = (
38
38
 
39
39
  return {
40
40
  summary: `${product} signs in with Google. By default it authorises against the OAuth client Lanes operates, so there is nothing to register and no client secret on this machine — the code is exchanged for a token by the Lanes API, which holds that secret. Pass --own-client to register a client of your own instead; the steps below are that path, asked once per profile and then covering every Google account you connect.`,
41
- docs: 'docs/detailed/setup/google.md',
41
+ docs: 'https://lanes.sh/docs/link/google',
42
42
  docs_url: 'https://console.cloud.google.com/auth',
43
43
  // AUDIENCE leads with Internal, and the ordering is the point rather than a
44
44
  // preference. The seven-day `invalid_grant` everyone hits is a property of
@@ -1085,38 +1085,6 @@
1085
1085
  "type": "string"
1086
1086
  }
1087
1087
  },
1088
- "key": {
1089
- "description": "API key. Your API key identifies your project and provides you with API access, quota, and reports. Required unless you provide an OAuth 2.0 token.",
1090
- "in": "query",
1091
- "name": "key",
1092
- "schema": {
1093
- "type": "string"
1094
- }
1095
- },
1096
- "oauth_token": {
1097
- "description": "OAuth 2.0 token for the current user.",
1098
- "in": "query",
1099
- "name": "oauth_token",
1100
- "schema": {
1101
- "type": "string"
1102
- }
1103
- },
1104
- "prettyPrint": {
1105
- "description": "Returns response with indentations and line breaks.",
1106
- "in": "query",
1107
- "name": "prettyPrint",
1108
- "schema": {
1109
- "type": "boolean"
1110
- }
1111
- },
1112
- "quotaUser": {
1113
- "description": "An opaque string that represents a user for quota purposes. Must not exceed 40 characters.",
1114
- "in": "query",
1115
- "name": "quotaUser",
1116
- "schema": {
1117
- "type": "string"
1118
- }
1119
- },
1120
1088
  "userIp": {
1121
1089
  "description": "Deprecated. Please use quotaUser instead.",
1122
1090
  "in": "query",
@@ -206,26 +206,6 @@
206
206
  },
207
207
  "components": {
208
208
  "parameters": {
209
- "_.xgafv": {
210
- "description": "V1 error format.",
211
- "in": "query",
212
- "name": "$.xgafv",
213
- "schema": {
214
- "enum": [
215
- "1",
216
- "2"
217
- ],
218
- "type": "string"
219
- }
220
- },
221
- "access_token": {
222
- "description": "OAuth access token.",
223
- "in": "query",
224
- "name": "access_token",
225
- "schema": {
226
- "type": "string"
227
- }
228
- },
229
209
  "alt": {
230
210
  "description": "Data format for response.",
231
211
  "in": "query",
@@ -239,14 +219,6 @@
239
219
  "type": "string"
240
220
  }
241
221
  },
242
- "callback": {
243
- "description": "JSONP",
244
- "in": "query",
245
- "name": "callback",
246
- "schema": {
247
- "type": "string"
248
- }
249
- },
250
222
  "fields": {
251
223
  "description": "Selector specifying which fields to include in a partial response.",
252
224
  "in": "query",
@@ -254,54 +226,6 @@
254
226
  "schema": {
255
227
  "type": "string"
256
228
  }
257
- },
258
- "key": {
259
- "description": "API key. Your API key identifies your project and provides you with API access, quota, and reports. Required unless you provide an OAuth 2.0 token.",
260
- "in": "query",
261
- "name": "key",
262
- "schema": {
263
- "type": "string"
264
- }
265
- },
266
- "oauth_token": {
267
- "description": "OAuth 2.0 token for the current user.",
268
- "in": "query",
269
- "name": "oauth_token",
270
- "schema": {
271
- "type": "string"
272
- }
273
- },
274
- "prettyPrint": {
275
- "description": "Returns response with indentations and line breaks.",
276
- "in": "query",
277
- "name": "prettyPrint",
278
- "schema": {
279
- "type": "boolean"
280
- }
281
- },
282
- "quotaUser": {
283
- "description": "Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters.",
284
- "in": "query",
285
- "name": "quotaUser",
286
- "schema": {
287
- "type": "string"
288
- }
289
- },
290
- "uploadType": {
291
- "description": "Legacy upload protocol for media (e.g. \"media\", \"multipart\").",
292
- "in": "query",
293
- "name": "uploadType",
294
- "schema": {
295
- "type": "string"
296
- }
297
- },
298
- "upload_protocol": {
299
- "description": "Upload protocol for media (e.g. \"raw\", \"multipart\").",
300
- "in": "query",
301
- "name": "upload_protocol",
302
- "schema": {
303
- "type": "string"
304
- }
305
229
  }
306
230
  },
307
231
  "schemas": {
@@ -1236,38 +1236,6 @@
1236
1236
  "type": "string"
1237
1237
  }
1238
1238
  },
1239
- "key": {
1240
- "description": "API key. Your API key identifies your project and provides you with API access, quota, and reports. Required unless you provide an OAuth 2.0 token.",
1241
- "in": "query",
1242
- "name": "key",
1243
- "schema": {
1244
- "type": "string"
1245
- }
1246
- },
1247
- "oauth_token": {
1248
- "description": "OAuth 2.0 token for the current user.",
1249
- "in": "query",
1250
- "name": "oauth_token",
1251
- "schema": {
1252
- "type": "string"
1253
- }
1254
- },
1255
- "prettyPrint": {
1256
- "description": "Returns response with indentations and line breaks.",
1257
- "in": "query",
1258
- "name": "prettyPrint",
1259
- "schema": {
1260
- "type": "boolean"
1261
- }
1262
- },
1263
- "quotaUser": {
1264
- "description": "An opaque string that represents a user for quota purposes. Must not exceed 40 characters.",
1265
- "in": "query",
1266
- "name": "quotaUser",
1267
- "schema": {
1268
- "type": "string"
1269
- }
1270
- },
1271
1239
  "userIp": {
1272
1240
  "description": "Deprecated. Please use quotaUser instead.",
1273
1241
  "in": "query",
@@ -2057,26 +2057,6 @@
2057
2057
  },
2058
2058
  "components": {
2059
2059
  "parameters": {
2060
- "_.xgafv": {
2061
- "description": "V1 error format.",
2062
- "in": "query",
2063
- "name": "$.xgafv",
2064
- "schema": {
2065
- "enum": [
2066
- "1",
2067
- "2"
2068
- ],
2069
- "type": "string"
2070
- }
2071
- },
2072
- "access_token": {
2073
- "description": "OAuth access token.",
2074
- "in": "query",
2075
- "name": "access_token",
2076
- "schema": {
2077
- "type": "string"
2078
- }
2079
- },
2080
2060
  "alt": {
2081
2061
  "description": "Data format for response.",
2082
2062
  "in": "query",
@@ -2090,14 +2070,6 @@
2090
2070
  "type": "string"
2091
2071
  }
2092
2072
  },
2093
- "callback": {
2094
- "description": "JSONP",
2095
- "in": "query",
2096
- "name": "callback",
2097
- "schema": {
2098
- "type": "string"
2099
- }
2100
- },
2101
2073
  "fields": {
2102
2074
  "description": "Selector specifying which fields to include in a partial response.",
2103
2075
  "in": "query",
@@ -2105,54 +2077,6 @@
2105
2077
  "schema": {
2106
2078
  "type": "string"
2107
2079
  }
2108
- },
2109
- "key": {
2110
- "description": "API key. Your API key identifies your project and provides you with API access, quota, and reports. Required unless you provide an OAuth 2.0 token.",
2111
- "in": "query",
2112
- "name": "key",
2113
- "schema": {
2114
- "type": "string"
2115
- }
2116
- },
2117
- "oauth_token": {
2118
- "description": "OAuth 2.0 token for the current user.",
2119
- "in": "query",
2120
- "name": "oauth_token",
2121
- "schema": {
2122
- "type": "string"
2123
- }
2124
- },
2125
- "prettyPrint": {
2126
- "description": "Returns response with indentations and line breaks.",
2127
- "in": "query",
2128
- "name": "prettyPrint",
2129
- "schema": {
2130
- "type": "boolean"
2131
- }
2132
- },
2133
- "quotaUser": {
2134
- "description": "Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters.",
2135
- "in": "query",
2136
- "name": "quotaUser",
2137
- "schema": {
2138
- "type": "string"
2139
- }
2140
- },
2141
- "uploadType": {
2142
- "description": "Legacy upload protocol for media (e.g. \"media\", \"multipart\").",
2143
- "in": "query",
2144
- "name": "uploadType",
2145
- "schema": {
2146
- "type": "string"
2147
- }
2148
- },
2149
- "upload_protocol": {
2150
- "description": "Upload protocol for media (e.g. \"raw\", \"multipart\").",
2151
- "in": "query",
2152
- "name": "upload_protocol",
2153
- "schema": {
2154
- "type": "string"
2155
- }
2156
2080
  }
2157
2081
  },
2158
2082
  "schemas": {