@lanes-sh/link 0.6.11 → 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 (201) hide show
  1. package/README.md +62 -45
  2. package/instructions/skills/lanes-link/SKILL.md +25 -0
  3. package/package.json +3 -2
  4. package/src/audit/conformance.ts +1 -1
  5. package/src/cli/argv.ts +14 -1
  6. package/src/cli/commands/connect/acquire.ts +100 -0
  7. package/src/cli/commands/connect/custom/index.ts +1 -1
  8. package/src/cli/commands/connect/declare.ts +19 -0
  9. package/src/cli/commands/connect/index.ts +54 -56
  10. package/src/cli/commands/connect/requirements.ts +23 -1
  11. package/src/cli/commands/connect/setup.ts +1 -1
  12. package/src/cli/commands/connect/variables.ts +220 -0
  13. package/src/cli/commands/knowledge/index.ts +22 -4
  14. package/src/cli/commands/knowledge/migrate.ts +61 -43
  15. package/src/cli/commands/operate/tools.ts +1 -1
  16. package/src/cli/commands/owner/entities.ts +244 -0
  17. package/src/cli/commands/owner/shared.ts +14 -0
  18. package/src/cli/commands/owner/vault.ts +2 -2
  19. package/src/cli/commands/owner.ts +13 -3
  20. package/src/cli/commands/setup.ts +12 -0
  21. package/src/cli/config-edit.ts +16 -6
  22. package/src/cli/config-repair.ts +10 -0
  23. package/src/cli/dispatch-owner.ts +30 -1
  24. package/src/cli/main.ts +2 -1
  25. package/src/cli/runtime/open.ts +14 -7
  26. package/src/cli/runtime/registry.ts +6 -1
  27. package/src/cli/selection.ts +11 -0
  28. package/src/cli/usage.ts +13 -2
  29. package/src/connectivity/auth/strategy/index.ts +1 -1
  30. package/src/connectivity/capability.ts +1 -1
  31. package/src/connectivity/context.ts +1 -1
  32. package/src/connectivity/index.ts +1 -1
  33. package/src/connectivity/mail/attachments.ts +42 -73
  34. package/src/connectivity/mail/content-type.ts +71 -0
  35. package/src/connectivity/mail/index.ts +1 -1
  36. package/src/connectivity/manifest/provider.ts +50 -0
  37. package/src/connectivity/manifest/variables.ts +173 -0
  38. package/src/connectivity/transports/factory.ts +74 -1
  39. package/src/connectivity/transports/http/index.ts +8 -2
  40. package/src/connectivity/transports/http/keys.ts +83 -0
  41. package/src/connectivity/transports/imap/attachment.ts +53 -10
  42. package/src/connectivity/transports/imap/capabilities.ts +28 -0
  43. package/src/connectivity/transports/imap/download.ts +111 -0
  44. package/src/connectivity/transports/imap/index.ts +13 -0
  45. package/src/connectivity/transports/imap/operations.ts +1 -0
  46. package/src/deployments/adapters/s3.ts +1 -1
  47. package/src/deployments/gcp/provision.ts +1 -1
  48. package/src/deployments/knowledge.ts +44 -18
  49. package/src/deployments/prepare.ts +1 -1
  50. package/src/deployments/serving.ts +1 -1
  51. package/src/dispatch/dispatch.ts +17 -10
  52. package/src/dispatch/staging.ts +76 -1
  53. package/src/profile/knowledge.ts +19 -11
  54. package/src/profile/schema.ts +2 -2
  55. package/src/providers/README.md +189 -0
  56. package/src/providers/airtable/index.ts +10 -0
  57. package/src/providers/algolia/index.ts +10 -0
  58. package/src/providers/amplitude/index.ts +10 -0
  59. package/src/providers/apify/index.ts +10 -0
  60. package/src/providers/asana/index.ts +10 -0
  61. package/src/providers/assets/provider.ts +11 -4
  62. package/src/providers/atlassian/index.ts +19 -0
  63. package/src/providers/attio/index.ts +10 -0
  64. package/src/providers/betterstack/index.ts +10 -0
  65. package/src/providers/box/index.ts +64 -0
  66. package/src/providers/brightdata/index.ts +10 -0
  67. package/src/providers/buildkite/index.ts +10 -0
  68. package/src/providers/bunq/index.ts +2 -2
  69. package/src/providers/calendly/index.ts +10 -0
  70. package/src/providers/canva/index.ts +10 -0
  71. package/src/providers/circleci/index.ts +10 -0
  72. package/src/providers/clickup/index.ts +10 -0
  73. package/src/providers/close/index.ts +10 -0
  74. package/src/providers/cloudflare_bindings/index.ts +14 -0
  75. package/src/providers/cloudflare_observability/index.ts +10 -0
  76. package/src/providers/contentful/index.ts +10 -0
  77. package/src/providers/custom/load.ts +1 -1
  78. package/src/providers/datadog/index.ts +10 -0
  79. package/src/providers/discord/index.ts +1 -1
  80. package/src/providers/discord/specs/discord.v10.json +0 -88
  81. package/src/providers/discord/specs/vendor.ts +1 -1
  82. package/src/providers/dropbox/index.ts +10 -0
  83. package/src/providers/entities/catalogue.ts +366 -0
  84. package/src/providers/entities/find.ts +279 -0
  85. package/src/providers/entities/provider.ts +334 -0
  86. package/src/providers/entities/render.ts +142 -0
  87. package/src/providers/entities/store.ts +276 -0
  88. package/src/providers/entities/writes.ts +279 -0
  89. package/src/providers/example/provider.ts +1 -1
  90. package/src/providers/expensify/index.ts +10 -0
  91. package/src/providers/fastmail/calendar/index.ts +21 -0
  92. package/src/providers/fastmail/contacts/index.ts +17 -0
  93. package/src/providers/fastmail/index.ts +4 -0
  94. package/src/providers/fastmail/mail/index.ts +38 -0
  95. package/src/providers/fastmail/shared/setup.ts +52 -0
  96. package/src/providers/figma/index.ts +10 -0
  97. package/src/providers/fireflies/index.ts +10 -0
  98. package/src/providers/flagsmith/index.ts +10 -0
  99. package/src/providers/gamma/index.ts +10 -0
  100. package/src/providers/github/index.ts +2 -2
  101. package/src/providers/google/gmail/attachment.ts +16 -1
  102. package/src/providers/google/gmail-imap/index.ts +7 -2
  103. package/src/providers/google/shared/oauth.ts +1 -1
  104. package/src/providers/google/shared/service-account.ts +1 -1
  105. package/src/providers/google/shared/setup.ts +1 -1
  106. package/src/providers/google/specs/calendar.v3.json +0 -32
  107. package/src/providers/google/specs/docs.v1.json +0 -76
  108. package/src/providers/google/specs/drive.v3.json +0 -32
  109. package/src/providers/google/specs/gmail.v1.json +0 -76
  110. package/src/providers/google/specs/people.v1.json +0 -77
  111. package/src/providers/google/specs/sheets.v4.json +0 -76
  112. package/src/providers/google/specs/tasks.v1.json +0 -76
  113. package/src/providers/grafana/index.ts +10 -0
  114. package/src/providers/heroku/index.ts +10 -0
  115. package/src/providers/hubspot/index.ts +75 -0
  116. package/src/providers/hygraph/index.ts +10 -0
  117. package/src/providers/icloud/mail/index.ts +5 -0
  118. package/src/providers/icloud/shared/setup.ts +1 -1
  119. package/src/providers/index.ts +255 -0
  120. package/src/providers/insightly/index.ts +10 -0
  121. package/src/providers/jam/index.ts +10 -0
  122. package/src/providers/klaviyo/index.ts +10 -0
  123. package/src/providers/mailbox/index.ts +87 -0
  124. package/src/providers/memory/provider.ts +3 -8
  125. package/src/providers/mercury/index.ts +10 -0
  126. package/src/providers/microsoft/calendar/index.ts +49 -0
  127. package/src/providers/microsoft/contacts/index.ts +50 -0
  128. package/src/providers/microsoft/drive/index.ts +62 -0
  129. package/src/providers/microsoft/index.ts +12 -0
  130. package/src/providers/microsoft/mail/index.ts +76 -0
  131. package/src/providers/microsoft/shared/oauth.ts +58 -0
  132. package/src/providers/microsoft/shared/scopes.ts +56 -0
  133. package/src/providers/microsoft/shared/setup.ts +45 -0
  134. package/src/providers/microsoft/specs/microsoft-todo.v1.json +755 -0
  135. package/src/providers/microsoft/specs/onedrive.v1.json +993 -0
  136. package/src/providers/microsoft/specs/outlook-calendar.v1.json +1743 -0
  137. package/src/providers/microsoft/specs/outlook-contacts.v1.json +430 -0
  138. package/src/providers/microsoft/specs/outlook-mail.v1.json +901 -0
  139. package/src/providers/microsoft/specs/vendor.ts +232 -0
  140. package/src/providers/microsoft/todo/index.ts +54 -0
  141. package/src/providers/miro/index.ts +10 -0
  142. package/src/providers/mixpanel/index.ts +10 -0
  143. package/src/providers/monday/index.ts +10 -0
  144. package/src/providers/mux/index.ts +10 -0
  145. package/src/providers/navan/index.ts +10 -0
  146. package/src/providers/neon/index.ts +10 -0
  147. package/src/providers/netlify/index.ts +10 -0
  148. package/src/providers/nextcloud/calendar/index.ts +28 -0
  149. package/src/providers/nextcloud/contacts/index.ts +18 -0
  150. package/src/providers/nextcloud/index.ts +3 -0
  151. package/src/providers/nextcloud/shared/setup.ts +59 -0
  152. package/src/providers/owner.ts +15 -3
  153. package/src/providers/paddle/index.ts +10 -0
  154. package/src/providers/paypal/index.ts +10 -0
  155. package/src/providers/posthog/index.ts +10 -0
  156. package/src/providers/prisma/index.ts +10 -0
  157. package/src/providers/ramp/index.ts +10 -0
  158. package/src/providers/recurly/index.ts +10 -0
  159. package/src/providers/reddit/index.ts +1 -1
  160. package/src/providers/remote/index.ts +10 -0
  161. package/src/providers/render/index.ts +35 -0
  162. package/src/providers/replicate/index.ts +10 -0
  163. package/src/providers/resend/index.ts +10 -0
  164. package/src/providers/riverside/index.ts +10 -0
  165. package/src/providers/rootly/index.ts +10 -0
  166. package/src/providers/rudderstack/index.ts +10 -0
  167. package/src/providers/salesloft/index.ts +10 -0
  168. package/src/providers/sanity/index.ts +10 -0
  169. package/src/providers/scopes.ts +2 -0
  170. package/src/providers/sentry/index.ts +10 -0
  171. package/src/providers/setup/plan.ts +11 -0
  172. package/src/providers/setup/provider.ts +12 -0
  173. package/src/providers/shared/openapi.ts +70 -0
  174. package/src/providers/shared/slug.ts +28 -0
  175. package/src/providers/shared/vendor-operations.ts +43 -1
  176. package/src/providers/shared/vendor-spec.ts +98 -17
  177. package/src/providers/shortcut/index.ts +10 -0
  178. package/src/providers/slack/index.ts +2 -2
  179. package/src/providers/square/index.ts +10 -0
  180. package/src/providers/storyblok/index.ts +10 -0
  181. package/src/providers/stripe/index.ts +10 -0
  182. package/src/providers/supabase/index.ts +10 -0
  183. package/src/providers/tasks/store.ts +2 -7
  184. package/src/providers/tavily/index.ts +10 -0
  185. package/src/providers/todoist/index.ts +10 -0
  186. package/src/providers/untested.ts +105 -0
  187. package/src/providers/vault/provider.ts +1 -1
  188. package/src/providers/vercel/index.ts +10 -0
  189. package/src/providers/vimeo/index.ts +10 -0
  190. package/src/providers/webflow/index.ts +10 -0
  191. package/src/providers/whimsical/index.ts +10 -0
  192. package/src/providers/wix/index.ts +10 -0
  193. package/src/providers/workable/index.ts +10 -0
  194. package/src/providers/yahoo_mail/index.ts +73 -0
  195. package/src/providers/zapier/index.ts +10 -0
  196. package/src/providers/zoho_mail/index.ts +79 -0
  197. package/src/secrets/document.ts +1 -1
  198. package/src/server/attachments.ts +129 -5
  199. package/src/server/index.ts +3 -3
  200. package/src/server/mcp/instructions.ts +88 -18
  201. package/src/server/stdio.ts +1 -1
@@ -0,0 +1,232 @@
1
+ /**
2
+ * Vendor trimmed OpenAPI specs for Microsoft Graph.
3
+ *
4
+ * One API behind five providers. Graph is a single enormous surface — 7,419
5
+ * paths and 2,599 schemas in the published document — and mail, calendars,
6
+ * contacts, files, and to-do are all reached through it under one OAuth client.
7
+ * That is the opposite of Google, where each product has its own spec, and it is
8
+ * why the selection below is keyed by *provider* rather than by API: what
9
+ * separates `outlook_mail` from `outlook_calendar` is the operations each is
10
+ * allowed to name, nothing else.
11
+ *
12
+ * bun run vendor:microsoft
13
+ *
14
+ * The pipeline is `src/providers/shared/vendor-spec.ts`. What stays here is what
15
+ * only Graph knows: which operations are worth exposing, and which of its OData
16
+ * parameters have to go.
17
+ */
18
+
19
+ import { vendorSpec } from '../../shared/vendor-spec.ts';
20
+
21
+ /** APIs.guru's generated OpenAPI for Graph v1.0 — the same source Google's uses. */
22
+ const SOURCE = 'https://api.apis.guru/v2/specs/microsoft.com/graph/1.0.1/openapi.json';
23
+
24
+ /**
25
+ * Bodies narrowed to what the call actually writes.
26
+ *
27
+ * `PATCH /me/messages/{id}` takes a whole `microsoft.graph.message` — every
28
+ * field of a mail item, including the ones the service computes — which inlines
29
+ * to 346 KB against a 64 KB budget. What a caller changes on an existing message
30
+ * is whether it has been read, how it is filed, and how urgent it is.
31
+ *
32
+ * Projected rather than hand-written, so a field Microsoft renames or makes
33
+ * read-only fails `bun run vendor:microsoft` instead of becoming an argument
34
+ * Graph silently ignores.
35
+ */
36
+ const PROJECTED: Record<string, readonly string[]> = {
37
+ 'me.UpdateMessages': ['isRead', 'categories', 'flag', 'importance'],
38
+ 'me.CreateEvents': [
39
+ 'subject',
40
+ 'body',
41
+ 'start',
42
+ 'end',
43
+ 'location',
44
+ 'attendees',
45
+ 'isAllDay',
46
+ 'isOnlineMeeting',
47
+ 'reminderMinutesBeforeStart',
48
+ 'showAs',
49
+ 'recurrence',
50
+ ],
51
+ 'me.UpdateEvents': [
52
+ 'subject',
53
+ 'body',
54
+ 'start',
55
+ 'end',
56
+ 'location',
57
+ 'attendees',
58
+ 'isAllDay',
59
+ 'isOnlineMeeting',
60
+ 'reminderMinutesBeforeStart',
61
+ 'showAs',
62
+ ],
63
+ 'me.todo.CreateLists': ['displayName'],
64
+ 'me.todo.lists.CreateTasks': [
65
+ 'title',
66
+ 'body',
67
+ 'dueDateTime',
68
+ 'reminderDateTime',
69
+ 'importance',
70
+ 'status',
71
+ 'categories',
72
+ ],
73
+ 'me.todo.lists.UpdateTasks': [
74
+ 'title',
75
+ 'body',
76
+ 'dueDateTime',
77
+ 'reminderDateTime',
78
+ 'importance',
79
+ 'status',
80
+ 'categories',
81
+ ],
82
+ 'drives.items.CreateChildren': ['name', 'folder', 'file'],
83
+ };
84
+
85
+ /**
86
+ * `sendMail`'s body, written out rather than projected.
87
+ *
88
+ * The only one that cannot be projected: Graph declares it as a shared
89
+ * `requestBodies` component rather than a schema reference, so there is no
90
+ * entity to narrow — and the wide form is the same 346 KB `message` again.
91
+ *
92
+ * Deliberately without `attachments`. Graph takes those as base64 inside the
93
+ * message, which is precisely what ADR-017 exists to avoid; attaching a file
94
+ * here would mean the model emitting the file. Sending an attachment from
95
+ * Outlook needs the same treatment `gmail.send_message` has, and until it has
96
+ * one, offering the field would be advertising a route that costs a context
97
+ * window.
98
+ */
99
+ const SEND_MAIL_BODY = {
100
+ type: 'object',
101
+ required: ['message'],
102
+ properties: {
103
+ message: {
104
+ type: 'object',
105
+ required: ['subject', 'body', 'toRecipients'],
106
+ properties: {
107
+ subject: { type: 'string' },
108
+ body: {
109
+ type: 'object',
110
+ required: ['contentType', 'content'],
111
+ properties: {
112
+ contentType: { type: 'string', enum: ['text', 'html'] },
113
+ content: { type: 'string' },
114
+ },
115
+ },
116
+ toRecipients: { type: 'array', items: { $ref: '#/components/schemas/microsoft.graph.recipient' } },
117
+ ccRecipients: { type: 'array', items: { $ref: '#/components/schemas/microsoft.graph.recipient' } },
118
+ bccRecipients: { type: 'array', items: { $ref: '#/components/schemas/microsoft.graph.recipient' } },
119
+ },
120
+ },
121
+ saveToSentItems: {
122
+ type: 'boolean',
123
+ description: 'Keep a copy in Sent Items. Defaults to true.',
124
+ },
125
+ },
126
+ } as const;
127
+
128
+ const SELECTION: Record<string, { out: string; operations: string[]; opaque?: string[] }> = {
129
+ outlook_mail: {
130
+ out: 'outlook-mail.v1.json',
131
+ operations: [
132
+ 'me.ListMessages',
133
+ 'me.GetMessages',
134
+ 'me.UpdateMessages',
135
+ 'me.ListMailFolders',
136
+ 'me.sendMail',
137
+ 'me.messages.ListAttachments',
138
+ 'me.messages.GetAttachments',
139
+ ],
140
+ },
141
+ outlook_calendar: {
142
+ out: 'outlook-calendar.v1.json',
143
+ operations: [
144
+ 'me.ListCalendars',
145
+ 'me.ListEvents',
146
+ 'me.GetEvents',
147
+ 'me.CreateEvents',
148
+ 'me.UpdateEvents',
149
+ 'me.DeleteEvents',
150
+ 'me.ListCalendarView',
151
+ ],
152
+ },
153
+ outlook_contacts: {
154
+ out: 'outlook-contacts.v1.json',
155
+ operations: [
156
+ // Read-only, matching Google's contacts provider rather than what Graph
157
+ // would allow. An address book is what makes "email Bob" resolve; nothing
158
+ // here needs to write one, and `Contacts.Read` is a materially smaller
159
+ // grant than `Contacts.ReadWrite`.
160
+ 'me.ListContacts',
161
+ 'me.GetContacts',
162
+ 'me.ListContactFolders',
163
+ ],
164
+ },
165
+ onedrive: {
166
+ out: 'onedrive.v1.json',
167
+ operations: [
168
+ 'me.GetDrive',
169
+ 'drives.GetRoot',
170
+ 'drives.GetItems',
171
+ 'drives.items.ListChildren',
172
+ 'drives.GetItemsContent',
173
+ 'drives.items.CreateChildren',
174
+ 'drives.drive.items.driveItem.search',
175
+ ],
176
+ },
177
+ microsoft_todo: {
178
+ out: 'microsoft-todo.v1.json',
179
+ operations: [
180
+ 'me.todo.ListLists',
181
+ 'me.todo.CreateLists',
182
+ 'me.todo.lists.ListTasks',
183
+ 'me.todo.lists.GetTasks',
184
+ 'me.todo.lists.CreateTasks',
185
+ 'me.todo.lists.UpdateTasks',
186
+ 'me.todo.lists.DeleteTasks',
187
+ ],
188
+ },
189
+ };
190
+
191
+ /**
192
+ * OData parameters that are noise rather than capability.
193
+ *
194
+ * Not the same judgement as Google's list, and worth saying why. `$select`,
195
+ * `$filter`, `$top`, `$orderby` and `$search` are how a caller asks Graph for
196
+ * less, and dropping them would leave an agent pulling whole mailboxes — so they
197
+ * stay. What goes is the paging and shaping machinery an agent has no use for
198
+ * here: `$expand` pulls related entities and multiplies the response, `$count`
199
+ * and `$skip` belong to a pagination scheme `@odata.nextLink` already answers,
200
+ * and `ConsistencyLevel` is a header for advanced queries none of these make.
201
+ */
202
+ const SYSTEM_PARAMETERS = new Set(['$expand', '$count', '$skip', 'ConsistencyLevel']);
203
+
204
+ const VENDORED_NOTE =
205
+ 'Trimmed by src/providers/microsoft/specs/vendor.ts. Committed deliberately: a spec decides which paths are called with the operator token, so it must be reviewable rather than fetched at connect time.';
206
+
207
+ const OPAQUE_NOTE =
208
+ 'Structure omitted: it expands to megabytes when inlined. ' +
209
+ 'Pass the object as documented at https://learn.microsoft.com/graph/api/overview.';
210
+
211
+ for (const [id, selection] of Object.entries(SELECTION)) {
212
+ await vendorSpec(id, {
213
+ source: SOURCE,
214
+ outputDirectory: import.meta.dir,
215
+ out: selection.out,
216
+ operations: selection.operations,
217
+ ...(selection.opaque ? { opaque: selection.opaque } : {}),
218
+ opaqueNote: OPAQUE_NOTE,
219
+ vendoredNote: VENDORED_NOTE,
220
+ systemParameters: SYSTEM_PARAMETERS,
221
+ // Graph declares a path's parameters on the path item rather than on each
222
+ // operation, so `{message-id}` is in the URL and in nothing the generator
223
+ // reads. Without this it refuses the whole document with MISSING_PATH_PARAMETER
224
+ // — the same shape Discord has, and the reason the flag is not Discord's.
225
+ hoistPathParameters: true,
226
+ projectRequestBody: PROJECTED,
227
+ projectionNote:
228
+ 'The fields this call writes. A message carries far more, and the rest is computed by Graph ' +
229
+ 'rather than set here.',
230
+ rewriteRequestBody: { 'me.sendMail': SEND_MAIL_BODY },
231
+ });
232
+ }
@@ -0,0 +1,54 @@
1
+ import { defineProvider } from '#connectivity';
2
+ import {
3
+ CALENDAR_SCOPES,
4
+ CONTACTS_SCOPES,
5
+ FILES_SCOPES,
6
+ GRAPH_BASE_URL,
7
+ MAIL_SCOPES,
8
+ MICROSOFT_APP,
9
+ MICROSOFT_AUTHORIZE_URL,
10
+ MICROSOFT_IDENTITY,
11
+ MICROSOFT_TOKEN_URL,
12
+ TODO_SCOPES,
13
+ specPath,
14
+ } from '../shared/oauth.ts';
15
+ import { microsoftSetup } from '../shared/setup.ts';
16
+
17
+ /**
18
+ * Microsoft To Do.
19
+ *
20
+ * Named `microsoft_todo` rather than `todo` for the same reason Google's list is
21
+ * `google_tasks`: `tasks` is the owner layer's own store and is a reserved id,
22
+ * so a vendor's task list has to say whose it is. See ADR-051.
23
+ */
24
+ export const microsoftTodo = defineProvider({
25
+ id: 'microsoft_todo',
26
+ name: 'Microsoft To Do',
27
+ description: 'Create, edit, complete, and organise tasks and lists in Microsoft To Do.',
28
+ connector: { kind: 'http', base_url: GRAPH_BASE_URL, openapi: specPath('microsoft-todo.v1.json') },
29
+ auth: {
30
+ kind: 'oauth',
31
+ registration: 'manual',
32
+ app: MICROSOFT_APP,
33
+ // Declared rather than discovered. An `http` connector has no metadata
34
+ // document to read — a REST API does not announce where its authorization
35
+ // server lives — so the two endpoints are part of the manifest.
36
+ authorize_url: MICROSOFT_AUTHORIZE_URL,
37
+ token_url: MICROSOFT_TOKEN_URL,
38
+ scopes: TODO_SCOPES,
39
+ revoke_url: 'https://account.live.com/consent/Manage',
40
+ },
41
+ identity: MICROSOFT_IDENTITY,
42
+ setup: microsoftSetup('Microsoft To Do', TODO_SCOPES),
43
+ redact: {
44
+ 'me.todo.ListLists': ['top', 'orderby', 'select'],
45
+ // The list's name is what the owner called it, which is theirs.
46
+ 'me.todo.CreateLists': [],
47
+ 'me.todo.lists.ListTasks': ['todoTaskList-id', 'top', 'orderby', 'select'],
48
+ 'me.todo.lists.GetTasks': ['todoTaskList-id', 'todoTask-id', 'select'],
49
+ // Which list, and afterwards which task — never the title or the note.
50
+ 'me.todo.lists.CreateTasks': ['todoTaskList-id', 'importance', 'status'],
51
+ 'me.todo.lists.UpdateTasks': ['todoTaskList-id', 'todoTask-id', 'importance', 'status'],
52
+ 'me.todo.lists.DeleteTasks': ['todoTaskList-id', 'todoTask-id'],
53
+ },
54
+ });
@@ -0,0 +1,10 @@
1
+ import { defineProvider } from '#connectivity';
2
+
3
+ /** Miro registers us at connect time — nothing for an operator to set up. */
4
+ export const miro = defineProvider({
5
+ id: 'miro',
6
+ name: 'Miro',
7
+ description: 'Boards, frames, sticky notes, and shapes, via Miro\'s official MCP server.',
8
+ connector: { kind: 'mcp', endpoint: 'https://mcp.miro.com/mcp' },
9
+ auth: { kind: 'oauth', registration: 'dynamic' },
10
+ });
@@ -0,0 +1,10 @@
1
+ import { defineProvider } from '#connectivity';
2
+
3
+ /** Mixpanel registers us at connect time — nothing for an operator to set up. */
4
+ export const mixpanel = defineProvider({
5
+ id: 'mixpanel',
6
+ name: 'Mixpanel',
7
+ description: 'Events, funnels, retention, and cohorts, via Mixpanel\'s official MCP server.',
8
+ connector: { kind: 'mcp', endpoint: 'https://mcp.mixpanel.com/mcp' },
9
+ auth: { kind: 'oauth', registration: 'dynamic' },
10
+ });
@@ -0,0 +1,10 @@
1
+ import { defineProvider } from '#connectivity';
2
+
3
+ /** monday.com registers us at connect time — nothing for an operator to set up. */
4
+ export const monday = defineProvider({
5
+ id: 'monday',
6
+ name: 'monday.com',
7
+ description: 'Boards, items, groups, columns, and updates, via monday.com\'s official MCP server.',
8
+ connector: { kind: 'mcp', endpoint: 'https://mcp.monday.com/mcp' },
9
+ auth: { kind: 'oauth', registration: 'dynamic' },
10
+ });
@@ -0,0 +1,10 @@
1
+ import { defineProvider } from '#connectivity';
2
+
3
+ /** Mux registers us at connect time — nothing for an operator to set up. */
4
+ export const mux = defineProvider({
5
+ id: 'mux',
6
+ name: 'Mux',
7
+ description: 'Video assets, live streams, and playback analytics, via Mux\'s official MCP server.',
8
+ connector: { kind: 'mcp', endpoint: 'https://mcp.mux.com' },
9
+ auth: { kind: 'oauth', registration: 'dynamic' },
10
+ });
@@ -0,0 +1,10 @@
1
+ import { defineProvider } from '#connectivity';
2
+
3
+ /** Navan registers us at connect time — nothing for an operator to set up. */
4
+ export const navan = defineProvider({
5
+ id: 'navan',
6
+ name: 'Navan',
7
+ description: 'Trips, bookings, and travel expenses, via Navan\'s official MCP server.',
8
+ connector: { kind: 'mcp', endpoint: 'https://mcp.navan.com/mcp' },
9
+ auth: { kind: 'oauth', registration: 'dynamic' },
10
+ });
@@ -0,0 +1,10 @@
1
+ import { defineProvider } from '#connectivity';
2
+
3
+ /** Neon registers us at connect time — nothing for an operator to set up. */
4
+ export const neon = defineProvider({
5
+ id: 'neon',
6
+ name: 'Neon',
7
+ description: 'Postgres projects, branches, SQL, and docs, via Neon\'s official MCP server.',
8
+ connector: { kind: 'mcp', endpoint: 'https://mcp.neon.tech/mcp' },
9
+ auth: { kind: 'oauth', registration: 'dynamic' },
10
+ });
@@ -0,0 +1,10 @@
1
+ import { defineProvider } from '#connectivity';
2
+
3
+ /** Netlify registers us at connect time — nothing for an operator to set up. */
4
+ export const netlify = defineProvider({
5
+ id: 'netlify',
6
+ name: 'Netlify',
7
+ description: 'Sites, deploys, functions, and environment variables, via Netlify\'s official MCP server.',
8
+ connector: { kind: 'mcp', endpoint: 'https://netlify-mcp.netlify.app/mcp' },
9
+ auth: { kind: 'oauth', registration: 'dynamic' },
10
+ });
@@ -0,0 +1,28 @@
1
+ import { defineProvider } from '#connectivity';
2
+ import { NEXTCLOUD_APP, NEXTCLOUD_HOST, nextcloudSetup } from '../shared/setup.ts';
3
+
4
+ /**
5
+ * Nextcloud calendars, on whichever server the operator runs.
6
+ *
7
+ * The first built-in whose address is not a property of the provider. Until a
8
+ * connection could carry one, a self-hosted service could only be reached by
9
+ * hand-writing a YAML manifest per instance — which worked, and which nobody
10
+ * discovers. See ADR-055.
11
+ */
12
+ export const nextcloudCalendar = defineProvider({
13
+ id: 'nextcloud_calendar',
14
+ name: 'Nextcloud Calendar',
15
+ description: 'Read and create events in Nextcloud calendars over CalDAV, on your own server.',
16
+ connector: { kind: 'dav', base_url: 'https://{host}', service: 'caldav' },
17
+ auth: { kind: 'basic', app: NEXTCLOUD_APP },
18
+ identity: { kind: 'connector' },
19
+ variables: [NEXTCLOUD_HOST],
20
+ setup: nextcloudSetup('Nextcloud Calendar'),
21
+ redact: {
22
+ list_events: ['calendar', 'start', 'end', 'limit'],
23
+ get_event: ['calendar', 'uid'],
24
+ update_event: ['calendar', 'uid'],
25
+ delete_event: ['calendar', 'uid'],
26
+ create_event: ['calendar'],
27
+ },
28
+ });
@@ -0,0 +1,18 @@
1
+ import { defineProvider } from '#connectivity';
2
+ import { NEXTCLOUD_APP, NEXTCLOUD_HOST, nextcloudSetup } from '../shared/setup.ts';
3
+
4
+ export const nextcloudContacts = defineProvider({
5
+ id: 'nextcloud_contacts',
6
+ name: 'Nextcloud Contacts',
7
+ description: 'Search and read contacts in a Nextcloud address book over CardDAV, on your own server.',
8
+ connector: { kind: 'dav', base_url: 'https://{host}', service: 'carddav' },
9
+ auth: { kind: 'basic', app: NEXTCLOUD_APP },
10
+ identity: { kind: 'connector' },
11
+ variables: [NEXTCLOUD_HOST],
12
+ setup: nextcloudSetup('Nextcloud Contacts'),
13
+ redact: {
14
+ // Not `query`: a name is content.
15
+ search_contacts: ['addressbook', 'limit'],
16
+ create_contact: ['addressbook'],
17
+ },
18
+ });
@@ -0,0 +1,3 @@
1
+ /** Nextcloud's two, sharing one app password and one server address. */
2
+ export { nextcloudCalendar } from './calendar/index.ts';
3
+ export { nextcloudContacts } from './contacts/index.ts';
@@ -0,0 +1,59 @@
1
+ export const NEXTCLOUD_APP = 'nextcloud';
2
+
3
+ /**
4
+ * A hostname pattern, because the default forbids the dots a hostname is made of.
5
+ *
6
+ * The default exists to stop a value escaping the domain its manifest named —
7
+ * `{site}.zendesk.com` must stay at zendesk.com. Here there is no domain to
8
+ * escape: the manifest is `https://{host}` and the whole address is the
9
+ * operator's to choose, because the whole point is that it is their server.
10
+ *
11
+ * What it still refuses is everything that is not a hostname — no slash, no `@`,
12
+ * no colon, no port. Those are the characters that would turn a host into a URL
13
+ * with a different meaning, and none of them belongs in this value.
14
+ */
15
+ export const HOSTNAME = '^[a-z0-9][a-z0-9.-]*[a-z0-9]$';
16
+
17
+ export const nextcloudSetup = (product: string) => ({
18
+ summary:
19
+ `${product} is on a server you run, so it asks for the address as well as the login. ` +
20
+ `Nextcloud issues app passwords for exactly this — a per-app password that does not expire and ` +
21
+ `can be revoked on its own, without changing the one you sign in with.`,
22
+ docs_url: 'https://docs.nextcloud.com/server/latest/user_manual/en/session_management.html',
23
+ steps: [
24
+ 'Sign in to your Nextcloud and open Settings → Personal → Security.',
25
+ 'Under "Devices & sessions", enter "Lanes Link" as the app name and choose "Create new app password".',
26
+ 'Copy the password it shows. It is shown once, and it is not your account password.',
27
+ 'You are asked for the server address next — the hostname you sign in at, without https:// and without a path.',
28
+ 'If your Nextcloud lives under a path — cloud.example.com/nextcloud — this provider cannot reach it: the address it builds ends at the host. Declare a manifest of your own in providers.d/ with the full base_url.',
29
+ ],
30
+ troubleshooting:
31
+ 'A refused login is usually the account password used where an app password belongs, or a server address ' +
32
+ 'typed with https:// or a trailing path — it wants the bare hostname. Create an app password under ' +
33
+ 'Settings → Security and re-run: lanes link connect nextcloud --replace.',
34
+ prompts: [
35
+ {
36
+ key: 'username',
37
+ label: 'Nextcloud username',
38
+ secret: false,
39
+ scope: 'connection' as const,
40
+ field: 'username' as const,
41
+ },
42
+ {
43
+ key: 'password',
44
+ label: 'App password',
45
+ secret: true,
46
+ scope: 'connection' as const,
47
+ field: 'password' as const,
48
+ },
49
+ ],
50
+ });
51
+
52
+ /** The one thing that differs per person, and the reason this provider can exist at all. */
53
+ export const NEXTCLOUD_HOST = {
54
+ key: 'host',
55
+ label: 'Nextcloud server address',
56
+ description: 'The hostname you sign in at, with no https:// and no path.',
57
+ example: 'cloud.example.com',
58
+ pattern: HOSTNAME,
59
+ };
@@ -1,7 +1,8 @@
1
1
  /**
2
- * The owner layer — memory, tasks, assets, skills, vault, setup, identity.
2
+ * The owner layer — memory, tasks, assets, skills, vault, setup, identity,
3
+ * entities.
3
4
  *
4
- * Seven providers that hold no third-party account: no OAuth, no vendor API, no
5
+ * Eight providers that hold no third-party account: no OAuth, no vendor API, no
5
6
  * rate limit anyone else imposes. They are ordinary `defineLocalProvider`
6
7
  * registrations, scoped by the same profiles and gated by the same policy
7
8
  * evaluation as everything else — which was the claim `docs/detailed/init.md`
@@ -33,7 +34,16 @@
33
34
  * naming the owner and describing what is connected are two policy decisions
34
35
  * instead of one.
35
36
  *
36
- * All seven ids are reserved (`RESERVED_PROVIDER_IDS`) and still refused by
37
+ * `entities` is its mirror: who and what *everyone else* is — the people,
38
+ * companies and projects the owner deals with, and their canonical addresses —
39
+ * so that a reference is looked up rather than inferred from whatever was in
40
+ * the conversation. It is writable where `identity` is not, and the two are
41
+ * consistent rather than in tension: identity is configuration, so an agent
42
+ * able to edit it could edit the one fact that stops it signing as the wrong
43
+ * person, while everyone else's details are ordinary owner material that
44
+ * accumulates on the same surface that reads it (ADR-056).
45
+ *
46
+ * All eight ids are reserved (`RESERVED_PROVIDER_IDS`) and still refused by
37
47
  * default — the registry has to be built with `allowReserved` to hold them, so a
38
48
  * third-party provider cannot claim a namespace whose policy rules would then
39
49
  * mean something else. `tasks` cost something to reserve: Google Tasks held that
@@ -57,6 +67,8 @@ export { createSkillsProvider, type SkillsProviderOptions } from './skills/provi
57
67
  export { createVaultProvider, type VaultProviderOptions } from './vault/provider.ts';
58
68
  export { createSetupProvider, type SetupProviderOptions } from './setup/provider.ts';
59
69
  export { createIdentityProvider, type IdentityProviderOptions } from './identity/provider.ts';
70
+ export { entitiesProvider } from './entities/provider.ts';
71
+ export { entityStorage, type Attribute, type Entity, type Relation } from './entities/store.ts';
60
72
  export { planAll, planFor, type PlanContext, type ProviderPlan } from './setup/plan.ts';
61
73
  // The vault's *store* is not here: it is `#secrets`, beside the system
62
74
  // credential store it must never become. What lives in `./vault/` is the
@@ -0,0 +1,10 @@
1
+ import { defineProvider } from '#connectivity';
2
+
3
+ /** Paddle registers us at connect time — nothing for an operator to set up. */
4
+ export const paddle = defineProvider({
5
+ id: 'paddle',
6
+ name: 'Paddle',
7
+ description: 'Products, prices, subscriptions, and transactions, via Paddle\'s official MCP server.',
8
+ connector: { kind: 'mcp', endpoint: 'https://mcp.paddle.com/mcp' },
9
+ auth: { kind: 'oauth', registration: 'dynamic' },
10
+ });
@@ -0,0 +1,10 @@
1
+ import { defineProvider } from '#connectivity';
2
+
3
+ /** PayPal registers us at connect time — nothing for an operator to set up. */
4
+ export const paypal = defineProvider({
5
+ id: 'paypal',
6
+ name: 'PayPal',
7
+ description: 'Invoices, orders, payments, subscriptions, and disputes, via PayPal\'s official MCP server.',
8
+ connector: { kind: 'mcp', endpoint: 'https://mcp.paypal.com/mcp' },
9
+ auth: { kind: 'oauth', registration: 'dynamic' },
10
+ });
@@ -0,0 +1,10 @@
1
+ import { defineProvider } from '#connectivity';
2
+
3
+ /** PostHog registers us at connect time — nothing for an operator to set up. */
4
+ export const posthog = defineProvider({
5
+ id: 'posthog',
6
+ name: 'PostHog',
7
+ description: 'Events, insights, feature flags, and session replays, via PostHog\'s official MCP server.',
8
+ connector: { kind: 'mcp', endpoint: 'https://mcp.posthog.com/mcp' },
9
+ auth: { kind: 'oauth', registration: 'dynamic' },
10
+ });
@@ -0,0 +1,10 @@
1
+ import { defineProvider } from '#connectivity';
2
+
3
+ /** Prisma registers us at connect time — nothing for an operator to set up. */
4
+ export const prisma = defineProvider({
5
+ id: 'prisma',
6
+ name: 'Prisma',
7
+ description: 'Postgres databases, schema, and migrations, via Prisma\'s official MCP server.',
8
+ connector: { kind: 'mcp', endpoint: 'https://mcp.prisma.io/mcp' },
9
+ auth: { kind: 'oauth', registration: 'dynamic' },
10
+ });
@@ -0,0 +1,10 @@
1
+ import { defineProvider } from '#connectivity';
2
+
3
+ /** Ramp registers us at connect time — nothing for an operator to set up. */
4
+ export const ramp = defineProvider({
5
+ id: 'ramp',
6
+ name: 'Ramp',
7
+ description: 'Cards, transactions, reimbursements, and spend limits, via Ramp\'s official MCP server.',
8
+ connector: { kind: 'mcp', endpoint: 'https://mcp.ramp.com/mcp' },
9
+ auth: { kind: 'oauth', registration: 'dynamic' },
10
+ });
@@ -0,0 +1,10 @@
1
+ import { defineProvider } from '#connectivity';
2
+
3
+ /** Recurly registers us at connect time — nothing for an operator to set up. */
4
+ export const recurly = defineProvider({
5
+ id: 'recurly',
6
+ name: 'Recurly',
7
+ description: 'Subscriptions, invoices, and accounts, via Recurly\'s official MCP server.',
8
+ connector: { kind: 'mcp', endpoint: 'https://mcp.recurly.com/mcp' },
9
+ auth: { kind: 'oauth', registration: 'dynamic' },
10
+ });
@@ -79,7 +79,7 @@ export const reddit = defineProvider({
79
79
  'Reddit needs an app of your own, which takes a couple of minutes in a browser. There is no shared ' +
80
80
  'client for this one on purpose: Reddit rate-limits per client id, so a client everyone shared would ' +
81
81
  'mean strangers using up your hundred requests a minute. Your own app gets its own budget.',
82
- docs: 'docs/detailed/setup/reddit.md',
82
+ docs: 'https://lanes.sh/docs/link/reddit',
83
83
  docs_url: 'https://www.reddit.com/prefs/apps',
84
84
  steps: [
85
85
  'Open https://www.reddit.com/prefs/apps and choose "create another app...".',
@@ -0,0 +1,10 @@
1
+ import { defineProvider } from '#connectivity';
2
+
3
+ /** Remote registers us at connect time — nothing for an operator to set up. */
4
+ export const remote = defineProvider({
5
+ id: 'remote',
6
+ name: 'Remote',
7
+ description: 'Employees, contracts, payroll, and time off, via Remote\'s official MCP server.',
8
+ connector: { kind: 'mcp', endpoint: 'https://mcp.remote.com/mcp' },
9
+ auth: { kind: 'oauth', registration: 'dynamic' },
10
+ });
@@ -0,0 +1,35 @@
1
+ import { defineProvider } from '#connectivity';
2
+
3
+ /**
4
+ * Render, with an API key you paste.
5
+ *
6
+ * Render's server offers OAuth but not dynamic client registration, and its
7
+ * authorization server accepts only public clients — so the browser route needs
8
+ * a client registered somewhere this program cannot reach. Render also accepts
9
+ * an ordinary API key, which is the route that works without one, and is the
10
+ * same trade `../github` makes for the same reason.
11
+ */
12
+ export const render = defineProvider({
13
+ id: 'render',
14
+ name: 'Render',
15
+ description: 'Services, deploys, logs, and environment variables, via Render\'s official MCP server.',
16
+ connector: { kind: 'mcp', endpoint: 'https://mcp.render.com/mcp' },
17
+ auth: { kind: 'bearer' },
18
+ setup: {
19
+ summary:
20
+ 'Render takes an API key rather than a browser sign-in here. It is created in your account ' +
21
+ 'settings, it does not expire, and it reaches every workspace your account can.',
22
+ docs_url: 'https://render.com/docs/api',
23
+ steps: [
24
+ 'Sign in at https://dashboard.render.com and open Account Settings → API Keys.',
25
+ 'Create a key and name it "Lanes Link" — the name is how you revoke this one later without cutting off anything else.',
26
+ 'Copy it. Render shows it once.',
27
+ ],
28
+ troubleshooting:
29
+ 'A rejected key is usually one that was rotated, or one copied from a different account. Create a new one at ' +
30
+ 'https://dashboard.render.com under Account Settings → API Keys and re-run: lanes link connect render --replace.',
31
+ prompts: [
32
+ { key: 'token', label: 'Render API key', secret: true, scope: 'connection' as const },
33
+ ],
34
+ },
35
+ });