@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.
Files changed (157) hide show
  1. package/README.md +20 -10
  2. package/instructions/agents/lanes-link-scout.md +2 -2
  3. package/instructions/skills/lanes-link/SKILL.md +136 -61
  4. package/package.json +2 -1
  5. package/src/audit/index.ts +8 -1
  6. package/src/auth/index.ts +58 -2
  7. package/src/auth/lanes/assertion.ts +256 -0
  8. package/src/auth/lanes/callback.ts +135 -0
  9. package/src/auth/lanes/federation.ts +50 -0
  10. package/src/auth/lanes/login.ts +294 -0
  11. package/src/auth/lanes/members.ts +103 -0
  12. package/src/auth/lanes/session.ts +97 -0
  13. package/src/auth/oauth/grant.ts +183 -0
  14. package/src/auth/oauth/result.ts +27 -0
  15. package/src/auth/oauth/server.ts +176 -203
  16. package/src/auth/oauth/store.ts +65 -0
  17. package/src/auth/remote.ts +32 -9
  18. package/src/cli/accepts.ts +108 -0
  19. package/src/cli/argv.ts +57 -3
  20. package/src/cli/audit-change.ts +140 -0
  21. package/src/cli/brand.ts +39 -10
  22. package/src/cli/callback-page.ts +37 -104
  23. package/src/cli/commands/auth-dispatch.ts +48 -0
  24. package/src/cli/commands/auth.ts +229 -0
  25. package/src/cli/commands/connect/accounts.ts +4 -4
  26. package/src/cli/commands/connect/authorise.ts +4 -4
  27. package/src/cli/commands/connect/bind-credential.ts +2 -1
  28. package/src/cli/commands/connect/custom/index.ts +1 -1
  29. package/src/cli/commands/connect/custom/write.ts +2 -2
  30. package/src/cli/commands/connect/grant.ts +29 -14
  31. package/src/cli/commands/connect/index.ts +88 -87
  32. package/src/cli/commands/connect/options.ts +83 -0
  33. package/src/cli/commands/connect/registration.ts +50 -0
  34. package/src/cli/commands/connect/requirements.ts +1 -1
  35. package/src/cli/commands/connect/settle.ts +4 -2
  36. package/src/cli/commands/connect/target-note.ts +7 -2
  37. package/src/cli/commands/connect/unknown.ts +1 -1
  38. package/src/cli/commands/connect/variables.ts +3 -2
  39. package/src/cli/commands/connection-list.ts +116 -0
  40. package/src/cli/commands/connection.ts +182 -165
  41. package/src/cli/commands/grant.ts +140 -0
  42. package/src/cli/commands/identity.ts +21 -9
  43. package/src/cli/commands/knowledge/index.ts +46 -79
  44. package/src/cli/commands/knowledge/migrate.ts +74 -13
  45. package/src/cli/commands/knowledge/show.ts +92 -0
  46. package/src/cli/commands/knowledge.ts +2 -1
  47. package/src/cli/commands/mcp/harnesses.ts +30 -8
  48. package/src/cli/commands/mcp/onboarding.ts +86 -0
  49. package/src/cli/commands/mcp/register.ts +16 -2
  50. package/src/cli/commands/mcp.ts +1 -0
  51. package/src/cli/commands/members.ts +288 -0
  52. package/src/cli/commands/operate/attach.ts +3 -3
  53. package/src/cli/commands/operate/audit.ts +11 -7
  54. package/src/cli/commands/operate/auth.ts +28 -11
  55. package/src/cli/commands/operate/findings.ts +2 -1
  56. package/src/cli/commands/operate/inspect.ts +37 -19
  57. package/src/cli/commands/operate/migrate.ts +29 -12
  58. package/src/cli/commands/operate/outputs.ts +3 -3
  59. package/src/cli/commands/operate/pair-certificate.ts +141 -0
  60. package/src/cli/commands/operate/pair.ts +324 -0
  61. package/src/cli/commands/operate/policy.ts +73 -22
  62. package/src/cli/commands/operate/serve.ts +52 -4
  63. package/src/cli/commands/operate/status.ts +18 -10
  64. package/src/cli/commands/operate/tools.ts +2 -2
  65. package/src/cli/commands/operate.ts +2 -0
  66. package/src/cli/commands/owner/shared.ts +13 -2
  67. package/src/cli/commands/owner/skills.ts +28 -8
  68. package/src/cli/commands/profile/removal.ts +79 -76
  69. package/src/cli/commands/profile/remove.ts +16 -1
  70. package/src/cli/commands/profile.ts +46 -10
  71. package/src/cli/commands/relabel.ts +112 -0
  72. package/src/cli/commands/secrets.ts +34 -12
  73. package/src/cli/commands/set-workspace.ts +96 -0
  74. package/src/cli/commands/setup.ts +2 -2
  75. package/src/cli/commands/sync.ts +8 -8
  76. package/src/cli/commands/target.ts +9 -7
  77. package/src/cli/commands/update.ts +58 -17
  78. package/src/cli/config-edit.ts +75 -140
  79. package/src/cli/config-migrate.ts +82 -64
  80. package/src/cli/config-repair.ts +89 -32
  81. package/src/cli/config-templates.ts +198 -0
  82. package/src/cli/contract3-data.ts +328 -0
  83. package/src/cli/contract3-shape.ts +186 -0
  84. package/src/cli/contract3.ts +282 -0
  85. package/src/cli/endpoint-url.ts +1 -1
  86. package/src/cli/lanes.ts +25 -1
  87. package/src/cli/main.ts +89 -14
  88. package/src/cli/migrate-plan.ts +12 -6
  89. package/src/cli/output.ts +34 -1
  90. package/src/cli/publish.ts +5 -2
  91. package/src/cli/runtime/open.ts +63 -98
  92. package/src/cli/runtime/registry.ts +6 -7
  93. package/src/cli/runtime/stores.ts +53 -0
  94. package/src/cli/runtime/types.ts +106 -0
  95. package/src/cli/runtime/vault.ts +19 -4
  96. package/src/cli/runtime/workspace.ts +60 -0
  97. package/src/cli/runtime.ts +2 -1
  98. package/src/cli/selection-require.ts +44 -13
  99. package/src/cli/selection.ts +127 -145
  100. package/src/cli/usage.ts +34 -18
  101. package/src/cli/workspace-migrate.ts +125 -16
  102. package/src/connectivity/manifest/provider.ts +3 -1
  103. package/src/connectivity/manifest/requirements.ts +1 -1
  104. package/src/deployments/bind.ts +1 -1
  105. package/src/deployments/deploy.ts +36 -27
  106. package/src/deployments/gcp/bucket.ts +18 -7
  107. package/src/deployments/gcp/provision.ts +7 -7
  108. package/src/deployments/prepare.ts +72 -24
  109. package/src/deployments/record.ts +1 -1
  110. package/src/deployments/report.ts +2 -2
  111. package/src/deployments/serving.ts +15 -74
  112. package/src/deployments/target.ts +15 -15
  113. package/src/deployments/upload.ts +46 -22
  114. package/src/dispatch/deps.ts +88 -0
  115. package/src/dispatch/dispatch.ts +21 -62
  116. package/src/policy/index.ts +47 -15
  117. package/src/profile/connections.ts +183 -0
  118. package/src/profile/deployments.ts +3 -3
  119. package/src/profile/index.ts +30 -5
  120. package/src/profile/layout.ts +86 -89
  121. package/src/profile/load.ts +80 -47
  122. package/src/profile/pairing.ts +32 -0
  123. package/src/profile/primitives.ts +35 -1
  124. package/src/profile/registry.ts +6 -6
  125. package/src/profile/schema.ts +172 -21
  126. package/src/profile/targets.ts +21 -9
  127. package/src/profile/testing.ts +69 -2
  128. package/src/profile/workspace.ts +58 -3
  129. package/src/providers/custom/index.ts +1 -1
  130. package/src/providers/custom/load.ts +2 -3
  131. package/src/providers/identity/provider.ts +1 -1
  132. package/src/providers/memory/provider.ts +20 -2
  133. package/src/providers/setup/plan.ts +1 -1
  134. package/src/providers/slack/index.ts +2 -2
  135. package/src/registry/policy-bridge.ts +33 -11
  136. package/src/registry/reconcile.ts +4 -4
  137. package/src/server/authorization.ts +94 -0
  138. package/src/server/edge.ts +14 -1
  139. package/src/server/endpoint.ts +85 -104
  140. package/src/server/generation.ts +10 -1
  141. package/src/server/harness.ts +71 -13
  142. package/src/server/index.ts +31 -0
  143. package/src/server/mcp/build.ts +20 -1
  144. package/src/server/mcp/client-info.ts +54 -0
  145. package/src/server/mcp/guide.ts +120 -0
  146. package/src/server/mcp/instructions.ts +1 -1
  147. package/src/server/mcp/prompts.ts +7 -3
  148. package/src/server/mcp/resources.ts +16 -8
  149. package/src/server/mcp/tools.ts +9 -3
  150. package/src/server/mcp/visibility.ts +18 -3
  151. package/src/server/oauth.ts +29 -75
  152. package/src/server/read/credential.ts +134 -0
  153. package/src/server/read/deployed.ts +56 -0
  154. package/src/server/read/listener.ts +54 -0
  155. package/src/server/read/open.ts +101 -0
  156. package/src/server/read/routes.ts +247 -0
  157. 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+. Nothing else no account anywhere.
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 link profile add personal --target local
59
- $ lanes link start --profile personal --target local
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 --target local # every agent installed; or name one: claude, codex
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 target
80
- every command runs against, connects your accounts, starts and stops the endpoint, and registers it
81
- with Claude Code or Codex.
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
- ![The Lanes Link page in the Lanes desktop app: the CLI status card and its version, the target and profile selectors, the endpoint row with its running state, and the list of connected accounts.](docs/images/lanes-link-desktop.png)
93
+ ![The Lanes Link page in the Lanes desktop app: the CLI status card and its version, the workspace and profile selectors, the endpoint row with its running state, and the list of connected accounts.](docs/images/lanes-link-desktop.png)
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** | **Your own cloud** | **Lanes Cloud** |
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. **Your own cloud** is what you
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> --target <name>
71
- $ lanes link policy list --profile <name> --target <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, checking what a target serves, 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.
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 under one token, and each call names which profile it means.
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. This file is the part that does not change:
15
- how to behave with it.
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 share no database and no credential store.
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 --target <name>` shows what exists *in that target*. A
26
- profile lives in exactly one, so `personal` on `local` and `personal` on `cloud`
27
- are two profiles that share a name rather than one profile in two places.
28
-
29
- **What a command must be told is never inferred — but it is not always both.**
30
- Nothing resolves from an environment variable or a config default, so a command
31
- missing what it needs refuses rather than acting somewhere else. Passing a flag a
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 target list`, `lanes link mcp list`,
36
- `lanes link version`.
37
- - **`--target` alone.** `lanes link profile list`, `lanes link profile add`,
38
- `lanes link profile remove`, `lanes link target show`. A profile lives inside
39
- one target's workspace, so listing or creating one names which workspace.
40
- - **`--target`, with the profiles derived from it.** `lanes link status`,
41
- `lanes link deploy` and `lanes link sync targets` act on one endpoint serving
42
- every profile that declares that target. `--profile` is accepted and *narrows*
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 memory list`,
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 within that profile. One profile may hold
57
- several of the same kind, and naming a connection belonging to a *different*
58
- profile is refused rather than guessed at.
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> --target <name>` and a text editor.
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> --target <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> --target <name> --connection <provider>.<account>`,
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> --target <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> --target <name>` and `lanes link skills show <skill> --profile <name> --target <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
- --target <target>` — both flags, because neither has a fallback.
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> --target <name> --connection <provider>.<account>`, which prints a
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> --target <name>` shows the rules, `lanes link policy allow <capability> --profile <name> --target <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> --target <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> --target <name> --json` lists what to store,
280
- `lanes link secrets set <ref> --profile <name> --target <name>` takes the value on stdin, and
281
- `lanes link connect <provider> --profile <name> --target <name> --id <id> --non-interactive --json` finishes.
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 target's endpoint reads it and
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 target list`,
306
- `lanes link target show`, `lanes link tools`, `lanes link config show` and
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 targets`,
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 targets`, `target list`, `target show`, `profile list`,
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> --target <name>` writes a new one *into that target's workspace* — one
329
- target, not a list, because a profile lives in exactly one.
330
- `lanes link profile remove <name> --target <name>` takes `--dry-run` and then
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 and no default target. `lanes link profile default`
336
- and `lanes link target use` are gone and now refuse with an explanation — if you
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
- **target**, and the profiles behind it are every profile *in* that target's
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 target, so this decides who gets in. With several candidates and
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 target that does not exist yet has no workspace to derive
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 target it does not itself hold, and a
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 target you know is up.
450
+ for a workspace you know is up.
376
451
 
377
- `lanes link sync targets` adopts it. `--discover` looks for a deployment the
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> --target <name>` runs each harness's own registration command and installs
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> --target <name>` — add `--force`, since Claude Code stores the token as
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> --target <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> --target <name>`.
484
+ `lanes link token rotate --profile <name> --workspace <name>`.
410
485
 
411
- Prefer `lanes link mcp add --profile <name> --target <name>` to writing the command yourself: it checks the
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> --target <name>` rather than writing it blind — that command checks whether
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> --target <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> --target <name>`; both
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> --target <name>` runs in the foreground and
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.7.1",
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/*",
@@ -58,11 +58,18 @@ export interface AuditEvent {
58
58
  readonly principal: string;
59
59
 
60
60
  /**
61
- * The MCP `clientInfo` name, when the caller supplied one.
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 { OAuthServer, pkceChallengeFor, type AuthorizeRequest, type OAuthResult } from './oauth/server.ts';
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';