@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,63 @@
|
|
|
1
|
+
import { SCOPE_MEANINGS } from '#providers/scopes.ts';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Saying out loud what a connection is about to be granted.
|
|
5
|
+
*
|
|
6
|
+
* Least privilege is the goal, and a vendor's *advertised* scope list is not the
|
|
7
|
+
* same as its required one. Google's Gmail MCP server advertises five, including
|
|
8
|
+
* `mail.google.com` — read, send, and permanently delete — while its own
|
|
9
|
+
* documentation names two. Granting all five was tested against the real service
|
|
10
|
+
* and changed nothing, so the two are what we ask for.
|
|
11
|
+
*
|
|
12
|
+
* Where a grant does have to be broad, it must not happen quietly: an over-broad
|
|
13
|
+
* grant is invisible after the fact, and a consent screen listing five
|
|
14
|
+
* Google-worded scopes is not where someone discovers that "Gmail" meant
|
|
15
|
+
* permanent delete. We print it first, in plain words, and make the broad ones an
|
|
16
|
+
* explicit yes.
|
|
17
|
+
*
|
|
18
|
+
* What each scope *means* is the provider's knowledge and lives in its folder
|
|
19
|
+
* (`#providers/scopes.ts` merges the contributions). This file is the rendering,
|
|
20
|
+
* and it works the same for a provider it has never heard of: a scope missing
|
|
21
|
+
* from the table is printed unannotated rather than mis-described — silence is
|
|
22
|
+
* the safe failure here.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
export interface ScopeDescription {
|
|
26
|
+
readonly scope: string;
|
|
27
|
+
/** Plain-English meaning, where the provider supplied one. */
|
|
28
|
+
readonly meaning?: string;
|
|
29
|
+
/** Unrestricted access to the service, or close enough to warrant a stop. */
|
|
30
|
+
readonly broad: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function describeScope(scope: string): ScopeDescription {
|
|
34
|
+
const known = SCOPE_MEANINGS[scope];
|
|
35
|
+
return {
|
|
36
|
+
scope,
|
|
37
|
+
...(known?.meaning ? { meaning: known.meaning } : {}),
|
|
38
|
+
broad: known?.broad === true,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function describeScopes(scopes: readonly string[]): ScopeDescription[] {
|
|
43
|
+
return scopes.map(describeScope);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Shorten a scope for display; the full value is still what is requested.
|
|
48
|
+
*
|
|
49
|
+
* Generic on purpose. The last path segment after `/auth/` is the convention
|
|
50
|
+
* every OAuth vendor that uses URL-shaped scopes follows, and anything else is
|
|
51
|
+
* left alone rather than guessed at.
|
|
52
|
+
*/
|
|
53
|
+
export function shortScope(scope: string): string {
|
|
54
|
+
const match = scope.match(/\/auth\/([^/]+)$/);
|
|
55
|
+
if (match?.[1]) return match[1];
|
|
56
|
+
|
|
57
|
+
// A bare host with nothing after it — `https://mail.google.com/` — reads
|
|
58
|
+
// better without the scheme, and there is nothing else to take.
|
|
59
|
+
const url = URL.parse?.(scope);
|
|
60
|
+
if (url && (url.pathname === '/' || url.pathname === '')) return url.host;
|
|
61
|
+
|
|
62
|
+
return scope;
|
|
63
|
+
}
|
package/src/cli/usage.ts
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { style } from './output.ts';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The help text, and the name the CLI calls itself.
|
|
5
|
+
*
|
|
6
|
+
* `PROGRAM` is a constant rather than 59 string literals because it had been 59
|
|
7
|
+
* string literals: the rename that produced `lanes link` meant editing the
|
|
8
|
+
* usage text, every `Usage:` line, and every `Unknown:` line by hand, with no
|
|
9
|
+
* way to tell from a green test suite whether one had been missed. The next
|
|
10
|
+
* rename touches this line, and `argv.test.ts` holds the two to each other.
|
|
11
|
+
*
|
|
12
|
+
* The text lives here rather than in `main.ts` because it is 65 lines of prose
|
|
13
|
+
* that changes for entirely different reasons from the dispatch below it.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/** How this CLI is invoked — the `link` area of the `lanes` command. */
|
|
17
|
+
export const PROGRAM = 'lanes link';
|
|
18
|
+
|
|
19
|
+
export const USAGE = `${style.bold(PROGRAM)} — a self-hosted MCP gateway for your accounts, memory, skills, and secrets
|
|
20
|
+
|
|
21
|
+
${style.bold('Everyday')}
|
|
22
|
+
${PROGRAM} setup plan [--json] what each provider needs, and which are connected
|
|
23
|
+
${PROGRAM} setup plan <provider> the console steps, the values, and the command
|
|
24
|
+
${PROGRAM} connect <provider> add an account (run once per account)
|
|
25
|
+
${PROGRAM} connect <provider>.<id> re-authorise one existing account
|
|
26
|
+
${PROGRAM} connect <...> --replace ask for the stored password or key again
|
|
27
|
+
${PROGRAM} connect <...> --non-interactive [--json]
|
|
28
|
+
answer nothing from a terminal: take every value
|
|
29
|
+
from the credential store, or say what is missing
|
|
30
|
+
${PROGRAM} start [--only] reconcile and serve every profile on one endpoint
|
|
31
|
+
${PROGRAM} outputs [--show] [--json] the endpoint an agent needs
|
|
32
|
+
${PROGRAM} mcp add [claude|codex] register this endpoint, and install the agent skill
|
|
33
|
+
${PROGRAM} mcp add --no-skill register only, leaving the agent's own files alone
|
|
34
|
+
${PROGRAM} mcp list where it is registered, and whether the skill is current
|
|
35
|
+
${PROGRAM} mcp stdio serve on stdin/stdout, for a client that spawns it
|
|
36
|
+
${PROGRAM} mcp skill [--print] the bundled skill — its path, or the document itself
|
|
37
|
+
${PROGRAM} status [--json] connections, reachable capabilities, endpoint
|
|
38
|
+
|
|
39
|
+
${style.bold('Profiles')}
|
|
40
|
+
${PROGRAM} profile add <name> [--default] [--json]
|
|
41
|
+
${PROGRAM} profile list [--json]
|
|
42
|
+
${PROGRAM} profile default <name>
|
|
43
|
+
${PROGRAM} profile remove <name> [--target t] [--dry-run] [--yes] [--json]
|
|
44
|
+
the profile, its credentials, and its data
|
|
45
|
+
|
|
46
|
+
${style.bold('Targets')}
|
|
47
|
+
${PROGRAM} target list [--urls] where this profile can run, and which one is in play
|
|
48
|
+
${PROGRAM} target show [<name>] one target's adapters, and the address it answers on
|
|
49
|
+
${PROGRAM} target use <name> make one the profile's default_target
|
|
50
|
+
|
|
51
|
+
${style.bold('Permissions')}
|
|
52
|
+
${PROGRAM} policy list
|
|
53
|
+
${PROGRAM} policy allow <capability> e.g. gmail.* or gmail.send_message
|
|
54
|
+
${PROGRAM} policy deny <capability>
|
|
55
|
+
${PROGRAM} token show [--show|--raw] --raw prints only the token, for $(…)
|
|
56
|
+
${PROGRAM} token rotate [--show]
|
|
57
|
+
|
|
58
|
+
${style.bold('Your own context')}
|
|
59
|
+
${PROGRAM} memory list [--tag t] what you have stored
|
|
60
|
+
${PROGRAM} memory get <id>
|
|
61
|
+
${PROGRAM} memory write <id> --title <t> [--tag t] body on stdin
|
|
62
|
+
${PROGRAM} memory forget <id>
|
|
63
|
+
|
|
64
|
+
${PROGRAM} skills list the procedures agents can invoke
|
|
65
|
+
${PROGRAM} skills show <name>
|
|
66
|
+
${PROGRAM} skills add <name> [--file f] document on stdin
|
|
67
|
+
${PROGRAM} skills remove <name>
|
|
68
|
+
|
|
69
|
+
${PROGRAM} vault list names only, never values
|
|
70
|
+
${PROGRAM} vault get <id> [--show|--raw]
|
|
71
|
+
${PROGRAM} vault set <id> [--description d] value on stdin
|
|
72
|
+
${PROGRAM} vault remove <id>
|
|
73
|
+
${PROGRAM} vault key generate a fresh LANES_LINK_VAULT_KEY, printed once
|
|
74
|
+
|
|
75
|
+
${style.bold('Deploying')}
|
|
76
|
+
${PROGRAM} deploy [--dry-run] set up, build, and roll a revision
|
|
77
|
+
${PROGRAM} deploy --non-interactive take the stored answers, never prompt
|
|
78
|
+
${PROGRAM} deploy --access iam|public who gets past the platform's own door
|
|
79
|
+
${PROGRAM} deploy --target <name> deploy a second one, under its own name
|
|
80
|
+
${PROGRAM} secrets list credential references in this target
|
|
81
|
+
${PROGRAM} secrets set <ref> store one value, read from stdin
|
|
82
|
+
${PROGRAM} secrets push --from local --to cloud
|
|
83
|
+
|
|
84
|
+
${style.bold('Inspection')}
|
|
85
|
+
${PROGRAM} check static validation, no external calls
|
|
86
|
+
${PROGRAM} doctor [--json] credentials resolve, stores reachable
|
|
87
|
+
${PROGRAM} plan what reconcile would change
|
|
88
|
+
${PROGRAM} audit tail [--limit N] [--denied-only] [--format md]
|
|
89
|
+
${PROGRAM} audit verify has anything in the log been altered or removed
|
|
90
|
+
${PROGRAM} config show
|
|
91
|
+
${PROGRAM} version which release this is — same as lanes --version
|
|
92
|
+
|
|
93
|
+
${style.bold('Attachments')}
|
|
94
|
+
${PROGRAM} attach <file> --connection <provider>.<account>
|
|
95
|
+
stage a file, print a handle to send it by
|
|
96
|
+
|
|
97
|
+
${style.bold('Global flags')}
|
|
98
|
+
--profile <name> overrides LANES_LINK_PROFILE and the workspace default
|
|
99
|
+
--target <name> overrides LANES_LINK_TARGET and instance.default_target
|
|
100
|
+
--connection <id> which memory/skills/vault connection, if a profile has several
|
|
101
|
+
--yes skip the confirmation a destructive command would ask for
|
|
102
|
+
--json machine-readable output, where a command offers it
|
|
103
|
+
--non-interactive never prompt: connect refuses with what to store,
|
|
104
|
+
deploy takes the answers its config already holds
|
|
105
|
+
--accept-broad-scopes agree in advance to scopes broader than a provider needs
|
|
106
|
+
--own-client register your own OAuth client instead of using the
|
|
107
|
+
one this project operates (connect only)
|
|
108
|
+
--port <n> override the configured port (start only)
|
|
109
|
+
|
|
110
|
+
Every command prints the resolved profile and target before acting.
|
|
111
|
+
`;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { installRoot } from '#profile';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* What this CLI calls itself when asked.
|
|
7
|
+
*
|
|
8
|
+
* Read from `package.json` at call time rather than substituted in at build
|
|
9
|
+
* time, because there is no build step: the file that declares the version and
|
|
10
|
+
* the file that ships are the same file. `installRoot` walks up to whichever
|
|
11
|
+
* root this is — a checkout during development, or the package directory under
|
|
12
|
+
* `node_modules` once installed — which is the same way `mcp/assets.ts` finds
|
|
13
|
+
* `instructions/`.
|
|
14
|
+
*
|
|
15
|
+
* Answering at all is new with publishing to npm. While the only way to get
|
|
16
|
+
* this CLI was to clone it, `git log` was the answer and a better one; two
|
|
17
|
+
* machines can now be a release apart with nothing on either to say so.
|
|
18
|
+
*/
|
|
19
|
+
export function version(): string {
|
|
20
|
+
const path = join(installRoot(import.meta.dir), 'package.json');
|
|
21
|
+
const declared = (JSON.parse(readFileSync(path, 'utf8')) as { version?: string }).version;
|
|
22
|
+
|
|
23
|
+
if (!declared) throw new Error(`No "version" in ${path}`);
|
|
24
|
+
return declared;
|
|
25
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Credential types
|
|
2
|
+
|
|
3
|
+
One folder per method. Each owns both halves of its job: `resolve*` turns the
|
|
4
|
+
stored secret into a `ResolvedCredential`, and `attach*` puts that shape on an
|
|
5
|
+
outbound request. `resolve.ts` and `authorize.ts` are the only files that know
|
|
6
|
+
the whole set.
|
|
7
|
+
|
|
8
|
+
| Folder | `auth.kind` | What is stored |
|
|
9
|
+
|---|---|---|
|
|
10
|
+
| `none/` | `none` | nothing — `fs` and `local` providers hold no account |
|
|
11
|
+
| `bearer/` | `bearer` | a token, sent as `Authorization: Bearer <token>` |
|
|
12
|
+
| `header/` | `header` | a value, sent raw in a named header (shares api-key's attach) |
|
|
13
|
+
| `api-key/` | `api_key` | a key, in a header or the query string |
|
|
14
|
+
| `basic/` | `basic` | `username:password`, RFC 7617's own encoding |
|
|
15
|
+
| `oauth-authcode/` | `oauth` | a refresh token, exchanged on every use |
|
|
16
|
+
| `strategy/` | `strategy` | the escape hatch — per-vendor code, none registered |
|
|
17
|
+
|
|
18
|
+
## Adding one
|
|
19
|
+
|
|
20
|
+
A folder, a member of `authSchema` in `../manifest/auth.ts`, and a case in
|
|
21
|
+
`resolve.ts` (plus `authorize.ts` if it touches the request). Nothing else in
|
|
22
|
+
the codebase learns about it — that is the point of the split.
|
|
23
|
+
|
|
24
|
+
These are named in the credential-type list this design is measured against and
|
|
25
|
+
are **not built**:
|
|
26
|
+
|
|
27
|
+
- `sigv4/` — AWS SigV4 request signing
|
|
28
|
+
- `gcp-token/` — service account → GCP access token
|
|
29
|
+
- `gcp-iap/` — service account → an IAP-signed JWT
|
|
30
|
+
- `oauth-jwt/` — OAuth 2.0 JWT bearer (RFC 7523)
|
|
31
|
+
- `oauth-client-creds/` — OAuth 2.0 client credentials
|
|
32
|
+
- `body-param/` — the credential as a request body parameter
|
|
33
|
+
|
|
34
|
+
Each is a folder here plus a schema member. None of them is a change to a
|
|
35
|
+
transport, a provider, or the dispatch path, which is the property this layout
|
|
36
|
+
exists to give.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { ResolvedCredential } from '../credential.ts';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A key in a header, or — where a vendor insists — in the query string.
|
|
5
|
+
*
|
|
6
|
+
* `query` had been in the schema since M2 and was never read, so an
|
|
7
|
+
* api-key-in-query manifest sent an unauthenticated request and got a 401
|
|
8
|
+
* naming nothing.
|
|
9
|
+
*/
|
|
10
|
+
export function resolveApiKey(
|
|
11
|
+
value: string,
|
|
12
|
+
options: { header?: string | undefined; query?: string | undefined },
|
|
13
|
+
): ResolvedCredential {
|
|
14
|
+
return {
|
|
15
|
+
kind: 'api_key',
|
|
16
|
+
value,
|
|
17
|
+
...(options.query ? { query: options.query } : { header: options.header ?? 'x-api-key' }),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Returns a replacement request when the key belongs in the URL.
|
|
23
|
+
*
|
|
24
|
+
* A Request's URL is immutable, so moving the key into the query means
|
|
25
|
+
* rebuilding rather than mutating. Passing the Request itself as the init
|
|
26
|
+
* carries method, headers, and body verbatim; the cast is only because
|
|
27
|
+
* TypeScript types the init as `RequestInit`, whose `body` is narrower than the
|
|
28
|
+
* `ReadableStream` a built Request holds.
|
|
29
|
+
*/
|
|
30
|
+
export function attachApiKey(
|
|
31
|
+
credential: Extract<ResolvedCredential, { kind: 'api_key' }>,
|
|
32
|
+
request: Request,
|
|
33
|
+
original: Request,
|
|
34
|
+
): Request | undefined {
|
|
35
|
+
if (!credential.query) {
|
|
36
|
+
request.headers.set(credential.header ?? 'x-api-key', credential.value);
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const relocated = new URL(original.url);
|
|
41
|
+
relocated.searchParams.set(credential.query, credential.value);
|
|
42
|
+
return new Request(relocated.href, request as unknown as RequestInit);
|
|
43
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { ProviderRegistry } from '#registry';
|
|
2
|
+
import type { SecretStore } from '#secrets';
|
|
3
|
+
import { attachApiKey } from './api-key/index.ts';
|
|
4
|
+
import { attachBasic } from './basic/index.ts';
|
|
5
|
+
import { attachBearer } from './bearer/index.ts';
|
|
6
|
+
import { credentialResolver } from './resolve.ts';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Attach whatever the manifest's auth method requires to an outbound request.
|
|
10
|
+
*
|
|
11
|
+
* Connectors never see a raw credential: they hand core a request and get an
|
|
12
|
+
* authorised one back. A pure function of the resolved shape, so there is one
|
|
13
|
+
* place that reads the store (`resolve.ts`) and one that decides what a request
|
|
14
|
+
* looks like.
|
|
15
|
+
*/
|
|
16
|
+
export function requestAuthorizer(
|
|
17
|
+
registry: ProviderRegistry,
|
|
18
|
+
secrets: SecretStore,
|
|
19
|
+
): (providerId: string, connectionId: string, request: Request) => Promise<Request> {
|
|
20
|
+
const resolve = credentialResolver(registry, secrets);
|
|
21
|
+
|
|
22
|
+
return async (providerId, connectionId, request) => {
|
|
23
|
+
const credential = await resolve(providerId, connectionId);
|
|
24
|
+
if (credential.kind === 'none') return request;
|
|
25
|
+
|
|
26
|
+
const authorised = new Request(request, { headers: new Headers(request.headers) });
|
|
27
|
+
|
|
28
|
+
switch (credential.kind) {
|
|
29
|
+
case 'oauth':
|
|
30
|
+
authorised.headers.set('authorization', `Bearer ${credential.accessToken}`);
|
|
31
|
+
break;
|
|
32
|
+
case 'bearer':
|
|
33
|
+
attachBearer(credential, authorised);
|
|
34
|
+
break;
|
|
35
|
+
case 'basic':
|
|
36
|
+
attachBasic(credential, authorised);
|
|
37
|
+
break;
|
|
38
|
+
case 'api_key': {
|
|
39
|
+
// The only method that may need to rebuild the request rather than add
|
|
40
|
+
// a header, because a key in the query string changes the URL.
|
|
41
|
+
const relocated = attachApiKey(credential, authorised, request);
|
|
42
|
+
if (relocated) return relocated;
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return authorised;
|
|
48
|
+
};
|
|
49
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { credentialRefForConnection, type ProviderManifest } from '#connectivity';
|
|
2
|
+
import type { ProviderRegistry } from '#registry';
|
|
3
|
+
import type { SecretStore } from '#secrets';
|
|
4
|
+
import type { ResolvedCredential } from '../credential.ts';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* RFC 7617 Basic — a username and a password, stored as one string.
|
|
8
|
+
*
|
|
9
|
+
* `user:pass`, split on the *first* colon, which is lossless because the spec
|
|
10
|
+
* forbids a colon in the userid. Storing the pair exactly as the header carries
|
|
11
|
+
* it means nothing has to agree about a JSON shape.
|
|
12
|
+
*/
|
|
13
|
+
export function resolveBasic(value: string, ref: string, providerId: string): ResolvedCredential {
|
|
14
|
+
const colon = value.indexOf(':');
|
|
15
|
+
if (colon === -1) {
|
|
16
|
+
throw new Error(
|
|
17
|
+
`The credential at ${ref} is not a "username:password" pair, which basic auth requires. ` +
|
|
18
|
+
`Re-run: lanes link connect ${providerId}`,
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return { kind: 'basic', username: value.slice(0, colon), password: value.slice(colon + 1) };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function attachBasic(
|
|
26
|
+
credential: Extract<ResolvedCredential, { kind: 'basic' }>,
|
|
27
|
+
request: Request,
|
|
28
|
+
): void {
|
|
29
|
+
// `btoa` is wrong here twice: it throws outright above U+00FF, and below it
|
|
30
|
+
// silently encodes Latin-1 bytes where RFC 7617 requires UTF-8 — so a
|
|
31
|
+
// password with an umlaut produced a header that was well-formed, accepted by
|
|
32
|
+
// the type system, and rejected by the server.
|
|
33
|
+
request.headers.set(
|
|
34
|
+
'authorization',
|
|
35
|
+
`Basic ${Buffer.from(`${credential.username}:${credential.password}`, 'utf8').toString('base64')}`,
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* The username and password for a connection, for a protocol that wants them as
|
|
41
|
+
* a pair rather than as a header.
|
|
42
|
+
*
|
|
43
|
+
* IMAP and DAV take this closure as a constructor option. It is bound to one
|
|
44
|
+
* provider and one connection and takes no arguments, so a connector holding it
|
|
45
|
+
* cannot name a *different* account's credential — the same boundary
|
|
46
|
+
* `scopeSecrets` enforces with an allowlist, reached here by having nothing to
|
|
47
|
+
* pass.
|
|
48
|
+
*/
|
|
49
|
+
export async function basicCredential(
|
|
50
|
+
manifest: ProviderManifest,
|
|
51
|
+
connectionId: string,
|
|
52
|
+
secrets: SecretStore,
|
|
53
|
+
): Promise<{ username: string; password: string }> {
|
|
54
|
+
const { credentialResolver } = await import('../resolve.ts');
|
|
55
|
+
const registry = { manifest: () => manifest } as unknown as ProviderRegistry;
|
|
56
|
+
const resolved = await credentialResolver(registry, secrets)(manifest.id, connectionId);
|
|
57
|
+
|
|
58
|
+
if (resolved.kind !== 'basic') {
|
|
59
|
+
throw new Error(
|
|
60
|
+
`Provider "${manifest.id}" needs a username and password, but its auth resolves to "${resolved.kind}".`,
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return { username: resolved.username, password: resolved.password };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Re-exported so the factory can derive a ref without reaching for the manifest module. */
|
|
68
|
+
export { credentialRefForConnection };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ResolvedCredential } from '../credential.ts';
|
|
2
|
+
|
|
3
|
+
/** A long-lived token the operator pasted in, sent as `Bearer <token>`. */
|
|
4
|
+
export function resolveBearer(value: string, header: string | undefined): ResolvedCredential {
|
|
5
|
+
return { kind: 'bearer', token: value, header: header ?? 'authorization' };
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function attachBearer(
|
|
9
|
+
credential: Extract<ResolvedCredential, { kind: 'bearer' }>,
|
|
10
|
+
request: Request,
|
|
11
|
+
): void {
|
|
12
|
+
request.headers.set(credential.header, `Bearer ${credential.token}`);
|
|
13
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A credential, resolved but not yet attached to anything.
|
|
3
|
+
*
|
|
4
|
+
* The seam between "read the store and work out what this secret is" and "put
|
|
5
|
+
* it on an outbound request". Two steps rather than one because not every
|
|
6
|
+
* transport has a request to put it on: IMAP wants a username and password as a
|
|
7
|
+
* pair, and gets this value directly.
|
|
8
|
+
*/
|
|
9
|
+
export type ResolvedCredential =
|
|
10
|
+
| { readonly kind: 'none' }
|
|
11
|
+
| { readonly kind: 'bearer'; readonly token: string; readonly header: string }
|
|
12
|
+
| { readonly kind: 'basic'; readonly username: string; readonly password: string }
|
|
13
|
+
| {
|
|
14
|
+
readonly kind: 'api_key';
|
|
15
|
+
readonly value: string;
|
|
16
|
+
readonly header?: string;
|
|
17
|
+
readonly query?: string;
|
|
18
|
+
}
|
|
19
|
+
| { readonly kind: 'oauth'; readonly accessToken: string };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { resolveApiKey } from '../api-key/index.ts';
|
|
2
|
+
import type { ResolvedCredential } from '../credential.ts';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A raw value in a named header.
|
|
6
|
+
*
|
|
7
|
+
* Distinct from `bearer`, which prefixes `Bearer `. Resolves to the same shape
|
|
8
|
+
* as `api_key` and travels the same attach path, because "put this string in
|
|
9
|
+
* this header" is one behaviour whichever of the two names a manifest picked —
|
|
10
|
+
* and `api_key` is the one that also knows how to put it in the query string.
|
|
11
|
+
*
|
|
12
|
+
* Its own folder rather than an alias in the dispatcher because the *names* are
|
|
13
|
+
* what a manifest declares, and a reader asking "what does `kind: header` do"
|
|
14
|
+
* should find a file rather than a missing case.
|
|
15
|
+
*/
|
|
16
|
+
export function resolveHeader(value: string, header: string | undefined): ResolvedCredential {
|
|
17
|
+
return resolveApiKey(value, { header });
|
|
18
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credential types — how we prove who we are to a service.
|
|
3
|
+
*
|
|
4
|
+
* One folder per method, each owning both halves of its job: turning the stored
|
|
5
|
+
* secret into a resolved shape (`resolve*`) and putting that shape on an
|
|
6
|
+
* outbound request (`attach*`). The two dispatchers here are the only files
|
|
7
|
+
* that know the whole set.
|
|
8
|
+
*
|
|
9
|
+
* This is the axis the manifest's `auth:` block selects, and it is deliberately
|
|
10
|
+
* independent of `../transports/` — which is why iCloud can speak IMAP with a
|
|
11
|
+
* password while Gmail speaks HTTP with OAuth, and neither costs the other any
|
|
12
|
+
* code.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
export { credentialResolver, type ResolvedCredential } from './resolve.ts';
|
|
16
|
+
export { requestAuthorizer } from './authorize.ts';
|
|
17
|
+
export { basicCredential } from './basic/index.ts';
|
|
18
|
+
export {
|
|
19
|
+
CredentialOAuthProvider,
|
|
20
|
+
clearUpstreamTokens,
|
|
21
|
+
upstreamAccessToken,
|
|
22
|
+
type OAuthProviderOptions,
|
|
23
|
+
} from './oauth-authcode/provider.ts';
|
|
24
|
+
export { resolveUpstreamToken } from './oauth-authcode/index.ts';
|
|
25
|
+
export {
|
|
26
|
+
BROKER_ORIGIN_ENV,
|
|
27
|
+
BROKERED,
|
|
28
|
+
BrokerError,
|
|
29
|
+
brokerConfig,
|
|
30
|
+
brokerExchange,
|
|
31
|
+
brokerOriginOverride,
|
|
32
|
+
brokerRefresh,
|
|
33
|
+
type BrokerConfig,
|
|
34
|
+
type BrokerTokens,
|
|
35
|
+
} from './oauth-authcode/broker.ts';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ResolvedCredential } from '../credential.ts';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* No credential at all.
|
|
5
|
+
*
|
|
6
|
+
* Not an absence to be handled everywhere — a method like any other, so the
|
|
7
|
+
* `fs` and `local` transports go down the same path as Gmail and the callers
|
|
8
|
+
* never branch on "is there auth".
|
|
9
|
+
*/
|
|
10
|
+
export function resolveNone(): ResolvedCredential {
|
|
11
|
+
return { kind: 'none' };
|
|
12
|
+
}
|