runline 0.29.0 → 0.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/dist/auth/errors.d.ts +22 -0
  2. package/dist/auth/errors.js +25 -0
  3. package/dist/auth/oauth2.d.ts +21 -0
  4. package/dist/auth/oauth2.js +154 -0
  5. package/dist/auth/token.d.ts +11 -0
  6. package/dist/auth/token.js +231 -0
  7. package/dist/auth/types.d.ts +93 -0
  8. package/dist/auth/types.js +1 -0
  9. package/dist/commands/auth.js +26 -13
  10. package/dist/commands/connection.js +3 -6
  11. package/dist/commands/exec.js +7 -2
  12. package/dist/commands/init.js +9 -2
  13. package/dist/config/loader.d.ts +7 -13
  14. package/dist/config/loader.js +58 -88
  15. package/dist/config/store.d.ts +17 -0
  16. package/dist/config/store.js +85 -0
  17. package/dist/connections/file.d.ts +13 -0
  18. package/dist/connections/file.js +53 -0
  19. package/dist/connections/memory.d.ts +15 -0
  20. package/dist/connections/memory.js +70 -0
  21. package/dist/connections/types.d.ts +47 -0
  22. package/dist/connections/types.js +10 -0
  23. package/dist/core/engine.d.ts +5 -2
  24. package/dist/core/engine.js +48 -55
  25. package/dist/core/oauth.d.ts +20 -15
  26. package/dist/core/oauth.js +195 -98
  27. package/dist/credentials/http.d.ts +19 -0
  28. package/dist/credentials/http.js +109 -0
  29. package/dist/credentials/policy.d.ts +5 -0
  30. package/dist/credentials/policy.js +92 -0
  31. package/dist/credentials/registry.d.ts +31 -0
  32. package/dist/credentials/registry.js +171 -0
  33. package/dist/credentials/transport.d.ts +38 -0
  34. package/dist/credentials/transport.js +283 -0
  35. package/dist/credentials/types.d.ts +80 -0
  36. package/dist/credentials/types.js +1 -0
  37. package/dist/index.d.ts +12 -0
  38. package/dist/index.js +8 -0
  39. package/dist/plugin/types.d.ts +30 -17
  40. package/dist/plugins/_shared/credentialAdapter.js +88 -0
  41. package/dist/plugins/_shared/googleAuth.js +89 -103
  42. package/dist/plugins/_shared/googleCredentials.js +160 -0
  43. package/dist/plugins/_shared/microsoftAuth.js +81 -74
  44. package/dist/plugins/_shared/microsoftCredentials.js +90 -0
  45. package/dist/plugins/_shared/tokenRefresh.js +39 -0
  46. package/dist/plugins/bitwarden/src/index.js +13 -31
  47. package/dist/plugins/gmail/src/index.js +8 -44
  48. package/dist/plugins/googleAppsScript/src/index.js +3 -25
  49. package/dist/plugins/googleCalendar/src/index.js +3 -41
  50. package/dist/plugins/googleContacts/src/index.js +3 -36
  51. package/dist/plugins/googleDocs/src/index.js +5 -0
  52. package/dist/plugins/googleDocs/src/shared.js +2 -30
  53. package/dist/plugins/googleDrive/src/index.js +52 -98
  54. package/dist/plugins/googleSheets/src/index.js +3 -40
  55. package/dist/plugins/googleSlides/src/index.js +5 -39
  56. package/dist/plugins/googleTasks/src/index.js +3 -30
  57. package/dist/plugins/halopsa/src/index.js +16 -31
  58. package/dist/plugins/microsoftCalendar/src/index.js +2 -1
  59. package/dist/plugins/microsoftFiles/src/index.js +10 -24
  60. package/dist/plugins/microsoftMail/src/index.js +2 -1
  61. package/dist/plugins/paypal/src/index.js +14 -29
  62. package/dist/plugins/plaud/src/index.js +210 -0
  63. package/dist/plugins/plaud/src/shared.js +221 -0
  64. package/dist/plugins/salesforce/src/shared.js +17 -32
  65. package/dist/sdk.d.ts +13 -5
  66. package/dist/sdk.js +35 -29
  67. package/dist/utils/observer.d.ts +2 -0
  68. package/dist/utils/observer.js +11 -0
  69. package/package.json +1 -1
@@ -0,0 +1,109 @@
1
+ import { AuthError } from "../auth/errors.js";
2
+ /** Internal send primitive. Callers validate destination policy before reaching it. */
3
+ export async function sendResource(url, method, headers, body, options) {
4
+ const controller = new AbortController();
5
+ let timer;
6
+ const timeout = new Promise((_, reject) => {
7
+ timer = setTimeout(() => {
8
+ controller.abort();
9
+ reject(new AuthError("transport_failed"));
10
+ }, options.timeoutMs);
11
+ });
12
+ const work = async () => {
13
+ const response = await options.fetch(url, {
14
+ method,
15
+ headers,
16
+ body: body === undefined ? undefined : Buffer.from(body),
17
+ redirect: "error",
18
+ credentials: "omit",
19
+ signal: controller.signal,
20
+ });
21
+ if (controller.signal.aborted ||
22
+ response.redirected ||
23
+ (response.status >= 300 &&
24
+ response.status < 400 &&
25
+ !(options.resumableUpload &&
26
+ method === "PUT" &&
27
+ headers.has("content-range") &&
28
+ response.status === 308 &&
29
+ !response.headers.has("location")))) {
30
+ void response.body?.cancel().catch(() => { });
31
+ throw new AuthError("transport_failed");
32
+ }
33
+ const reader = response.body?.getReader();
34
+ const cancel = () => {
35
+ void reader?.cancel().catch(() => { });
36
+ };
37
+ controller.signal.addEventListener("abort", cancel, { once: true });
38
+ const chunks = [];
39
+ let size = 0;
40
+ try {
41
+ if (reader)
42
+ while (true) {
43
+ const { done, value } = await reader.read();
44
+ if (done)
45
+ break;
46
+ size += value.byteLength;
47
+ if (size > options.maxResponseBytes) {
48
+ void reader.cancel().catch(() => { });
49
+ throw new AuthError("response_too_large");
50
+ }
51
+ if (value.byteLength)
52
+ chunks.push(value);
53
+ }
54
+ }
55
+ finally {
56
+ controller.signal.removeEventListener("abort", cancel);
57
+ reader?.releaseLock();
58
+ }
59
+ const responseHeaders = new Headers(response.headers);
60
+ // Fetch supplies decoded bytes, not the original wire representation.
61
+ responseHeaders.delete("content-encoding");
62
+ responseHeaders.delete("content-length");
63
+ responseHeaders.delete("set-cookie");
64
+ return new Response(method === "HEAD" || [204, 205, 304].includes(response.status)
65
+ ? null
66
+ : Buffer.concat(chunks), { status: response.status, headers: responseHeaders });
67
+ };
68
+ try {
69
+ return await Promise.race([work(), timeout]);
70
+ }
71
+ catch (error) {
72
+ if (error instanceof AuthError && error.code === "response_too_large")
73
+ throw error;
74
+ throw new AuthError("transport_failed");
75
+ }
76
+ finally {
77
+ clearTimeout(timer);
78
+ }
79
+ }
80
+ /**
81
+ * Fetch a provider-issued preauthenticated URL without bearer tokens or cookies.
82
+ * Hosts approve exact origins and enforce DNS/IP egress through their fetch hook.
83
+ * The signed URL is itself secret: errors never include it. No redirects or retries.
84
+ */
85
+ export async function downloadResource(value, options) {
86
+ let url;
87
+ try {
88
+ url = new URL(value);
89
+ if (url.protocol !== "https:" ||
90
+ url.username ||
91
+ url.password ||
92
+ url.hash ||
93
+ !options.allowedOrigins.includes(url.origin))
94
+ throw new Error();
95
+ }
96
+ catch {
97
+ throw new AuthError("request_not_allowed");
98
+ }
99
+ const maxResponseBytes = options.maxResponseBytes ?? 64 * 1024 * 1024;
100
+ if (!Number.isSafeInteger(maxResponseBytes) ||
101
+ maxResponseBytes <= 0 ||
102
+ maxResponseBytes > 64 * 1024 * 1024)
103
+ throw new AuthError("invalid_definition");
104
+ return sendResource(url.toString(), "GET", new Headers(), undefined, {
105
+ fetch: options.fetch,
106
+ timeoutMs: 20_000,
107
+ maxResponseBytes,
108
+ });
109
+ }
@@ -0,0 +1,5 @@
1
+ import type { CredentialTarget, HttpMethod } from "./types.js";
2
+ export declare const HTTP_METHODS: readonly HttpMethod[];
3
+ export declare function headerName(name: string): string;
4
+ export declare function targetBase(target: CredentialTarget): URL;
5
+ export declare function resourceUrl(target: CredentialTarget, path: string): URL;
@@ -0,0 +1,92 @@
1
+ import { AuthError } from "../auth/errors.js";
2
+ export const HTTP_METHODS = [
3
+ "GET",
4
+ "HEAD",
5
+ "POST",
6
+ "PUT",
7
+ "PATCH",
8
+ "DELETE",
9
+ ];
10
+ export function headerName(name) {
11
+ if (typeof name !== "string" || !/^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/.test(name))
12
+ throw new AuthError("invalid_definition");
13
+ const lower = name.toLowerCase();
14
+ if ([
15
+ "host",
16
+ "cookie",
17
+ "set-cookie",
18
+ "content-length",
19
+ "connection",
20
+ "transfer-encoding",
21
+ "proxy-authorization",
22
+ "proxy-connection",
23
+ "upgrade",
24
+ "te",
25
+ "trailer",
26
+ ].includes(lower) ||
27
+ lower.startsWith("sec-") ||
28
+ lower.startsWith("proxy-"))
29
+ throw new AuthError("invalid_definition");
30
+ return lower;
31
+ }
32
+ export function targetBase(target) {
33
+ try {
34
+ const url = new URL(target.baseUrl);
35
+ if (url.protocol !== "https:" ||
36
+ url.username ||
37
+ url.password ||
38
+ url.search ||
39
+ url.hash ||
40
+ !url.pathname.endsWith("/"))
41
+ throw new Error();
42
+ safePath(url.pathname);
43
+ // Reject normalization rather than silently widening a configured boundary.
44
+ if (url.href !== target.baseUrl)
45
+ throw new Error();
46
+ return url;
47
+ }
48
+ catch {
49
+ throw new AuthError("invalid_definition");
50
+ }
51
+ }
52
+ function controls(value) {
53
+ return [...value].some((char) => char.charCodeAt(0) <= 32 || char.charCodeAt(0) === 127);
54
+ }
55
+ function safePath(path) {
56
+ // Reject ambiguous encodings and segments before URL normalization. Query values
57
+ // are separate data; encoded URLs in query parameters are not destinations.
58
+ if (/[\\\s]/.test(path) || controls(path))
59
+ throw new Error();
60
+ for (const segment of path.split("/")) {
61
+ const decoded = decodeURIComponent(segment);
62
+ if (decoded === "." ||
63
+ decoded === ".." ||
64
+ /[%/\\]/.test(decoded) ||
65
+ [...decoded].some((char) => char.charCodeAt(0) < 32 || char.charCodeAt(0) === 127))
66
+ throw new Error();
67
+ }
68
+ }
69
+ export function resourceUrl(target, path) {
70
+ const base = targetBase(target);
71
+ try {
72
+ if (typeof path !== "string" ||
73
+ path.startsWith("/") ||
74
+ path.includes("#") ||
75
+ /^[a-z][a-z0-9+.-]*:/i.test(path) ||
76
+ path.includes("\\") ||
77
+ controls(path))
78
+ throw new Error();
79
+ safePath(path.split("?")[0]);
80
+ const url = new URL(path, base);
81
+ if (url.origin !== base.origin ||
82
+ !url.pathname.startsWith(base.pathname) ||
83
+ url.username ||
84
+ url.password ||
85
+ url.hash)
86
+ throw new Error();
87
+ return url;
88
+ }
89
+ catch {
90
+ throw new AuthError("request_not_allowed");
91
+ }
92
+ }
@@ -0,0 +1,31 @@
1
+ import * as t from "typebox";
2
+ import type { CredentialMethod, CredentialType } from "./types.js";
3
+ /** Normalized grant storage; application registration remains host-owned. */
4
+ export declare const OAuthTokensSchema: t.TObject<{
5
+ accessToken: t.TString;
6
+ refreshToken: t.TOptional<t.TString>;
7
+ expiresAt: t.TOptional<t.TInteger>;
8
+ scope: t.TOptional<t.TString>;
9
+ tokenType: t.TOptional<t.TString>;
10
+ metadata: t.TOptional<t.TRecord<"^.*$", t.TString>>;
11
+ }>;
12
+ export declare const OAuthGrantSchema: t.TObject<{
13
+ tokens: t.TObject<{
14
+ accessToken: t.TOptional<t.TString>;
15
+ refreshToken: t.TOptional<t.TString>;
16
+ expiresAt: t.TOptional<t.TInteger>;
17
+ scope: t.TOptional<t.TString>;
18
+ tokenType: t.TOptional<t.TString>;
19
+ metadata: t.TOptional<t.TRecord<"^.*$", t.TString>>;
20
+ }>;
21
+ revision: t.TString;
22
+ }>;
23
+ /** No implicit defaults, provider inference, executable expressions, or replacement. */
24
+ export declare class CredentialRegistry {
25
+ private readonly types;
26
+ register(definition: CredentialType): void;
27
+ select(type: string, method: string): CredentialMethod;
28
+ list(): CredentialType[];
29
+ }
30
+ /** Validation never formats schema errors containing credential values. */
31
+ export declare function validateCredential(method: CredentialMethod, config: unknown): asserts config is Record<string, unknown>;
@@ -0,0 +1,171 @@
1
+ import * as t from "typebox";
2
+ import { Check } from "typebox/value";
3
+ import { AuthError } from "../auth/errors.js";
4
+ import { validateOAuth2ExchangePolicy } from "../auth/oauth2.js";
5
+ import { oauthEndpoint, providerParameters } from "../auth/token.js";
6
+ import { HTTP_METHODS, headerName, resourceUrl, targetBase } from "./policy.js";
7
+ /** Normalized grant storage; application registration remains host-owned. */
8
+ export const OAuthTokensSchema = t.Object({
9
+ accessToken: t.String({ minLength: 1 }),
10
+ refreshToken: t.Optional(t.String({ minLength: 1 })),
11
+ expiresAt: t.Optional(t.Integer({ minimum: 0, maximum: Number.MAX_SAFE_INTEGER })),
12
+ scope: t.Optional(t.String()),
13
+ tokenType: t.Optional(t.String({ minLength: 1 })),
14
+ metadata: t.Optional(t.Record(t.String(), t.String())),
15
+ }, { additionalProperties: false });
16
+ export const OAuthGrantSchema = t.Object({
17
+ tokens: t.Partial(OAuthTokensSchema),
18
+ revision: t.String({ minLength: 1 }),
19
+ }, { additionalProperties: false });
20
+ function identifier(value) {
21
+ if (typeof value !== "string" || !/^[a-zA-Z][a-zA-Z0-9_.-]*$/.test(value))
22
+ throw new AuthError("invalid_definition");
23
+ }
24
+ function validateMethod(method) {
25
+ const schema = method.schema;
26
+ if (schema.type !== "object" ||
27
+ schema.additionalProperties !== false ||
28
+ !schema.properties)
29
+ throw new AuthError("invalid_definition");
30
+ const auth = method.authentication;
31
+ const field = auth.kind === "oauth2" ? auth.grantField : auth.field;
32
+ identifier(field);
33
+ if (!Object.hasOwn(schema.properties, field))
34
+ throw new AuthError("invalid_definition");
35
+ let injected = "authorization";
36
+ if (auth.kind === "apiKey")
37
+ injected = headerName(auth.header);
38
+ else if (auth.kind === "oauth2") {
39
+ identifier(auth.definition.id);
40
+ identifier(auth.definition.provider);
41
+ validateOAuth2ExchangePolicy(auth.definition.exchange);
42
+ if (!["refresh", "clientCredentials", "jwtBearer"].includes(auth.renewal) ||
43
+ !auth.definition[auth.renewal])
44
+ throw new AuthError("invalid_definition");
45
+ if (auth.rejectionStatus !== undefined &&
46
+ ![401, 403].includes(auth.rejectionStatus))
47
+ throw new AuthError("invalid_definition");
48
+ if (auth.scopes &&
49
+ (!Array.isArray(auth.scopes) ||
50
+ auth.scopes.some((scope) => typeof scope !== "string" || !scope)))
51
+ throw new AuthError("invalid_definition");
52
+ if (auth.renewal === "jwtBearer" &&
53
+ (!auth.scopes?.length ||
54
+ auth.definition.jwtBearer?.clientAuthentication !== "none" ||
55
+ auth.definition.jwtBearer?.grantType !== undefined))
56
+ throw new AuthError("invalid_definition");
57
+ if (auth.definition.authorization) {
58
+ oauthEndpoint(auth.definition.authorization.url);
59
+ providerParameters(auth.definition.authorization.parameters);
60
+ }
61
+ for (const operation of [
62
+ "exchange",
63
+ "refresh",
64
+ "clientCredentials",
65
+ "jwtBearer",
66
+ ]) {
67
+ const endpoint = auth.definition[operation];
68
+ if (!endpoint)
69
+ continue;
70
+ oauthEndpoint(endpoint.url);
71
+ providerParameters(endpoint.parameters);
72
+ if (![
73
+ "none",
74
+ "client_id",
75
+ "client_id_basic",
76
+ "client_secret_basic",
77
+ "client_secret_post",
78
+ ].includes(endpoint.clientAuthentication) ||
79
+ (endpoint.encoding !== undefined &&
80
+ !["form", "json"].includes(endpoint.encoding)))
81
+ throw new AuthError("invalid_definition");
82
+ }
83
+ }
84
+ else if (auth.kind !== "bearer")
85
+ throw new AuthError("invalid_definition");
86
+ if (!Object.keys(method.targets).length)
87
+ throw new AuthError("invalid_definition");
88
+ for (const [name, target] of Object.entries(method.targets)) {
89
+ identifier(name);
90
+ targetBase(target);
91
+ if (!Array.isArray(target.methods) ||
92
+ !target.methods.length ||
93
+ target.methods.some((m) => !HTTP_METHODS.includes(m)))
94
+ throw new AuthError("invalid_definition");
95
+ if (target.allowedHeaders !== undefined &&
96
+ !Array.isArray(target.allowedHeaders))
97
+ throw new AuthError("invalid_definition");
98
+ for (const name of target.allowedHeaders ?? []) {
99
+ const header = headerName(name);
100
+ if (header === injected || header === "authorization")
101
+ throw new AuthError("invalid_definition");
102
+ }
103
+ if (target.resumableUpload !== undefined &&
104
+ (typeof target.resumableUpload !== "boolean" ||
105
+ !target.methods.includes("PUT") ||
106
+ !target.allowedHeaders?.some((name) => name.toLowerCase() === "content-range")))
107
+ throw new AuthError("invalid_definition");
108
+ if (target.idempotency) {
109
+ const header = headerName(target.idempotency.header);
110
+ if (header === injected ||
111
+ header === "authorization" ||
112
+ !target.idempotency.methods.length ||
113
+ target.idempotency.methods.some((m) => !target.methods.includes(m) || m === "GET" || m === "HEAD"))
114
+ throw new AuthError("invalid_definition");
115
+ }
116
+ }
117
+ if (method.probe) {
118
+ const probe = method.probe;
119
+ const target = Object.hasOwn(method.targets, probe.target)
120
+ ? method.targets[probe.target]
121
+ : undefined;
122
+ if (!target ||
123
+ !["GET", "HEAD"].includes(probe.method) ||
124
+ !target.methods.includes(probe.method) ||
125
+ !probe.acceptedStatuses.length ||
126
+ probe.acceptedStatuses.some((status) => !Number.isInteger(status) || status < 200 || status > 299))
127
+ throw new AuthError("invalid_definition");
128
+ resourceUrl(target, probe.path);
129
+ }
130
+ }
131
+ /** No implicit defaults, provider inference, executable expressions, or replacement. */
132
+ export class CredentialRegistry {
133
+ types = new Map();
134
+ register(definition) {
135
+ let copy;
136
+ try {
137
+ copy = structuredClone(definition);
138
+ identifier(copy.id);
139
+ if (this.types.has(copy.id) || !Object.keys(copy.methods).length)
140
+ throw new Error();
141
+ for (const [name, method] of Object.entries(copy.methods)) {
142
+ identifier(name);
143
+ validateMethod(method);
144
+ }
145
+ }
146
+ catch {
147
+ throw new AuthError("invalid_definition");
148
+ }
149
+ this.types.set(copy.id, copy);
150
+ }
151
+ select(type, method) {
152
+ const definition = this.types.get(type);
153
+ if (!definition || !Object.hasOwn(definition.methods, method))
154
+ throw new AuthError("invalid_definition");
155
+ return structuredClone(definition.methods[method]);
156
+ }
157
+ list() {
158
+ return structuredClone([...this.types.values()]);
159
+ }
160
+ }
161
+ /** Validation never formats schema errors containing credential values. */
162
+ export function validateCredential(method, config) {
163
+ try {
164
+ if (Check(method.schema, config))
165
+ return;
166
+ }
167
+ catch {
168
+ throw new AuthError("invalid_definition");
169
+ }
170
+ throw new AuthError("invalid_credentials");
171
+ }
@@ -0,0 +1,38 @@
1
+ import { type CredentialRegistry } from "./registry.js";
2
+ import type { CredentialBinding, CredentialProbeResult, HttpMethod } from "./types.js";
3
+ export interface AuthenticatedRequest {
4
+ target: string;
5
+ path: string;
6
+ method?: HttpMethod;
7
+ headers?: Record<string, string>;
8
+ /** Buffered, copied before the first await. Streams and FormData are not accepted. */
9
+ body?: string | Uint8Array;
10
+ /** Enables write replay only with a provider-declared idempotency contract. */
11
+ idempotencyKey?: string;
12
+ /** Disable even otherwise-safe replay for a particular request. */
13
+ retry?: "never";
14
+ }
15
+ export interface CredentialTransportOptions {
16
+ /** Mandatory trusted transport: enforce DNS/IP egress policy for token AND API requests. */
17
+ fetch: typeof globalThis.fetch;
18
+ timeoutMs?: number;
19
+ maxResponseBytes?: number;
20
+ maxRequestBytes?: number;
21
+ }
22
+ /**
23
+ * Trusted-host primitive, not a workspace sandbox. Callers authorize a binding on
24
+ * every invocation. Handles own lifecycle fencing; no second lock or token cache.
25
+ */
26
+ export declare class CredentialTransport {
27
+ private readonly registry;
28
+ private readonly options;
29
+ constructor(registry: CredentialRegistry, options: CredentialTransportOptions);
30
+ private read;
31
+ private renew;
32
+ request(selection: CredentialBinding, input: AuthenticatedRequest): Promise<Response>;
33
+ /** Only a declared read-only probe runs. Results never contain provider text or secrets. */
34
+ probe(binding: CredentialBinding): Promise<CredentialProbeResult>;
35
+ /** Trusted-host compatibility primitive. Never expose raw tokens through a broker. */
36
+ accessToken(selection: CredentialBinding): Promise<string>;
37
+ private authorize;
38
+ }