@lanes-sh/link 0.1.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 (276) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +144 -0
  3. package/bin/lanes +42 -0
  4. package/instructions/agents/lanes-link-scout.md +73 -0
  5. package/instructions/skills/lanes-link/SKILL.md +187 -0
  6. package/package.json +95 -0
  7. package/src/audit/chain.ts +278 -0
  8. package/src/audit/conformance.ts +374 -0
  9. package/src/audit/fanout.ts +97 -0
  10. package/src/audit/index.ts +218 -0
  11. package/src/audit/stdout.ts +60 -0
  12. package/src/auth/index.ts +220 -0
  13. package/src/auth/oauth/metadata.ts +75 -0
  14. package/src/auth/oauth/server.ts +385 -0
  15. package/src/auth/oauth/store.ts +215 -0
  16. package/src/auth/oidc.ts +206 -0
  17. package/src/auth/remote.ts +72 -0
  18. package/src/cli/argv.ts +94 -0
  19. package/src/cli/callback-page.ts +256 -0
  20. package/src/cli/commands/connect/accounts.ts +94 -0
  21. package/src/cli/commands/connect/authorise.ts +298 -0
  22. package/src/cli/commands/connect/client.ts +284 -0
  23. package/src/cli/commands/connect/index.ts +398 -0
  24. package/src/cli/commands/connect/outcome.ts +119 -0
  25. package/src/cli/commands/connect/requirements.ts +103 -0
  26. package/src/cli/commands/connect/scopes-gate.ts +146 -0
  27. package/src/cli/commands/connect/settle.ts +136 -0
  28. package/src/cli/commands/connect/setup.ts +276 -0
  29. package/src/cli/commands/mcp/assets.ts +189 -0
  30. package/src/cli/commands/mcp/harnesses.ts +143 -0
  31. package/src/cli/commands/mcp/list.ts +70 -0
  32. package/src/cli/commands/mcp/register.ts +199 -0
  33. package/src/cli/commands/mcp/stdio.ts +57 -0
  34. package/src/cli/commands/mcp.ts +22 -0
  35. package/src/cli/commands/operate/attach.ts +121 -0
  36. package/src/cli/commands/operate/audit.ts +119 -0
  37. package/src/cli/commands/operate/inspect.ts +355 -0
  38. package/src/cli/commands/operate/outputs.ts +206 -0
  39. package/src/cli/commands/operate/policy.ts +80 -0
  40. package/src/cli/commands/operate/serve.ts +55 -0
  41. package/src/cli/commands/operate/status.ts +133 -0
  42. package/src/cli/commands/operate/token.ts +70 -0
  43. package/src/cli/commands/operate.ts +27 -0
  44. package/src/cli/commands/owner/memory.ts +110 -0
  45. package/src/cli/commands/owner/shared.ts +125 -0
  46. package/src/cli/commands/owner/skills.ts +92 -0
  47. package/src/cli/commands/owner/vault.ts +140 -0
  48. package/src/cli/commands/owner.ts +46 -0
  49. package/src/cli/commands/profile/removal.ts +278 -0
  50. package/src/cli/commands/profile/remove.ts +315 -0
  51. package/src/cli/commands/profile.ts +156 -0
  52. package/src/cli/commands/secrets.ts +176 -0
  53. package/src/cli/commands/setup.ts +150 -0
  54. package/src/cli/commands/target.ts +310 -0
  55. package/src/cli/config-edit.ts +397 -0
  56. package/src/cli/endpoint-url.ts +77 -0
  57. package/src/cli/identity.ts +109 -0
  58. package/src/cli/lanes.ts +78 -0
  59. package/src/cli/main.ts +333 -0
  60. package/src/cli/oauth-error.ts +13 -0
  61. package/src/cli/oauth-exchange.ts +146 -0
  62. package/src/cli/oauth.ts +354 -0
  63. package/src/cli/output.ts +184 -0
  64. package/src/cli/prompt.ts +180 -0
  65. package/src/cli/publish.ts +185 -0
  66. package/src/cli/runtime/discovery.ts +123 -0
  67. package/src/cli/runtime/open.ts +342 -0
  68. package/src/cli/runtime/registry.ts +185 -0
  69. package/src/cli/runtime/select.ts +124 -0
  70. package/src/cli/runtime.ts +34 -0
  71. package/src/cli/scopes.ts +63 -0
  72. package/src/cli/usage.ts +111 -0
  73. package/src/cli/version.ts +25 -0
  74. package/src/connectivity/auth/README.md +36 -0
  75. package/src/connectivity/auth/api-key/index.ts +43 -0
  76. package/src/connectivity/auth/authorize.ts +49 -0
  77. package/src/connectivity/auth/basic/index.ts +68 -0
  78. package/src/connectivity/auth/bearer/index.ts +13 -0
  79. package/src/connectivity/auth/credential.ts +19 -0
  80. package/src/connectivity/auth/header/index.ts +18 -0
  81. package/src/connectivity/auth/index.ts +35 -0
  82. package/src/connectivity/auth/none/index.ts +12 -0
  83. package/src/connectivity/auth/oauth-authcode/broker.ts +261 -0
  84. package/src/connectivity/auth/oauth-authcode/index.ts +64 -0
  85. package/src/connectivity/auth/oauth-authcode/provider.ts +279 -0
  86. package/src/connectivity/auth/oauth-authcode/refresh.ts +118 -0
  87. package/src/connectivity/auth/resolve.ts +61 -0
  88. package/src/connectivity/auth/strategy/index.ts +14 -0
  89. package/src/connectivity/capability.ts +164 -0
  90. package/src/connectivity/connector.ts +175 -0
  91. package/src/connectivity/context.ts +77 -0
  92. package/src/connectivity/index.ts +94 -0
  93. package/src/connectivity/mail/attachments.ts +368 -0
  94. package/src/connectivity/mail/compose.ts +73 -0
  95. package/src/connectivity/mail/index.ts +40 -0
  96. package/src/connectivity/mail/message.ts +82 -0
  97. package/src/connectivity/mail/nodemailer.d.ts +83 -0
  98. package/src/connectivity/mail/staging.ts +143 -0
  99. package/src/connectivity/mail/url.ts +301 -0
  100. package/src/connectivity/manifest/auth.ts +135 -0
  101. package/src/connectivity/manifest/bundles.ts +23 -0
  102. package/src/connectivity/manifest/connector.ts +168 -0
  103. package/src/connectivity/manifest/credential-ref.ts +73 -0
  104. package/src/connectivity/manifest/identity.ts +43 -0
  105. package/src/connectivity/manifest/index.ts +46 -0
  106. package/src/connectivity/manifest/primitives.ts +20 -0
  107. package/src/connectivity/manifest/provider.ts +208 -0
  108. package/src/connectivity/manifest/requirements.ts +146 -0
  109. package/src/connectivity/manifest/setup.ts +55 -0
  110. package/src/connectivity/provider.ts +163 -0
  111. package/src/connectivity/transports/README.md +33 -0
  112. package/src/connectivity/transports/composite/index.ts +68 -0
  113. package/src/connectivity/transports/dav/calendar.ts +217 -0
  114. package/src/connectivity/transports/dav/capabilities.ts +151 -0
  115. package/src/connectivity/transports/dav/client.ts +226 -0
  116. package/src/connectivity/transports/dav/contacts.ts +75 -0
  117. package/src/connectivity/transports/dav/ical.ts +412 -0
  118. package/src/connectivity/transports/dav/index.ts +143 -0
  119. package/src/connectivity/transports/dav/operations.ts +43 -0
  120. package/src/connectivity/transports/dav/request.ts +161 -0
  121. package/src/connectivity/transports/dav/xml.ts +123 -0
  122. package/src/connectivity/transports/factory.ts +181 -0
  123. package/src/connectivity/transports/fs/capabilities.ts +88 -0
  124. package/src/connectivity/transports/fs/commands.ts +258 -0
  125. package/src/connectivity/transports/fs/index.ts +121 -0
  126. package/src/connectivity/transports/fs/operations.ts +45 -0
  127. package/src/connectivity/transports/fs/paths.ts +120 -0
  128. package/src/connectivity/transports/fs/result.ts +12 -0
  129. package/src/connectivity/transports/http/index.ts +255 -0
  130. package/src/connectivity/transports/imap/attachment.ts +166 -0
  131. package/src/connectivity/transports/imap/capabilities.ts +158 -0
  132. package/src/connectivity/transports/imap/client.ts +398 -0
  133. package/src/connectivity/transports/imap/commands.ts +385 -0
  134. package/src/connectivity/transports/imap/index.ts +152 -0
  135. package/src/connectivity/transports/imap/operations.ts +64 -0
  136. package/src/connectivity/transports/imap/parse.ts +130 -0
  137. package/src/connectivity/transports/imap/parser.ts +272 -0
  138. package/src/connectivity/transports/imap/result.ts +15 -0
  139. package/src/connectivity/transports/imap/send.ts +92 -0
  140. package/src/connectivity/transports/imap/socket.ts +111 -0
  141. package/src/connectivity/transports/imap/utf7.ts +136 -0
  142. package/src/connectivity/transports/index.ts +20 -0
  143. package/src/connectivity/transports/local/index.ts +173 -0
  144. package/src/connectivity/transports/mcp/index.ts +215 -0
  145. package/src/deployments/README.md +63 -0
  146. package/src/deployments/adapters/audit-blob.ts +203 -0
  147. package/src/deployments/adapters/filesystem.ts +184 -0
  148. package/src/deployments/adapters/gcp-secret-manager.ts +492 -0
  149. package/src/deployments/adapters/gcs.ts +191 -0
  150. package/src/deployments/adapters/otlp.ts +128 -0
  151. package/src/deployments/adapters/s3.ts +195 -0
  152. package/src/deployments/azure/README.md +21 -0
  153. package/src/deployments/bootstrap.ts +177 -0
  154. package/src/deployments/deploy.ts +290 -0
  155. package/src/deployments/driver.ts +157 -0
  156. package/src/deployments/drivers.ts +35 -0
  157. package/src/deployments/gcp/Dockerfile +70 -0
  158. package/src/deployments/gcp/cloudbuild.yaml +31 -0
  159. package/src/deployments/gcp/driver.ts +175 -0
  160. package/src/deployments/gcp/gcloud.ts +178 -0
  161. package/src/deployments/gcp/provision.ts +290 -0
  162. package/src/deployments/gcp/survey.ts +319 -0
  163. package/src/deployments/local/README.md +12 -0
  164. package/src/deployments/prepare.ts +257 -0
  165. package/src/deployments/steps.ts +137 -0
  166. package/src/deployments/target.ts +295 -0
  167. package/src/deployments/upload.ts +207 -0
  168. package/src/dispatch/context.ts +195 -0
  169. package/src/dispatch/dispatch.ts +350 -0
  170. package/src/dispatch/index.ts +32 -0
  171. package/src/dispatch/staging.ts +102 -0
  172. package/src/policy/index.ts +179 -0
  173. package/src/policy/limits.ts +77 -0
  174. package/src/profile/authorization.ts +81 -0
  175. package/src/profile/files.ts +71 -0
  176. package/src/profile/index.ts +76 -0
  177. package/src/profile/layout.ts +123 -0
  178. package/src/profile/load.ts +199 -0
  179. package/src/profile/primitives.ts +45 -0
  180. package/src/profile/schema.ts +347 -0
  181. package/src/profile/secret-detection.ts +162 -0
  182. package/src/profile/targets.ts +152 -0
  183. package/src/profile/workspace.ts +262 -0
  184. package/src/providers/custom/index.ts +21 -0
  185. package/src/providers/custom/load.ts +115 -0
  186. package/src/providers/custom/template.ts +156 -0
  187. package/src/providers/example/provider.ts +207 -0
  188. package/src/providers/google/calendar/index.ts +66 -0
  189. package/src/providers/google/calendar/redact.ts +40 -0
  190. package/src/providers/google/contacts/index.ts +50 -0
  191. package/src/providers/google/contacts/redact.ts +21 -0
  192. package/src/providers/google/docs/index.ts +45 -0
  193. package/src/providers/google/drive/hints.ts +28 -0
  194. package/src/providers/google/drive/index.ts +34 -0
  195. package/src/providers/google/drive/redact.ts +39 -0
  196. package/src/providers/google/drive-mcp/index.ts +21 -0
  197. package/src/providers/google/gmail/api.ts +42 -0
  198. package/src/providers/google/gmail/attachment.ts +142 -0
  199. package/src/providers/google/gmail/hints.ts +55 -0
  200. package/src/providers/google/gmail/index.ts +112 -0
  201. package/src/providers/google/gmail/redact.ts +56 -0
  202. package/src/providers/google/gmail/send.ts +365 -0
  203. package/src/providers/google/gmail-mcp/index.ts +35 -0
  204. package/src/providers/google/index.ts +10 -0
  205. package/src/providers/google/shared/oauth.ts +122 -0
  206. package/src/providers/google/shared/scopes.ts +99 -0
  207. package/src/providers/google/shared/setup.ts +80 -0
  208. package/src/providers/google/sheets/hints.ts +45 -0
  209. package/src/providers/google/sheets/index.ts +70 -0
  210. package/src/providers/google/sheets/redact.ts +45 -0
  211. package/src/providers/google/specs/calendar.v3.json +1829 -0
  212. package/src/providers/google/specs/docs.v1.json +381 -0
  213. package/src/providers/google/specs/drive.v3.json +2208 -0
  214. package/src/providers/google/specs/gmail.v1.json +2578 -0
  215. package/src/providers/google/specs/people.v1.json +506 -0
  216. package/src/providers/google/specs/sheets.v4.json +1269 -0
  217. package/src/providers/google/specs/tasks.v1.json +840 -0
  218. package/src/providers/google/specs/vendor.ts +661 -0
  219. package/src/providers/google/tasks/index.ts +53 -0
  220. package/src/providers/google/tasks/redact.ts +34 -0
  221. package/src/providers/harness.ts +95 -0
  222. package/src/providers/icloud/calendar/index.ts +27 -0
  223. package/src/providers/icloud/contacts/index.ts +17 -0
  224. package/src/providers/icloud/drive/index.ts +47 -0
  225. package/src/providers/icloud/index.ts +8 -0
  226. package/src/providers/icloud/mail/index.ts +37 -0
  227. package/src/providers/icloud/shared/setup.ts +66 -0
  228. package/src/providers/index.ts +93 -0
  229. package/src/providers/linear/index.ts +11 -0
  230. package/src/providers/linear/scopes.ts +7 -0
  231. package/src/providers/memory/provider.ts +429 -0
  232. package/src/providers/notion/index.ts +19 -0
  233. package/src/providers/owner.ts +49 -0
  234. package/src/providers/scopes.ts +26 -0
  235. package/src/providers/setup/plan.ts +141 -0
  236. package/src/providers/setup/provider.ts +323 -0
  237. package/src/providers/shared/frontmatter.ts +119 -0
  238. package/src/providers/skills/provider.ts +283 -0
  239. package/src/providers/skills/store.ts +252 -0
  240. package/src/providers/vault/provider.ts +194 -0
  241. package/src/registry/index.ts +36 -0
  242. package/src/registry/policy-bridge.ts +32 -0
  243. package/src/registry/reconcile.ts +313 -0
  244. package/src/registry/registry.ts +240 -0
  245. package/src/secrets/document.ts +293 -0
  246. package/src/secrets/index.ts +154 -0
  247. package/src/secrets/system.ts +173 -0
  248. package/src/secrets/vault.ts +336 -0
  249. package/src/server/attachments.ts +197 -0
  250. package/src/server/container.ts +96 -0
  251. package/src/server/edge.ts +53 -0
  252. package/src/server/endpoint.ts +352 -0
  253. package/src/server/generations.ts +362 -0
  254. package/src/server/harness.ts +400 -0
  255. package/src/server/index.ts +331 -0
  256. package/src/server/logging.ts +41 -0
  257. package/src/server/mcp/build.ts +68 -0
  258. package/src/server/mcp/icon.ts +145 -0
  259. package/src/server/mcp/index.ts +32 -0
  260. package/src/server/mcp/instructions.ts +245 -0
  261. package/src/server/mcp/naming.ts +39 -0
  262. package/src/server/mcp/prompts.ts +78 -0
  263. package/src/server/mcp/resources.ts +106 -0
  264. package/src/server/mcp/routing.ts +117 -0
  265. package/src/server/mcp/schema.ts +78 -0
  266. package/src/server/mcp/tools.ts +186 -0
  267. package/src/server/mcp/visibility.ts +132 -0
  268. package/src/server/oauth.ts +222 -0
  269. package/src/server/rebinding.ts +53 -0
  270. package/src/server/stdio.ts +192 -0
  271. package/src/stores/blobs/conformance.ts +223 -0
  272. package/src/stores/blobs/index.ts +125 -0
  273. package/src/stores/blobs/testing.ts +49 -0
  274. package/src/stores/state/index.ts +247 -0
  275. package/src/stores/state/keys.ts +68 -0
  276. package/src/stores/state/testing.ts +41 -0
@@ -0,0 +1,63 @@
1
+ import { SCOPE_MEANINGS } from '#providers/scopes.ts';
2
+
3
+ /**
4
+ * Saying out loud what a connection is about to be granted.
5
+ *
6
+ * Least privilege is the goal, and a vendor's *advertised* scope list is not the
7
+ * same as its required one. Google's Gmail MCP server advertises five, including
8
+ * `mail.google.com` — read, send, and permanently delete — while its own
9
+ * documentation names two. Granting all five was tested against the real service
10
+ * and changed nothing, so the two are what we ask for.
11
+ *
12
+ * Where a grant does have to be broad, it must not happen quietly: an over-broad
13
+ * grant is invisible after the fact, and a consent screen listing five
14
+ * Google-worded scopes is not where someone discovers that "Gmail" meant
15
+ * permanent delete. We print it first, in plain words, and make the broad ones an
16
+ * explicit yes.
17
+ *
18
+ * What each scope *means* is the provider's knowledge and lives in its folder
19
+ * (`#providers/scopes.ts` merges the contributions). This file is the rendering,
20
+ * and it works the same for a provider it has never heard of: a scope missing
21
+ * from the table is printed unannotated rather than mis-described — silence is
22
+ * the safe failure here.
23
+ */
24
+
25
+ export interface ScopeDescription {
26
+ readonly scope: string;
27
+ /** Plain-English meaning, where the provider supplied one. */
28
+ readonly meaning?: string;
29
+ /** Unrestricted access to the service, or close enough to warrant a stop. */
30
+ readonly broad: boolean;
31
+ }
32
+
33
+ export function describeScope(scope: string): ScopeDescription {
34
+ const known = SCOPE_MEANINGS[scope];
35
+ return {
36
+ scope,
37
+ ...(known?.meaning ? { meaning: known.meaning } : {}),
38
+ broad: known?.broad === true,
39
+ };
40
+ }
41
+
42
+ export function describeScopes(scopes: readonly string[]): ScopeDescription[] {
43
+ return scopes.map(describeScope);
44
+ }
45
+
46
+ /**
47
+ * Shorten a scope for display; the full value is still what is requested.
48
+ *
49
+ * Generic on purpose. The last path segment after `/auth/` is the convention
50
+ * every OAuth vendor that uses URL-shaped scopes follows, and anything else is
51
+ * left alone rather than guessed at.
52
+ */
53
+ export function shortScope(scope: string): string {
54
+ const match = scope.match(/\/auth\/([^/]+)$/);
55
+ if (match?.[1]) return match[1];
56
+
57
+ // A bare host with nothing after it — `https://mail.google.com/` — reads
58
+ // better without the scheme, and there is nothing else to take.
59
+ const url = URL.parse?.(scope);
60
+ if (url && (url.pathname === '/' || url.pathname === '')) return url.host;
61
+
62
+ return scope;
63
+ }
@@ -0,0 +1,111 @@
1
+ import { style } from './output.ts';
2
+
3
+ /**
4
+ * The help text, and the name the CLI calls itself.
5
+ *
6
+ * `PROGRAM` is a constant rather than 59 string literals because it had been 59
7
+ * string literals: the rename that produced `lanes link` meant editing the
8
+ * usage text, every `Usage:` line, and every `Unknown:` line by hand, with no
9
+ * way to tell from a green test suite whether one had been missed. The next
10
+ * rename touches this line, and `argv.test.ts` holds the two to each other.
11
+ *
12
+ * The text lives here rather than in `main.ts` because it is 65 lines of prose
13
+ * that changes for entirely different reasons from the dispatch below it.
14
+ */
15
+
16
+ /** How this CLI is invoked — the `link` area of the `lanes` command. */
17
+ export const PROGRAM = 'lanes link';
18
+
19
+ export const USAGE = `${style.bold(PROGRAM)} — a self-hosted MCP gateway for your accounts, memory, skills, and secrets
20
+
21
+ ${style.bold('Everyday')}
22
+ ${PROGRAM} setup plan [--json] what each provider needs, and which are connected
23
+ ${PROGRAM} setup plan <provider> the console steps, the values, and the command
24
+ ${PROGRAM} connect <provider> add an account (run once per account)
25
+ ${PROGRAM} connect <provider>.<id> re-authorise one existing account
26
+ ${PROGRAM} connect <...> --replace ask for the stored password or key again
27
+ ${PROGRAM} connect <...> --non-interactive [--json]
28
+ answer nothing from a terminal: take every value
29
+ from the credential store, or say what is missing
30
+ ${PROGRAM} start [--only] reconcile and serve every profile on one endpoint
31
+ ${PROGRAM} outputs [--show] [--json] the endpoint an agent needs
32
+ ${PROGRAM} mcp add [claude|codex] register this endpoint, and install the agent skill
33
+ ${PROGRAM} mcp add --no-skill register only, leaving the agent's own files alone
34
+ ${PROGRAM} mcp list where it is registered, and whether the skill is current
35
+ ${PROGRAM} mcp stdio serve on stdin/stdout, for a client that spawns it
36
+ ${PROGRAM} mcp skill [--print] the bundled skill — its path, or the document itself
37
+ ${PROGRAM} status [--json] connections, reachable capabilities, endpoint
38
+
39
+ ${style.bold('Profiles')}
40
+ ${PROGRAM} profile add <name> [--default] [--json]
41
+ ${PROGRAM} profile list [--json]
42
+ ${PROGRAM} profile default <name>
43
+ ${PROGRAM} profile remove <name> [--target t] [--dry-run] [--yes] [--json]
44
+ the profile, its credentials, and its data
45
+
46
+ ${style.bold('Targets')}
47
+ ${PROGRAM} target list [--urls] where this profile can run, and which one is in play
48
+ ${PROGRAM} target show [<name>] one target's adapters, and the address it answers on
49
+ ${PROGRAM} target use <name> make one the profile's default_target
50
+
51
+ ${style.bold('Permissions')}
52
+ ${PROGRAM} policy list
53
+ ${PROGRAM} policy allow <capability> e.g. gmail.* or gmail.send_message
54
+ ${PROGRAM} policy deny <capability>
55
+ ${PROGRAM} token show [--show|--raw] --raw prints only the token, for $(…)
56
+ ${PROGRAM} token rotate [--show]
57
+
58
+ ${style.bold('Your own context')}
59
+ ${PROGRAM} memory list [--tag t] what you have stored
60
+ ${PROGRAM} memory get <id>
61
+ ${PROGRAM} memory write <id> --title <t> [--tag t] body on stdin
62
+ ${PROGRAM} memory forget <id>
63
+
64
+ ${PROGRAM} skills list the procedures agents can invoke
65
+ ${PROGRAM} skills show <name>
66
+ ${PROGRAM} skills add <name> [--file f] document on stdin
67
+ ${PROGRAM} skills remove <name>
68
+
69
+ ${PROGRAM} vault list names only, never values
70
+ ${PROGRAM} vault get <id> [--show|--raw]
71
+ ${PROGRAM} vault set <id> [--description d] value on stdin
72
+ ${PROGRAM} vault remove <id>
73
+ ${PROGRAM} vault key generate a fresh LANES_LINK_VAULT_KEY, printed once
74
+
75
+ ${style.bold('Deploying')}
76
+ ${PROGRAM} deploy [--dry-run] set up, build, and roll a revision
77
+ ${PROGRAM} deploy --non-interactive take the stored answers, never prompt
78
+ ${PROGRAM} deploy --access iam|public who gets past the platform's own door
79
+ ${PROGRAM} deploy --target <name> deploy a second one, under its own name
80
+ ${PROGRAM} secrets list credential references in this target
81
+ ${PROGRAM} secrets set <ref> store one value, read from stdin
82
+ ${PROGRAM} secrets push --from local --to cloud
83
+
84
+ ${style.bold('Inspection')}
85
+ ${PROGRAM} check static validation, no external calls
86
+ ${PROGRAM} doctor [--json] credentials resolve, stores reachable
87
+ ${PROGRAM} plan what reconcile would change
88
+ ${PROGRAM} audit tail [--limit N] [--denied-only] [--format md]
89
+ ${PROGRAM} audit verify has anything in the log been altered or removed
90
+ ${PROGRAM} config show
91
+ ${PROGRAM} version which release this is — same as lanes --version
92
+
93
+ ${style.bold('Attachments')}
94
+ ${PROGRAM} attach <file> --connection <provider>.<account>
95
+ stage a file, print a handle to send it by
96
+
97
+ ${style.bold('Global flags')}
98
+ --profile <name> overrides LANES_LINK_PROFILE and the workspace default
99
+ --target <name> overrides LANES_LINK_TARGET and instance.default_target
100
+ --connection <id> which memory/skills/vault connection, if a profile has several
101
+ --yes skip the confirmation a destructive command would ask for
102
+ --json machine-readable output, where a command offers it
103
+ --non-interactive never prompt: connect refuses with what to store,
104
+ deploy takes the answers its config already holds
105
+ --accept-broad-scopes agree in advance to scopes broader than a provider needs
106
+ --own-client register your own OAuth client instead of using the
107
+ one this project operates (connect only)
108
+ --port <n> override the configured port (start only)
109
+
110
+ Every command prints the resolved profile and target before acting.
111
+ `;
@@ -0,0 +1,25 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { installRoot } from '#profile';
4
+
5
+ /**
6
+ * What this CLI calls itself when asked.
7
+ *
8
+ * Read from `package.json` at call time rather than substituted in at build
9
+ * time, because there is no build step: the file that declares the version and
10
+ * the file that ships are the same file. `installRoot` walks up to whichever
11
+ * root this is — a checkout during development, or the package directory under
12
+ * `node_modules` once installed — which is the same way `mcp/assets.ts` finds
13
+ * `instructions/`.
14
+ *
15
+ * Answering at all is new with publishing to npm. While the only way to get
16
+ * this CLI was to clone it, `git log` was the answer and a better one; two
17
+ * machines can now be a release apart with nothing on either to say so.
18
+ */
19
+ export function version(): string {
20
+ const path = join(installRoot(import.meta.dir), 'package.json');
21
+ const declared = (JSON.parse(readFileSync(path, 'utf8')) as { version?: string }).version;
22
+
23
+ if (!declared) throw new Error(`No "version" in ${path}`);
24
+ return declared;
25
+ }
@@ -0,0 +1,36 @@
1
+ # Credential types
2
+
3
+ One folder per method. Each owns both halves of its job: `resolve*` turns the
4
+ stored secret into a `ResolvedCredential`, and `attach*` puts that shape on an
5
+ outbound request. `resolve.ts` and `authorize.ts` are the only files that know
6
+ the whole set.
7
+
8
+ | Folder | `auth.kind` | What is stored |
9
+ |---|---|---|
10
+ | `none/` | `none` | nothing — `fs` and `local` providers hold no account |
11
+ | `bearer/` | `bearer` | a token, sent as `Authorization: Bearer <token>` |
12
+ | `header/` | `header` | a value, sent raw in a named header (shares api-key's attach) |
13
+ | `api-key/` | `api_key` | a key, in a header or the query string |
14
+ | `basic/` | `basic` | `username:password`, RFC 7617's own encoding |
15
+ | `oauth-authcode/` | `oauth` | a refresh token, exchanged on every use |
16
+ | `strategy/` | `strategy` | the escape hatch — per-vendor code, none registered |
17
+
18
+ ## Adding one
19
+
20
+ A folder, a member of `authSchema` in `../manifest/auth.ts`, and a case in
21
+ `resolve.ts` (plus `authorize.ts` if it touches the request). Nothing else in
22
+ the codebase learns about it — that is the point of the split.
23
+
24
+ These are named in the credential-type list this design is measured against and
25
+ are **not built**:
26
+
27
+ - `sigv4/` — AWS SigV4 request signing
28
+ - `gcp-token/` — service account → GCP access token
29
+ - `gcp-iap/` — service account → an IAP-signed JWT
30
+ - `oauth-jwt/` — OAuth 2.0 JWT bearer (RFC 7523)
31
+ - `oauth-client-creds/` — OAuth 2.0 client credentials
32
+ - `body-param/` — the credential as a request body parameter
33
+
34
+ Each is a folder here plus a schema member. None of them is a change to a
35
+ transport, a provider, or the dispatch path, which is the property this layout
36
+ exists to give.
@@ -0,0 +1,43 @@
1
+ import type { ResolvedCredential } from '../credential.ts';
2
+
3
+ /**
4
+ * A key in a header, or — where a vendor insists — in the query string.
5
+ *
6
+ * `query` had been in the schema since M2 and was never read, so an
7
+ * api-key-in-query manifest sent an unauthenticated request and got a 401
8
+ * naming nothing.
9
+ */
10
+ export function resolveApiKey(
11
+ value: string,
12
+ options: { header?: string | undefined; query?: string | undefined },
13
+ ): ResolvedCredential {
14
+ return {
15
+ kind: 'api_key',
16
+ value,
17
+ ...(options.query ? { query: options.query } : { header: options.header ?? 'x-api-key' }),
18
+ };
19
+ }
20
+
21
+ /**
22
+ * Returns a replacement request when the key belongs in the URL.
23
+ *
24
+ * A Request's URL is immutable, so moving the key into the query means
25
+ * rebuilding rather than mutating. Passing the Request itself as the init
26
+ * carries method, headers, and body verbatim; the cast is only because
27
+ * TypeScript types the init as `RequestInit`, whose `body` is narrower than the
28
+ * `ReadableStream` a built Request holds.
29
+ */
30
+ export function attachApiKey(
31
+ credential: Extract<ResolvedCredential, { kind: 'api_key' }>,
32
+ request: Request,
33
+ original: Request,
34
+ ): Request | undefined {
35
+ if (!credential.query) {
36
+ request.headers.set(credential.header ?? 'x-api-key', credential.value);
37
+ return undefined;
38
+ }
39
+
40
+ const relocated = new URL(original.url);
41
+ relocated.searchParams.set(credential.query, credential.value);
42
+ return new Request(relocated.href, request as unknown as RequestInit);
43
+ }
@@ -0,0 +1,49 @@
1
+ import type { ProviderRegistry } from '#registry';
2
+ import type { SecretStore } from '#secrets';
3
+ import { attachApiKey } from './api-key/index.ts';
4
+ import { attachBasic } from './basic/index.ts';
5
+ import { attachBearer } from './bearer/index.ts';
6
+ import { credentialResolver } from './resolve.ts';
7
+
8
+ /**
9
+ * Attach whatever the manifest's auth method requires to an outbound request.
10
+ *
11
+ * Connectors never see a raw credential: they hand core a request and get an
12
+ * authorised one back. A pure function of the resolved shape, so there is one
13
+ * place that reads the store (`resolve.ts`) and one that decides what a request
14
+ * looks like.
15
+ */
16
+ export function requestAuthorizer(
17
+ registry: ProviderRegistry,
18
+ secrets: SecretStore,
19
+ ): (providerId: string, connectionId: string, request: Request) => Promise<Request> {
20
+ const resolve = credentialResolver(registry, secrets);
21
+
22
+ return async (providerId, connectionId, request) => {
23
+ const credential = await resolve(providerId, connectionId);
24
+ if (credential.kind === 'none') return request;
25
+
26
+ const authorised = new Request(request, { headers: new Headers(request.headers) });
27
+
28
+ switch (credential.kind) {
29
+ case 'oauth':
30
+ authorised.headers.set('authorization', `Bearer ${credential.accessToken}`);
31
+ break;
32
+ case 'bearer':
33
+ attachBearer(credential, authorised);
34
+ break;
35
+ case 'basic':
36
+ attachBasic(credential, authorised);
37
+ break;
38
+ case 'api_key': {
39
+ // The only method that may need to rebuild the request rather than add
40
+ // a header, because a key in the query string changes the URL.
41
+ const relocated = attachApiKey(credential, authorised, request);
42
+ if (relocated) return relocated;
43
+ break;
44
+ }
45
+ }
46
+
47
+ return authorised;
48
+ };
49
+ }
@@ -0,0 +1,68 @@
1
+ import { credentialRefForConnection, type ProviderManifest } from '#connectivity';
2
+ import type { ProviderRegistry } from '#registry';
3
+ import type { SecretStore } from '#secrets';
4
+ import type { ResolvedCredential } from '../credential.ts';
5
+
6
+ /**
7
+ * RFC 7617 Basic — a username and a password, stored as one string.
8
+ *
9
+ * `user:pass`, split on the *first* colon, which is lossless because the spec
10
+ * forbids a colon in the userid. Storing the pair exactly as the header carries
11
+ * it means nothing has to agree about a JSON shape.
12
+ */
13
+ export function resolveBasic(value: string, ref: string, providerId: string): ResolvedCredential {
14
+ const colon = value.indexOf(':');
15
+ if (colon === -1) {
16
+ throw new Error(
17
+ `The credential at ${ref} is not a "username:password" pair, which basic auth requires. ` +
18
+ `Re-run: lanes link connect ${providerId}`,
19
+ );
20
+ }
21
+
22
+ return { kind: 'basic', username: value.slice(0, colon), password: value.slice(colon + 1) };
23
+ }
24
+
25
+ export function attachBasic(
26
+ credential: Extract<ResolvedCredential, { kind: 'basic' }>,
27
+ request: Request,
28
+ ): void {
29
+ // `btoa` is wrong here twice: it throws outright above U+00FF, and below it
30
+ // silently encodes Latin-1 bytes where RFC 7617 requires UTF-8 — so a
31
+ // password with an umlaut produced a header that was well-formed, accepted by
32
+ // the type system, and rejected by the server.
33
+ request.headers.set(
34
+ 'authorization',
35
+ `Basic ${Buffer.from(`${credential.username}:${credential.password}`, 'utf8').toString('base64')}`,
36
+ );
37
+ }
38
+
39
+ /**
40
+ * The username and password for a connection, for a protocol that wants them as
41
+ * a pair rather than as a header.
42
+ *
43
+ * IMAP and DAV take this closure as a constructor option. It is bound to one
44
+ * provider and one connection and takes no arguments, so a connector holding it
45
+ * cannot name a *different* account's credential — the same boundary
46
+ * `scopeSecrets` enforces with an allowlist, reached here by having nothing to
47
+ * pass.
48
+ */
49
+ export async function basicCredential(
50
+ manifest: ProviderManifest,
51
+ connectionId: string,
52
+ secrets: SecretStore,
53
+ ): Promise<{ username: string; password: string }> {
54
+ const { credentialResolver } = await import('../resolve.ts');
55
+ const registry = { manifest: () => manifest } as unknown as ProviderRegistry;
56
+ const resolved = await credentialResolver(registry, secrets)(manifest.id, connectionId);
57
+
58
+ if (resolved.kind !== 'basic') {
59
+ throw new Error(
60
+ `Provider "${manifest.id}" needs a username and password, but its auth resolves to "${resolved.kind}".`,
61
+ );
62
+ }
63
+
64
+ return { username: resolved.username, password: resolved.password };
65
+ }
66
+
67
+ /** Re-exported so the factory can derive a ref without reaching for the manifest module. */
68
+ export { credentialRefForConnection };
@@ -0,0 +1,13 @@
1
+ import type { ResolvedCredential } from '../credential.ts';
2
+
3
+ /** A long-lived token the operator pasted in, sent as `Bearer <token>`. */
4
+ export function resolveBearer(value: string, header: string | undefined): ResolvedCredential {
5
+ return { kind: 'bearer', token: value, header: header ?? 'authorization' };
6
+ }
7
+
8
+ export function attachBearer(
9
+ credential: Extract<ResolvedCredential, { kind: 'bearer' }>,
10
+ request: Request,
11
+ ): void {
12
+ request.headers.set(credential.header, `Bearer ${credential.token}`);
13
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * A credential, resolved but not yet attached to anything.
3
+ *
4
+ * The seam between "read the store and work out what this secret is" and "put
5
+ * it on an outbound request". Two steps rather than one because not every
6
+ * transport has a request to put it on: IMAP wants a username and password as a
7
+ * pair, and gets this value directly.
8
+ */
9
+ export type ResolvedCredential =
10
+ | { readonly kind: 'none' }
11
+ | { readonly kind: 'bearer'; readonly token: string; readonly header: string }
12
+ | { readonly kind: 'basic'; readonly username: string; readonly password: string }
13
+ | {
14
+ readonly kind: 'api_key';
15
+ readonly value: string;
16
+ readonly header?: string;
17
+ readonly query?: string;
18
+ }
19
+ | { readonly kind: 'oauth'; readonly accessToken: string };
@@ -0,0 +1,18 @@
1
+ import { resolveApiKey } from '../api-key/index.ts';
2
+ import type { ResolvedCredential } from '../credential.ts';
3
+
4
+ /**
5
+ * A raw value in a named header.
6
+ *
7
+ * Distinct from `bearer`, which prefixes `Bearer `. Resolves to the same shape
8
+ * as `api_key` and travels the same attach path, because "put this string in
9
+ * this header" is one behaviour whichever of the two names a manifest picked —
10
+ * and `api_key` is the one that also knows how to put it in the query string.
11
+ *
12
+ * Its own folder rather than an alias in the dispatcher because the *names* are
13
+ * what a manifest declares, and a reader asking "what does `kind: header` do"
14
+ * should find a file rather than a missing case.
15
+ */
16
+ export function resolveHeader(value: string, header: string | undefined): ResolvedCredential {
17
+ return resolveApiKey(value, { header });
18
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Credential types — how we prove who we are to a service.
3
+ *
4
+ * One folder per method, each owning both halves of its job: turning the stored
5
+ * secret into a resolved shape (`resolve*`) and putting that shape on an
6
+ * outbound request (`attach*`). The two dispatchers here are the only files
7
+ * that know the whole set.
8
+ *
9
+ * This is the axis the manifest's `auth:` block selects, and it is deliberately
10
+ * independent of `../transports/` — which is why iCloud can speak IMAP with a
11
+ * password while Gmail speaks HTTP with OAuth, and neither costs the other any
12
+ * code.
13
+ */
14
+
15
+ export { credentialResolver, type ResolvedCredential } from './resolve.ts';
16
+ export { requestAuthorizer } from './authorize.ts';
17
+ export { basicCredential } from './basic/index.ts';
18
+ export {
19
+ CredentialOAuthProvider,
20
+ clearUpstreamTokens,
21
+ upstreamAccessToken,
22
+ type OAuthProviderOptions,
23
+ } from './oauth-authcode/provider.ts';
24
+ export { resolveUpstreamToken } from './oauth-authcode/index.ts';
25
+ export {
26
+ BROKER_ORIGIN_ENV,
27
+ BROKERED,
28
+ BrokerError,
29
+ brokerConfig,
30
+ brokerExchange,
31
+ brokerOriginOverride,
32
+ brokerRefresh,
33
+ type BrokerConfig,
34
+ type BrokerTokens,
35
+ } from './oauth-authcode/broker.ts';
@@ -0,0 +1,12 @@
1
+ import type { ResolvedCredential } from '../credential.ts';
2
+
3
+ /**
4
+ * No credential at all.
5
+ *
6
+ * Not an absence to be handled everywhere — a method like any other, so the
7
+ * `fs` and `local` transports go down the same path as Gmail and the callers
8
+ * never branch on "is there auth".
9
+ */
10
+ export function resolveNone(): ResolvedCredential {
11
+ return { kind: 'none' };
12
+ }