@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.
- package/LICENSE +202 -0
- package/README.md +144 -0
- package/bin/lanes +42 -0
- package/instructions/agents/lanes-link-scout.md +73 -0
- package/instructions/skills/lanes-link/SKILL.md +187 -0
- package/package.json +95 -0
- package/src/audit/chain.ts +278 -0
- package/src/audit/conformance.ts +374 -0
- package/src/audit/fanout.ts +97 -0
- package/src/audit/index.ts +218 -0
- package/src/audit/stdout.ts +60 -0
- package/src/auth/index.ts +220 -0
- package/src/auth/oauth/metadata.ts +75 -0
- package/src/auth/oauth/server.ts +385 -0
- package/src/auth/oauth/store.ts +215 -0
- package/src/auth/oidc.ts +206 -0
- package/src/auth/remote.ts +72 -0
- package/src/cli/argv.ts +94 -0
- package/src/cli/callback-page.ts +256 -0
- package/src/cli/commands/connect/accounts.ts +94 -0
- package/src/cli/commands/connect/authorise.ts +298 -0
- package/src/cli/commands/connect/client.ts +284 -0
- package/src/cli/commands/connect/index.ts +398 -0
- package/src/cli/commands/connect/outcome.ts +119 -0
- package/src/cli/commands/connect/requirements.ts +103 -0
- package/src/cli/commands/connect/scopes-gate.ts +146 -0
- package/src/cli/commands/connect/settle.ts +136 -0
- package/src/cli/commands/connect/setup.ts +276 -0
- package/src/cli/commands/mcp/assets.ts +189 -0
- package/src/cli/commands/mcp/harnesses.ts +143 -0
- package/src/cli/commands/mcp/list.ts +70 -0
- package/src/cli/commands/mcp/register.ts +199 -0
- package/src/cli/commands/mcp/stdio.ts +57 -0
- package/src/cli/commands/mcp.ts +22 -0
- package/src/cli/commands/operate/attach.ts +121 -0
- package/src/cli/commands/operate/audit.ts +119 -0
- package/src/cli/commands/operate/inspect.ts +355 -0
- package/src/cli/commands/operate/outputs.ts +206 -0
- package/src/cli/commands/operate/policy.ts +80 -0
- package/src/cli/commands/operate/serve.ts +55 -0
- package/src/cli/commands/operate/status.ts +133 -0
- package/src/cli/commands/operate/token.ts +70 -0
- package/src/cli/commands/operate.ts +27 -0
- package/src/cli/commands/owner/memory.ts +110 -0
- package/src/cli/commands/owner/shared.ts +125 -0
- package/src/cli/commands/owner/skills.ts +92 -0
- package/src/cli/commands/owner/vault.ts +140 -0
- package/src/cli/commands/owner.ts +46 -0
- package/src/cli/commands/profile/removal.ts +278 -0
- package/src/cli/commands/profile/remove.ts +315 -0
- package/src/cli/commands/profile.ts +156 -0
- package/src/cli/commands/secrets.ts +176 -0
- package/src/cli/commands/setup.ts +150 -0
- package/src/cli/commands/target.ts +310 -0
- package/src/cli/config-edit.ts +397 -0
- package/src/cli/endpoint-url.ts +77 -0
- package/src/cli/identity.ts +109 -0
- package/src/cli/lanes.ts +78 -0
- package/src/cli/main.ts +333 -0
- package/src/cli/oauth-error.ts +13 -0
- package/src/cli/oauth-exchange.ts +146 -0
- package/src/cli/oauth.ts +354 -0
- package/src/cli/output.ts +184 -0
- package/src/cli/prompt.ts +180 -0
- package/src/cli/publish.ts +185 -0
- package/src/cli/runtime/discovery.ts +123 -0
- package/src/cli/runtime/open.ts +342 -0
- package/src/cli/runtime/registry.ts +185 -0
- package/src/cli/runtime/select.ts +124 -0
- package/src/cli/runtime.ts +34 -0
- package/src/cli/scopes.ts +63 -0
- package/src/cli/usage.ts +111 -0
- package/src/cli/version.ts +25 -0
- package/src/connectivity/auth/README.md +36 -0
- package/src/connectivity/auth/api-key/index.ts +43 -0
- package/src/connectivity/auth/authorize.ts +49 -0
- package/src/connectivity/auth/basic/index.ts +68 -0
- package/src/connectivity/auth/bearer/index.ts +13 -0
- package/src/connectivity/auth/credential.ts +19 -0
- package/src/connectivity/auth/header/index.ts +18 -0
- package/src/connectivity/auth/index.ts +35 -0
- package/src/connectivity/auth/none/index.ts +12 -0
- package/src/connectivity/auth/oauth-authcode/broker.ts +261 -0
- package/src/connectivity/auth/oauth-authcode/index.ts +64 -0
- package/src/connectivity/auth/oauth-authcode/provider.ts +279 -0
- package/src/connectivity/auth/oauth-authcode/refresh.ts +118 -0
- package/src/connectivity/auth/resolve.ts +61 -0
- package/src/connectivity/auth/strategy/index.ts +14 -0
- package/src/connectivity/capability.ts +164 -0
- package/src/connectivity/connector.ts +175 -0
- package/src/connectivity/context.ts +77 -0
- package/src/connectivity/index.ts +94 -0
- package/src/connectivity/mail/attachments.ts +368 -0
- package/src/connectivity/mail/compose.ts +73 -0
- package/src/connectivity/mail/index.ts +40 -0
- package/src/connectivity/mail/message.ts +82 -0
- package/src/connectivity/mail/nodemailer.d.ts +83 -0
- package/src/connectivity/mail/staging.ts +143 -0
- package/src/connectivity/mail/url.ts +301 -0
- package/src/connectivity/manifest/auth.ts +135 -0
- package/src/connectivity/manifest/bundles.ts +23 -0
- package/src/connectivity/manifest/connector.ts +168 -0
- package/src/connectivity/manifest/credential-ref.ts +73 -0
- package/src/connectivity/manifest/identity.ts +43 -0
- package/src/connectivity/manifest/index.ts +46 -0
- package/src/connectivity/manifest/primitives.ts +20 -0
- package/src/connectivity/manifest/provider.ts +208 -0
- package/src/connectivity/manifest/requirements.ts +146 -0
- package/src/connectivity/manifest/setup.ts +55 -0
- package/src/connectivity/provider.ts +163 -0
- package/src/connectivity/transports/README.md +33 -0
- package/src/connectivity/transports/composite/index.ts +68 -0
- package/src/connectivity/transports/dav/calendar.ts +217 -0
- package/src/connectivity/transports/dav/capabilities.ts +151 -0
- package/src/connectivity/transports/dav/client.ts +226 -0
- package/src/connectivity/transports/dav/contacts.ts +75 -0
- package/src/connectivity/transports/dav/ical.ts +412 -0
- package/src/connectivity/transports/dav/index.ts +143 -0
- package/src/connectivity/transports/dav/operations.ts +43 -0
- package/src/connectivity/transports/dav/request.ts +161 -0
- package/src/connectivity/transports/dav/xml.ts +123 -0
- package/src/connectivity/transports/factory.ts +181 -0
- package/src/connectivity/transports/fs/capabilities.ts +88 -0
- package/src/connectivity/transports/fs/commands.ts +258 -0
- package/src/connectivity/transports/fs/index.ts +121 -0
- package/src/connectivity/transports/fs/operations.ts +45 -0
- package/src/connectivity/transports/fs/paths.ts +120 -0
- package/src/connectivity/transports/fs/result.ts +12 -0
- package/src/connectivity/transports/http/index.ts +255 -0
- package/src/connectivity/transports/imap/attachment.ts +166 -0
- package/src/connectivity/transports/imap/capabilities.ts +158 -0
- package/src/connectivity/transports/imap/client.ts +398 -0
- package/src/connectivity/transports/imap/commands.ts +385 -0
- package/src/connectivity/transports/imap/index.ts +152 -0
- package/src/connectivity/transports/imap/operations.ts +64 -0
- package/src/connectivity/transports/imap/parse.ts +130 -0
- package/src/connectivity/transports/imap/parser.ts +272 -0
- package/src/connectivity/transports/imap/result.ts +15 -0
- package/src/connectivity/transports/imap/send.ts +92 -0
- package/src/connectivity/transports/imap/socket.ts +111 -0
- package/src/connectivity/transports/imap/utf7.ts +136 -0
- package/src/connectivity/transports/index.ts +20 -0
- package/src/connectivity/transports/local/index.ts +173 -0
- package/src/connectivity/transports/mcp/index.ts +215 -0
- package/src/deployments/README.md +63 -0
- package/src/deployments/adapters/audit-blob.ts +203 -0
- package/src/deployments/adapters/filesystem.ts +184 -0
- package/src/deployments/adapters/gcp-secret-manager.ts +492 -0
- package/src/deployments/adapters/gcs.ts +191 -0
- package/src/deployments/adapters/otlp.ts +128 -0
- package/src/deployments/adapters/s3.ts +195 -0
- package/src/deployments/azure/README.md +21 -0
- package/src/deployments/bootstrap.ts +177 -0
- package/src/deployments/deploy.ts +290 -0
- package/src/deployments/driver.ts +157 -0
- package/src/deployments/drivers.ts +35 -0
- package/src/deployments/gcp/Dockerfile +70 -0
- package/src/deployments/gcp/cloudbuild.yaml +31 -0
- package/src/deployments/gcp/driver.ts +175 -0
- package/src/deployments/gcp/gcloud.ts +178 -0
- package/src/deployments/gcp/provision.ts +290 -0
- package/src/deployments/gcp/survey.ts +319 -0
- package/src/deployments/local/README.md +12 -0
- package/src/deployments/prepare.ts +257 -0
- package/src/deployments/steps.ts +137 -0
- package/src/deployments/target.ts +295 -0
- package/src/deployments/upload.ts +207 -0
- package/src/dispatch/context.ts +195 -0
- package/src/dispatch/dispatch.ts +350 -0
- package/src/dispatch/index.ts +32 -0
- package/src/dispatch/staging.ts +102 -0
- package/src/policy/index.ts +179 -0
- package/src/policy/limits.ts +77 -0
- package/src/profile/authorization.ts +81 -0
- package/src/profile/files.ts +71 -0
- package/src/profile/index.ts +76 -0
- package/src/profile/layout.ts +123 -0
- package/src/profile/load.ts +199 -0
- package/src/profile/primitives.ts +45 -0
- package/src/profile/schema.ts +347 -0
- package/src/profile/secret-detection.ts +162 -0
- package/src/profile/targets.ts +152 -0
- package/src/profile/workspace.ts +262 -0
- package/src/providers/custom/index.ts +21 -0
- package/src/providers/custom/load.ts +115 -0
- package/src/providers/custom/template.ts +156 -0
- package/src/providers/example/provider.ts +207 -0
- package/src/providers/google/calendar/index.ts +66 -0
- package/src/providers/google/calendar/redact.ts +40 -0
- package/src/providers/google/contacts/index.ts +50 -0
- package/src/providers/google/contacts/redact.ts +21 -0
- package/src/providers/google/docs/index.ts +45 -0
- package/src/providers/google/drive/hints.ts +28 -0
- package/src/providers/google/drive/index.ts +34 -0
- package/src/providers/google/drive/redact.ts +39 -0
- package/src/providers/google/drive-mcp/index.ts +21 -0
- package/src/providers/google/gmail/api.ts +42 -0
- package/src/providers/google/gmail/attachment.ts +142 -0
- package/src/providers/google/gmail/hints.ts +55 -0
- package/src/providers/google/gmail/index.ts +112 -0
- package/src/providers/google/gmail/redact.ts +56 -0
- package/src/providers/google/gmail/send.ts +365 -0
- package/src/providers/google/gmail-mcp/index.ts +35 -0
- package/src/providers/google/index.ts +10 -0
- package/src/providers/google/shared/oauth.ts +122 -0
- package/src/providers/google/shared/scopes.ts +99 -0
- package/src/providers/google/shared/setup.ts +80 -0
- package/src/providers/google/sheets/hints.ts +45 -0
- package/src/providers/google/sheets/index.ts +70 -0
- package/src/providers/google/sheets/redact.ts +45 -0
- package/src/providers/google/specs/calendar.v3.json +1829 -0
- package/src/providers/google/specs/docs.v1.json +381 -0
- package/src/providers/google/specs/drive.v3.json +2208 -0
- package/src/providers/google/specs/gmail.v1.json +2578 -0
- package/src/providers/google/specs/people.v1.json +506 -0
- package/src/providers/google/specs/sheets.v4.json +1269 -0
- package/src/providers/google/specs/tasks.v1.json +840 -0
- package/src/providers/google/specs/vendor.ts +661 -0
- package/src/providers/google/tasks/index.ts +53 -0
- package/src/providers/google/tasks/redact.ts +34 -0
- package/src/providers/harness.ts +95 -0
- package/src/providers/icloud/calendar/index.ts +27 -0
- package/src/providers/icloud/contacts/index.ts +17 -0
- package/src/providers/icloud/drive/index.ts +47 -0
- package/src/providers/icloud/index.ts +8 -0
- package/src/providers/icloud/mail/index.ts +37 -0
- package/src/providers/icloud/shared/setup.ts +66 -0
- package/src/providers/index.ts +93 -0
- package/src/providers/linear/index.ts +11 -0
- package/src/providers/linear/scopes.ts +7 -0
- package/src/providers/memory/provider.ts +429 -0
- package/src/providers/notion/index.ts +19 -0
- package/src/providers/owner.ts +49 -0
- package/src/providers/scopes.ts +26 -0
- package/src/providers/setup/plan.ts +141 -0
- package/src/providers/setup/provider.ts +323 -0
- package/src/providers/shared/frontmatter.ts +119 -0
- package/src/providers/skills/provider.ts +283 -0
- package/src/providers/skills/store.ts +252 -0
- package/src/providers/vault/provider.ts +194 -0
- package/src/registry/index.ts +36 -0
- package/src/registry/policy-bridge.ts +32 -0
- package/src/registry/reconcile.ts +313 -0
- package/src/registry/registry.ts +240 -0
- package/src/secrets/document.ts +293 -0
- package/src/secrets/index.ts +154 -0
- package/src/secrets/system.ts +173 -0
- package/src/secrets/vault.ts +336 -0
- package/src/server/attachments.ts +197 -0
- package/src/server/container.ts +96 -0
- package/src/server/edge.ts +53 -0
- package/src/server/endpoint.ts +352 -0
- package/src/server/generations.ts +362 -0
- package/src/server/harness.ts +400 -0
- package/src/server/index.ts +331 -0
- package/src/server/logging.ts +41 -0
- package/src/server/mcp/build.ts +68 -0
- package/src/server/mcp/icon.ts +145 -0
- package/src/server/mcp/index.ts +32 -0
- package/src/server/mcp/instructions.ts +245 -0
- package/src/server/mcp/naming.ts +39 -0
- package/src/server/mcp/prompts.ts +78 -0
- package/src/server/mcp/resources.ts +106 -0
- package/src/server/mcp/routing.ts +117 -0
- package/src/server/mcp/schema.ts +78 -0
- package/src/server/mcp/tools.ts +186 -0
- package/src/server/mcp/visibility.ts +132 -0
- package/src/server/oauth.ts +222 -0
- package/src/server/rebinding.ts +53 -0
- package/src/server/stdio.ts +192 -0
- package/src/stores/blobs/conformance.ts +223 -0
- package/src/stores/blobs/index.ts +125 -0
- package/src/stores/blobs/testing.ts +49 -0
- package/src/stores/state/index.ts +247 -0
- package/src/stores/state/keys.ts +68 -0
- package/src/stores/state/testing.ts +41 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Connectivity types — how we reach a service.
|
|
5
|
+
*
|
|
6
|
+
* One schema per transport in `../transports/`, and the discriminated union
|
|
7
|
+
* below is the complete list. A provider picks exactly one; how it
|
|
8
|
+
* *authenticates* is the other axis entirely (`./auth.ts`), which is what lets
|
|
9
|
+
* iCloud speak IMAP with a password while Gmail speaks HTTP with OAuth and
|
|
10
|
+
* neither costs the other any code.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Proxy an upstream MCP server. Capabilities are **discovered**, never declared
|
|
15
|
+
* — the server is the source of truth for what it exposes.
|
|
16
|
+
*/
|
|
17
|
+
export const mcpConnectorSchema = z.object({
|
|
18
|
+
kind: z.literal('mcp'),
|
|
19
|
+
endpoint: z.url(),
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* A REST API described by OpenAPI. Operations become capabilities mechanically,
|
|
24
|
+
* so a service without an MCP server still costs no translation code.
|
|
25
|
+
*/
|
|
26
|
+
export const httpConnectorSchema = z.object({
|
|
27
|
+
kind: z.literal('http'),
|
|
28
|
+
base_url: z.url(),
|
|
29
|
+
/** URL or workspace-relative path to an OpenAPI 3.x document. */
|
|
30
|
+
openapi: z.string().min(1),
|
|
31
|
+
/**
|
|
32
|
+
* Glob filters on operationId, path, or tag.
|
|
33
|
+
*
|
|
34
|
+
* Not optional in spirit: a large spec yields hundreds of operations, which
|
|
35
|
+
* is a tool list no agent can reason over. For an `http` connector,
|
|
36
|
+
* "everything discovered" means everything selected here.
|
|
37
|
+
*/
|
|
38
|
+
operations: z
|
|
39
|
+
.object({
|
|
40
|
+
include: z.array(z.string()).default([]),
|
|
41
|
+
exclude: z.array(z.string()).default([]),
|
|
42
|
+
})
|
|
43
|
+
.optional(),
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* A mailbox over IMAP4rev1, and optionally SMTP submission.
|
|
48
|
+
*
|
|
49
|
+
* Vendor-neutral: iCloud, Fastmail, and a company Dovecot are the same handful
|
|
50
|
+
* of fields. Unlike `http` there is no document to read — IMAP describes its
|
|
51
|
+
* *extensions* through CAPABILITY but never its operations — so the capability
|
|
52
|
+
* set belongs to the connector rather than to the manifest.
|
|
53
|
+
*/
|
|
54
|
+
export const imapConnectorSchema = z.object({
|
|
55
|
+
kind: z.literal('imap'),
|
|
56
|
+
host: z.string().min(1),
|
|
57
|
+
port: z.number().int().min(1).max(65535).default(993),
|
|
58
|
+
smtp: z
|
|
59
|
+
.object({
|
|
60
|
+
host: z.string().min(1),
|
|
61
|
+
port: z.number().int().min(1).max(65535).default(587),
|
|
62
|
+
/** 587 upgrades in-band; 465 is implicit TLS. Either way TLS is required. */
|
|
63
|
+
starttls: z.boolean().default(true),
|
|
64
|
+
/**
|
|
65
|
+
* Largest message this host will accept, encoded, in bytes.
|
|
66
|
+
*
|
|
67
|
+
* A server limit the provider declares, like `max_range_days` above.
|
|
68
|
+
* Attachments are base64 in the wire format, so the usable weight of the
|
|
69
|
+
* files themselves is about three quarters of this — the send path derives
|
|
70
|
+
* that rather than making a caller do the arithmetic.
|
|
71
|
+
*
|
|
72
|
+
* The default is iCloud's 20 MB, which is also roughly where the common
|
|
73
|
+
* hosts sit. Worth checking one is refused *before* dialling: a message
|
|
74
|
+
* rejected part-way through `DATA` reads as a network failure, and a caller
|
|
75
|
+
* that cannot tell the difference tries again.
|
|
76
|
+
*/
|
|
77
|
+
max_message_bytes: z
|
|
78
|
+
.number()
|
|
79
|
+
.int()
|
|
80
|
+
.positive()
|
|
81
|
+
.max(157_286_400)
|
|
82
|
+
.default(20 * 1024 * 1024),
|
|
83
|
+
})
|
|
84
|
+
.optional(),
|
|
85
|
+
/**
|
|
86
|
+
* How much of a body one call may return.
|
|
87
|
+
*
|
|
88
|
+
* A 40 MB newsletter is not something an agent's context survives, and where
|
|
89
|
+
* to draw that line has to be our decision rather than whatever the message
|
|
90
|
+
* happens to weigh.
|
|
91
|
+
*/
|
|
92
|
+
max_body_bytes: z.number().int().positive().max(4_194_304).default(262_144),
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* CalDAV and CardDAV — HTTPS, but XML under PROPFIND and REPORT, which no
|
|
97
|
+
* OpenAPI document describes, so `http` cannot serve it. Auth is ordinary Basic,
|
|
98
|
+
* which is the payoff for keeping auth orthogonal to connectivity.
|
|
99
|
+
*/
|
|
100
|
+
export const davConnectorSchema = z.object({
|
|
101
|
+
kind: z.literal('dav'),
|
|
102
|
+
/** Where discovery begins; RFC 6764 well-known paths are tried under it. */
|
|
103
|
+
base_url: z.url(),
|
|
104
|
+
service: z.enum(['caldav', 'carddav']),
|
|
105
|
+
/**
|
|
106
|
+
* Longest `list_events` window this server will answer.
|
|
107
|
+
*
|
|
108
|
+
* A server limit, so the provider declares it rather than the transport
|
|
109
|
+
* assuming it. iCloud rejects a wider range than a year; every server slows
|
|
110
|
+
* down over one, which is why there is a default rather than no bound.
|
|
111
|
+
*/
|
|
112
|
+
max_range_days: z.number().int().positive().max(3650).default(366),
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* A directory on the machine this runs on.
|
|
117
|
+
*
|
|
118
|
+
* Vendor-neutral: iCloud Drive is a manifest pointing at its folder, and the
|
|
119
|
+
* same connector serves Dropbox, Syncthing, or a project directory. There is no
|
|
120
|
+
* credential — access to a Mac's iCloud Drive is a TCC grant bound to a binary
|
|
121
|
+
* on that Mac, not a token, so there is nothing to store and nothing that could
|
|
122
|
+
* be carried to another machine. See ADR-011.
|
|
123
|
+
*/
|
|
124
|
+
export const fsConnectorSchema = z.object({
|
|
125
|
+
kind: z.literal('fs'),
|
|
126
|
+
/** May start with `~`. Everything reachable is under it, symlinks included. */
|
|
127
|
+
root: z.string().min(1),
|
|
128
|
+
max_file_bytes: z.number().int().positive().max(4_194_304).default(262_144),
|
|
129
|
+
/** Extra names never listed or read, on top of the built-in refusals. */
|
|
130
|
+
exclude: z.array(z.string()).default([]),
|
|
131
|
+
/**
|
|
132
|
+
* How this folder's sync client marks a file it has not downloaded.
|
|
133
|
+
*
|
|
134
|
+
* macOS represents a dataless iCloud Drive file as a hidden sibling named
|
|
135
|
+
* `.<name>.icloud`, so a listing shows a name that cannot be read and a read
|
|
136
|
+
* returns a few hundred bytes of plist — which looks like a corrupt file
|
|
137
|
+
* rather than an absent one. A folder synced by something else uses a
|
|
138
|
+
* different convention, or none.
|
|
139
|
+
*
|
|
140
|
+
* Declared rather than assumed: this is the one place the word "icloud" would
|
|
141
|
+
* otherwise be hard-coded into a transport that also serves Dropbox,
|
|
142
|
+
* Syncthing, and a plain project directory. `hint` is the remedy, in the
|
|
143
|
+
* provider's own words, because "run brctl" is advice only Apple's answer can
|
|
144
|
+
* give.
|
|
145
|
+
*/
|
|
146
|
+
placeholder: z
|
|
147
|
+
.object({
|
|
148
|
+
suffix: z.string().min(1),
|
|
149
|
+
hint: z.string().optional(),
|
|
150
|
+
})
|
|
151
|
+
.optional(),
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
/** Our own code — `example`, and the owner layer. */
|
|
155
|
+
export const localConnectorSchema = z.object({
|
|
156
|
+
kind: z.literal('local'),
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
export const connectorSchema = z.discriminatedUnion('kind', [
|
|
160
|
+
mcpConnectorSchema,
|
|
161
|
+
httpConnectorSchema,
|
|
162
|
+
imapConnectorSchema,
|
|
163
|
+
davConnectorSchema,
|
|
164
|
+
fsConnectorSchema,
|
|
165
|
+
localConnectorSchema,
|
|
166
|
+
]);
|
|
167
|
+
|
|
168
|
+
export type ConnectorConfig = z.infer<typeof connectorSchema>;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { ProviderManifest } from './provider.ts';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Where this provider keeps the credential for one connection.
|
|
5
|
+
*
|
|
6
|
+
* The single authority, because there used to be two that disagreed: reconcile
|
|
7
|
+
* derived `<provider>/<id>` and never read the manifest, while the request
|
|
8
|
+
* authorizer read the manifest and never derived. A provider declaring
|
|
9
|
+
* `credential_ref: mything/api_key` was therefore reported unauthorized forever
|
|
10
|
+
* — `doctor` told you to run `connect`, and `connect` did not help.
|
|
11
|
+
*
|
|
12
|
+
* Four cases, in precedence order. The caller applies a fifth ahead of them all:
|
|
13
|
+
* a `credential_ref` on the *connection* is a hand placement and wins outright.
|
|
14
|
+
*/
|
|
15
|
+
export function credentialRefForConnection(
|
|
16
|
+
manifest: ProviderManifest,
|
|
17
|
+
connectionId: string,
|
|
18
|
+
): string | undefined {
|
|
19
|
+
const auth = manifest.auth;
|
|
20
|
+
|
|
21
|
+
if (auth.kind === 'none') return undefined;
|
|
22
|
+
|
|
23
|
+
// OAuth tokens are always per provider. `app` names the shared *client*, not
|
|
24
|
+
// where tokens land: Gmail and Drive authorise against one Google client but
|
|
25
|
+
// hold separate tokens, because they were granted different scopes, and
|
|
26
|
+
// pointing both at one ref would have the second connect silently narrow the
|
|
27
|
+
// first.
|
|
28
|
+
if (auth.kind === 'oauth') return `${manifest.id}/${connectionId}`;
|
|
29
|
+
|
|
30
|
+
if (auth.kind === 'strategy') return auth.credential_ref ?? `${manifest.id}/${connectionId}`;
|
|
31
|
+
|
|
32
|
+
if (auth.credential_ref) return auth.credential_ref;
|
|
33
|
+
return `${auth.app ?? manifest.id}/${connectionId}`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Which of this connection's references get written while serving a request,
|
|
38
|
+
* rather than only by a command the operator ran.
|
|
39
|
+
*
|
|
40
|
+
* Nothing about a credential says whether it is rewritten in flight, and the
|
|
41
|
+
* one place that knows — `CredentialOAuthProvider` — keeps its refs private.
|
|
42
|
+
* So a deployed revision was granted read on the store and write on nothing,
|
|
43
|
+
* which held right up until an access token expired: the refresh persists, and
|
|
44
|
+
* the request that triggered it failed on a store it was only allowed to read.
|
|
45
|
+
*
|
|
46
|
+
* Kept beside `credentialRefForConnection` because it is the same question
|
|
47
|
+
* asked for a different purpose, and answering it two files apart is how those
|
|
48
|
+
* two disagreed the first time. `deployments/grants.test.ts` pins this against
|
|
49
|
+
* what a deploy actually binds.
|
|
50
|
+
*/
|
|
51
|
+
export function rotatableCredentialRefs(
|
|
52
|
+
manifest: ProviderManifest,
|
|
53
|
+
connectionId: string,
|
|
54
|
+
): readonly string[] {
|
|
55
|
+
const auth = manifest.auth;
|
|
56
|
+
if (auth.kind !== 'oauth') return [];
|
|
57
|
+
|
|
58
|
+
// The token blob, rewritten by `saveTokens` on every refresh. Always, and it
|
|
59
|
+
// is the one that matters: this is the hot path of an ordinary read.
|
|
60
|
+
const refs = [`${manifest.id}/${connectionId}`];
|
|
61
|
+
|
|
62
|
+
// A dynamically registered client, rewritten by `saveClientInformation` if
|
|
63
|
+
// the SDK ever re-registers mid-flight. Rare enough that it has never been
|
|
64
|
+
// seen, and unbound it fails exactly the same way — the cost of listing it is
|
|
65
|
+
// one binding on a secret that already exists.
|
|
66
|
+
//
|
|
67
|
+
// Never for `manual`: that client is the operator's, shared across every
|
|
68
|
+
// connection using the same `oauth_apps` entry, and a revision has no
|
|
69
|
+
// business rewriting it.
|
|
70
|
+
if (auth.registration !== 'manual') refs.push(`${manifest.id}/client`);
|
|
71
|
+
|
|
72
|
+
return refs;
|
|
73
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* How to find out *whose* account was just authorised.
|
|
5
|
+
*
|
|
6
|
+
* Without this a config file says `Gmail main2`, which cannot answer the only
|
|
7
|
+
* question anyone asks of a connection list — which mailbox is this? The answer
|
|
8
|
+
* has to come from the provider, because it is the only party that knows, and
|
|
9
|
+
* it has to be declarative, or every vendor costs code again.
|
|
10
|
+
*
|
|
11
|
+
* Three sources cover everything we have met:
|
|
12
|
+
*
|
|
13
|
+
* - `http` — a GET returning JSON, for a vendor whose MCP server exposes no
|
|
14
|
+
* identity tool. Gmail answers `users/me/profile` with `emailAddress` under
|
|
15
|
+
* scopes we already hold, so this costs no extra consent.
|
|
16
|
+
* - `tool` — call a capability on the upstream MCP server and read a field of
|
|
17
|
+
* the result. Linear's `get_workspace` is exactly this.
|
|
18
|
+
* - `connector` — the connector already knows, so ask it. A protocol that
|
|
19
|
+
* authenticates by username has nothing to probe: the answer is the name the
|
|
20
|
+
* *server accepted*, which is a stronger claim than the one the operator
|
|
21
|
+
* typed, and there is no endpoint to GET for it.
|
|
22
|
+
*
|
|
23
|
+
* `field` is a dotted path. Resolution is best-effort by design: a provider with
|
|
24
|
+
* no identity block, or one whose probe fails, falls back to asking the
|
|
25
|
+
* operator. A connection is worth labelling, never worth blocking on.
|
|
26
|
+
*/
|
|
27
|
+
export const identitySchema = z.discriminatedUnion('kind', [
|
|
28
|
+
z.object({
|
|
29
|
+
kind: z.literal('http'),
|
|
30
|
+
url: z.url(),
|
|
31
|
+
/** Dotted path into the JSON body, e.g. `emailAddress` or `user.emailAddress`. */
|
|
32
|
+
field: z.string().min(1),
|
|
33
|
+
}),
|
|
34
|
+
z.object({
|
|
35
|
+
kind: z.literal('tool'),
|
|
36
|
+
tool: z.string().min(1),
|
|
37
|
+
field: z.string().min(1),
|
|
38
|
+
arguments: z.record(z.string(), z.unknown()).default({}),
|
|
39
|
+
}),
|
|
40
|
+
z.object({ kind: z.literal('connector') }),
|
|
41
|
+
]);
|
|
42
|
+
|
|
43
|
+
export type IdentityDeclaration = z.infer<typeof identitySchema>;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The provider declaration format, one file per section.
|
|
3
|
+
*
|
|
4
|
+
* It was one 513-line file, which hid the shape of the thing it describes: a
|
|
5
|
+
* manifest is a *connectivity type* plus an *auth method* plus the operator-
|
|
6
|
+
* facing text around them, and those are the two axes the whole design rests
|
|
7
|
+
* on. `connector.ts` and `auth.ts` are now the same size and sit side by side,
|
|
8
|
+
* which is what the sentence "auth is orthogonal to connectivity" looks like
|
|
9
|
+
* when the files agree with it.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export {
|
|
13
|
+
connectorSchema,
|
|
14
|
+
davConnectorSchema,
|
|
15
|
+
fsConnectorSchema,
|
|
16
|
+
httpConnectorSchema,
|
|
17
|
+
imapConnectorSchema,
|
|
18
|
+
localConnectorSchema,
|
|
19
|
+
mcpConnectorSchema,
|
|
20
|
+
type ConnectorConfig,
|
|
21
|
+
} from './connector.ts';
|
|
22
|
+
|
|
23
|
+
export {
|
|
24
|
+
authNoneSchema,
|
|
25
|
+
authBrokerSchema,
|
|
26
|
+
authOAuthSchema,
|
|
27
|
+
authSchema,
|
|
28
|
+
authStrategySchema,
|
|
29
|
+
authTokenSchema,
|
|
30
|
+
type AuthBroker,
|
|
31
|
+
type AuthConfig,
|
|
32
|
+
} from './auth.ts';
|
|
33
|
+
|
|
34
|
+
export { setupPromptSchema, setupSchema, type SetupDeclaration, type SetupPrompt } from './setup.ts';
|
|
35
|
+
export { READ_BUNDLE, WRITE_BUNDLE, bundleSchema, type ScopeBundle } from './bundles.ts';
|
|
36
|
+
export { identitySchema, type IdentityDeclaration } from './identity.ts';
|
|
37
|
+
export { credentialRefForConnection, rotatableCredentialRefs } from './credential-ref.ts';
|
|
38
|
+
export {
|
|
39
|
+
RESERVED_PROVIDER_IDS,
|
|
40
|
+
defineProvider,
|
|
41
|
+
providerManifestSchema,
|
|
42
|
+
type ProviderManifest,
|
|
43
|
+
} from './provider.ts';
|
|
44
|
+
|
|
45
|
+
export type { SetupRequirement, SetupNeeds } from './requirements.ts';
|
|
46
|
+
export { setupRequirements, UNNAMED_ID } from './requirements.ts';
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
/** Provider ids, capability names, app names: lowercase, digits, underscores. */
|
|
4
|
+
export const identifier = z
|
|
5
|
+
.string()
|
|
6
|
+
.regex(/^[a-z][a-z0-9_]*$/, 'must be lowercase letters, digits, and underscores');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* A pointer into the secret store, never a value.
|
|
10
|
+
*
|
|
11
|
+
* The pattern is what stops a manifest carrying the secret itself. A literal
|
|
12
|
+
* API key has no `/` in it, so it fails here rather than being written to a
|
|
13
|
+
* config file that someone later commits.
|
|
14
|
+
*/
|
|
15
|
+
export const credentialRef = z
|
|
16
|
+
.string()
|
|
17
|
+
.regex(
|
|
18
|
+
/^[a-z0-9][a-z0-9_-]*(?:\/[a-z0-9][a-z0-9_-]*)+$/,
|
|
19
|
+
'must be a credential reference like "bunq/api_key", not a literal value',
|
|
20
|
+
);
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { authSchema } from './auth.ts';
|
|
3
|
+
import { bundleSchema } from './bundles.ts';
|
|
4
|
+
import { connectorSchema } from './connector.ts';
|
|
5
|
+
import { identitySchema } from './identity.ts';
|
|
6
|
+
import { identifier } from './primitives.ts';
|
|
7
|
+
import { setupSchema } from './setup.ts';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* A provider manifest.
|
|
11
|
+
*
|
|
12
|
+
* A provider is a *declaration*, not a package: a connectivity type plus auth
|
|
13
|
+
* configuration. `providers/gmail` was 612 lines of hand-written endpoint
|
|
14
|
+
* translation; the same integration is now roughly fifteen lines of data.
|
|
15
|
+
*
|
|
16
|
+
* The same schema serves two callers, deliberately:
|
|
17
|
+
*
|
|
18
|
+
* - built-ins, written as typed TS modules under `#providers/` and validated
|
|
19
|
+
* at import
|
|
20
|
+
* - the profile's own manifests in `data/<profile>/providers.d/*.yaml`, validated on load
|
|
21
|
+
*
|
|
22
|
+
* That second one is the scalability claim. A service nobody has integrated is
|
|
23
|
+
* a YAML file the operator writes, not a pull request they wait on.
|
|
24
|
+
*/
|
|
25
|
+
export const providerManifestSchema = z.object({
|
|
26
|
+
id: identifier,
|
|
27
|
+
name: z.string().min(1),
|
|
28
|
+
version: z.string().default('1.0.0'),
|
|
29
|
+
description: z.string().default(''),
|
|
30
|
+
|
|
31
|
+
connector: connectorSchema,
|
|
32
|
+
auth: authSchema.default({ kind: 'none' }),
|
|
33
|
+
setup: setupSchema.optional(),
|
|
34
|
+
bundles: z.array(bundleSchema).optional(),
|
|
35
|
+
/** How to label a connection with the account it actually belongs to. */
|
|
36
|
+
identity: identitySchema.optional(),
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Per-capability audit redaction: capability name → argument keys worth
|
|
40
|
+
* recording. Everything unlisted is reduced to a type marker.
|
|
41
|
+
*
|
|
42
|
+
* The default is to withhold every value, which is the only safe default when
|
|
43
|
+
* we did not author the capability and cannot know what is sensitive.
|
|
44
|
+
*/
|
|
45
|
+
redact: z.record(z.string(), z.array(z.string())).optional(),
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Per-capability prose appended to a generated description: capability name →
|
|
49
|
+
* what the vendor's own wording leaves out.
|
|
50
|
+
*
|
|
51
|
+
* Keyed exactly like `redact`, and for a related reason. A generated tool
|
|
52
|
+
* describes its *arguments* faithfully and its *meaning* not at all, because
|
|
53
|
+
* the vendor wrote the document for someone who already knows the product.
|
|
54
|
+
* Where a capability is the answer to a question nothing about it mentions —
|
|
55
|
+
* an operation whose enum of magic strings is the whole feature, or a job that
|
|
56
|
+
* lives on a different provider entirely — that fact has to be sayable
|
|
57
|
+
* somewhere, and the only somewhere a model reads is the description.
|
|
58
|
+
*
|
|
59
|
+
* Appended rather than replacing, so a vendor improving their own wording is
|
|
60
|
+
* not silently discarded.
|
|
61
|
+
*/
|
|
62
|
+
hints: z.record(z.string(), z.string()).optional(),
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
export type ProviderManifest = z.infer<typeof providerManifestSchema>;
|
|
66
|
+
|
|
67
|
+
/** Provider ids reserved for the owner layer. */
|
|
68
|
+
export const RESERVED_PROVIDER_IDS: readonly string[] = ['memory', 'skills', 'vault', 'setup'];
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Validate a manifest, with the cross-field rules the schema alone cannot
|
|
72
|
+
* express.
|
|
73
|
+
*
|
|
74
|
+
* These stay here rather than in the section files precisely because they span
|
|
75
|
+
* sections: a `dav` connector constrains the *auth* kind, an `oauth` block with
|
|
76
|
+
* manual registration constrains *setup*. A rule about one section alone
|
|
77
|
+
* belongs in that section's schema; everything left over is here, which is a
|
|
78
|
+
* short list and should stay one.
|
|
79
|
+
*
|
|
80
|
+
* Built-ins call this at import so a malformed provider fails there rather than
|
|
81
|
+
* at first use; the YAML loader calls it too.
|
|
82
|
+
*/
|
|
83
|
+
export function defineProvider(input: unknown): ProviderManifest {
|
|
84
|
+
const parsed = providerManifestSchema.safeParse(input);
|
|
85
|
+
if (!parsed.success) {
|
|
86
|
+
const where = parsed.error.issues
|
|
87
|
+
.map((issue) => ` ${issue.path.join('.') || '<root>'}: ${issue.message}`)
|
|
88
|
+
.join('\n');
|
|
89
|
+
throw new Error(`Invalid provider manifest:\n${where}`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const manifest = parsed.data;
|
|
93
|
+
|
|
94
|
+
if (manifest.auth.kind === 'oauth' && manifest.auth.registration === 'manual') {
|
|
95
|
+
if (!manifest.auth.app) {
|
|
96
|
+
throw new Error(
|
|
97
|
+
`Provider "${manifest.id}": OAuth with registration "manual" needs an "app" naming the oauth_apps entry that holds the client.`,
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
// Prompts are required only when asking is the *only* way to get a client.
|
|
101
|
+
// A broker is the other way, so a provider that declares one and no prompts
|
|
102
|
+
// simply has no bring-your-own path — a legal thing to be. The CLI is where
|
|
103
|
+
// that absence becomes visible, by refusing the flag and saying why.
|
|
104
|
+
if (!manifest.auth.broker && (!manifest.setup || manifest.setup.prompts.length === 0)) {
|
|
105
|
+
throw new Error(
|
|
106
|
+
`Provider "${manifest.id}": OAuth with registration "manual" requires the operator to supply a client, so it must declare setup prompts. Otherwise there is no way to learn what to provide.`,
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (manifest.auth.kind === 'oauth' && manifest.auth.broker) {
|
|
112
|
+
if (manifest.auth.registration !== 'manual' || !manifest.auth.app) {
|
|
113
|
+
throw new Error(
|
|
114
|
+
`Provider "${manifest.id}": a broker supplies a pre-registered client, so auth must declare registration "manual" and an "app" naming the oauth_apps entry that overrides it.`,
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
// An MCP provider hands the exchange to the SDK, which posts to the token
|
|
118
|
+
// endpoint with whatever `clientInformation()` returned. There is no seam
|
|
119
|
+
// to route that through a broker without reimplementing its auth path, so
|
|
120
|
+
// this is refused at definition rather than discovered after consent.
|
|
121
|
+
if (manifest.connector.kind === 'mcp') {
|
|
122
|
+
throw new Error(
|
|
123
|
+
`Provider "${manifest.id}": an mcp connector runs the exchange through the SDK, which cannot route it through a broker. Register a client (registration "manual") or use dynamic registration.`,
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
if (!manifest.auth.authorize_url) {
|
|
127
|
+
throw new Error(
|
|
128
|
+
`Provider "${manifest.id}": a broker performs the exchange, but the browser still goes to the vendor, so auth.authorize_url is required.`,
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (
|
|
134
|
+
(manifest.auth.kind === 'bearer' ||
|
|
135
|
+
manifest.auth.kind === 'api_key' ||
|
|
136
|
+
manifest.auth.kind === 'header' ||
|
|
137
|
+
manifest.auth.kind === 'basic') &&
|
|
138
|
+
manifest.auth.app &&
|
|
139
|
+
manifest.auth.credential_ref
|
|
140
|
+
) {
|
|
141
|
+
throw new Error(
|
|
142
|
+
`Provider "${manifest.id}": auth declares both "app" and "credential_ref", which contradict. ` +
|
|
143
|
+
`"app" means one secret per account shared across a vendor's providers; "credential_ref" means one secret shared across every account. Pick the one that is true.`,
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
for (const prompt of manifest.setup?.prompts ?? []) {
|
|
148
|
+
if (prompt.scope === 'shared' && !prompt.credential_ref) {
|
|
149
|
+
throw new Error(
|
|
150
|
+
`Provider "${manifest.id}": setup prompt "${prompt.key}" is shared across accounts, so it must name the credential_ref it writes to.`,
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
if (prompt.scope === 'connection' && prompt.credential_ref) {
|
|
154
|
+
throw new Error(
|
|
155
|
+
`Provider "${manifest.id}": setup prompt "${prompt.key}" is per-account, so its credential_ref derives and must not be declared — it would name a connection that does not exist yet.`,
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (manifest.auth.kind === 'basic') {
|
|
161
|
+
const perAccount = (manifest.setup?.prompts ?? []).filter((p) => p.scope === 'connection');
|
|
162
|
+
if (perAccount.length > 0) {
|
|
163
|
+
const has = (field: string): number => perAccount.filter((p) => p.field === field).length;
|
|
164
|
+
if (has('username') !== 1 || has('password') !== 1) {
|
|
165
|
+
throw new Error(
|
|
166
|
+
`Provider "${manifest.id}": basic auth stores "username:password", so it needs exactly one prompt with field "username" and one with field "password".`,
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (
|
|
173
|
+
(manifest.connector.kind === 'imap' || manifest.connector.kind === 'dav') &&
|
|
174
|
+
manifest.auth.kind !== 'basic'
|
|
175
|
+
) {
|
|
176
|
+
// Every mail and DAV host that matters issues an app password and expects
|
|
177
|
+
// it over Basic. OAuth for these exists — Apple shipped one in Oct 2025 —
|
|
178
|
+
// but is partner-gated with no published scopes, so declaring it would be a
|
|
179
|
+
// manifest that validates and then cannot authenticate. Fail here instead.
|
|
180
|
+
throw new Error(
|
|
181
|
+
`Provider "${manifest.id}": a ${manifest.connector.kind} connector authenticates with a username and password, so it must declare auth "basic".`,
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (manifest.connector.kind === 'fs' && manifest.auth.kind !== 'none') {
|
|
186
|
+
// Nothing to authenticate to. The permission is the operating system's, held
|
|
187
|
+
// against the process, and there is no credential to store or to leak.
|
|
188
|
+
throw new Error(
|
|
189
|
+
`Provider "${manifest.id}": an fs connector reads a local folder and holds no account, so it must declare auth "none".`,
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (manifest.connector.kind === 'local' && manifest.auth.kind !== 'none') {
|
|
194
|
+
throw new Error(
|
|
195
|
+
`Provider "${manifest.id}": a local connector runs our own code and holds no third-party account, so it must declare auth "none".`,
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const names = new Set<string>();
|
|
200
|
+
for (const bundle of manifest.bundles ?? []) {
|
|
201
|
+
if (names.has(bundle.name)) {
|
|
202
|
+
throw new Error(`Provider "${manifest.id}" declares bundle "${bundle.name}" twice`);
|
|
203
|
+
}
|
|
204
|
+
names.add(bundle.name);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return manifest;
|
|
208
|
+
}
|