@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.
- package/.gcloudignore +25 -15
- package/README.md +62 -45
- package/instructions/skills/lanes-link/SKILL.md +25 -0
- package/package.json +3 -2
- package/src/audit/conformance.ts +1 -1
- package/src/cli/argv.ts +14 -1
- package/src/cli/commands/connect/acquire.ts +100 -0
- package/src/cli/commands/connect/custom/index.ts +1 -1
- package/src/cli/commands/connect/declare.ts +19 -0
- package/src/cli/commands/connect/index.ts +54 -56
- package/src/cli/commands/connect/requirements.ts +23 -1
- package/src/cli/commands/connect/setup.ts +1 -1
- package/src/cli/commands/connect/variables.ts +220 -0
- package/src/cli/commands/knowledge/index.ts +22 -4
- package/src/cli/commands/knowledge/migrate.ts +61 -43
- package/src/cli/commands/operate/tools.ts +1 -1
- package/src/cli/commands/owner/entities.ts +244 -0
- package/src/cli/commands/owner/shared.ts +14 -0
- package/src/cli/commands/owner/vault.ts +2 -2
- package/src/cli/commands/owner.ts +13 -3
- package/src/cli/commands/setup.ts +12 -0
- package/src/cli/config-edit.ts +16 -6
- package/src/cli/config-repair.ts +10 -0
- package/src/cli/dispatch-owner.ts +30 -1
- package/src/cli/main.ts +2 -1
- package/src/cli/runtime/open.ts +14 -7
- package/src/cli/runtime/registry.ts +6 -1
- package/src/cli/selection.ts +11 -0
- package/src/cli/usage.ts +13 -2
- package/src/connectivity/auth/strategy/index.ts +1 -1
- package/src/connectivity/capability.ts +1 -1
- package/src/connectivity/context.ts +1 -1
- package/src/connectivity/index.ts +1 -1
- package/src/connectivity/mail/attachments.ts +42 -73
- package/src/connectivity/mail/content-type.ts +71 -0
- package/src/connectivity/mail/index.ts +1 -1
- package/src/connectivity/manifest/provider.ts +50 -0
- package/src/connectivity/manifest/variables.ts +173 -0
- package/src/connectivity/transports/factory.ts +74 -1
- package/src/connectivity/transports/http/index.ts +8 -2
- package/src/connectivity/transports/http/keys.ts +83 -0
- package/src/connectivity/transports/imap/attachment.ts +53 -10
- package/src/connectivity/transports/imap/capabilities.ts +28 -0
- package/src/connectivity/transports/imap/download.ts +111 -0
- package/src/connectivity/transports/imap/index.ts +13 -0
- package/src/connectivity/transports/imap/operations.ts +1 -0
- package/src/deployments/adapters/s3.ts +1 -1
- package/src/deployments/gcp/provision.ts +1 -1
- package/src/deployments/knowledge.ts +44 -18
- package/src/deployments/prepare.ts +1 -1
- package/src/deployments/serving.ts +1 -1
- package/src/dispatch/dispatch.ts +17 -10
- package/src/dispatch/staging.ts +76 -1
- package/src/profile/knowledge.ts +19 -11
- package/src/profile/schema.ts +2 -2
- package/src/providers/README.md +189 -0
- package/src/providers/airtable/index.ts +10 -0
- package/src/providers/algolia/index.ts +10 -0
- package/src/providers/amplitude/index.ts +10 -0
- package/src/providers/apify/index.ts +10 -0
- package/src/providers/asana/index.ts +10 -0
- package/src/providers/assets/provider.ts +11 -4
- package/src/providers/atlassian/index.ts +19 -0
- package/src/providers/attio/index.ts +10 -0
- package/src/providers/betterstack/index.ts +10 -0
- package/src/providers/box/index.ts +64 -0
- package/src/providers/brightdata/index.ts +10 -0
- package/src/providers/buildkite/index.ts +10 -0
- package/src/providers/bunq/index.ts +2 -2
- package/src/providers/calendly/index.ts +10 -0
- package/src/providers/canva/index.ts +10 -0
- package/src/providers/circleci/index.ts +10 -0
- package/src/providers/clickup/index.ts +10 -0
- package/src/providers/close/index.ts +10 -0
- package/src/providers/cloudflare_bindings/index.ts +14 -0
- package/src/providers/cloudflare_observability/index.ts +10 -0
- package/src/providers/contentful/index.ts +10 -0
- package/src/providers/custom/load.ts +1 -1
- package/src/providers/datadog/index.ts +10 -0
- package/src/providers/discord/index.ts +1 -1
- package/src/providers/discord/specs/discord.v10.json +0 -88
- package/src/providers/discord/specs/vendor.ts +1 -1
- package/src/providers/dropbox/index.ts +10 -0
- package/src/providers/entities/catalogue.ts +366 -0
- package/src/providers/entities/find.ts +279 -0
- package/src/providers/entities/provider.ts +334 -0
- package/src/providers/entities/render.ts +142 -0
- package/src/providers/entities/store.ts +276 -0
- package/src/providers/entities/writes.ts +279 -0
- package/src/providers/example/provider.ts +1 -1
- package/src/providers/expensify/index.ts +10 -0
- package/src/providers/fastmail/calendar/index.ts +21 -0
- package/src/providers/fastmail/contacts/index.ts +17 -0
- package/src/providers/fastmail/index.ts +4 -0
- package/src/providers/fastmail/mail/index.ts +38 -0
- package/src/providers/fastmail/shared/setup.ts +52 -0
- package/src/providers/figma/index.ts +10 -0
- package/src/providers/fireflies/index.ts +10 -0
- package/src/providers/flagsmith/index.ts +10 -0
- package/src/providers/gamma/index.ts +10 -0
- package/src/providers/github/index.ts +2 -2
- package/src/providers/google/gmail/attachment.ts +16 -1
- package/src/providers/google/gmail-imap/index.ts +7 -2
- package/src/providers/google/shared/oauth.ts +1 -1
- package/src/providers/google/shared/service-account.ts +1 -1
- package/src/providers/google/shared/setup.ts +1 -1
- package/src/providers/google/specs/calendar.v3.json +0 -32
- package/src/providers/google/specs/docs.v1.json +0 -76
- package/src/providers/google/specs/drive.v3.json +0 -32
- package/src/providers/google/specs/gmail.v1.json +0 -76
- package/src/providers/google/specs/people.v1.json +0 -77
- package/src/providers/google/specs/sheets.v4.json +0 -76
- package/src/providers/google/specs/tasks.v1.json +0 -76
- package/src/providers/grafana/index.ts +10 -0
- package/src/providers/heroku/index.ts +10 -0
- package/src/providers/hubspot/index.ts +75 -0
- package/src/providers/hygraph/index.ts +10 -0
- package/src/providers/icloud/mail/index.ts +5 -0
- package/src/providers/icloud/shared/setup.ts +1 -1
- package/src/providers/index.ts +255 -0
- package/src/providers/insightly/index.ts +10 -0
- package/src/providers/jam/index.ts +10 -0
- package/src/providers/klaviyo/index.ts +10 -0
- package/src/providers/mailbox/index.ts +87 -0
- package/src/providers/memory/provider.ts +3 -8
- package/src/providers/mercury/index.ts +10 -0
- package/src/providers/microsoft/calendar/index.ts +49 -0
- package/src/providers/microsoft/contacts/index.ts +50 -0
- package/src/providers/microsoft/drive/index.ts +62 -0
- package/src/providers/microsoft/index.ts +12 -0
- package/src/providers/microsoft/mail/index.ts +76 -0
- package/src/providers/microsoft/shared/oauth.ts +58 -0
- package/src/providers/microsoft/shared/scopes.ts +56 -0
- package/src/providers/microsoft/shared/setup.ts +45 -0
- package/src/providers/microsoft/specs/microsoft-todo.v1.json +755 -0
- package/src/providers/microsoft/specs/onedrive.v1.json +993 -0
- package/src/providers/microsoft/specs/outlook-calendar.v1.json +1743 -0
- package/src/providers/microsoft/specs/outlook-contacts.v1.json +430 -0
- package/src/providers/microsoft/specs/outlook-mail.v1.json +901 -0
- package/src/providers/microsoft/specs/vendor.ts +232 -0
- package/src/providers/microsoft/todo/index.ts +54 -0
- package/src/providers/miro/index.ts +10 -0
- package/src/providers/mixpanel/index.ts +10 -0
- package/src/providers/monday/index.ts +10 -0
- package/src/providers/mux/index.ts +10 -0
- package/src/providers/navan/index.ts +10 -0
- package/src/providers/neon/index.ts +10 -0
- package/src/providers/netlify/index.ts +10 -0
- package/src/providers/nextcloud/calendar/index.ts +28 -0
- package/src/providers/nextcloud/contacts/index.ts +18 -0
- package/src/providers/nextcloud/index.ts +3 -0
- package/src/providers/nextcloud/shared/setup.ts +59 -0
- package/src/providers/owner.ts +15 -3
- package/src/providers/paddle/index.ts +10 -0
- package/src/providers/paypal/index.ts +10 -0
- package/src/providers/posthog/index.ts +10 -0
- package/src/providers/prisma/index.ts +10 -0
- package/src/providers/ramp/index.ts +10 -0
- package/src/providers/recurly/index.ts +10 -0
- package/src/providers/reddit/index.ts +1 -1
- package/src/providers/remote/index.ts +10 -0
- package/src/providers/render/index.ts +35 -0
- package/src/providers/replicate/index.ts +10 -0
- package/src/providers/resend/index.ts +10 -0
- package/src/providers/riverside/index.ts +10 -0
- package/src/providers/rootly/index.ts +10 -0
- package/src/providers/rudderstack/index.ts +10 -0
- package/src/providers/salesloft/index.ts +10 -0
- package/src/providers/sanity/index.ts +10 -0
- package/src/providers/scopes.ts +2 -0
- package/src/providers/sentry/index.ts +10 -0
- package/src/providers/setup/plan.ts +11 -0
- package/src/providers/setup/provider.ts +12 -0
- package/src/providers/shared/openapi.ts +70 -0
- package/src/providers/shared/slug.ts +28 -0
- package/src/providers/shared/vendor-operations.ts +43 -1
- package/src/providers/shared/vendor-spec.ts +98 -17
- package/src/providers/shortcut/index.ts +10 -0
- package/src/providers/slack/index.ts +2 -2
- package/src/providers/square/index.ts +10 -0
- package/src/providers/storyblok/index.ts +10 -0
- package/src/providers/stripe/index.ts +10 -0
- package/src/providers/supabase/index.ts +10 -0
- package/src/providers/tasks/store.ts +2 -7
- package/src/providers/tavily/index.ts +10 -0
- package/src/providers/todoist/index.ts +10 -0
- package/src/providers/untested.ts +105 -0
- package/src/providers/vault/provider.ts +1 -1
- package/src/providers/vercel/index.ts +10 -0
- package/src/providers/vimeo/index.ts +10 -0
- package/src/providers/webflow/index.ts +10 -0
- package/src/providers/whimsical/index.ts +10 -0
- package/src/providers/wix/index.ts +10 -0
- package/src/providers/workable/index.ts +10 -0
- package/src/providers/yahoo_mail/index.ts +73 -0
- package/src/providers/zapier/index.ts +10 -0
- package/src/providers/zoho_mail/index.ts +79 -0
- package/src/secrets/document.ts +1 -1
- package/src/server/attachments.ts +129 -5
- package/src/server/index.ts +3 -3
- package/src/server/mcp/instructions.ts +88 -18
- 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
|
-
#
|
|
16
|
-
#
|
|
17
|
-
|
|
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
|
-
|
|
36
|
+
/.git/
|
|
27
37
|
.gitignore
|
|
28
|
-
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
52
|
+
/.lanes/
|
|
53
|
+
/.claude/
|
|
54
|
+
/.vscode/
|
|
55
|
+
/.idea/
|
|
46
56
|
.DS_Store
|
|
47
|
-
|
|
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)
|
|
5
5
|
[](https://github.com/lanes-sh/link/actions/workflows/ci.yml)
|
|
6
6
|
|
|
7
|
-
**One
|
|
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,
|
|
10
|
-
only you have. Every agent you use — Claude, ChatGPT, and anything else
|
|
11
|
-
|
|
12
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
103
|
-
never anything to authorise. Memory, tasks, and skills are plain Markdown
|
|
104
|
-
stored under its own filename, so a text editor and an agent reach the same
|
|
105
|
-
belongs to a single profile: what you add under `work` is invisible under
|
|
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
|
-
|
|
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
|
-
| |
|
|
140
|
+
| | |
|
|
127
141
|
|---|---|
|
|
128
|
-
|
|
|
129
|
-
|
|
|
130
|
-
|
|
|
131
|
-
|
|
|
132
|
-
|
|
|
133
|
-
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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
|
|
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
|
-
[
|
|
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
|
-
**[
|
|
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
|
|
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
|
|
190
|
-
- **[Add it to your agent](docs/clients
|
|
191
|
-
- **[Deploy to your own cloud](docs/deploy
|
|
192
|
-
- **[Every command](docs/
|
|
193
|
-
- **[Full reference](docs/
|
|
194
|
-
|
|
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/
|
|
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.
|
|
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",
|
package/src/audit/conformance.ts
CHANGED
|
@@ -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/
|
|
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/
|
|
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
|