runline 0.28.1 → 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 (73) 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/linear/src/comments.js +2 -2
  59. package/dist/plugins/linear/src/index.js +1 -1
  60. package/dist/plugins/linear/src/issues.js +1 -17
  61. package/dist/plugins/microsoftCalendar/src/index.js +2 -1
  62. package/dist/plugins/microsoftFiles/src/index.js +10 -24
  63. package/dist/plugins/microsoftMail/src/index.js +2 -1
  64. package/dist/plugins/openai/src/index.js +13 -8
  65. package/dist/plugins/paypal/src/index.js +14 -29
  66. package/dist/plugins/plaud/src/index.js +210 -0
  67. package/dist/plugins/plaud/src/shared.js +221 -0
  68. package/dist/plugins/salesforce/src/shared.js +17 -32
  69. package/dist/sdk.d.ts +13 -5
  70. package/dist/sdk.js +35 -29
  71. package/dist/utils/observer.d.ts +2 -0
  72. package/dist/utils/observer.js +11 -0
  73. package/package.json +1 -1
@@ -1,5 +1,5 @@
1
1
  import * as t from "typebox";
2
- import { assertIssueInScope, buildConnArgs, COMMENT_FIELDS, ensureScopeLabelsOnCreateOrReplace, forbidScopeLabelRemoval, gql, ISSUE_FIELDS, ISSUE_LITE, issueHasScope, key, LIST_INPUT_SCHEMA, mergeIssueScopeFilter, } from "./shared.js";
2
+ import { assertIssueInScope, buildConnArgs, ensureScopeLabelsOnCreateOrReplace, forbidScopeLabelRemoval, gql, ISSUE_FIELDS, ISSUE_LITE, issueHasScope, key, LIST_INPUT_SCHEMA, mergeIssueScopeFilter, } from "./shared.js";
3
3
  /**
4
4
  * Linear's IssueUpdateInput accepts explicit null for clearable relations
5
5
  * (e.g. cycleId: null removes the issue from its cycle). Omitted = leave
@@ -342,20 +342,4 @@ export function registerIssueActions(rl) {
342
342
  return data.issueRelationCreate;
343
343
  },
344
344
  });
345
- rl.registerAction("issue.listComments", {
346
- access: "read",
347
- description: "List comments on an issue.",
348
- inputSchema: t.Object({
349
- issueId: t.String(),
350
- limit: t.Optional(t.Number()),
351
- }),
352
- async execute(input, ctx) {
353
- const { issueId, limit } = input;
354
- await assertIssueInScope(ctx, issueId);
355
- const data = await gql(key(ctx), `query($id: String!, $first: Int) {
356
- issue(id: $id) { comments(first: $first) { nodes { ${COMMENT_FIELDS} } } }
357
- }`, { id: issueId, first: limit ?? 50 });
358
- return data.issue?.comments?.nodes;
359
- },
360
- });
361
345
  }
@@ -5,6 +5,7 @@ export default function microsoftCalendar(rl) {
5
5
  rl.setName(NAME);
6
6
  rl.setVersion("1.0.0");
7
7
  rl.setConnectionSchema({
8
+ authMethod: { type: "string", required: false, description: "delegated or appOnly; legacy configs infer the method from existing credentials" },
8
9
  tenantId: { type: "string", required: false, env: "MS_GRAPH_TENANT_ID", description: "Entra tenant id (app-only) or omit for OAuth /common" },
9
10
  clientId: { type: "string", required: false, env: "MS_GRAPH_CLIENT_ID", description: "App (client) id" },
10
11
  clientSecret: { type: "string", required: false, env: "MS_GRAPH_CLIENT_SECRET", description: "Client secret VALUE" },
@@ -42,7 +43,7 @@ export default function microsoftCalendar(rl) {
42
43
  description: "Get one calendar event by id (full details incl. body).",
43
44
  inputSchema: { id: { type: "string", required: true } },
44
45
  async execute(input, ctx) {
45
- return graphRequest(ctx, NAME, SCOPES, "GET", `${userBase(ctx)}/events/${input.id}`);
46
+ return graphRequest(ctx, NAME, SCOPES, "GET", `${userBase(ctx)}/events/${encodeURIComponent(input.id)}`);
46
47
  },
47
48
  });
48
49
  }
@@ -1,4 +1,4 @@
1
- import { graphRequest, microsoftAccessToken, microsoftSetupHelp, userBase, } from "../../_shared/microsoftAuth.js";
1
+ import { graphRequest, graphResponse, microsoftDownload, microsoftDriveBase, microsoftSetupHelp, } from "../../_shared/microsoftAuth.js";
2
2
  const NAME = "microsoftFiles";
3
3
  const SCOPES = [
4
4
  "https://graph.microsoft.com/Files.ReadWrite.All",
@@ -6,30 +6,16 @@ const SCOPES = [
6
6
  ];
7
7
  /** Resolve the drive root path: explicit drive/site, else the user's default drive. */
8
8
  function driveBase(ctx) {
9
- const cfg = ctx.connection.config;
10
- if (cfg.driveId)
11
- return `/drives/${cfg.driveId}`;
12
- if (cfg.siteId)
13
- return `/sites/${cfg.siteId}/drive`;
14
- return `${userBase(ctx)}/drive`;
9
+ return microsoftDriveBase(ctx.connection.config);
15
10
  }
16
11
  async function binaryFetch(ctx, method, path, body) {
17
- const token = await microsoftAccessToken(ctx, NAME, SCOPES);
18
- const init = {
19
- method,
20
- headers: {
21
- Authorization: `Bearer ${token}`,
22
- ...(body ? { "Content-Type": "application/octet-stream" } : {}),
23
- },
24
- };
25
- if (body)
26
- init.body = body;
27
- return fetch(`https://graph.microsoft.com/v1.0${path}`, init);
12
+ return graphResponse(ctx, NAME, SCOPES, method, path, body, body ? "application/octet-stream" : undefined);
28
13
  }
29
14
  export default function microsoftFiles(rl) {
30
15
  rl.setName(NAME);
31
16
  rl.setVersion("1.0.0");
32
17
  rl.setConnectionSchema({
18
+ authMethod: { type: "string", required: false, description: "delegated or appOnly; legacy configs infer the method from existing credentials" },
33
19
  tenantId: { type: "string", required: false, env: "MS_GRAPH_TENANT_ID", description: "Entra tenant id (app-only) or omit for OAuth /common" },
34
20
  clientId: { type: "string", required: false, env: "MS_GRAPH_CLIENT_ID", description: "App (client) id" },
35
21
  clientSecret: { type: "string", required: false, env: "MS_GRAPH_CLIENT_SECRET", description: "Client secret VALUE" },
@@ -69,7 +55,7 @@ export default function microsoftFiles(rl) {
69
55
  top: { type: "number", required: false, default: 100 },
70
56
  },
71
57
  async execute(input, ctx) {
72
- const where = input.folderId ? `/items/${input.folderId}/children` : "/root/children";
58
+ const where = input.folderId ? `/items/${encodeURIComponent(input.folderId)}/children` : "/root/children";
73
59
  const qs = new URLSearchParams({
74
60
  $top: String(input.top ?? 100),
75
61
  $select: "id,name,size,lastModifiedDateTime,webUrl,folder,file",
@@ -83,10 +69,10 @@ export default function microsoftFiles(rl) {
83
69
  description: "Download a file by id. Returns {id,name,size,contentType,base64}. base64 is the file bytes.",
84
70
  inputSchema: { id: { type: "string", required: true } },
85
71
  async execute(input, ctx) {
86
- const meta = await graphRequest(ctx, NAME, SCOPES, "GET", `${driveBase(ctx)}/items/${input.id}`);
87
- const res = await binaryFetch(ctx, "GET", `${driveBase(ctx)}/items/${input.id}/content`);
72
+ const meta = await graphRequest(ctx, NAME, SCOPES, "GET", `${driveBase(ctx)}/items/${encodeURIComponent(input.id)}`);
73
+ const res = await microsoftDownload(meta["@microsoft.graph.downloadUrl"]);
88
74
  if (!res.ok)
89
- throw new Error(`${NAME}: download ${input.id} → ${res.status} ${await res.text()}`);
75
+ throw new Error(`${NAME}: download failed (HTTP ${res.status})`);
90
76
  const buf = Buffer.from(await res.arrayBuffer());
91
77
  return {
92
78
  id: meta.id,
@@ -109,7 +95,7 @@ export default function microsoftFiles(rl) {
109
95
  const p = input.path.split("/").map(encodeURIComponent).join("/");
110
96
  const res = await binaryFetch(ctx, "PUT", `${driveBase(ctx)}/root:/${p}:/content`, bytes);
111
97
  if (!res.ok)
112
- throw new Error(`${NAME}: upload ${input.path} → ${res.status} ${await res.text()}`);
98
+ throw new Error(`${NAME}: upload failed (HTTP ${res.status})`);
113
99
  return JSON.parse(await res.text());
114
100
  },
115
101
  });
@@ -121,7 +107,7 @@ export default function microsoftFiles(rl) {
121
107
  parentId: { type: "string", required: false, description: "Parent folder id; omit for root" },
122
108
  },
123
109
  async execute(input, ctx) {
124
- const where = input.parentId ? `/items/${input.parentId}/children` : "/root/children";
110
+ const where = input.parentId ? `/items/${encodeURIComponent(input.parentId)}/children` : "/root/children";
125
111
  return graphRequest(ctx, NAME, SCOPES, "POST", `${driveBase(ctx)}${where}`, {
126
112
  name: input.name,
127
113
  folder: {},
@@ -20,6 +20,7 @@ export default function microsoftMail(rl) {
20
20
  rl.setName(NAME);
21
21
  rl.setVersion("1.0.0");
22
22
  rl.setConnectionSchema({
23
+ authMethod: { type: "string", required: false, description: "delegated or appOnly; legacy configs infer the method from existing credentials" },
23
24
  tenantId: { type: "string", required: false, env: "MS_GRAPH_TENANT_ID", description: "Entra tenant id (app-only) or omit for OAuth /common" },
24
25
  clientId: { type: "string", required: false, env: "MS_GRAPH_CLIENT_ID", description: "App (client) id" },
25
26
  clientSecret: { type: "string", required: false, env: "MS_GRAPH_CLIENT_SECRET", description: "Client secret VALUE" },
@@ -89,7 +90,7 @@ export default function microsoftMail(rl) {
89
90
  description: "Get one message with full body by id.",
90
91
  inputSchema: { id: { type: "string", required: true } },
91
92
  async execute(input, ctx) {
92
- return graphRequest(ctx, NAME, SCOPES, "GET", `${userBase(ctx)}/messages/${input.id}`);
93
+ return graphRequest(ctx, NAME, SCOPES, "GET", `${userBase(ctx)}/messages/${encodeURIComponent(input.id)}`);
93
94
  },
94
95
  });
95
96
  }
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * OpenAI image generation for runline.
3
3
  *
4
- * Wraps the GPT Image / DALL-E line at /v1/images/generations. Generated
4
+ * Wraps the GPT Image / DALL-E line: `image.create` at
5
+ * /v1/images/generations, `image.edit` at /v1/images/edits. Generated
5
6
  * images are written to disk and the action returns their file `path`s
6
7
  * (plus the optional revised prompt) — never raw base64, which bloats the
7
8
  * agent context and is stripped before delivery. Hand each `path` to the
@@ -19,6 +20,14 @@ const ENDPOINT = "https://api.openai.com/v1/images/generations";
19
20
  const EDIT_ENDPOINT = "https://api.openai.com/v1/images/edits";
20
21
  /** Newest GPT Image model; override per call or via the connection. */
21
22
  const DEFAULT_MODEL = "gpt-image-2";
23
+ /**
24
+ * Model precedence, shared by create and edit: the call wins, then the
25
+ * connection's `defaultModel` (or OPENAI_IMAGE_MODEL), then the newest
26
+ * model.
27
+ */
28
+ function resolveModel(explicit, config) {
29
+ return (explicit ?? config.defaultModel ?? DEFAULT_MODEL);
30
+ }
22
31
  export default function openai(rl) {
23
32
  rl.setName("openai");
24
33
  rl.setVersion("0.1.0");
@@ -32,7 +41,7 @@ export default function openai(rl) {
32
41
  defaultModel: {
33
42
  type: "string",
34
43
  required: false,
35
- description: "Default image model when a call omits `model` (e.g. gpt-image-1 for the older line). Falls back to gpt-image-2.",
44
+ description: "Default image model when a call omits `model` — e.g. gpt-image-1 to pin the older line. Defaults to gpt-image-2.",
36
45
  env: "OPENAI_IMAGE_MODEL",
37
46
  },
38
47
  });
@@ -82,9 +91,7 @@ export default function openai(rl) {
82
91
  throw new Error("openai: prompt is required");
83
92
  }
84
93
  const apiKey = ctx.connection.config.apiKey;
85
- const model = p.model ??
86
- ctx.connection.config.defaultModel ??
87
- DEFAULT_MODEL;
94
+ const model = resolveModel(p.model, ctx.connection.config);
88
95
  const body = {
89
96
  model,
90
97
  prompt: p.prompt,
@@ -189,9 +196,7 @@ export default function openai(rl) {
189
196
  throw new Error("openai: imagePath (or imagePaths) is required");
190
197
  }
191
198
  const apiKey = ctx.connection.config.apiKey;
192
- const model = p.model ??
193
- ctx.connection.config.defaultModel ??
194
- DEFAULT_MODEL;
199
+ const model = resolveModel(p.model, ctx.connection.config);
195
200
  const form = new FormData();
196
201
  form.append("model", model);
197
202
  form.append("prompt", p.prompt);
@@ -1,35 +1,20 @@
1
- let cachedToken = null;
2
- function getConn(ctx) {
3
- const c = ctx.connection.config;
1
+ import { coordinatedAccessToken, requestToken, } from "../../_shared/tokenRefresh.js";
2
+ function getConn(c) {
4
3
  const env = c.env ?? "sandbox";
5
4
  const base = env === "live"
6
5
  ? "https://api-m.paypal.com"
7
6
  : "https://api-m.sandbox.paypal.com";
8
7
  return { base, clientId: c.clientId, secret: c.secret };
9
8
  }
10
- async function getAccessToken(conn) {
11
- if (cachedToken && Date.now() < cachedToken.expiresAt)
12
- return cachedToken.token;
13
- const res = await fetch(`${conn.base}/v1/oauth2/token`, {
14
- method: "POST",
15
- headers: {
16
- Authorization: "Basic " + btoa(`${conn.clientId}:${conn.secret}`),
17
- "Content-Type": "application/x-www-form-urlencoded",
18
- },
19
- body: "grant_type=client_credentials",
9
+ async function apiRequest(ctx, method, endpoint, body, qs) {
10
+ const token = await coordinatedAccessToken(ctx, (current) => {
11
+ const conn = getConn(current);
12
+ return requestToken({
13
+ url: `${conn.base}/v1/oauth2/token`,
14
+ clientAuthentication: "client_secret_basic",
15
+ }, { grant_type: "client_credentials" }, { clientId: conn.clientId, clientSecret: conn.secret });
20
16
  });
21
- if (!res.ok)
22
- throw new Error(`PayPal auth error ${res.status}: ${await res.text()}`);
23
- const data = (await res.json());
24
- cachedToken = {
25
- token: data.access_token,
26
- expiresAt: Date.now() + (data.expires_in - 60) * 1000,
27
- };
28
- return cachedToken.token;
29
- }
30
- async function apiRequest(conn, method, endpoint, body, qs) {
31
- const token = await getAccessToken(conn);
32
- const url = new URL(`${conn.base}/v1${endpoint}`);
17
+ const url = new URL(`${getConn(ctx.connection.config).base}/v1${endpoint}`);
33
18
  if (qs) {
34
19
  for (const [k, v] of Object.entries(qs)) {
35
20
  if (v !== undefined && v !== null)
@@ -103,7 +88,7 @@ export default function paypal(rl) {
103
88
  header.email_message = p.emailMessage;
104
89
  if (p.note)
105
90
  header.note = p.note;
106
- return apiRequest(getConn(ctx), "POST", "/payments/payouts", {
91
+ return apiRequest(ctx, "POST", "/payments/payouts", {
107
92
  sender_batch_header: header,
108
93
  items: p.items,
109
94
  });
@@ -125,7 +110,7 @@ export default function paypal(rl) {
125
110
  const qs = {};
126
111
  if (p.limit)
127
112
  qs.page_size = p.limit;
128
- const data = (await apiRequest(getConn(ctx), "GET", `/payments/payouts/${p.payoutBatchId}`, undefined, qs));
113
+ const data = (await apiRequest(ctx, "GET", `/payments/payouts/${p.payoutBatchId}`, undefined, qs));
129
114
  return data.items;
130
115
  },
131
116
  });
@@ -135,7 +120,7 @@ export default function paypal(rl) {
135
120
  inputSchema: { payoutItemId: { type: "string", required: true } },
136
121
  async execute(input, ctx) {
137
122
  const { payoutItemId } = input;
138
- return apiRequest(getConn(ctx), "GET", `/payments/payouts-item/${payoutItemId}`);
123
+ return apiRequest(ctx, "GET", `/payments/payouts-item/${payoutItemId}`);
139
124
  },
140
125
  });
141
126
  rl.registerAction("payoutItem.cancel", {
@@ -144,7 +129,7 @@ export default function paypal(rl) {
144
129
  inputSchema: { payoutItemId: { type: "string", required: true } },
145
130
  async execute(input, ctx) {
146
131
  const { payoutItemId } = input;
147
- return apiRequest(getConn(ctx), "POST", `/payments/payouts-item/${payoutItemId}/cancel`);
132
+ return apiRequest(ctx, "POST", `/payments/payouts-item/${payoutItemId}/cancel`);
148
133
  },
149
134
  });
150
135
  }
@@ -0,0 +1,210 @@
1
+ import * as t from "typebox";
2
+ import { blockContent, blocks, dateBoundary, list, PLAUD_OAUTH, PLAUD_PUBLIC_CLIENT_ID, PLAUD_REDIRECT_URI, recording, request, scan, } from "./shared.js";
3
+ const strict = { additionalProperties: false };
4
+ const Id = t.String({ minLength: 1, maxLength: 512 });
5
+ const RecordingInput = t.Object({ id: Id }, strict);
6
+ const ScanFields = {
7
+ limit: t.Optional(t.Integer({
8
+ minimum: 1,
9
+ maximum: 1000,
10
+ description: "Maximum matching recordings (default 50)",
11
+ })),
12
+ maxPages: t.Optional(t.Integer({
13
+ minimum: 1,
14
+ maximum: 10,
15
+ description: "Bounded scan of 100 recordings per page (default 5)",
16
+ })),
17
+ };
18
+ const Day = t.String({
19
+ pattern: "^\\d{4}-\\d{2}-\\d{2}$",
20
+ description: "UTC date, YYYY-MM-DD",
21
+ });
22
+ const ListInput = t.Object({
23
+ page: t.Optional(t.Integer({ minimum: 1, maximum: 1000 })),
24
+ pageSize: t.Optional(t.Integer({ minimum: 10, maximum: 100 })),
25
+ }, strict);
26
+ const SearchInput = t.Object({
27
+ query: t.String({ minLength: 1, pattern: "\\S" }),
28
+ from: t.Optional(Day),
29
+ to: t.Optional(Day),
30
+ ...ScanFields,
31
+ }, strict);
32
+ const RecentInput = t.Object({ days: t.Optional(t.Integer({ minimum: 1, maximum: 365 })), ...ScanFields }, strict);
33
+ const TranscriptInput = t.Object({
34
+ id: Id,
35
+ block: t.Optional(t.Union([
36
+ t.Literal("transaction"),
37
+ t.Literal("transaction_polish"),
38
+ t.Literal("outline"),
39
+ t.Literal("mark_memo"),
40
+ ])),
41
+ }, strict);
42
+ const SummaryInput = t.Object({ id: Id, index: t.Optional(t.Integer({ minimum: 0, maximum: 999 })) }, strict);
43
+ export default function plaud(rl) {
44
+ rl.setName("plaud");
45
+ rl.setVersion("0.1.0");
46
+ rl.setOAuth({
47
+ protocol: PLAUD_OAUTH,
48
+ scopes: [],
49
+ redirectUri: PLAUD_REDIRECT_URI,
50
+ publicClient: true,
51
+ defaultClientId: PLAUD_PUBLIC_CLIENT_ID,
52
+ setupHelp: [
53
+ "Plaud publishes a public OAuth client (PKCE, no secret) with the fixed callback {{redirectUri}}; the same flow as the official CLI.",
54
+ "Set PLAUD_CLIENT_ID to use a different client registered for that callback.",
55
+ "Plaud Embedded partner credentials are for a different API and are not interchangeable.",
56
+ ],
57
+ });
58
+ rl.setConnectionSchema({
59
+ clientId: {
60
+ type: "string",
61
+ required: false,
62
+ env: "PLAUD_CLIENT_ID",
63
+ description: "Public third-party OAuth client ID used at login; defaults to Plaud's published client",
64
+ },
65
+ refreshToken: {
66
+ type: "string",
67
+ required: false,
68
+ env: "PLAUD_REFRESH_TOKEN",
69
+ description: "Third-party OAuth refresh token",
70
+ },
71
+ accessToken: {
72
+ type: "string",
73
+ required: false,
74
+ env: "PLAUD_ACCESS_TOKEN",
75
+ description: "Cached third-party OAuth access token",
76
+ },
77
+ accessTokenExpiresAt: {
78
+ type: "number",
79
+ required: false,
80
+ description: "Access-token expiry in epoch milliseconds; omit if unknown",
81
+ },
82
+ contentOrigins: {
83
+ type: "array",
84
+ required: false,
85
+ description: "Host-approved exact HTTPS origins for signed transcript/summary data_link downloads. Empty by default; never accepts action-supplied origins.",
86
+ },
87
+ });
88
+ rl.registerAction("user.get", {
89
+ access: "read",
90
+ description: "Get the authenticated Plaud user.",
91
+ inputSchema: t.Object({}, strict),
92
+ execute: (_input, ctx) => request(ctx, "users/current"),
93
+ });
94
+ rl.registerAction("recording.list", {
95
+ access: "read",
96
+ description: "List one page of recordings; preserves Plaud's data and pagination fields. Durations are milliseconds.",
97
+ inputSchema: ListInput,
98
+ execute: (input, ctx) => {
99
+ const p = input;
100
+ return list(ctx, p.page ?? 1, p.pageSize ?? 20);
101
+ },
102
+ });
103
+ rl.registerAction("recording.get", {
104
+ access: "read",
105
+ description: "Get recording metadata, source_list, note_list and any signed audio URL. Returned content is private application data.",
106
+ inputSchema: RecordingInput,
107
+ execute: (input, ctx) => recording(ctx, input.id),
108
+ });
109
+ rl.registerAction("recording.search", {
110
+ access: "read",
111
+ description: "Case-insensitive name search over a bounded number of pages, not transcript/full-text search. Returns scan counts and truncated; dates are inclusive UTC.",
112
+ inputSchema: SearchInput,
113
+ execute: (input, ctx) => {
114
+ const p = input;
115
+ return scan(ctx, {
116
+ query: p.query,
117
+ from: dateBoundary(p.from, false),
118
+ to: dateBoundary(p.to, true),
119
+ limit: p.limit ?? 50,
120
+ maxPages: p.maxPages ?? 5,
121
+ });
122
+ },
123
+ });
124
+ rl.registerAction("recording.recent", {
125
+ access: "read",
126
+ description: "Recordings created within the last N days (default 7). Scans bounded pages; truncated means the result may be incomplete.",
127
+ inputSchema: RecentInput,
128
+ execute: (input, ctx) => {
129
+ const p = input;
130
+ const now = Date.now();
131
+ return scan(ctx, {
132
+ from: now - (p.days ?? 7) * 86_400_000,
133
+ to: now,
134
+ limit: p.limit ?? 50,
135
+ maxPages: p.maxPages ?? 5,
136
+ });
137
+ },
138
+ });
139
+ rl.registerAction("recording.audioUrl", {
140
+ access: "read",
141
+ description: "Return Plaud's temporary signed audio URL without downloading audio. Treat the URL as a secret; missing URLs may be transient.",
142
+ inputSchema: RecordingInput,
143
+ async execute(input, ctx) {
144
+ const { id } = input;
145
+ const file = await recording(ctx, id);
146
+ return {
147
+ recordingId: id,
148
+ url: typeof file.presigned_url === "string" && file.presigned_url
149
+ ? file.presigned_url
150
+ : null,
151
+ };
152
+ },
153
+ });
154
+ rl.registerAction("transcript.get", {
155
+ access: "read",
156
+ description: "Get an original/polished transcript, outline, or highlights. Uses inline content first; signed links require host-approved contentOrigins. JSON content is decoded; segment times remain milliseconds.",
157
+ inputSchema: TranscriptInput,
158
+ async execute(input, ctx) {
159
+ const { id, block = "transaction" } = input;
160
+ const file = await recording(ctx, id);
161
+ const sources = blocks(file, "source_list");
162
+ const content = await blockContent(ctx, sources.find((source) => source.data_type === block));
163
+ let parsed = content.content;
164
+ if (content.content) {
165
+ try {
166
+ parsed = JSON.parse(content.content);
167
+ }
168
+ catch {
169
+ /* Plain-text transcripts are valid. */
170
+ }
171
+ }
172
+ return {
173
+ recordingId: id,
174
+ block,
175
+ ...content,
176
+ content: parsed,
177
+ availableBlocks: sources
178
+ .map((source) => source.data_type)
179
+ .filter((type) => typeof type === "string"),
180
+ };
181
+ },
182
+ });
183
+ rl.registerAction("summary.get", {
184
+ access: "read",
185
+ description: "Get an auto_sum_note summary (Markdown). index selects among multiple summaries, starting at 0. Linked content requires host-approved contentOrigins.",
186
+ inputSchema: SummaryInput,
187
+ async execute(input, ctx) {
188
+ const { id, index = 0 } = input;
189
+ const notes = blocks(await recording(ctx, id), "note_list").filter((note) => note.data_type === "auto_sum_note");
190
+ return {
191
+ recordingId: id,
192
+ index,
193
+ count: notes.length,
194
+ ...(await blockContent(ctx, notes[index])),
195
+ };
196
+ },
197
+ });
198
+ rl.registerAction("note.list", {
199
+ access: "read",
200
+ description: "List every note block verbatim, including data_type, titles, inline data_content, and signed data_link. Does not download links.",
201
+ inputSchema: RecordingInput,
202
+ async execute(input, ctx) {
203
+ const { id } = input;
204
+ return {
205
+ recordingId: id,
206
+ notes: blocks(await recording(ctx, id), "note_list"),
207
+ };
208
+ },
209
+ });
210
+ }