@lanes-sh/link 0.7.1 → 0.8.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/README.md +20 -10
- package/instructions/agents/lanes-link-scout.md +2 -2
- package/instructions/skills/lanes-link/SKILL.md +136 -61
- package/package.json +2 -1
- package/src/audit/index.ts +8 -1
- package/src/auth/index.ts +58 -2
- package/src/auth/lanes/assertion.ts +256 -0
- package/src/auth/lanes/callback.ts +135 -0
- package/src/auth/lanes/federation.ts +50 -0
- package/src/auth/lanes/login.ts +294 -0
- package/src/auth/lanes/members.ts +103 -0
- package/src/auth/lanes/session.ts +97 -0
- package/src/auth/oauth/grant.ts +183 -0
- package/src/auth/oauth/result.ts +27 -0
- package/src/auth/oauth/server.ts +176 -203
- package/src/auth/oauth/store.ts +65 -0
- package/src/auth/remote.ts +32 -9
- package/src/cli/accepts.ts +108 -0
- package/src/cli/argv.ts +57 -3
- package/src/cli/audit-change.ts +140 -0
- package/src/cli/brand.ts +39 -10
- package/src/cli/callback-page.ts +37 -104
- package/src/cli/commands/auth-dispatch.ts +48 -0
- package/src/cli/commands/auth.ts +229 -0
- package/src/cli/commands/connect/accounts.ts +4 -4
- package/src/cli/commands/connect/authorise.ts +4 -4
- package/src/cli/commands/connect/bind-credential.ts +2 -1
- package/src/cli/commands/connect/custom/index.ts +1 -1
- package/src/cli/commands/connect/custom/write.ts +2 -2
- package/src/cli/commands/connect/grant.ts +29 -14
- package/src/cli/commands/connect/index.ts +88 -87
- package/src/cli/commands/connect/options.ts +83 -0
- package/src/cli/commands/connect/registration.ts +50 -0
- package/src/cli/commands/connect/requirements.ts +1 -1
- package/src/cli/commands/connect/settle.ts +4 -2
- package/src/cli/commands/connect/target-note.ts +7 -2
- package/src/cli/commands/connect/unknown.ts +1 -1
- package/src/cli/commands/connect/variables.ts +3 -2
- package/src/cli/commands/connection-list.ts +116 -0
- package/src/cli/commands/connection.ts +182 -165
- package/src/cli/commands/grant.ts +140 -0
- package/src/cli/commands/identity.ts +21 -9
- package/src/cli/commands/knowledge/index.ts +46 -79
- package/src/cli/commands/knowledge/migrate.ts +74 -13
- package/src/cli/commands/knowledge/show.ts +92 -0
- package/src/cli/commands/knowledge.ts +2 -1
- package/src/cli/commands/mcp/harnesses.ts +30 -8
- package/src/cli/commands/mcp/onboarding.ts +86 -0
- package/src/cli/commands/mcp/register.ts +16 -2
- package/src/cli/commands/mcp.ts +1 -0
- package/src/cli/commands/members.ts +288 -0
- package/src/cli/commands/operate/attach.ts +3 -3
- package/src/cli/commands/operate/audit.ts +11 -7
- package/src/cli/commands/operate/auth.ts +28 -11
- package/src/cli/commands/operate/findings.ts +2 -1
- package/src/cli/commands/operate/inspect.ts +37 -19
- package/src/cli/commands/operate/migrate.ts +29 -12
- package/src/cli/commands/operate/outputs.ts +3 -3
- package/src/cli/commands/operate/pair-certificate.ts +141 -0
- package/src/cli/commands/operate/pair.ts +324 -0
- package/src/cli/commands/operate/policy.ts +73 -22
- package/src/cli/commands/operate/serve.ts +52 -4
- package/src/cli/commands/operate/status.ts +18 -10
- package/src/cli/commands/operate/tools.ts +2 -2
- package/src/cli/commands/operate.ts +2 -0
- package/src/cli/commands/owner/shared.ts +13 -2
- package/src/cli/commands/owner/skills.ts +28 -8
- package/src/cli/commands/profile/removal.ts +79 -76
- package/src/cli/commands/profile/remove.ts +16 -1
- package/src/cli/commands/profile.ts +46 -10
- package/src/cli/commands/relabel.ts +112 -0
- package/src/cli/commands/secrets.ts +34 -12
- package/src/cli/commands/set-workspace.ts +96 -0
- package/src/cli/commands/setup.ts +2 -2
- package/src/cli/commands/sync.ts +8 -8
- package/src/cli/commands/target.ts +9 -7
- package/src/cli/commands/update.ts +58 -17
- package/src/cli/config-edit.ts +75 -140
- package/src/cli/config-migrate.ts +82 -64
- package/src/cli/config-repair.ts +89 -32
- package/src/cli/config-templates.ts +198 -0
- package/src/cli/contract3-data.ts +328 -0
- package/src/cli/contract3-shape.ts +186 -0
- package/src/cli/contract3.ts +282 -0
- package/src/cli/endpoint-url.ts +1 -1
- package/src/cli/lanes.ts +25 -1
- package/src/cli/main.ts +89 -14
- package/src/cli/migrate-plan.ts +12 -6
- package/src/cli/output.ts +34 -1
- package/src/cli/publish.ts +5 -2
- package/src/cli/runtime/open.ts +63 -98
- package/src/cli/runtime/registry.ts +6 -7
- package/src/cli/runtime/stores.ts +53 -0
- package/src/cli/runtime/types.ts +106 -0
- package/src/cli/runtime/vault.ts +19 -4
- package/src/cli/runtime/workspace.ts +60 -0
- package/src/cli/runtime.ts +2 -1
- package/src/cli/selection-require.ts +44 -13
- package/src/cli/selection.ts +127 -145
- package/src/cli/usage.ts +34 -18
- package/src/cli/workspace-migrate.ts +125 -16
- package/src/connectivity/manifest/provider.ts +3 -1
- package/src/connectivity/manifest/requirements.ts +1 -1
- package/src/deployments/bind.ts +1 -1
- package/src/deployments/deploy.ts +36 -27
- package/src/deployments/gcp/bucket.ts +18 -7
- package/src/deployments/gcp/provision.ts +7 -7
- package/src/deployments/prepare.ts +72 -24
- package/src/deployments/record.ts +1 -1
- package/src/deployments/report.ts +2 -2
- package/src/deployments/serving.ts +15 -74
- package/src/deployments/target.ts +15 -15
- package/src/deployments/upload.ts +46 -22
- package/src/dispatch/deps.ts +88 -0
- package/src/dispatch/dispatch.ts +21 -62
- package/src/policy/index.ts +47 -15
- package/src/profile/connections.ts +183 -0
- package/src/profile/deployments.ts +3 -3
- package/src/profile/index.ts +30 -5
- package/src/profile/layout.ts +86 -89
- package/src/profile/load.ts +80 -47
- package/src/profile/pairing.ts +32 -0
- package/src/profile/primitives.ts +35 -1
- package/src/profile/registry.ts +6 -6
- package/src/profile/schema.ts +172 -21
- package/src/profile/targets.ts +21 -9
- package/src/profile/testing.ts +69 -2
- package/src/profile/workspace.ts +58 -3
- package/src/providers/custom/index.ts +1 -1
- package/src/providers/custom/load.ts +2 -3
- package/src/providers/identity/provider.ts +1 -1
- package/src/providers/memory/provider.ts +20 -2
- package/src/providers/setup/plan.ts +1 -1
- package/src/providers/slack/index.ts +2 -2
- package/src/registry/policy-bridge.ts +33 -11
- package/src/registry/reconcile.ts +4 -4
- package/src/server/authorization.ts +94 -0
- package/src/server/edge.ts +14 -1
- package/src/server/endpoint.ts +85 -104
- package/src/server/generation.ts +10 -1
- package/src/server/harness.ts +71 -13
- package/src/server/index.ts +31 -0
- package/src/server/mcp/build.ts +20 -1
- package/src/server/mcp/client-info.ts +54 -0
- package/src/server/mcp/guide.ts +120 -0
- package/src/server/mcp/instructions.ts +1 -1
- package/src/server/mcp/prompts.ts +7 -3
- package/src/server/mcp/resources.ts +16 -8
- package/src/server/mcp/tools.ts +9 -3
- package/src/server/mcp/visibility.ts +18 -3
- package/src/server/oauth.ts +29 -75
- package/src/server/read/credential.ts +134 -0
- package/src/server/read/deployed.ts +56 -0
- package/src/server/read/listener.ts +54 -0
- package/src/server/read/open.ts +101 -0
- package/src/server/read/routes.ts +247 -0
- package/src/server/read/state.ts +171 -0
package/README.md
CHANGED
|
@@ -51,12 +51,14 @@ middle of your data.
|
|
|
51
51
|
|
|
52
52
|
## Quickstart
|
|
53
53
|
|
|
54
|
-
Needs [Bun](https://bun.com) 1.3.11
|
|
54
|
+
Needs [Bun](https://bun.com) 1.3.11+, and a Lanes sign-in.
|
|
55
55
|
|
|
56
56
|
```console
|
|
57
57
|
$ bun install -g @lanes-sh/link # puts `lanes` on your PATH
|
|
58
|
-
$ lanes
|
|
59
|
-
$ lanes link
|
|
58
|
+
$ lanes auth login # opens a browser once
|
|
59
|
+
$ lanes link profile add personal --workspace local
|
|
60
|
+
$ lanes link profile members add --me --profile personal --workspace local
|
|
61
|
+
$ lanes link start --profile personal --workspace local
|
|
60
62
|
ok serving http://127.0.0.1:7337/mcp
|
|
61
63
|
profiles: personal
|
|
62
64
|
```
|
|
@@ -64,7 +66,7 @@ ok serving http://127.0.0.1:7337/mcp
|
|
|
64
66
|
Then, in another shell:
|
|
65
67
|
|
|
66
68
|
```console
|
|
67
|
-
$ lanes link mcp add --profile personal --
|
|
69
|
+
$ lanes link mcp add --profile personal --workspace local # every agent installed; or name one: claude, codex
|
|
68
70
|
ok registered lanes-link with Claude Code (user scope)
|
|
69
71
|
ok registered lanes-link with Codex
|
|
70
72
|
```
|
|
@@ -73,14 +75,22 @@ Your agents can now use it. Memory, tasks, files, skills, and the vault hold you
|
|
|
73
75
|
rather than an account, so they are already there — nothing to connect, no credentials, no browser.
|
|
74
76
|
Mail and calendar are the next step. **[Full quickstart →](https://lanes.sh/docs/link/quickstart)**
|
|
75
77
|
|
|
78
|
+
**Why the sign-in.** A profile declares who may consume it, and there is nothing to check that
|
|
79
|
+
against if the endpoint has no idea who is asking. That is a real dependency for a self-hostable
|
|
80
|
+
tool and worth stating plainly; what it is not is a dependency per request. The network is needed
|
|
81
|
+
to sign in and to refresh, and a machine offline for a day keeps serving. `lanes link token
|
|
82
|
+
show` still mints a static token for CI, which has no browser to sign in with.
|
|
83
|
+
|
|
76
84
|
## In the Lanes desktop app
|
|
77
85
|
|
|
78
86
|
Prefer not to use a terminal? The [Lanes desktop app](https://lanes.sh/desktop) drives this CLI from
|
|
79
|
-
a settings page. **Settings → Integrations → Lanes Link** installs it, holds the profile and
|
|
80
|
-
every command runs against,
|
|
81
|
-
|
|
87
|
+
a settings page. **Settings → Integrations → Lanes Link** installs it, holds the profile and
|
|
88
|
+
workspace every command runs against, starts and stops the endpoint, and registers it with Claude
|
|
89
|
+
Code or Codex. From 0.8.0 your connections, profiles and audit log are on the
|
|
90
|
+
[Lanes dashboard](https://lanes.sh/dashboard/link) instead, which reads your endpoint directly over
|
|
91
|
+
loopback: run `lanes link pair` once to let it.
|
|
82
92
|
|
|
83
|
-

|
|
84
94
|
|
|
85
95
|
It runs the commands above rather than reimplementing them, so consent and the token stay here where
|
|
86
96
|
they belong, and an endpoint set up in the app is the same one you get from a shell. Available from
|
|
@@ -183,7 +193,7 @@ the whole inventory by connector and credential type:
|
|
|
183
193
|
|
|
184
194
|
The same code, the same config, in all three. Only the storage adapters change.
|
|
185
195
|
|
|
186
|
-
| | **Local** | **
|
|
196
|
+
| | **Local** | **Self-Hosted** | **Lanes Cloud** |
|
|
187
197
|
|---|---|---|---|
|
|
188
198
|
| Runs on | your machine | your GCP project, on Cloud Run | managed for you |
|
|
189
199
|
| Needs | Bun, nothing else | a Google Cloud billing account | — |
|
|
@@ -191,7 +201,7 @@ The same code, the same config, in all three. Only the storage adapters change.
|
|
|
191
201
|
| Reachable from | that machine | anywhere, including your phone | anywhere |
|
|
192
202
|
| Status | ready | ready | **coming soon** |
|
|
193
203
|
|
|
194
|
-
**Local** is the fastest way to start, and where most people stay. **
|
|
204
|
+
**Local** is the fastest way to start, and where most people stay. **Self-Hosted** is what you
|
|
195
205
|
want if you need to reach it from claude.ai, ChatGPT, or a phone — `lanes link deploy` creates the
|
|
196
206
|
project, the bucket, the service account, and the revision on its first run. **Lanes Cloud** is the
|
|
197
207
|
managed version; because it is the same data model, a workspace you build today moves across rather
|
|
@@ -67,8 +67,8 @@ to every future session.
|
|
|
67
67
|
stops a write is policy on the endpoint:
|
|
68
68
|
|
|
69
69
|
```console
|
|
70
|
-
$ lanes link policy deny memory.write --profile <name> --
|
|
71
|
-
$ lanes link policy list --profile <name> --
|
|
70
|
+
$ lanes link policy deny memory.write --connection memory.main --profile <name> --workspace <name>
|
|
71
|
+
$ lanes link policy list --profile <name> --workspace <name>
|
|
72
72
|
```
|
|
73
73
|
|
|
74
74
|
If you are running against a profile that grants writes, that is the owner's
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: lanes-link
|
|
3
|
-
description: Use when the user refers to their own accounts, knowledge, procedures, or secrets through Lanes Link — "check my mail", "what do I know about X", "remember this", "which profile am I in" — or asks to connect, register, or set up their Lanes Link MCP server with this agent. Also covers operating the workspace from a shell — adding or removing a profile,
|
|
3
|
+
description: Use when the user refers to their own accounts, knowledge, procedures, or secrets through Lanes Link — "check my mail", "what do I know about X", "remember this", "which profile am I in" — or asks to connect, register, or set up their Lanes Link MCP server with this agent. Also covers operating the workspace from a shell — signing in, adding a connection, granting it to a profile, adding or removing a profile, deciding who may consume one, deploying an endpoint or recovering a lost deployment — and what to do when a Lanes Link call is refused, or when the endpoint is not running.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Lanes Link
|
|
@@ -8,42 +8,63 @@ description: Use when the user refers to their own accounts, knowledge, procedur
|
|
|
8
8
|
A self-hostable gateway to one person's own context: the accounts they have
|
|
9
9
|
connected, the knowledge they have accumulated, the procedures they have
|
|
10
10
|
written down, and their secrets. One endpoint serves every profile in a
|
|
11
|
-
workspace
|
|
11
|
+
workspace, and each call names which profile it means.
|
|
12
12
|
|
|
13
13
|
The endpoint describes its own live state when you connect — which profiles
|
|
14
|
-
exist, what is reachable in each
|
|
15
|
-
|
|
14
|
+
exist, what is reachable in each — and serves the long form of this at
|
|
15
|
+
`lanes://instructions`. This file is the part that does not change: how to
|
|
16
|
+
behave with it.
|
|
17
|
+
|
|
18
|
+
## Three words, and they changed in 0.8.0
|
|
19
|
+
|
|
20
|
+
A **connection** is one authorised account, and it belongs to the **workspace**.
|
|
21
|
+
A **profile** is a selection: which connections it includes, what it allows on
|
|
22
|
+
each, and who may consume it.
|
|
23
|
+
|
|
24
|
+
That ordering is the change. A connection used to live inside one profile, so
|
|
25
|
+
reaching the same mailbox from two of them meant authorising it twice, and every
|
|
26
|
+
account of a provider within a profile was governed identically. Now the account
|
|
27
|
+
is authorised once and each profile decides what it may do with it, which is what
|
|
28
|
+
makes "read this mailbox, write that calendar" something a person can write down.
|
|
29
|
+
|
|
30
|
+
**"Target" is gone.** It named the thing a workspace already was. `--workspace`
|
|
31
|
+
is the flag; `--target` still works for one minor and warns.
|
|
16
32
|
|
|
17
33
|
## Profiles are a boundary, not a setting
|
|
18
34
|
|
|
19
35
|
Every tool takes `profile` and `connection`. Profiles are how someone keeps work
|
|
20
|
-
and personal apart; they
|
|
36
|
+
and personal apart; a connection they do not grant is not visible in one at all.
|
|
21
37
|
|
|
22
38
|
**Ask which profile is meant when it is ambiguous. Do not default to whichever
|
|
23
39
|
is listed first.** Quietly picking one crosses the line the profile exists to
|
|
24
40
|
draw. There is no "current profile" to switch — the choice is made per call, and
|
|
25
|
-
`lanes link profile list --
|
|
26
|
-
profile lives in exactly one, so `personal` on `local` and
|
|
27
|
-
are two profiles that share a name rather than one profile
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
41
|
+
`lanes link profile list --workspace <name>` shows what exists *in that
|
|
42
|
+
workspace*. A profile lives in exactly one, so `personal` on `local` and
|
|
43
|
+
`personal` on `cloud` are two profiles that share a name rather than one profile
|
|
44
|
+
in two places.
|
|
45
|
+
|
|
46
|
+
**What a command must be told is never inferred from a profile — but the
|
|
47
|
+
workspace may have a default.** `lanes set-workspace <name>` writes one, every
|
|
48
|
+
command that uses it echoes the name it resolved, and the commands where being
|
|
49
|
+
wrong is expensive refuse it and demand the flag: `deploy`, `sync`,
|
|
50
|
+
`secrets push`, `profile remove`, `disconnect`, `token rotate`. Passing a flag a
|
|
32
51
|
command does not read is refused too, which makes "add both to be safe" its own
|
|
33
52
|
failure. Four levels:
|
|
34
53
|
|
|
35
|
-
- **Neither.** `lanes link
|
|
36
|
-
`lanes link version`.
|
|
37
|
-
- **`--
|
|
38
|
-
`lanes link profile remove`, `lanes link
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
54
|
+
- **Neither.** `lanes link workspace list`, `lanes link mcp list`,
|
|
55
|
+
`lanes link version`, `lanes link mcp install-instructions`.
|
|
56
|
+
- **`--workspace` alone.** `lanes link profile list`, `lanes link profile add`,
|
|
57
|
+
`lanes link profile remove`, `lanes link workspace show`,
|
|
58
|
+
`lanes link connection list`. A connection and a profile both live inside one
|
|
59
|
+
workspace, so listing or creating either names which.
|
|
60
|
+
- **`--workspace`, with the profiles derived from it.** `lanes link status`,
|
|
61
|
+
`lanes link deploy` and `lanes link sync workspaces` act on one endpoint
|
|
62
|
+
serving every profile in that workspace. `--profile` is accepted and *narrows*
|
|
43
63
|
the answer; it does not choose the subject.
|
|
44
64
|
- **Both.** Everything acting on one account, and everything reaching the
|
|
45
65
|
owner's own stores: `lanes link connect`, `lanes link token rotate`,
|
|
46
|
-
`lanes link secrets set`, `lanes link policy allow`, `lanes link
|
|
66
|
+
`lanes link secrets set`, `lanes link policy allow`, `lanes link grant add`,
|
|
67
|
+
`lanes link profile members`, `lanes link memory list`,
|
|
47
68
|
`lanes link tasks list`, `lanes link assets list`, `lanes link mcp add`.
|
|
48
69
|
|
|
49
70
|
`lanes link profile add` and `lanes link profile remove` **reject** `--profile`.
|
|
@@ -53,9 +74,12 @@ disagree with it.
|
|
|
53
74
|
When you write a command out for the owner, fill in what that command needs or
|
|
54
75
|
leave it as `<name>` for them to complete — never drop a required one.
|
|
55
76
|
|
|
56
|
-
A `connection` names an account
|
|
57
|
-
several of the same kind
|
|
58
|
-
|
|
77
|
+
A `connection` names an account the profile grants. One profile may grant
|
|
78
|
+
several of the same kind and govern each differently, so `gmail.work` may be
|
|
79
|
+
readable where `gmail.personal` is writable. Naming a connection the profile does
|
|
80
|
+
not grant is refused rather than guessed at, and a connection it does not grant
|
|
81
|
+
is absent from the enum entirely: if you cannot see it there, it was not
|
|
82
|
+
withheld by accident.
|
|
59
83
|
|
|
60
84
|
## Which store a thing goes in
|
|
61
85
|
|
|
@@ -90,7 +114,7 @@ try more than one wording before deciding it is not there.
|
|
|
90
114
|
Writing is a separate grant, and it should be. What you write is served back to
|
|
91
115
|
every later session, including to a different agent, so **write when you are
|
|
92
116
|
asked to remember something, not as a habit.** The owner reaches the same
|
|
93
|
-
entries with `lanes link memory list --profile <name> --
|
|
117
|
+
entries with `lanes link memory list --profile <name> --workspace <name>` and a text editor.
|
|
94
118
|
|
|
95
119
|
## Tasks have a status, so finish them rather than deleting them
|
|
96
120
|
|
|
@@ -116,7 +140,7 @@ being suggested again next week. Remove is for something recorded by mistake.
|
|
|
116
140
|
Do not mute a task on your own initiative. It means "stop telling me about
|
|
117
141
|
this", which is the owner's judgement, not yours.
|
|
118
142
|
|
|
119
|
-
They manage these with `lanes link tasks list --profile <name> --
|
|
143
|
+
They manage these with `lanes link tasks list --profile <name> --workspace <name>`.
|
|
120
144
|
|
|
121
145
|
## Assets are files kept by name
|
|
122
146
|
|
|
@@ -131,11 +155,11 @@ no form of a read that hands you a PDF, and a megabyte of base64 in the
|
|
|
131
155
|
conversation would not help you if there were.
|
|
132
156
|
|
|
133
157
|
To attach a stored file to something you are sending, ask the owner to run
|
|
134
|
-
`lanes link attach <file> --profile <name> --
|
|
158
|
+
`lanes link attach <file> --profile <name> --workspace <name> --connection <provider>.<account>`,
|
|
135
159
|
which prints a handle the send tools take. An asset's own store is not reachable
|
|
136
160
|
from a mailbox's, deliberately.
|
|
137
161
|
|
|
138
|
-
They manage these with `lanes link assets list --profile <name> --
|
|
162
|
+
They manage these with `lanes link assets list --profile <name> --workspace <name>`.
|
|
139
163
|
|
|
140
164
|
## Skills are theirs, not yours
|
|
141
165
|
|
|
@@ -145,7 +169,7 @@ cannot read a skill's body; that is deliberate, not a gap to work around.
|
|
|
145
169
|
|
|
146
170
|
So when a task has a skill for it, **say the skill exists and let them invoke
|
|
147
171
|
it** rather than improvising your own version of their procedure. They manage
|
|
148
|
-
these with `lanes link skills list --profile <name> --
|
|
172
|
+
these with `lanes link skills list --profile <name> --workspace <name>` and `lanes link skills show <skill> --profile <name> --workspace <name>`.
|
|
149
173
|
|
|
150
174
|
## Vault values are credentials
|
|
151
175
|
|
|
@@ -171,7 +195,7 @@ crossing is the specific mistake this exists to prevent.
|
|
|
171
195
|
|
|
172
196
|
If the tool is not there, the profile has declared nothing. Ask rather than
|
|
173
197
|
inventing something; they add one with `lanes link identity add <kind> <value> --profile <profile>
|
|
174
|
-
--
|
|
198
|
+
--workspace <name>` — both flags, because neither has a fallback.
|
|
175
199
|
Nothing you can call writes here, deliberately.
|
|
176
200
|
|
|
177
201
|
## Who you are writing *to* is declared as well
|
|
@@ -228,7 +252,7 @@ its content.
|
|
|
228
252
|
|
|
229
253
|
**If the endpoint is not on the same machine as the file**, `path` names the
|
|
230
254
|
*server's* filesystem rather than theirs, and will not find it. Ask them to run
|
|
231
|
-
`lanes link attach <file> --profile <name> --
|
|
255
|
+
`lanes link attach <file> --profile <name> --workspace <name> --connection <provider>.<account>`, which prints a
|
|
232
256
|
handle to use instead.
|
|
233
257
|
|
|
234
258
|
**`draft_only: true`** saves instead of sending, where they should see it before
|
|
@@ -248,9 +272,9 @@ granted, and retrying will not reveal it. A call that *is* refused was refused b
|
|
|
248
272
|
policy on purpose.
|
|
249
273
|
|
|
250
274
|
Report it plainly and let the owner decide whether to widen the grant —
|
|
251
|
-
`lanes link policy list --profile <name> --
|
|
275
|
+
`lanes link policy list --profile <name> --workspace <name>` shows the rules, `lanes link policy allow <capability> --connection <provider>.<id> --profile <name> --workspace <name>`
|
|
252
276
|
changes them, and that is their call, not yours. **Do not look for another route
|
|
253
|
-
to the same data.** Every call is audited either way; `lanes link audit tail --profile <name> --
|
|
277
|
+
to the same data.** Every call is audited either way; `lanes link audit tail --profile <name> --workspace <name>`
|
|
254
278
|
shows what was attempted, refusals included.
|
|
255
279
|
|
|
256
280
|
## Setting something up
|
|
@@ -267,6 +291,21 @@ one is missing, it was switched off with a `deny`, which is their decision; do n
|
|
|
267
291
|
offer to connect it. What setup is for is accounts: mail, calendar, files,
|
|
268
292
|
issues.
|
|
269
293
|
|
|
294
|
+
**Connecting authorises once; a grant is what a second profile needs.**
|
|
295
|
+
`lanes link connect <provider> --profile <name> --workspace <name>` authorises
|
|
296
|
+
the account into the workspace *and* grants it to the profile you named. Reaching
|
|
297
|
+
the same account from another profile is then a grant rather than a second
|
|
298
|
+
consent screen, which is the point of connections belonging to the workspace:
|
|
299
|
+
|
|
300
|
+
```
|
|
301
|
+
lanes link grant add gmail.<id> --profile work --workspace local
|
|
302
|
+
lanes link policy allow gmail.users.messages.list --connection gmail.<id> --profile work --workspace local
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
`lanes link connection list --workspace <name>` shows every connection with the
|
|
306
|
+
profiles that grant it, which is the answer when an owner says an account they
|
|
307
|
+
connected is not showing up somewhere.
|
|
308
|
+
|
|
270
309
|
**Take the command from `setup_provider`; never compose one yourself.** It carries
|
|
271
310
|
the right profile and, where the provider stores a credential per account, the
|
|
272
311
|
`--id` it needs. A command you assembled is one the owner pastes and watches fail.
|
|
@@ -276,9 +315,9 @@ credential — those are `lanes link` in a terminal, deliberately. What you can
|
|
|
276
315
|
know exactly what to hand over, and say what it will ask for before they start.
|
|
277
316
|
|
|
278
317
|
If you have a shell, you can run it yourself for anything that does *not* need a
|
|
279
|
-
browser: `lanes link setup plan <provider> --profile <name> --
|
|
280
|
-
`lanes link secrets set <ref> --profile <name> --
|
|
281
|
-
`lanes link connect <provider> --profile <name> --
|
|
318
|
+
browser: `lanes link setup plan <provider> --profile <name> --workspace <name> --json` lists what to store,
|
|
319
|
+
`lanes link secrets set <ref> --profile <name> --workspace <name>` takes the value on stdin, and
|
|
320
|
+
`lanes link connect <provider> --profile <name> --workspace <name> --id <id> --non-interactive --json` finishes.
|
|
282
321
|
Anything with a browser sign-in belongs to whoever owns the account — give them
|
|
283
322
|
the line.
|
|
284
323
|
|
|
@@ -286,7 +325,7 @@ the line.
|
|
|
286
325
|
it needs, print the scopes and let the owner add the flag. Deciding that is theirs.
|
|
287
326
|
|
|
288
327
|
**A new connection is served at once; the tools you were handed are not.**
|
|
289
|
-
Connecting publishes the config to wherever that
|
|
328
|
+
Connecting publishes the config to wherever that workspace's endpoint reads it and
|
|
290
329
|
asks the endpoint to re-read it, so a `setup_overview` straight after connecting
|
|
291
330
|
*does* show the account. What has not changed is the set of tools this session
|
|
292
331
|
was given when it connected — the endpoint does not announce that its tools
|
|
@@ -302,15 +341,15 @@ endpoint restarted before the new token opens anything.
|
|
|
302
341
|
|
|
303
342
|
If you have a shell, the commands that only *read* are yours to run without
|
|
304
343
|
asking: `lanes link status`, `lanes link check`, `lanes link plan`,
|
|
305
|
-
`lanes link doctor`, `lanes link profile list`, `lanes link
|
|
306
|
-
`lanes link
|
|
344
|
+
`lanes link doctor`, `lanes link profile list`, `lanes link workspace list`,
|
|
345
|
+
`lanes link workspace show`, `lanes link tools`, `lanes link config show` and
|
|
307
346
|
`lanes link audit tail`. None writes config, opens a browser, or costs anything,
|
|
308
347
|
and running one beats asking the owner to paste its output back. Give each what
|
|
309
348
|
its own level requires — they are not all the same, and the four levels are at
|
|
310
349
|
the top of this file.
|
|
311
350
|
|
|
312
351
|
**A command that writes runs `--dry-run` first, where it has one.** Show what it
|
|
313
|
-
reported and wait for an answer. `lanes link deploy`, `lanes link sync
|
|
352
|
+
reported and wait for an answer. `lanes link deploy`, `lanes link sync workspaces`,
|
|
314
353
|
`lanes link profile remove`, `lanes link secrets push` and `lanes link mcp add`
|
|
315
354
|
all take it. For a write with no dry run — `lanes link token rotate`,
|
|
316
355
|
`lanes link policy allow`, `lanes link secrets set` — say in one sentence what it
|
|
@@ -320,27 +359,63 @@ whoever owns the account, as above.
|
|
|
320
359
|
**`--json` is not everywhere.** It parses on every command and is read by only
|
|
321
360
|
some, so one that ignores it prints its ordinary output and gives you nothing to
|
|
322
361
|
key on — do not treat the absence of JSON as a failure. `status`, `doctor`,
|
|
323
|
-
`tools`, `outputs`, `sync
|
|
362
|
+
`tools`, `outputs`, `sync workspaces`, `workspace list`, `workspace show`, `profile list`,
|
|
324
363
|
`connect` and `setup plan` implement it. `deploy`, `check`, `plan`,
|
|
325
364
|
`config show`, `audit tail` and every `mcp` subcommand do not.
|
|
326
365
|
|
|
327
366
|
**A profile is created and removed, never switched.** `lanes link profile add
|
|
328
|
-
<name> --
|
|
329
|
-
|
|
330
|
-
`lanes link profile remove <name> --
|
|
367
|
+
<name> --workspace <name>` writes a new one *into that workspace* — one
|
|
368
|
+
workspace, not a list, because a profile lives in exactly one.
|
|
369
|
+
`lanes link profile remove <name> --workspace <name>` takes `--dry-run` and then
|
|
331
370
|
`--yes`, and removes the profile itself along with its stores: the file is in
|
|
332
371
|
that workspace, so there is nowhere left for it to survive. Neither reads
|
|
333
372
|
`--profile`; both name the profile positionally.
|
|
334
373
|
|
|
335
|
-
There is no current profile
|
|
336
|
-
|
|
374
|
+
There is no current profile. There *is* a default workspace, written by
|
|
375
|
+
`lanes set-workspace`, echoed by every command that uses it and refused by every
|
|
376
|
+
command that publishes or destroys. The two commands that used to pin a
|
|
377
|
+
profile or a workspace are gone and refuse with an explanation — if you
|
|
337
378
|
meet one of those refusals, it is not a broken install, and there is no
|
|
338
379
|
replacement to find. The choice is made per command, on purpose.
|
|
339
380
|
|
|
381
|
+
## Signing in, and who may consume a profile
|
|
382
|
+
|
|
383
|
+
Every human caller signs in with Lanes, on a local endpoint as much as a deployed
|
|
384
|
+
one. `lanes auth login` opens a browser once; `lanes auth status` says who this
|
|
385
|
+
machine is and when the session lapses. The network is needed to sign in and to
|
|
386
|
+
refresh, not per call, so a machine offline for a day keeps serving.
|
|
387
|
+
|
|
388
|
+
`lanes link start` refuses without a session, and names the command.
|
|
389
|
+
|
|
390
|
+
A profile declares who may consume it, and **empty means nobody**:
|
|
391
|
+
|
|
392
|
+
```
|
|
393
|
+
lanes link profile members add --me --profile assistant --workspace local
|
|
394
|
+
lanes link profile members list --profile assistant --workspace local
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
That list is a selection from the Lanes workspace rather than a second list
|
|
398
|
+
beside it, so `members list` shows both who may consume this profile and who is
|
|
399
|
+
in the workspace and could be given it. Somebody with an unaccepted invitation
|
|
400
|
+
has no subject yet, so they are listed, marked, and refused; the answer there is
|
|
401
|
+
for them to accept, not for anyone to invent a subject.
|
|
402
|
+
|
|
403
|
+
Removing somebody does not end a session they already hold: membership is read
|
|
404
|
+
when a token is minted. Rotating the endpoint token closes that
|
|
405
|
+
window now, and it names both flags: `lanes link token rotate --profile <name>
|
|
406
|
+
--workspace <name>`.
|
|
407
|
+
|
|
408
|
+
**A client is not given a token any more.** `lanes link mcp add` registers the
|
|
409
|
+
bare URL, and the client discovers the endpoint, sends its owner to sign in, and
|
|
410
|
+
comes back with a token of its own. The static token is for CI, which has no
|
|
411
|
+
browser, and `--headless` is what writes it into a registration. If you are
|
|
412
|
+
writing a registration command for somebody, do not add an `Authorization`
|
|
413
|
+
header: it is no longer how a client connects.
|
|
414
|
+
|
|
340
415
|
## Deploying, and what it decides
|
|
341
416
|
|
|
342
417
|
`lanes link deploy` builds an image and rolls a revision. Its subject is a
|
|
343
|
-
**
|
|
418
|
+
**workspace**, and the profiles behind it are every profile *in* that
|
|
344
419
|
workspace, so one deploy serves all of them — there is no per-profile deploy to
|
|
345
420
|
run and no reason to loop over them.
|
|
346
421
|
|
|
@@ -352,9 +427,9 @@ avoidable.
|
|
|
352
427
|
Two things it refuses to guess, and both are the owner's to answer:
|
|
353
428
|
|
|
354
429
|
- **Whose bearer token opens the endpoint.** One token reaches every profile
|
|
355
|
-
behind that
|
|
430
|
+
behind that workspace, so this decides who gets in. With several candidates and
|
|
356
431
|
nothing recorded, it refuses and prints the command that names one.
|
|
357
|
-
- **A first deploy.** A
|
|
432
|
+
- **A first deploy.** A workspace that does not exist yet has nothing to derive
|
|
358
433
|
a set from, so `--profile` is required there. It may be repeated, and the first
|
|
359
434
|
one named is the primary.
|
|
360
435
|
|
|
@@ -368,13 +443,13 @@ connected and not how a config change lands; both of those publish themselves.
|
|
|
368
443
|
|
|
369
444
|
## When a machine has lost track of a deployment
|
|
370
445
|
|
|
371
|
-
A workspace holds a *pointer* to each
|
|
446
|
+
A workspace holds a *pointer* to each workspace it does not itself hold, and a
|
|
372
447
|
machine can lose one — a new laptop, a reinstall, a workspace file restored from
|
|
373
448
|
something older. The endpoint is still serving; what went missing is the line
|
|
374
449
|
saying where it lives. The symptom is a `lanes link status` that reports nothing
|
|
375
|
-
for a
|
|
450
|
+
for a workspace you know is up.
|
|
376
451
|
|
|
377
|
-
`lanes link sync
|
|
452
|
+
`lanes link sync workspaces` adopts it. `--discover` looks for a deployment the
|
|
378
453
|
workspace has no pointer to, `--from <location>` names one directly, and
|
|
379
454
|
`--dry-run` reports what it would write without writing it. Run the dry run and
|
|
380
455
|
show it.
|
|
@@ -386,10 +461,10 @@ existed in two copies that could disagree, and there is one copy now.
|
|
|
386
461
|
|
|
387
462
|
## Registering it, and re-registering it
|
|
388
463
|
|
|
389
|
-
`lanes link mcp add --profile <name> --
|
|
464
|
+
`lanes link mcp add --profile <name> --workspace <name>` runs each harness's own registration command and installs
|
|
390
465
|
this skill where that harness keeps them. With no argument it does every harness
|
|
391
466
|
installed; name one (`claude`, `codex`) to be specific. Run it again after
|
|
392
|
-
`lanes link token rotate --profile <name> --
|
|
467
|
+
`lanes link token rotate --profile <name> --workspace <name>` — add `--force`, since Claude Code stores the token as
|
|
393
468
|
a value rather than a command.
|
|
394
469
|
|
|
395
470
|
**Never paste the token.** There is a right way and a wrong way, and the
|
|
@@ -398,7 +473,7 @@ difference matters:
|
|
|
398
473
|
```bash
|
|
399
474
|
# RIGHT — the token goes from the CLI to the harness. You never see it.
|
|
400
475
|
claude mcp add --transport http lanes-link http://127.0.0.1:7337/mcp \
|
|
401
|
-
--header "Authorization: Bearer $(lanes link token show --raw --profile <name> --
|
|
476
|
+
--header "Authorization: Bearer $(lanes link token show --raw --profile <name> --workspace <name>)"
|
|
402
477
|
|
|
403
478
|
# WRONG — the token is now in your context, and in the transcript, forever.
|
|
404
479
|
lanes link token show --show # then copying the value into the command
|
|
@@ -406,12 +481,12 @@ lanes link token show --show # then copying the value into the command
|
|
|
406
481
|
|
|
407
482
|
The token reaches every account of every profile the endpoint serves. Use the
|
|
408
483
|
substitution form. If you have already printed one by accident, say so and offer
|
|
409
|
-
`lanes link token rotate --profile <name> --
|
|
484
|
+
`lanes link token rotate --profile <name> --workspace <name>`.
|
|
410
485
|
|
|
411
|
-
Prefer `lanes link mcp add --profile <name> --
|
|
486
|
+
Prefer `lanes link mcp add --profile <name> --workspace <name>` to writing the command yourself: it checks the
|
|
412
487
|
endpoint is reachable, refuses to silently shadow an existing registration, and
|
|
413
488
|
cannot mistype the token. For a harness it does not know, take the command from
|
|
414
|
-
`lanes link outputs --profile <name> --
|
|
489
|
+
`lanes link outputs --profile <name> --workspace <name>` rather than writing it blind — that command checks whether
|
|
415
490
|
`lanes` resolves on this machine and prints a longer working form if it does
|
|
416
491
|
not, where guessing gives you an empty substitution, a `Bearer ` header, and a
|
|
417
492
|
401 that reads as a bad token.
|
|
@@ -420,7 +495,7 @@ If you register Codex, tell the user to export the token — Codex stores only t
|
|
|
420
495
|
variable name, so nothing works until it is set:
|
|
421
496
|
|
|
422
497
|
```bash
|
|
423
|
-
export LANES_LINK_TOKEN="$(lanes link token show --raw --profile <name> --
|
|
498
|
+
export LANES_LINK_TOKEN="$(lanes link token show --raw --profile <name> --workspace <name>)"
|
|
424
499
|
```
|
|
425
500
|
|
|
426
501
|
One registration covers every profile. Do not add one per profile; they share a
|
|
@@ -433,12 +508,12 @@ copy means the rules you are reading are not the ones that shipped.
|
|
|
433
508
|
|
|
434
509
|
Claude Desktop cannot be handed a URL, so it spawns the endpoint over stdio
|
|
435
510
|
instead. That one is named in the client's own config file rather than registered
|
|
436
|
-
by a command, as `lanes link mcp stdio --profile <name> --
|
|
511
|
+
by a command, as `lanes link mcp stdio --profile <name> --workspace <name>`; both
|
|
437
512
|
flags are required, and nothing may be written to stdout.
|
|
438
513
|
|
|
439
514
|
## When it is not running
|
|
440
515
|
|
|
441
|
-
`lanes link start --profile <name> --
|
|
516
|
+
`lanes link start --profile <name> --workspace <name>` runs in the foreground and
|
|
442
517
|
serves until stopped. Tell the
|
|
443
518
|
user the command rather than backgrounding it silently on their behalf.
|
|
444
519
|
Registration works while it is down — the harness simply cannot reach it yet,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lanes-sh/link",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.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",
|
|
@@ -61,6 +61,7 @@
|
|
|
61
61
|
"#audit": "./src/audit/index.ts",
|
|
62
62
|
"#audit/*": "./src/audit/*",
|
|
63
63
|
"#auth": "./src/auth/index.ts",
|
|
64
|
+
"#auth/*": "./src/auth/*",
|
|
64
65
|
"#cli/*": "./src/cli/*",
|
|
65
66
|
"#connectivity": "./src/connectivity/index.ts",
|
|
66
67
|
"#connectivity/*": "./src/connectivity/*",
|
package/src/audit/index.ts
CHANGED
|
@@ -58,11 +58,18 @@ export interface AuditEvent {
|
|
|
58
58
|
readonly principal: string;
|
|
59
59
|
|
|
60
60
|
/**
|
|
61
|
-
* The MCP `clientInfo` name, when the caller
|
|
61
|
+
* The MCP `clientInfo` name, when the caller repeated it on the request.
|
|
62
62
|
*
|
|
63
63
|
* OBSERVABILITY ONLY. This is self-reported by the client and is never
|
|
64
64
|
* consulted for authorization — it exists so you can see which agent made a
|
|
65
65
|
* call, not to decide what that agent may do.
|
|
66
|
+
*
|
|
67
|
+
* "On the request" is load-bearing and was wrong for this field's whole life:
|
|
68
|
+
* it claimed to hold the `clientInfo` name and read an `x-mcp-client` header
|
|
69
|
+
* that no MCP client sends, so it was empty on every event ever written. The
|
|
70
|
+
* name arrives in the request envelope now (`mcp/client-info.ts`). A client
|
|
71
|
+
* that announces itself only at `initialize` and never repeats it is still
|
|
72
|
+
* anonymous here, which is the honest answer rather than one inferred.
|
|
66
73
|
*/
|
|
67
74
|
readonly clientLabel?: string;
|
|
68
75
|
|
package/src/auth/index.ts
CHANGED
|
@@ -28,13 +28,61 @@ import type { SecretRef, SecretStore } from '#secrets';
|
|
|
28
28
|
export interface Principal {
|
|
29
29
|
readonly id: string;
|
|
30
30
|
readonly profile: string;
|
|
31
|
-
readonly kind: 'owner';
|
|
31
|
+
readonly kind: 'owner' | 'member' | 'machine';
|
|
32
|
+
/**
|
|
33
|
+
* Every profile this caller may reach, or `undefined` for "all of them".
|
|
34
|
+
*
|
|
35
|
+
* `undefined` is the machine token and the stdio pipe: neither is a person,
|
|
36
|
+
* both reach the whole workspace, and saying so explicitly is better than
|
|
37
|
+
* enumerating a list that would then need keeping in step. A `member` always
|
|
38
|
+
* carries a list, because the list *is* the delegation (ADR-060).
|
|
39
|
+
*/
|
|
40
|
+
readonly profiles?: readonly string[] | undefined;
|
|
32
41
|
}
|
|
33
42
|
|
|
34
43
|
export function ownerPrincipal(profile: string): Principal {
|
|
35
44
|
return { id: `${profile}:owner`, profile, kind: 'owner' };
|
|
36
45
|
}
|
|
37
46
|
|
|
47
|
+
/**
|
|
48
|
+
* A person, and the profiles whose `members:` name them.
|
|
49
|
+
*
|
|
50
|
+
* `profile` carries the one this call is acting within, which is what the audit
|
|
51
|
+
* log records and what policy is evaluated against. `profiles` is the whole set
|
|
52
|
+
* they may choose from, and `mayReach` is the check — kept here rather than in
|
|
53
|
+
* the dispatcher so discovery and enforcement cannot answer it differently,
|
|
54
|
+
* which is the same rule `allowedConnections` follows on the capability axis.
|
|
55
|
+
*/
|
|
56
|
+
export function memberPrincipal(
|
|
57
|
+
subject: string,
|
|
58
|
+
profile: string,
|
|
59
|
+
profiles: readonly string[],
|
|
60
|
+
): Principal {
|
|
61
|
+
return { id: subject, profile, kind: 'member', profiles };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* The same caller, acting within a different profile.
|
|
66
|
+
*
|
|
67
|
+
* An endpoint serves several profiles and a principal is built once, from the
|
|
68
|
+
* primary — so the profile on it is where the *connection* was opened, not
|
|
69
|
+
* where this call is going. Every dispatch has to say which, because
|
|
70
|
+
* `principal.profile` is what the audit event records and what `mayReach` is
|
|
71
|
+
* checked against; without this the log attributes a member's call to a profile
|
|
72
|
+
* they may never have been able to reach.
|
|
73
|
+
*
|
|
74
|
+
* It does not widen anything. `profiles` carries over untouched, so a name this
|
|
75
|
+
* caller may not reach is still refused — one step later, by the check below.
|
|
76
|
+
*/
|
|
77
|
+
export function forProfile(principal: Principal, profile: string): Principal {
|
|
78
|
+
return principal.profile === profile ? principal : { ...principal, profile };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Whether this caller may act within the named profile. */
|
|
82
|
+
export function mayReach(principal: Principal, profile: string): boolean {
|
|
83
|
+
return principal.profiles === undefined || principal.profiles.includes(profile);
|
|
84
|
+
}
|
|
85
|
+
|
|
38
86
|
export type AuthOutcome =
|
|
39
87
|
| { readonly ok: true; readonly principal: Principal }
|
|
40
88
|
| { readonly ok: false; readonly reason: 'missing' | 'malformed' | 'invalid' | 'not_configured' };
|
|
@@ -215,7 +263,15 @@ export {
|
|
|
215
263
|
type ChallengeError,
|
|
216
264
|
type ResourceIdentity,
|
|
217
265
|
} from './oauth/metadata.ts';
|
|
218
|
-
export {
|
|
266
|
+
export {
|
|
267
|
+
OAuthServer,
|
|
268
|
+
pkceChallengeFor,
|
|
269
|
+
type EndpointIdentity,
|
|
270
|
+
type Federation,
|
|
271
|
+
type OAuthResult,
|
|
272
|
+
} from './oauth/server.ts';
|
|
273
|
+
export { AssertionVerifier, type Assertion } from './lanes/assertion.ts';
|
|
274
|
+
export { lanesFederation, DEFAULT_WEB_URL, type FederationOptions } from './lanes/federation.ts';
|
|
219
275
|
export { matchesRegistered } from './oauth/redirects.ts';
|
|
220
276
|
export { OAuthStore, hashToken, randomToken } from './oauth/store.ts';
|
|
221
277
|
export { OidcVerifier, type OidcVerifierOptions, type VerifiedSubject } from './oidc.ts';
|