@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,53 @@
|
|
|
1
|
+
import { defineProvider } from '#connectivity';
|
|
2
|
+
import { GOOGLE_APP, GOOGLE_OAUTH, specPath } from '../shared/oauth.ts';
|
|
3
|
+
import { googleSetup } from '../shared/setup.ts';
|
|
4
|
+
import { TASKS_REDACT } from './redact.ts';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The narrowest scope Google offers here is the widest one there is.
|
|
8
|
+
*
|
|
9
|
+
* Tasks publishes exactly two: `tasks` and `tasks.readonly`. There is no
|
|
10
|
+
* per-list scope, no app-created scope, and no separate create verb — so adding
|
|
11
|
+
* one task to one list means holding read, write, and delete over every list in
|
|
12
|
+
* the account. It is marked broad in `../shared/scopes.ts` for that reason, and
|
|
13
|
+
* unlike `gmail.modify` or `spreadsheets` there was no alternative to weigh:
|
|
14
|
+
* this is not the widest of several, it is the only one that can write at all.
|
|
15
|
+
*
|
|
16
|
+
* What bounds it is what bounds the others. The token can delete a whole list;
|
|
17
|
+
* the tool surface cannot, because `tasklists.delete` is not vendored — Tasks
|
|
18
|
+
* has no trash, so destroying a list destroys every task in it. `lanes link
|
|
19
|
+
* policy deny tasks.*` narrows it further.
|
|
20
|
+
*
|
|
21
|
+
* No `identity` block, and that is a decision rather than an omission. Nothing
|
|
22
|
+
* reachable under `auth/tasks` returns an address: `tasklists.list` answers
|
|
23
|
+
* with titles, and `userinfo.email` is a scope this provider has no other use
|
|
24
|
+
* for. Adding one for a cosmetic label is exactly what the documented-minimum
|
|
25
|
+
* rule refuses, so `connect` asks instead — and because it matches the typed
|
|
26
|
+
* address against existing connections, a reconnect still repairs the row it
|
|
27
|
+
* already made rather than appending `main2`.
|
|
28
|
+
*
|
|
29
|
+
* Version in the path, host without it (`tasks.googleapis.com` +
|
|
30
|
+
* `/tasks/v1/lists/...`) — the Sheets shape, not Drive's.
|
|
31
|
+
*/
|
|
32
|
+
const TASKS_SCOPES = ['https://www.googleapis.com/auth/tasks'];
|
|
33
|
+
|
|
34
|
+
export const tasks = defineProvider({
|
|
35
|
+
id: 'tasks',
|
|
36
|
+
name: 'Google Tasks',
|
|
37
|
+
description:
|
|
38
|
+
'Read and write task lists and tasks — create, edit, complete, reorder, and delete — via the Tasks REST API.',
|
|
39
|
+
connector: {
|
|
40
|
+
kind: 'http',
|
|
41
|
+
base_url: 'https://tasks.googleapis.com',
|
|
42
|
+
openapi: specPath('tasks.v1.json'),
|
|
43
|
+
},
|
|
44
|
+
auth: {
|
|
45
|
+
kind: 'oauth',
|
|
46
|
+
registration: 'manual',
|
|
47
|
+
app: GOOGLE_APP,
|
|
48
|
+
scopes: TASKS_SCOPES,
|
|
49
|
+
...GOOGLE_OAUTH,
|
|
50
|
+
},
|
|
51
|
+
setup: googleSetup('Tasks', TASKS_SCOPES, { apis: ['tasks.googleapis.com'] }),
|
|
52
|
+
redact: TASKS_REDACT,
|
|
53
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What survives into the audit log when a task is read or changed.
|
|
3
|
+
*
|
|
4
|
+
* `title` and `notes` are the task — the same call as Gmail's `labels.create`,
|
|
5
|
+
* where the name is the user's own words and the log has no business keeping
|
|
6
|
+
* them. `due` and `status` are kept, because "marked the thing due Friday done"
|
|
7
|
+
* is the shape of the change and neither argument says what the thing was.
|
|
8
|
+
*
|
|
9
|
+
* Two argument names are not what they look like, and a wrong key here fails
|
|
10
|
+
* silently — the lookup misses, every value is withheld, and it reads exactly
|
|
11
|
+
* like working redaction. The generator disambiguates a collision between a
|
|
12
|
+
* path or query parameter and a body field by prefixing the location, so
|
|
13
|
+
* `tasks.insert` takes `queryParent` (the query one) beside the body's
|
|
14
|
+
* `parent`. `tasks.move` has no such collision and takes a plain `parent`.
|
|
15
|
+
*/
|
|
16
|
+
export const TASKS_REDACT: Record<string, string[]> = {
|
|
17
|
+
'tasklists.list': ['maxResults'],
|
|
18
|
+
'tasklists.insert': [],
|
|
19
|
+
'tasklists.patch': ['tasklist'],
|
|
20
|
+
'tasks.list': [
|
|
21
|
+
'tasklist',
|
|
22
|
+
'showCompleted',
|
|
23
|
+
'showDeleted',
|
|
24
|
+
'showHidden',
|
|
25
|
+
'dueMin',
|
|
26
|
+
'dueMax',
|
|
27
|
+
'maxResults',
|
|
28
|
+
],
|
|
29
|
+
'tasks.get': ['tasklist', 'task'],
|
|
30
|
+
'tasks.insert': ['tasklist', 'queryParent', 'previous', 'due', 'status'],
|
|
31
|
+
'tasks.patch': ['tasklist', 'task', 'due', 'status'],
|
|
32
|
+
'tasks.delete': ['tasklist', 'task'],
|
|
33
|
+
'tasks.move': ['tasklist', 'task', 'parent', 'previous'],
|
|
34
|
+
};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { createLocalConnector } from '#connectivity/transports';
|
|
2
|
+
import { buildProviderContext } from '#dispatch';
|
|
3
|
+
import { createMemoryCredentials, createMemoryState } from '#stores/state/testing.ts';
|
|
4
|
+
import { createMemoryBlobStore } from '#stores/blobs/testing.ts';
|
|
5
|
+
import type {
|
|
6
|
+
AnyConnector,
|
|
7
|
+
CapabilityResult,
|
|
8
|
+
ConnectorContext,
|
|
9
|
+
ProviderContext,
|
|
10
|
+
ProviderDefinition,
|
|
11
|
+
} from '#connectivity';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* One owner provider, wired the way the runtime wires it.
|
|
15
|
+
*
|
|
16
|
+
* Through the local connector rather than by calling handlers directly, because
|
|
17
|
+
* the connector is what routes a resource read and a prompt render — the part
|
|
18
|
+
* that did not exist before M4. The dispatcher and the MCP layer are covered end
|
|
19
|
+
* to end in `apps/server/src/owner.test.ts`; this is the provider's own
|
|
20
|
+
* behaviour.
|
|
21
|
+
*/
|
|
22
|
+
export interface ProviderHarness {
|
|
23
|
+
readonly connector: AnyConnector;
|
|
24
|
+
readonly context: ProviderContext;
|
|
25
|
+
invoke(name: string, args?: Record<string, unknown>): Promise<CapabilityResult>;
|
|
26
|
+
annotations(): Record<string, unknown>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function harnessFor(
|
|
30
|
+
definition: ProviderDefinition,
|
|
31
|
+
connectionId = 'owner',
|
|
32
|
+
): ProviderHarness {
|
|
33
|
+
const annotations: Record<string, unknown> = {};
|
|
34
|
+
|
|
35
|
+
const context = buildProviderContext({
|
|
36
|
+
manifest: definition.manifest,
|
|
37
|
+
definition,
|
|
38
|
+
connection: { id: connectionId, provider: definition.manifest.id, account: 'Owner' },
|
|
39
|
+
state: createMemoryState(),
|
|
40
|
+
// Seeded deliberately: an owner provider must not be able to read any of
|
|
41
|
+
// this, and the credential-boundary test says so by name.
|
|
42
|
+
credentials: createMemoryCredentials({
|
|
43
|
+
'gmail/main': 'refresh-token',
|
|
44
|
+
'profile/token': 'llk_token',
|
|
45
|
+
}),
|
|
46
|
+
storage: createMemoryBlobStore(),
|
|
47
|
+
audit: {
|
|
48
|
+
annotate(detail) {
|
|
49
|
+
Object.assign(annotations, detail);
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
log: { debug() {}, info() {}, warn() {}, error() {} },
|
|
53
|
+
signal: new AbortController().signal,
|
|
54
|
+
// The runtime hands this to any provider that is not `local`, so a provider
|
|
55
|
+
// authoring a capability against its own vendor's API has it here too.
|
|
56
|
+
authorize: async (request) => request,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const connector = createLocalConnector(definition);
|
|
60
|
+
|
|
61
|
+
const connectorContext: ConnectorContext = {
|
|
62
|
+
manifest: definition.manifest,
|
|
63
|
+
provider: context,
|
|
64
|
+
authorize: async (request) => request,
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
connector,
|
|
69
|
+
context,
|
|
70
|
+
annotations: () => annotations,
|
|
71
|
+
invoke: (name, args = {}) =>
|
|
72
|
+
connector.invoke({ name, description: '', inputSchema: {} }, args, connectorContext),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** The resource links a tool result carries, unrouted as the provider wrote them. */
|
|
77
|
+
export function linksOf(result: CapabilityResult): string[] {
|
|
78
|
+
if (!('content' in result)) return [];
|
|
79
|
+
return result.content
|
|
80
|
+
.filter((block): block is { type: 'resource_link'; uri: string } => block.type === 'resource_link')
|
|
81
|
+
.map((block) => block.uri);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** The text of a tool or resource result, joined — what a caller would read. */
|
|
85
|
+
export function textOf(result: CapabilityResult): string {
|
|
86
|
+
if ('content' in result) {
|
|
87
|
+
return result.content
|
|
88
|
+
.filter((block): block is { type: 'text'; text: string } => block.type === 'text')
|
|
89
|
+
.map((block) => block.text)
|
|
90
|
+
.join('\n');
|
|
91
|
+
}
|
|
92
|
+
if ('contents' in result) return result.contents.map((entry) => entry.text).join('\n');
|
|
93
|
+
if ('messages' in result) return result.messages.map((message) => message.text).join('\n');
|
|
94
|
+
return result.resources.map((resource) => resource.uri).join('\n');
|
|
95
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { defineProvider } from '#connectivity';
|
|
2
|
+
import { ICLOUD_APP, icloudSetup } from '../shared/setup.ts';
|
|
3
|
+
|
|
4
|
+
export const icloudCalendar = defineProvider({
|
|
5
|
+
id: 'icloud_calendar',
|
|
6
|
+
name: 'iCloud Calendar',
|
|
7
|
+
description: 'Read and create events in iCloud calendars over CalDAV.',
|
|
8
|
+
connector: {
|
|
9
|
+
kind: 'dav',
|
|
10
|
+
base_url: 'https://caldav.icloud.com',
|
|
11
|
+
service: 'caldav',
|
|
12
|
+
// Apple's limit, declared rather than assumed by the transport.
|
|
13
|
+
max_range_days: 366,
|
|
14
|
+
},
|
|
15
|
+
auth: { kind: 'basic', app: ICLOUD_APP },
|
|
16
|
+
identity: { kind: 'connector' },
|
|
17
|
+
setup: icloudSetup('iCloud Calendar'),
|
|
18
|
+
redact: {
|
|
19
|
+
list_events: ['calendar', 'start', 'end', 'limit'],
|
|
20
|
+
get_event: ['calendar', 'uid'],
|
|
21
|
+
// The uid and calendar say *which* event changed, which is what makes the
|
|
22
|
+
// log worth having; the summary and location are the content of it.
|
|
23
|
+
update_event: ['calendar', 'uid'],
|
|
24
|
+
delete_event: ['calendar', 'uid'],
|
|
25
|
+
create_event: ['calendar'],
|
|
26
|
+
},
|
|
27
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { defineProvider } from '#connectivity';
|
|
2
|
+
import { ICLOUD_APP, icloudSetup } from '../shared/setup.ts';
|
|
3
|
+
|
|
4
|
+
export const icloudContacts = defineProvider({
|
|
5
|
+
id: 'icloud_contacts',
|
|
6
|
+
name: 'iCloud Contacts',
|
|
7
|
+
description: 'Search and read contacts in an iCloud address book over CardDAV.',
|
|
8
|
+
connector: { kind: 'dav', base_url: 'https://contacts.icloud.com', service: 'carddav' },
|
|
9
|
+
auth: { kind: 'basic', app: ICLOUD_APP },
|
|
10
|
+
identity: { kind: 'connector' },
|
|
11
|
+
setup: icloudSetup('iCloud Contacts'),
|
|
12
|
+
redact: {
|
|
13
|
+
// Not `query`: a name is content.
|
|
14
|
+
search_contacts: ['addressbook', 'limit'],
|
|
15
|
+
create_contact: ['addressbook'],
|
|
16
|
+
},
|
|
17
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { defineProvider } from '#connectivity';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* iCloud Drive, which is only reachable from the Mac that holds it.
|
|
5
|
+
*
|
|
6
|
+
* Not a fourth service on the same app-specific password: Apple exposes no
|
|
7
|
+
* protocol for Drive at all. What it does expose is a folder, synced by the
|
|
8
|
+
* system, that any process on that Mac may read with the operating system's
|
|
9
|
+
* permission. So this is `auth: none` — there is no credential, and nothing that
|
|
10
|
+
* could be copied to a server somewhere else. ADR-011 covers why the cloud
|
|
11
|
+
* answer has to be a relay to a local instance rather than a stored token.
|
|
12
|
+
*/
|
|
13
|
+
export const icloudDrive = defineProvider({
|
|
14
|
+
id: 'icloud_drive',
|
|
15
|
+
name: 'iCloud Drive',
|
|
16
|
+
description: 'Read and organise files in iCloud Drive, on the Mac that syncs them.',
|
|
17
|
+
connector: {
|
|
18
|
+
kind: 'fs',
|
|
19
|
+
root: '~/Library/Mobile Documents/com~apple~CloudDocs',
|
|
20
|
+
// Desktop and Documents sync into here, so the root is most of a person's
|
|
21
|
+
// files. The read limit is deliberately modest.
|
|
22
|
+
max_file_bytes: 262_144,
|
|
23
|
+
// macOS names an undownloaded file `.<name>.icloud`, and `brctl` is how you
|
|
24
|
+
// pull it down. Both are Apple's conventions, so both are declared here
|
|
25
|
+
// rather than known by the filesystem transport.
|
|
26
|
+
placeholder: {
|
|
27
|
+
suffix: '.icloud',
|
|
28
|
+
hint: 'Open it once in Finder, or run: brctl download',
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
auth: { kind: 'none' },
|
|
32
|
+
identity: { kind: 'connector' },
|
|
33
|
+
redact: {
|
|
34
|
+
// Paths are recorded — an audit line that cannot say *which* file was read
|
|
35
|
+
// answers nothing — but never contents.
|
|
36
|
+
list_files: ['path', 'recursive', 'limit'],
|
|
37
|
+
read_file: ['path'],
|
|
38
|
+
file_info: ['path'],
|
|
39
|
+
// Not `query` or `contains`: a search term is frequently more revealing than
|
|
40
|
+
// the result, which is the same reasoning mail search uses.
|
|
41
|
+
search_files: ['path', 'limit'],
|
|
42
|
+
write_file: ['path', 'overwrite'],
|
|
43
|
+
move_file: ['from', 'to'],
|
|
44
|
+
create_folder: ['path'],
|
|
45
|
+
trash_file: ['path'],
|
|
46
|
+
},
|
|
47
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* iCloud's four providers: three sharing one app-specific password, and Drive,
|
|
3
|
+
* which shares nothing because Apple exposes no protocol for it at all.
|
|
4
|
+
*/
|
|
5
|
+
export { icloudCalendar } from './calendar/index.ts';
|
|
6
|
+
export { icloudContacts } from './contacts/index.ts';
|
|
7
|
+
export { icloudDrive } from './drive/index.ts';
|
|
8
|
+
export { icloudMail } from './mail/index.ts';
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { defineProvider } from '#connectivity';
|
|
2
|
+
import { ICLOUD_APP, icloudSetup } from '../shared/setup.ts';
|
|
3
|
+
|
|
4
|
+
export const icloudMail = defineProvider({
|
|
5
|
+
id: 'icloud_mail',
|
|
6
|
+
name: 'iCloud Mail',
|
|
7
|
+
description: 'Read, search, and send mail in an iCloud mailbox over IMAP and SMTP.',
|
|
8
|
+
connector: {
|
|
9
|
+
kind: 'imap',
|
|
10
|
+
host: 'imap.mail.me.com',
|
|
11
|
+
port: 993,
|
|
12
|
+
smtp: { host: 'smtp.mail.me.com', port: 587, starttls: true },
|
|
13
|
+
},
|
|
14
|
+
auth: { kind: 'basic', app: ICLOUD_APP },
|
|
15
|
+
identity: { kind: 'connector' },
|
|
16
|
+
setup: icloudSetup('iCloud Mail'),
|
|
17
|
+
redact: {
|
|
18
|
+
// Opted back in one key at a time; everything unlisted is withheld. Never
|
|
19
|
+
// the search terms — a query is content, and "who did I email about the
|
|
20
|
+
// diagnosis" is the whole message.
|
|
21
|
+
search_messages: ['mailbox', 'limit', 'unseen', 'flagged'],
|
|
22
|
+
get_message: ['mailbox', 'uid', 'include_body'],
|
|
23
|
+
mark_messages: ['mailbox', 'add_flags', 'remove_flags'],
|
|
24
|
+
// `destination_flag` alongside `destination`: they are two spellings of the
|
|
25
|
+
// same fact, and keeping only one means a junk move — the case the flag
|
|
26
|
+
// exists for — logs with no destination at all.
|
|
27
|
+
move_messages: ['mailbox', 'destination', 'destination_flag'],
|
|
28
|
+
// Still nothing, now that this can carry attachments — and *especially* now.
|
|
29
|
+
// The recipients and the body are the message, and `attachments` is the one
|
|
30
|
+
// argument that may literally contain a file, so keeping it verbatim would
|
|
31
|
+
// put base64 in the log. What did get attached is recorded instead by the
|
|
32
|
+
// send path itself, through `audit.annotate`: filename, size, type, SHA-256
|
|
33
|
+
// and where the bytes came from. Identifiers, not content — and with `path`
|
|
34
|
+
// unrestricted, that record is the only trace of which file left the machine.
|
|
35
|
+
send_message: [],
|
|
36
|
+
},
|
|
37
|
+
});
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
export const ICLOUD_APP = 'icloud';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* iCloud: one account, three providers.
|
|
5
|
+
*
|
|
6
|
+
* Apple publishes no REST API and no MCP server for Mail, Calendar, or
|
|
7
|
+
* Contacts. There *is* an OAuth path — shipped October 2025 and used by Outlook
|
|
8
|
+
* — but it is partner-gated, with no scopes published for these services, so an
|
|
9
|
+
* app-specific password over IMAP and DAV is the only route actually open. If
|
|
10
|
+
* that changes, auth is orthogonal to connectivity here: it is a change to those
|
|
11
|
+
* three `auth` blocks and nothing else.
|
|
12
|
+
*
|
|
13
|
+
* Three manifests because a manifest has one connector and mail is a different
|
|
14
|
+
* protocol from calendars — and because that is the better shape anyway.
|
|
15
|
+
* `connect` writes one policy line per provider, so an operator can allow
|
|
16
|
+
* `icloud_calendar.*` while never granting mail. They share `app: icloud`, which
|
|
17
|
+
* resolves all three to the same `icloud/<account>` credential, so the
|
|
18
|
+
* app-specific password is typed exactly once: Apple issues it at *account*
|
|
19
|
+
* scope, so one password genuinely does unlock all three.
|
|
20
|
+
*
|
|
21
|
+
* Reminders and Notes are deliberately absent. Apple moved to-do lists to a
|
|
22
|
+
* private store after iOS 13, and CalDAV now returns either legacy data or
|
|
23
|
+
* tombstones for them; Notes were never exposed. Saying so here is better than
|
|
24
|
+
* letting someone discover it as a bug.
|
|
25
|
+
*/
|
|
26
|
+
export const icloudSetup = (product: string) => ({
|
|
27
|
+
summary:
|
|
28
|
+
`${product} uses an app-specific password. Your Apple Account password will not work — Apple refuses it ` +
|
|
29
|
+
`for third-party clients — and Lanes Link never sees it. You are asked once: the same password covers ` +
|
|
30
|
+
`Mail, Calendar, and Contacts, because Apple issues it per account rather than per service.`,
|
|
31
|
+
docs: 'docs/detailed/setup/icloud.md',
|
|
32
|
+
docs_url: 'https://support.apple.com/en-us/102654',
|
|
33
|
+
steps: [
|
|
34
|
+
'Sign in at https://account.apple.com and open "Sign-In and Security".',
|
|
35
|
+
'Two-factor authentication must be on. Without it Apple does not offer app-specific passwords at all, and the section below simply will not appear.',
|
|
36
|
+
'App-Specific Passwords → Generate. Name it "Lanes Link" — the name is the only way to revoke this one later without cutting off your other devices.',
|
|
37
|
+
'Copy the sixteen characters. Apple shows them once, as xxxx-xxxx-xxxx-xxxx; the hyphens are cosmetic and it is accepted either way.',
|
|
38
|
+
'Note that changing your Apple Account password revokes every app-specific password at once. That is the cause of most sudden iCloud sync failures, and the fix is to generate a new one and run: lanes link connect icloud --replace.',
|
|
39
|
+
],
|
|
40
|
+
// What a transport cannot say for itself, because it must not know which
|
|
41
|
+
// vendor it is talking to. This is the sentence that used to be hard-coded in
|
|
42
|
+
// three places across `imap` and `dav`.
|
|
43
|
+
//
|
|
44
|
+
// `--replace` rather than a bare re-run: the stored credential is the one
|
|
45
|
+
// that was just refused, and every other spelling of the command finds it
|
|
46
|
+
// already there and reuses it. Advising the loop is how a mistyped password
|
|
47
|
+
// became unrecoverable.
|
|
48
|
+
troubleshooting:
|
|
49
|
+
'For iCloud this is almost always an Apple Account password used where an app-specific password belongs. Generate one at https://account.apple.com and re-run: lanes link connect icloud --replace.',
|
|
50
|
+
prompts: [
|
|
51
|
+
{
|
|
52
|
+
key: 'username',
|
|
53
|
+
label: 'Apple Account (the full email address)',
|
|
54
|
+
secret: false,
|
|
55
|
+
scope: 'connection' as const,
|
|
56
|
+
field: 'username' as const,
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
key: 'password',
|
|
60
|
+
label: 'App-specific password',
|
|
61
|
+
secret: true,
|
|
62
|
+
scope: 'connection' as const,
|
|
63
|
+
field: 'password' as const,
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
});
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import type { ProviderDefinition, ProviderManifest } from '#connectivity';
|
|
2
|
+
import { calendar } from './google/calendar/index.ts';
|
|
3
|
+
import { contacts } from './google/contacts/index.ts';
|
|
4
|
+
import { docs } from './google/docs/index.ts';
|
|
5
|
+
import { drive } from './google/drive/index.ts';
|
|
6
|
+
import { driveMcp } from './google/drive-mcp/index.ts';
|
|
7
|
+
import { gmail } from './google/gmail/index.ts';
|
|
8
|
+
import { gmailMcp } from './google/gmail-mcp/index.ts';
|
|
9
|
+
import { sheets } from './google/sheets/index.ts';
|
|
10
|
+
import { tasks } from './google/tasks/index.ts';
|
|
11
|
+
import { icloudCalendar } from './icloud/calendar/index.ts';
|
|
12
|
+
import { icloudContacts } from './icloud/contacts/index.ts';
|
|
13
|
+
import { icloudDrive } from './icloud/drive/index.ts';
|
|
14
|
+
import { icloudMail } from './icloud/mail/index.ts';
|
|
15
|
+
import { linear } from './linear/index.ts';
|
|
16
|
+
import { notion } from './notion/index.ts';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Every provider, in one list.
|
|
20
|
+
*
|
|
21
|
+
* Names and imports only. There is no vendor knowledge in this file and there
|
|
22
|
+
* must never be any — a Google scope, an Apple error message, or a spec path
|
|
23
|
+
* appearing here means it has escaped the folder that owns it.
|
|
24
|
+
*
|
|
25
|
+
* Each folder holds *all* of its provider: the manifest, the scopes it asks
|
|
26
|
+
* for, what it redacts from the audit log, the setup walkthrough, and any
|
|
27
|
+
* vendored specification. Adding a provider is a folder and a line below.
|
|
28
|
+
*
|
|
29
|
+
* The list is a convenience, not a boundary: anything not here is a YAML
|
|
30
|
+
* manifest in the profile's own `providers.d/`, validated by the same schema and
|
|
31
|
+
* loaded by `./custom/load.ts`.
|
|
32
|
+
*
|
|
33
|
+
* The owner layer — `memory/`, `skills/`, `vault/` — is deliberately absent
|
|
34
|
+
* from this list and not from this directory. Those three are providers in
|
|
35
|
+
* every sense the architecture cares about, but they are *constructed* rather
|
|
36
|
+
* than declared: each needs a store handed to it at startup, so they are
|
|
37
|
+
* registered by `#profile`'s registry builder instead of being static data.
|
|
38
|
+
*/
|
|
39
|
+
/**
|
|
40
|
+
* Manifests, and the rare provider that carries a capability of its own.
|
|
41
|
+
*
|
|
42
|
+
* The union is not an invitation. A provider is a *declaration*, and that is what
|
|
43
|
+
* makes adding one cheap; `gmail` is a definition only because sending mail means
|
|
44
|
+
* assembling a MIME message and no document describes doing that. Everything else
|
|
45
|
+
* here is still fifteen lines of data. `registry.register` already accepted both
|
|
46
|
+
* shapes, so nothing downstream had to change.
|
|
47
|
+
*/
|
|
48
|
+
export const PROVIDERS: readonly (ProviderManifest | ProviderDefinition)[] = [
|
|
49
|
+
notion,
|
|
50
|
+
linear,
|
|
51
|
+
gmail,
|
|
52
|
+
drive,
|
|
53
|
+
sheets,
|
|
54
|
+
docs,
|
|
55
|
+
calendar,
|
|
56
|
+
tasks,
|
|
57
|
+
contacts,
|
|
58
|
+
gmailMcp,
|
|
59
|
+
driveMcp,
|
|
60
|
+
icloudMail,
|
|
61
|
+
icloudCalendar,
|
|
62
|
+
icloudContacts,
|
|
63
|
+
icloudDrive,
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
/** The manifest half of an entry, whichever shape it arrived in. */
|
|
67
|
+
export const manifestOf = (entry: ProviderManifest | ProviderDefinition): ProviderManifest =>
|
|
68
|
+
'manifest' in entry ? entry.manifest : entry;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Every provider's manifest, for the callers that only ask about declarations —
|
|
72
|
+
* which scopes are requested, which are `http`, what `base_url` each names.
|
|
73
|
+
*
|
|
74
|
+
* Derived rather than a second list, so it cannot fall out of step with the one
|
|
75
|
+
* above.
|
|
76
|
+
*/
|
|
77
|
+
export const PROVIDER_MANIFESTS: readonly ProviderManifest[] = PROVIDERS.map(manifestOf);
|
|
78
|
+
|
|
79
|
+
export {
|
|
80
|
+
calendar,
|
|
81
|
+
contacts,
|
|
82
|
+
docs,
|
|
83
|
+
drive,
|
|
84
|
+
driveMcp,
|
|
85
|
+
gmail,
|
|
86
|
+
gmailMcp,
|
|
87
|
+
sheets,
|
|
88
|
+
tasks,
|
|
89
|
+
} from './google/index.ts';
|
|
90
|
+
export { icloudCalendar, icloudContacts, icloudDrive, icloudMail } from './icloud/index.ts';
|
|
91
|
+
export { linear } from './linear/index.ts';
|
|
92
|
+
export { notion } from './notion/index.ts';
|
|
93
|
+
export { SCOPE_MEANINGS, type ScopeMeaning } from './scopes.ts';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { defineProvider } from '#connectivity';
|
|
2
|
+
|
|
3
|
+
/** Linear, like Notion, registers itself — nothing for an operator to set up. */
|
|
4
|
+
export const linear = defineProvider({
|
|
5
|
+
id: 'linear',
|
|
6
|
+
name: 'Linear',
|
|
7
|
+
description: 'Issues, projects, comments, and cycles, via Linear\'s official MCP server.',
|
|
8
|
+
connector: { kind: 'mcp', endpoint: 'https://mcp.linear.app/mcp' },
|
|
9
|
+
auth: { kind: 'oauth', registration: 'dynamic', scopes: ['read', 'write'] },
|
|
10
|
+
identity: { kind: 'tool', tool: 'get_workspace', field: 'name' },
|
|
11
|
+
});
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ScopeMeaning } from '../scopes.ts';
|
|
2
|
+
|
|
3
|
+
/** Linear's two scopes, which are as plain as they look. */
|
|
4
|
+
export const LINEAR_SCOPE_MEANINGS: Record<string, ScopeMeaning> = {
|
|
5
|
+
read: { meaning: 'read-only access' },
|
|
6
|
+
write: { meaning: 'create and modify' },
|
|
7
|
+
};
|