@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
package/.gcloudignore CHANGED
@@ -12,9 +12,19 @@
12
12
  # method is a directory under `~/.bun` with no `.git` in it — so the safe
13
13
  # behaviour was being inherited from a coincidence.
14
14
  #
15
- # Keep the first block in step with `.dockerignore`. `data/` holds the encrypted
16
- # credential store *and* the key that opens it.
17
- data/
15
+ # **Anchor every directory pattern with a leading slash.** This file is gitignore
16
+ # syntax and `.dockerignore` is not: there, a pattern is relative to the context
17
+ # root, so `docs/` means the one at the top. Here `docs/` means *any* directory
18
+ # called `docs`, at any depth — which is `src/providers/google/docs/`, and
19
+ # stripping it produced an image that built cleanly and died on its first import
20
+ # with `Cannot find module './google/docs/index.ts'`. The two files must say the
21
+ # same thing, not carry the same characters.
22
+ #
23
+ # The credential patterns below are deliberately *not* anchored: a stray `*.key`
24
+ # is worth excluding wherever it turns up. `data/` is, because the credential
25
+ # store is one known directory and a source directory called `data` would be a
26
+ # legitimate part of the package.
27
+ /data/
18
28
  *.key
19
29
  *.pem
20
30
  *.enc
@@ -23,28 +33,28 @@ data/
23
33
  .env.*
24
34
 
25
35
  # Not needed to build, and `.git` in particular carries every branch.
26
- .git/
36
+ /.git/
27
37
  .gitignore
28
- .worktrees/
38
+ /.worktrees/
29
39
  node_modules/
30
40
  **/node_modules/
31
- coverage/
32
- dist/
33
- build/
41
+ /coverage/
42
+ /dist/
43
+ /build/
34
44
  *.tsbuildinfo
35
45
 
36
46
  # Tests, docs and tooling: the image runs the endpoint and nothing else.
37
47
  **/*.test.ts
38
48
  **/*.test.json
39
- docs/
40
- instructions/
49
+ /docs/
50
+ /instructions/
41
51
  **/README.md
42
- .lanes/
43
- .claude/
44
- .vscode/
45
- .idea/
52
+ /.lanes/
53
+ /.claude/
54
+ /.vscode/
55
+ /.idea/
46
56
  .DS_Store
47
- .playwright-mcp/
57
+ /.playwright-mcp/
48
58
 
49
59
  # The compiled binary from `bun build --compile`, if one was made locally.
50
60
  /lanes
package/README.md CHANGED
@@ -4,12 +4,13 @@
4
4
  [![license Apache-2.0](https://img.shields.io/github/license/lanes-sh/link?style=flat-square&color=black)](LICENSE)
5
5
  [![ci](https://img.shields.io/github/actions/workflow/status/lanes-sh/link/ci.yml?branch=main&style=flat-square&label=ci)](https://github.com/lanes-sh/link/actions/workflows/ci.yml)
6
6
 
7
- **One secure endpoint between your AI agents and all your connections, memory, tasks, files, and secrets.**
7
+ **One endpoint you own, holding everything your agents need to know you: connections, memory, tasks, files, contacts, and secrets.**
8
8
 
9
- Connect your mail, calendar, files, and notes once, and add the memory, tasks, and procedures that
10
- only you have. Every agent you use — Claude, ChatGPT, and anything else that speaks MCP — reaches them
11
- through a single MCP endpoint that you own and run. Open source, self-hostable, no vendor sitting in
12
- the middle of your data.
9
+ You own it and you run it. Connect your mail, calendar, files, and notes once, and add the memory,
10
+ tasks, and procedures that only you have. Every agent you use — Claude, ChatGPT, and anything else
11
+ that speaks MCP reaches all of it through that one endpoint. Change your AI and you keep your
12
+ context, because none of it ever lived in the agent. Open source, self-hostable, no vendor in the
13
+ middle of your data.
13
14
 
14
15
  <picture>
15
16
  <source media="(prefers-color-scheme: dark)" srcset="docs/images/lanes-link-dark.svg">
@@ -23,6 +24,9 @@ the middle of your data.
23
24
  them — it does not ask the model to behave.
24
25
  - **Work and personal never mix.** Separate profiles, separate credentials, separate stores.
25
26
  - **Every call is recorded.** An append-only log of what was reached, and what was refused.
27
+ - **It is yours to move.** What you store is plain files on your own disk — readable in an editor,
28
+ backed up like anything else, and portable to a private Git repository or to another machine
29
+ whenever you want. There is no export to ask for.
26
30
 
27
31
  ## What people use it for
28
32
 
@@ -67,7 +71,7 @@ ok registered lanes-link with Codex
67
71
 
68
72
  Your agents can now use it. Memory, tasks, files, skills, and the vault hold your own material
69
73
  rather than an account, so they are already there — nothing to connect, no credentials, no browser.
70
- Mail and calendar are the next step. **[Full quickstart →](docs/quickstart.md)**
74
+ Mail and calendar are the next step. **[Full quickstart →](https://lanes.sh/docs/link/quickstart)**
71
75
 
72
76
  ## In the Lanes desktop app
73
77
 
@@ -88,7 +92,10 @@ $ lanes link desktop # opens the app on that page, installing it if it is no
88
92
 
89
93
  **[How to use it →](https://lanes.sh/docs/desktop/lanes-link)**
90
94
 
91
- ## What your agent gets
95
+ ## What you keep in it
96
+
97
+ Not "what your agent gets" — the distinction is the whole point. These are yours. An agent is a
98
+ visitor to them, and you decide per profile how far in it comes.
92
99
 
93
100
  | | | Manage it with |
94
101
  |---|---|---|
@@ -97,12 +104,18 @@ $ lanes link desktop # opens the app on that page, installing it if it is no
97
104
  | **Tasks** | what you have to do, each with a status | `lanes link tasks` |
98
105
  | **Assets** | files you want kept, by name | `lanes link assets` |
99
106
  | **Skills** | your own procedures, handed to an agent as instructions | `lanes link skills` |
107
+ | **Identity** | who you are, and the people and companies that recur in your work | `lanes link identity` |
100
108
  | **Vault** | passwords and API keys, released only where you allow it | `lanes link vault` |
101
109
 
102
- The first five arrive switched on: they hold your own material rather than an account, so there was
103
- never anything to authorise. Memory, tasks, and skills are plain Markdown files and an asset is
104
- stored under its own filename, so a text editor and an agent reach the same bytes. Every one of them
105
- belongs to a single profile: what you add under `work` is invisible under `personal`.
110
+ Every one of these except connections arrives switched on: they hold your own material rather than
111
+ an account, so there was never anything to authorise. Memory, tasks, and skills are plain Markdown
112
+ files and an asset is stored under its own filename, so a text editor and an agent reach the same
113
+ bytes. Every one of them belongs to a single profile: what you add under `work` is invisible under
114
+ `personal`.
115
+
116
+ Identity is the one that is read-only by construction. An agent able to rewrite whose name it signs
117
+ with would be rewriting the one fact that stops it signing as the wrong person, so you declare it in
118
+ a terminal and the endpoint only reads it back.
106
119
 
107
120
  Which store a thing goes in is the one thing worth knowing. **Memory is what is true, tasks is what
108
121
  is to be done, assets is a file.** "Remember to chase the invoice" is a task — filed as memory it
@@ -121,34 +134,35 @@ are, and there is no setting that would put them in a repository.
121
134
 
122
135
  ## Connect an account
123
136
 
124
- One command per account. Run it again to add a second mailbox, a second calendar, a second anything.
137
+ **Over a hundred accounts and services, one command each.** Run it again to add a second mailbox, a
138
+ second calendar, a second anything.
125
139
 
126
- | | Connect with |
140
+ | | |
127
141
  |---|---|
128
- | Gmail | `lanes link connect gmail` |
129
- | Google Drive | `lanes link connect drive` |
130
- | Google Sheets | `lanes link connect sheets` |
131
- | Google Docs | `lanes link connect docs` |
132
- | Google Calendar | `lanes link connect calendar` |
133
- | Google Tasks | `lanes link connect google_tasks` |
134
- | Google Contacts | `lanes link connect contacts` |
135
- | iCloud Mail | `lanes link connect icloud_mail` |
136
- | iCloud Calendar | `lanes link connect icloud_calendar` |
137
- | iCloud Contacts | `lanes link connect icloud_contacts` |
138
- | iCloud Drive | `lanes link connect icloud_drive` |
139
- | Notion | `lanes link connect notion` |
140
- | Linear | `lanes link connect linear` |
141
- | GitHub | `lanes link connect github` |
142
- | Slack | `lanes link connect slack` |
143
- | Reddit | `lanes link connect reddit` |
144
- | Discord | `lanes link connect discord` |
145
- | Gmail (IMAP, app password) | `lanes link connect gmail_imap` |
146
- | Gmail (Google MCP) | `lanes link connect gmail_mcp` |
147
- | Drive (Google MCP) | `lanes link connect drive_mcp` |
148
- | bunq | `lanes link connect bunq` |
142
+ | **Mail, calendar, contacts, files** | Gmail · Google Calendar · Google Drive · Google Docs · Google Sheets · Google Tasks · Google Contacts · iCloud Mail, Calendar, Contacts and Drive · Outlook Mail, Calendar and Contacts · OneDrive · Microsoft To Do · Fastmail · Zoho Mail · Yahoo Mail · Nextcloud · any IMAP mailbox |
143
+ | **Work** | Notion · Linear · Slack · GitHub · Asana · Atlassian (Jira, Confluence) · Todoist · ClickUp · monday.com · Shortcut · Miro · Whimsical · Figma · Canva · Calendly · Fireflies · HubSpot |
144
+ | **Money** | Stripe · PayPal · Square · Mercury · Ramp · bunq · Paddle · Recurly · Expensify |
145
+ | **Build and run** | Sentry · Vercel · Netlify · Cloudflare · Supabase · Neon · Prisma · Heroku · CircleCI · Buildkite · Datadog · Grafana · Better Stack · Rootly · Flagsmith |
146
+ | **Content and data** | Contentful · Storyblok · Hygraph · Sanity · Webflow · Wix · Algolia · PostHog · Mixpanel · Amplitude · RudderStack |
147
+ | **Everything else** | Reddit · Discord · Dropbox · Box · Airtable · Zapier · Attio · Klaviyo · Salesloft · Vimeo · Mux · Replicate · Apify · Tavily · Bright Data |
148
+
149
+ **[The full list, with the command for each →](https://lanes.sh/docs/link/connect)**
150
+
151
+ Most of them need nothing set up. Seventy of them run their own MCP server and offer dynamic client
152
+ registration, so `connect` registers us on the spot: browser, approve, done. The rest are one of
153
+ three shapes an app password you issue yourself (iCloud, Fastmail, Gmail over IMAP), a token you
154
+ paste (GitHub, Discord), or an OAuth client of your own (Reddit, Microsoft). A few also ask *where*
155
+ they are, because the address belongs to the account rather than the vendor — a Nextcloud you run,
156
+ or any IMAP server.
157
+
158
+ **Most of them are also untested**, and the tables say which. A provider marked † validates, generates
159
+ tools inside the budget, and answers a probe but nobody has connected it to a real account yet, and
160
+ that is the part only a real account proves. See
161
+ [`src/providers/README.md`](src/providers/README.md) for what that means and how the list is kept.
149
162
 
150
163
  Three things worth knowing up front. `lanes link connect icloud` sets up Mail, Calendar, and
151
- Contacts together, because one app-specific password covers all three. Reddit is the one that does
164
+ Contacts together, because one app-specific password covers all three and `lanes link connect
165
+ fastmail` does the same for Fastmail's three. Reddit is the one that does
152
166
  need an app of your own — it rate-limits per client id, so a shared client would mean strangers
153
167
  spending your budget. Google and Slack need no
154
168
  OAuth client of your own: both authorise against the one Lanes operates, so there is no console to
@@ -158,10 +172,12 @@ token you paste rather than a browser sign-in, because it will not register a cl
158
172
  bunq — which wants a key from inside its app rather than a console at all — is the one that can move
159
173
  money, and it says so: its payment tool executes immediately and is not reversible. Set a spending
160
174
  limit on the API key while you are in there, and read
161
- [docs/detailed/setup/bunq.md](docs/detailed/setup/bunq.md) before connecting it.
175
+ [Connecting bunq](https://lanes.sh/docs/link/bunq) before connecting it.
162
176
 
163
177
  Full guide — what each one gives your agent, what it needs, and adding your own:
164
- **[docs/connect.md](docs/connect.md)**.
178
+ **[Connect your accounts](https://lanes.sh/docs/link/connect)**. How the provider layer works, and
179
+ the whole inventory by connector and credential type:
180
+ **[src/providers/README.md](src/providers/README.md)**.
165
181
 
166
182
  ## Run it anywhere
167
183
 
@@ -184,19 +200,20 @@ than being rebuilt.
184
200
 
185
201
  ## Docs
186
202
 
187
- - **[Quickstart](docs/quickstart.md)** — from nothing to a working endpoint
203
+ - **[Quickstart](https://lanes.sh/docs/link/quickstart)** — from nothing to a working endpoint
188
204
  - **[In the Lanes desktop app](https://lanes.sh/docs/desktop/lanes-link)** — the page that drives it
189
- - **[Connect your accounts](docs/connect.md)** — every provider, and what each one needs
190
- - **[Add it to your agent](docs/clients.md)** — Claude Code, Codex, Claude Desktop, claude.ai, ChatGPT
191
- - **[Deploy to your own cloud](docs/deploy.md)** — five commands to a URL
192
- - **[Every command](docs/detailed/commands.md)** — arguments and flags, one entry each
193
- - **[Full reference](docs/detailed/)** — architecture, configuration, the CLI, the security model,
194
- writing a provider, and the decision records
205
+ - **[Connect your accounts](https://lanes.sh/docs/link/connect)** — every provider, and what each one needs
206
+ - **[Add it to your agent](https://lanes.sh/docs/link/clients)** — Claude Code, Codex, Claude Desktop, claude.ai, ChatGPT
207
+ - **[Deploy to your own cloud](https://lanes.sh/docs/link/deploy)** — five commands to a URL
208
+ - **[Every command](https://lanes.sh/docs/link/commands)** — arguments and flags, one entry each
209
+ - **[Full reference](https://lanes.sh/docs/link)** — architecture, configuration, the CLI, the
210
+ security model, and writing a provider
211
+ - **[Decision records](docs/detailed/adr/)** — why each choice was made, kept here with the source
195
212
 
196
213
  ## Security
197
214
 
198
215
  Lanes Link holds live credentials to your email and documents. The
199
- [security model](docs/detailed/security.md) states its limits plainly rather than implying
216
+ [security model](https://lanes.sh/docs/link/security) states its limits plainly rather than implying
200
217
  guarantees the code does not deliver — read it before you trust it with an account. To report a
201
218
  vulnerability, see [`SECURITY.md`](SECURITY.md); please do not open a public issue.
202
219
 
@@ -174,6 +174,31 @@ inventing something; they add one with `lanes link identity add <kind> <value> -
174
174
  --target <target>` — both flags, because neither has a fallback.
175
175
  Nothing you can call writes here, deliberately.
176
176
 
177
+ ## Who you are writing *to* is declared as well
178
+
179
+ `entities_find` holds the people, companies and projects this owner deals with,
180
+ with the addresses and handles to reach each of them. Call it before using
181
+ anyone's address — do not recall one from earlier in the conversation, and do
182
+ not lift one off a message you happen to have read.
183
+
184
+ **It answers with every match and never chooses between them.** One match is an
185
+ answer. More than one is a question: the reply shows what separates the
186
+ candidates, so settle it from what you already know if that is genuinely
187
+ unambiguous, and otherwise **ask**. Do not take the first — the order is not a
188
+ ranking, and nothing about the reply is an error you need to work around.
189
+
190
+ None matching is not a failure either. It means the owner has not written that
191
+ person down, so ask rather than using an address from somewhere else.
192
+
193
+ Where an entity holds two of a kind — a work address and a personal one — the
194
+ first is the default and the notes say when to prefer the other, exactly as
195
+ identity works.
196
+
197
+ Writing is a separate grant. Where you have it, `entities_write` declares one
198
+ and `entities_link` relates two; a field you do not send is left as it is.
199
+ `entities_forget` does not clean up edges pointing at what it removed, and says
200
+ which ones will dangle.
201
+
177
202
  ## Attachments are named, not carried
178
203
 
179
204
  Where a tool takes `attachments`, each entry names **one** source and the endpoint
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lanes-sh/link",
3
- "version": "0.6.10",
3
+ "version": "0.7.0",
4
4
  "description": "A self-hostable MCP gateway for all your connections, memory, tasks, files, and secrets",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://lanes.sh/link",
@@ -54,7 +54,8 @@
54
54
  "audit": "bun audit",
55
55
  "vendor:bunq": "bun run ./src/providers/bunq/specs/vendor.ts",
56
56
  "vendor:discord": "bun run ./src/providers/discord/specs/vendor.ts",
57
- "vendor:google": "bun run ./src/providers/google/specs/vendor.ts"
57
+ "vendor:google": "bun run ./src/providers/google/specs/vendor.ts",
58
+ "vendor:microsoft": "bun run ./src/providers/microsoft/specs/vendor.ts"
58
59
  },
59
60
  "imports": {
60
61
  "#audit": "./src/audit/index.ts",
@@ -358,7 +358,7 @@ export function describeAuditSinkContract(
358
358
  await use(async (fixture) => {
359
359
  // The append-only guarantee is the absence of these methods, so assert
360
360
  // the absence directly rather than trusting the interface to stay
361
- // honest. `docs/detailed/security.md` lists `audit.append-only` as ENFORCED on
361
+ // honest. `https://lanes.sh/docs/link/security` lists `audit.append-only` as ENFORCED on
362
362
  // the grounds that the store interface has no update or delete — this
363
363
  // is the check behind that claim, and it holds for every adapter.
364
364
  const sink = fixture.open();
package/src/cli/argv.ts CHANGED
@@ -107,7 +107,7 @@ export function globalFlags(flags: Flags): GlobalFlags {
107
107
  * The kebab-case spellings live here and nowhere else, so the one place that
108
108
  * knows `--display-name` is the one place that parses argv.
109
109
  */
110
- export function ownerFlags(flags: Flags): OwnerFlags {
110
+ export function ownerFlags(flags: Flags, argv: readonly string[] = []): OwnerFlags {
111
111
  return {
112
112
  ...globalFlags(flags),
113
113
  show: flags['show'] === true,
@@ -124,10 +124,23 @@ export function ownerFlags(flags: Flags): OwnerFlags {
124
124
  due: text(flags, 'due'),
125
125
  name: text(flags, 'name'),
126
126
  contentType: text(flags, 'content-type'),
127
+ type: text(flags, 'type'),
128
+ // Repeatable, so read from argv rather than from the parsed map: `parseArgv`
129
+ // keeps only the last value of a repeated flag, and two email addresses on
130
+ // one entity is the case entities exists for. `customFlags` does the same.
131
+ alias: list(argv, 'alias'),
132
+ attr: list(argv, 'attr'),
133
+ related: list(argv, 'related'),
127
134
  yes: flags['yes'] === true,
128
135
  };
129
136
  }
130
137
 
138
+ /** A repeatable flag's values, or undefined when it was not passed at all. */
139
+ function list(argv: readonly string[], name: string): string[] | undefined {
140
+ const values = all(argv, name);
141
+ return values.length > 0 ? values : undefined;
142
+ }
143
+
131
144
  /**
132
145
  * `lanes link knowledge`'s flags.
133
146
  *
@@ -0,0 +1,100 @@
1
+ import type { ConfigDocument } from '../../config-edit.ts';
2
+ import type { ProviderManifest } from '#connectivity';
3
+ import type { SecretStore } from '#secrets';
4
+ import type { Prompter } from '../../prompt.ts';
5
+ import { authorise } from './authorise.ts';
6
+ import { authoriseWithKey } from './assertion.ts';
7
+ import { authorisePastedToken } from './pasted-token.ts';
8
+ import { ensureStaticCredential } from './setup.ts';
9
+ import type { ChosenMethod } from './method.ts';
10
+
11
+ /**
12
+ * Getting the credential, whichever way this provider offers.
13
+ *
14
+ * Four routes and exactly one of them runs: a signed assertion from a key the
15
+ * operator holds, a token they paste, a browser round trip, or a static
16
+ * credential asked for and stored. `none` is the fifth case and does nothing —
17
+ * an `fs` provider has no account to prove.
18
+ *
19
+ * Its own file because it is the one part of `runConnect` that is a decision
20
+ * rather than a sequence, and because that file was at the size budget. Each arm
21
+ * already lives in a module of its own; this is only the choosing.
22
+ */
23
+ export async function acquireCredential(input: {
24
+ readonly method: ChosenMethod;
25
+ readonly manifest: ProviderManifest;
26
+ readonly provisionalId: string;
27
+ readonly credentials: SecretStore;
28
+ readonly document: ConfigDocument;
29
+ readonly changes: string[];
30
+ readonly providerId: string;
31
+ readonly connections: readonly { readonly provider: string }[];
32
+ /** How the operator spelled the target, so a refusal names a command they typed. */
33
+ readonly target: string | undefined;
34
+ readonly profile: string;
35
+ readonly prompter: Prompter;
36
+ /** True when a name was given, which reads as "that one again". */
37
+ readonly named: boolean;
38
+ /** The connection id is still the placeholder, so nothing has been settled. */
39
+ readonly provisional: boolean;
40
+ readonly replace: boolean;
41
+ readonly nonInteractive: boolean;
42
+ readonly acceptBroadScopes: boolean;
43
+ readonly fetch?: typeof globalThis.fetch | undefined;
44
+ }): Promise<void> {
45
+ const { manifest, method, provisionalId, prompter, changes, document } = input;
46
+ const { credentials, providerId, target, profile } = input;
47
+
48
+ /**
49
+ * "That one again", which is what asks for a value the store already has.
50
+ *
51
+ * Naming a connection says it, and so does `--replace`. Never
52
+ * non-interactively: there, again has already happened — the new value was
53
+ * written with `secrets set` before this ran, so asking would be asking for
54
+ * something the store is holding.
55
+ */
56
+ const askAgain = !input.nonInteractive && (input.replace || input.named);
57
+
58
+ if (method.kind === 'assertion') {
59
+ await authoriseWithKey({
60
+ manifest,
61
+ assertion: method.assertion,
62
+ connectionId: provisionalId,
63
+ credentials,
64
+ changes,
65
+ replace: askAgain,
66
+ prompter,
67
+ });
68
+ } else if (method.kind === 'pasted') {
69
+ await authorisePastedToken({
70
+ manifest,
71
+ connectionId: provisionalId,
72
+ credentials,
73
+ prompter,
74
+ });
75
+ } else if (manifest.auth.kind === 'oauth') {
76
+ await authorise({
77
+ manifest,
78
+ connectionId: provisionalId,
79
+ credentials,
80
+ document,
81
+ changes,
82
+ firstForProvider: !input.connections.some((c) => c.provider === providerId),
83
+ ...(target === undefined ? {} : { target }),
84
+ profile,
85
+ client: method.client,
86
+ prompter,
87
+ acceptBroadScopes: input.acceptBroadScopes,
88
+ ...(input.fetch ? { fetch: input.fetch } : {}),
89
+ });
90
+ } else if (manifest.auth.kind !== 'none') {
91
+ await ensureStaticCredential({
92
+ manifest,
93
+ connectionId: provisionalId,
94
+ credentials,
95
+ replace: askAgain,
96
+ provisional: input.provisional,
97
+ prompter,
98
+ });
99
+ }
100
+ }
@@ -35,7 +35,7 @@ import {
35
35
  * So this composes the two closed unions: a connectivity type and a credential
36
36
  * type. Anything not covered by a pair of them is not something to bolt on here
37
37
  * — it is a member missing from one of those lists, which is a folder and a
38
- * schema entry away. `docs/detailed/connectivity-coverage.md` is the standing
38
+ * schema entry away. `https://lanes.sh/docs/link/connectivity-coverage` is the standing
39
39
  * account of which pairs work, which are closed on purpose, and which are not
40
40
  * built yet.
41
41
  *
@@ -24,8 +24,17 @@ export function declareConnection(input: {
24
24
  readonly label: string;
25
25
  /** Which route in, where the provider offered a choice. */
26
26
  readonly method: string | undefined;
27
+ /**
28
+ * Where this connection's service is, for a provider that asked.
29
+ *
30
+ * Empty for every provider whose address is fixed, which is almost all of
31
+ * them. Written on a reconnect as well as on an add: the operator may be
32
+ * reconnecting *because* the server moved.
33
+ */
34
+ readonly config: Readonly<Record<string, string>>;
27
35
  }): readonly string[] {
28
36
  const { document, connections, providerId, connectionId, account, label, method } = input;
37
+ const config = input.config;
29
38
 
30
39
  const key = `${providerId}.${connectionId}`;
31
40
  const index = connections.findIndex((c) => `${c.provider}.${c.id}` === key);
@@ -44,6 +53,7 @@ export function declareConnection(input: {
44
53
  provider: providerId,
45
54
  account,
46
55
  ...(label === account ? {} : { label }),
56
+ ...(Object.keys(config).length > 0 ? { config } : {}),
47
57
  });
48
58
  changes.push(`connections += ${key} (${account})`);
49
59
  return changes;
@@ -67,6 +77,15 @@ export function declareConnection(input: {
67
77
  changes.push(`connections.${key}.label = ${label}`);
68
78
  }
69
79
 
80
+ // The address, when the provider has one to keep. Written whenever it differs,
81
+ // because a reconnect is exactly when a self-hosted service has moved — and a
82
+ // row whose config is silently stale points the credential at the old host.
83
+ for (const [key_, value] of Object.entries(config)) {
84
+ if ((declared?.config as Record<string, unknown> | undefined)?.[key_] === value) continue;
85
+ document.setIn(['connections', index, 'config', key_], value);
86
+ changes.push(`connections.${key}.config.${key_} = ${value}`);
87
+ }
88
+
70
89
  // Named where the provider offered a choice, because this is the line an
71
90
  // operator reads to see that a re-connect swapped the route rather than
72
91
  // refreshed it — and `--auth` reaches here having asked nothing. Unnamed for a