@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,333 @@
1
+ import { connect } from './commands/connect/index.ts';
2
+ import {
3
+ attachFile,
4
+ auditTail,
5
+ auditVerify,
6
+ check,
7
+ configShow,
8
+ doctor,
9
+ outputs,
10
+ plan,
11
+ policyList,
12
+ policyRule,
13
+ start,
14
+ status,
15
+ tokenRotate,
16
+ tokenShow,
17
+ } from './commands/operate.ts';
18
+ import { profileAdd, profileDefault, profileList } from './commands/profile.ts';
19
+ import { removeProfile as profileRemove } from './commands/profile/remove.ts';
20
+ import { targetList, targetShow, targetUse } from './commands/target.ts';
21
+ import { setupPlan } from './commands/setup.ts';
22
+ import { mcpAdd, mcpList, mcpStdio, skillDocument } from './commands/mcp.ts';
23
+ import { deploy } from '#deployments/deploy.ts';
24
+ import { secretsList, secretsPush, secretsSet } from './commands/secrets.ts';
25
+ import {
26
+ memoryForget,
27
+ memoryGet,
28
+ memoryList,
29
+ memoryWrite,
30
+ skillsAdd,
31
+ skillsList,
32
+ skillsRemove,
33
+ skillsShow,
34
+ vaultGet,
35
+ vaultKeyGenerate,
36
+ vaultList,
37
+ vaultRemove,
38
+ vaultSet,
39
+ } from './commands/owner.ts';
40
+ import { globalFlags, ownerFlags, parseArgv, text } from './argv.ts';
41
+ import { PROGRAM, USAGE } from './usage.ts';
42
+ import { version } from './version.ts';
43
+ import { print } from './output.ts';
44
+
45
+ /**
46
+ * `lanes link` — the control plane.
47
+ *
48
+ * Every control-plane operation lives here and nowhere else. Policy changes,
49
+ * token management, credential writing, and config mutation each authorise
50
+ * *future* agent behaviour, so the decision has to originate outside the
51
+ * agent — ADR-007. There is no admin API, and a running instance never
52
+ * mutates its own configuration.
53
+ *
54
+ * What is left in this file is the grammar and nothing else: which word maps to
55
+ * which function. Parsing argv is `argv.ts`, the help text is `usage.ts`, and
56
+ * every command is under `commands/`.
57
+ */
58
+
59
+ export async function run(argv: readonly string[]): Promise<void> {
60
+ const { command, flags } = parseArgv(argv);
61
+ const [first, second, ...rest] = command;
62
+
63
+ const global = globalFlags(flags);
64
+ const owner = ownerFlags(flags);
65
+
66
+ const show = flags['show'] === true;
67
+ const raw = flags['raw'] === true;
68
+ const json = flags['json'] === true;
69
+
70
+ if (!first || first === 'help' || flags['help'] === true) {
71
+ print(USAGE);
72
+ return;
73
+ }
74
+
75
+ switch (first) {
76
+ case 'connect':
77
+ if (!second) throw new Error(`Usage: ${PROGRAM} connect <provider>`);
78
+ return connect(second, {
79
+ ...global,
80
+ id: text(flags, 'id'),
81
+ displayName: text(flags, 'display-name'),
82
+ replace: flags['replace'] === true,
83
+ nonInteractive: flags['non-interactive'] === true,
84
+ acceptBroadScopes: flags['accept-broad-scopes'] === true,
85
+ ownClient: flags['own-client'] === true,
86
+ json,
87
+ });
88
+
89
+ case 'setup':
90
+ if (second !== 'plan' && second !== undefined) {
91
+ throw new Error(`Unknown: ${PROGRAM} setup ${second}`);
92
+ }
93
+ return setupPlan(rest[0], { ...global, json, id: text(flags, 'id') });
94
+
95
+ case 'profile':
96
+ switch (second) {
97
+ case 'add':
98
+ if (!rest[0]) throw new Error(`Usage: ${PROGRAM} profile add <name> [--default]`);
99
+ return profileAdd(rest[0], { default: flags['default'] === true, json });
100
+ case 'list':
101
+ case undefined:
102
+ return profileList({ json });
103
+ case 'default':
104
+ if (!rest[0]) throw new Error(`Usage: ${PROGRAM} profile default <name>`);
105
+ return profileDefault(rest[0]);
106
+ case 'remove':
107
+ if (!rest[0]) {
108
+ throw new Error(
109
+ `Usage: ${PROGRAM} profile remove <name> [--target t] [--dry-run] [--yes]`,
110
+ );
111
+ }
112
+ return profileRemove(rest[0], {
113
+ ...global,
114
+ json,
115
+ dryRun: flags['dry-run'] === true,
116
+ yes: flags['yes'] === true,
117
+ });
118
+ default:
119
+ throw new Error(`Unknown: ${PROGRAM} profile ${second}`);
120
+ }
121
+
122
+ case 'target':
123
+ switch (second) {
124
+ case 'list':
125
+ case undefined:
126
+ return targetList({ ...global, json, urls: flags['urls'] === true });
127
+ case 'use':
128
+ if (!rest[0]) throw new Error(`Usage: ${PROGRAM} target use <name>`);
129
+ return targetUse(rest[0], global);
130
+ case 'show':
131
+ return targetShow(rest[0], { ...global, json });
132
+ default:
133
+ throw new Error(`Unknown: ${PROGRAM} target ${second}`);
134
+ }
135
+
136
+ case 'policy':
137
+ switch (second) {
138
+ case 'list':
139
+ case undefined:
140
+ return policyList(global);
141
+ case 'allow':
142
+ case 'deny': {
143
+ const [capability] = rest;
144
+ if (!capability) {
145
+ throw new Error(
146
+ `Usage: ${PROGRAM} policy ${second} <capability>\n` +
147
+ ` e.g. ${PROGRAM} policy ${second} gmail.send_message, or gmail.* for the whole provider`,
148
+ );
149
+ }
150
+ return policyRule(second, capability, global);
151
+ }
152
+ default:
153
+ throw new Error(`Unknown: ${PROGRAM} policy ${second}`);
154
+ }
155
+
156
+ case 'token':
157
+ switch (second) {
158
+ case 'show':
159
+ case undefined:
160
+ return tokenShow({ ...global, show, raw });
161
+ case 'rotate':
162
+ return tokenRotate({ ...global, show });
163
+ default:
164
+ throw new Error(`Unknown: ${PROGRAM} token ${second}`);
165
+ }
166
+
167
+ case 'audit':
168
+ if (second === 'verify') return auditVerify(global);
169
+ if (second !== 'tail' && second !== undefined) {
170
+ throw new Error(`Unknown: ${PROGRAM} audit ${second}`);
171
+ }
172
+ return auditTail({
173
+ ...global,
174
+ limit: typeof flags['limit'] === 'string' ? Number(flags['limit']) : undefined,
175
+ deniedOnly: flags['denied-only'] === true,
176
+ ...(typeof flags['format'] === 'string' ? { format: flags['format'] } : {}),
177
+ });
178
+
179
+ // The file is positional rather than `--file`, because `attach <file>` is
180
+ // how every other tool spells it and this one is typed by a person.
181
+ case 'attach':
182
+ return attachFile({
183
+ ...global,
184
+ file: second,
185
+ connection: text(flags, 'connection'),
186
+ });
187
+
188
+ case 'config':
189
+ if (second !== 'show' && second !== undefined) throw new Error(`Unknown: ${PROGRAM} config ${second}`);
190
+ return configShow(global);
191
+
192
+ case 'memory':
193
+ switch (second) {
194
+ case 'list':
195
+ case undefined:
196
+ return memoryList(owner);
197
+ case 'get':
198
+ return memoryGet(rest[0], owner);
199
+ case 'write':
200
+ return memoryWrite(rest[0], owner);
201
+ case 'forget':
202
+ return memoryForget(rest[0], owner);
203
+ default:
204
+ throw new Error(`Unknown: ${PROGRAM} memory ${second}`);
205
+ }
206
+
207
+ case 'skills':
208
+ switch (second) {
209
+ case 'list':
210
+ case undefined:
211
+ return skillsList(owner);
212
+ case 'show':
213
+ return skillsShow(rest[0], owner);
214
+ case 'add':
215
+ return skillsAdd(rest[0], owner);
216
+ case 'remove':
217
+ return skillsRemove(rest[0], owner);
218
+ default:
219
+ throw new Error(`Unknown: ${PROGRAM} skills ${second}`);
220
+ }
221
+
222
+ case 'vault':
223
+ switch (second) {
224
+ case 'list':
225
+ case undefined:
226
+ return vaultList(owner);
227
+ case 'get':
228
+ return vaultGet(rest[0], owner);
229
+ case 'set':
230
+ return vaultSet(rest[0], owner);
231
+ case 'remove':
232
+ return vaultRemove(rest[0], owner);
233
+ case 'key':
234
+ if (rest[0] !== 'generate') {
235
+ throw new Error(`Usage: ${PROGRAM} vault key generate`);
236
+ }
237
+ return vaultKeyGenerate(owner);
238
+ default:
239
+ throw new Error(`Unknown: ${PROGRAM} vault ${second}`);
240
+ }
241
+
242
+ case 'check':
243
+ return check(global);
244
+ case 'plan':
245
+ return plan(global);
246
+ case 'doctor':
247
+ return doctor({ ...global, json });
248
+ case 'status':
249
+ return status({ ...global, json });
250
+ case 'outputs':
251
+ return outputs({ ...global, show, json });
252
+
253
+ // Undocumented alias for `mcp skill`, which is where it moved when `skills`
254
+ // arrived. Anyone who learned the old spelling keeps it.
255
+ case 'skill':
256
+ return skillDocument({ print: flags['print'] === true });
257
+
258
+ case 'mcp':
259
+ switch (second) {
260
+ // `lanes link skill` was this, before `lanes link skills` existed. Two
261
+ // commands one letter apart, meaning entirely different things — the
262
+ // agent-side skill file that teaches a harness to register this
263
+ // endpoint, and the owner's own procedures — is a trap worth closing.
264
+ // Kept working below rather than removed, undocumented.
265
+ case 'skill':
266
+ return skillDocument({ print: flags['print'] === true });
267
+ case 'add':
268
+ return mcpAdd(rest[0], {
269
+ ...global,
270
+ name: text(flags, 'name'),
271
+ scope: text(flags, 'scope'),
272
+ tokenEnv: text(flags, 'token-env'),
273
+ dryRun: flags['dry-run'] === true,
274
+ force: flags['force'] === true,
275
+ noSkill: flags['no-skill'] === true,
276
+ });
277
+ case 'stdio':
278
+ return mcpStdio({ ...global, ...(flags['only'] === true ? { only: true } : {}) });
279
+ case 'list':
280
+ case undefined:
281
+ return mcpList({ name: text(flags, 'name'), scope: text(flags, 'scope') });
282
+ default:
283
+ throw new Error(`Unknown: ${PROGRAM} mcp ${second}`);
284
+ }
285
+ case 'start':
286
+ return start({
287
+ ...global,
288
+ port: typeof flags['port'] === 'string' ? Number(flags['port']) : undefined,
289
+ only: flags['only'] === true,
290
+ });
291
+
292
+ case 'deploy':
293
+ return deploy({
294
+ ...global,
295
+ dryRun: flags['dry-run'] === true,
296
+ // `--iam` was a boolean that meant "add the platform's own check on top".
297
+ // It is now one of two values of a declared field, because the opposite
298
+ // is a choice too and a flag that only turns something on cannot express
299
+ // turning it off again for a target whose config says otherwise.
300
+ access: flags['iam'] === true ? 'iam' : text(flags, 'access'),
301
+ serviceAccount: text(flags, 'service-account'),
302
+ tag: text(flags, 'tag'),
303
+ yes: flags['yes'] === true,
304
+ nonInteractive: flags['non-interactive'] === true,
305
+ });
306
+
307
+ case 'secrets':
308
+ switch (second) {
309
+ case 'push':
310
+ return secretsPush({
311
+ ...global,
312
+ from: text(flags, 'from'),
313
+ to: text(flags, 'to'),
314
+ overwrite: flags['overwrite'] === true,
315
+ dryRun: flags['dry-run'] === true,
316
+ });
317
+ case 'set':
318
+ return secretsSet(rest[0], global);
319
+ case 'list':
320
+ case undefined:
321
+ return secretsList(global);
322
+ default:
323
+ throw new Error(`Unknown: ${PROGRAM} secrets ${second}`);
324
+ }
325
+
326
+ case 'version':
327
+ print(version());
328
+ return;
329
+
330
+ default:
331
+ throw new Error(`Unknown command "${first}". Run: ${PROGRAM} help`);
332
+ }
333
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * The one error type the OAuth flow throws.
3
+ *
4
+ * Its own file because both halves of the flow raise it — the listener in
5
+ * `oauth.ts` and the exchange in `oauth-exchange.ts` — and having either import
6
+ * the other would be a cycle for the sake of one class.
7
+ */
8
+ export class OAuthError extends Error {
9
+ constructor(message: string) {
10
+ super(message);
11
+ this.name = 'OAuthError';
12
+ }
13
+ }
@@ -0,0 +1,146 @@
1
+ import { BrokerError, brokerExchange } from '#connectivity/auth/index.ts';
2
+ import { OAuthError } from './oauth-error.ts';
3
+
4
+ /**
5
+ * Turning an authorization code into tokens — the one step that needs a secret.
6
+ *
7
+ * Split from `oauth.ts` because it is the only part of the flow that differs
8
+ * between a client the operator registered and a client somebody operates on
9
+ * their behalf. Everything else — the loopback listener, PKCE, the state check,
10
+ * the browser — is identical either way, and keeping it that way is the point:
11
+ * a connection should not behave differently at runtime because of where its
12
+ * client came from.
13
+ */
14
+
15
+ export interface OAuthTokens {
16
+ readonly refreshToken: string;
17
+ readonly accessToken: string;
18
+ readonly expiresIn: number;
19
+ readonly scope: string;
20
+ /**
21
+ * An identity assertion, present when `openid` was granted.
22
+ *
23
+ * Opaque here on purpose. It is stored and handed back to whoever asked for
24
+ * it, never decoded — the claim inside is unverified, and a tamperable local
25
+ * store is not somewhere to read an identity from. What names an account is
26
+ * the provider's own identity call, which asks and gets a checkable answer.
27
+ */
28
+ readonly idToken?: string;
29
+ }
30
+
31
+ export interface ExchangeInput {
32
+ readonly code: string;
33
+ readonly redirectUri: string;
34
+ readonly codeVerifier: string;
35
+ /** What was asked for, used only to fill in a response that omits `scope`. */
36
+ readonly scopes: readonly string[];
37
+ }
38
+
39
+ export type ExchangeCode = (input: ExchangeInput) => Promise<OAuthTokens>;
40
+
41
+ /** Straight to the vendor, signed with a client this machine holds. */
42
+ export function directExchange(options: {
43
+ readonly tokenUrl: string;
44
+ readonly clientId: string;
45
+ readonly clientSecret: string;
46
+ readonly fetch?: typeof globalThis.fetch | undefined;
47
+ }): ExchangeCode {
48
+ return async (input) => {
49
+ const doFetch = options.fetch ?? globalThis.fetch;
50
+
51
+ const response = await doFetch(options.tokenUrl, {
52
+ method: 'POST',
53
+ headers: { 'content-type': 'application/x-www-form-urlencoded' },
54
+ body: new URLSearchParams({
55
+ grant_type: 'authorization_code',
56
+ code: input.code,
57
+ redirect_uri: input.redirectUri,
58
+ client_id: options.clientId,
59
+ client_secret: options.clientSecret,
60
+ code_verifier: input.codeVerifier,
61
+ }),
62
+ });
63
+
64
+ const body = (await response.json().catch(() => ({}))) as {
65
+ access_token?: string;
66
+ refresh_token?: string;
67
+ id_token?: string;
68
+ expires_in?: number;
69
+ scope?: string;
70
+ error?: string;
71
+ error_description?: string;
72
+ };
73
+
74
+ if (!response.ok || !body.access_token) {
75
+ throw new OAuthError(
76
+ `Token exchange failed: ${body.error ?? response.status} ${body.error_description ?? ''}`.trim(),
77
+ );
78
+ }
79
+
80
+ return settle(body, input.scopes);
81
+ };
82
+ }
83
+
84
+ /** Through a broker, which holds the secret this machine does not have. */
85
+ export function brokerExchangeVia(options: {
86
+ readonly url: string;
87
+ readonly fetch?: typeof globalThis.fetch | undefined;
88
+ }): ExchangeCode {
89
+ return async (input) => {
90
+ let tokens;
91
+ try {
92
+ tokens = await brokerExchange(
93
+ options.url,
94
+ {
95
+ code: input.code,
96
+ codeVerifier: input.codeVerifier,
97
+ redirectUri: input.redirectUri,
98
+ },
99
+ options.fetch ?? globalThis.fetch,
100
+ );
101
+ } catch (cause) {
102
+ // Rethrown as-is when it is a BrokerError: it carries the notice and the
103
+ // "your own client would fix this" flag that the caller renders, and
104
+ // flattening it to a string here would throw both away.
105
+ if (cause instanceof BrokerError) throw cause;
106
+ throw new OAuthError(`Token exchange failed: ${String(cause)}`);
107
+ }
108
+
109
+ if (!tokens.access_token) {
110
+ throw new OAuthError('The token exchange returned no access token.');
111
+ }
112
+
113
+ return settle(tokens, input.scopes);
114
+ };
115
+ }
116
+
117
+ function settle(
118
+ body: {
119
+ access_token?: string | undefined;
120
+ refresh_token?: string | undefined;
121
+ id_token?: string | undefined;
122
+ expires_in?: number | undefined;
123
+ scope?: string | undefined;
124
+ },
125
+ scopes: readonly string[],
126
+ ): OAuthTokens {
127
+ if (!body.refresh_token) {
128
+ // Without one, the connection works until the access token expires and then
129
+ // quietly stops. Better to fail now with the actual cause. Google is named
130
+ // because Google is who reaches this path: every provider that redeems a
131
+ // code here is a Google REST one, and the others hand the exchange to the
132
+ // MCP SDK. Naming the page beats describing it.
133
+ throw new OAuthError(
134
+ 'Google returned no refresh token. This usually means the account was already authorised ' +
135
+ 'for this app; revoke it at https://myaccount.google.com/permissions and try again.',
136
+ );
137
+ }
138
+
139
+ return {
140
+ refreshToken: body.refresh_token,
141
+ accessToken: body.access_token!,
142
+ expiresIn: body.expires_in ?? 3600,
143
+ scope: body.scope ?? scopes.join(' '),
144
+ ...(body.id_token ? { idToken: body.id_token } : {}),
145
+ };
146
+ }