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.
- package/dist/auth/errors.d.ts +22 -0
- package/dist/auth/errors.js +25 -0
- package/dist/auth/oauth2.d.ts +21 -0
- package/dist/auth/oauth2.js +154 -0
- package/dist/auth/token.d.ts +11 -0
- package/dist/auth/token.js +231 -0
- package/dist/auth/types.d.ts +93 -0
- package/dist/auth/types.js +1 -0
- package/dist/commands/auth.js +26 -13
- package/dist/commands/connection.js +3 -6
- package/dist/commands/exec.js +7 -2
- package/dist/commands/init.js +9 -2
- package/dist/config/loader.d.ts +7 -13
- package/dist/config/loader.js +58 -88
- package/dist/config/store.d.ts +17 -0
- package/dist/config/store.js +85 -0
- package/dist/connections/file.d.ts +13 -0
- package/dist/connections/file.js +53 -0
- package/dist/connections/memory.d.ts +15 -0
- package/dist/connections/memory.js +70 -0
- package/dist/connections/types.d.ts +47 -0
- package/dist/connections/types.js +10 -0
- package/dist/core/engine.d.ts +5 -2
- package/dist/core/engine.js +48 -55
- package/dist/core/oauth.d.ts +20 -15
- package/dist/core/oauth.js +195 -98
- package/dist/credentials/http.d.ts +19 -0
- package/dist/credentials/http.js +109 -0
- package/dist/credentials/policy.d.ts +5 -0
- package/dist/credentials/policy.js +92 -0
- package/dist/credentials/registry.d.ts +31 -0
- package/dist/credentials/registry.js +171 -0
- package/dist/credentials/transport.d.ts +38 -0
- package/dist/credentials/transport.js +283 -0
- package/dist/credentials/types.d.ts +80 -0
- package/dist/credentials/types.js +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +8 -0
- package/dist/plugin/types.d.ts +30 -17
- package/dist/plugins/_shared/credentialAdapter.js +88 -0
- package/dist/plugins/_shared/googleAuth.js +89 -103
- package/dist/plugins/_shared/googleCredentials.js +160 -0
- package/dist/plugins/_shared/microsoftAuth.js +81 -74
- package/dist/plugins/_shared/microsoftCredentials.js +90 -0
- package/dist/plugins/_shared/tokenRefresh.js +39 -0
- package/dist/plugins/bitwarden/src/index.js +13 -31
- package/dist/plugins/gmail/src/index.js +8 -44
- package/dist/plugins/googleAppsScript/src/index.js +3 -25
- package/dist/plugins/googleCalendar/src/index.js +3 -41
- package/dist/plugins/googleContacts/src/index.js +3 -36
- package/dist/plugins/googleDocs/src/index.js +5 -0
- package/dist/plugins/googleDocs/src/shared.js +2 -30
- package/dist/plugins/googleDrive/src/index.js +52 -98
- package/dist/plugins/googleSheets/src/index.js +3 -40
- package/dist/plugins/googleSlides/src/index.js +5 -39
- package/dist/plugins/googleTasks/src/index.js +3 -30
- package/dist/plugins/halopsa/src/index.js +16 -31
- package/dist/plugins/linear/src/comments.js +2 -2
- package/dist/plugins/linear/src/index.js +1 -1
- package/dist/plugins/linear/src/issues.js +1 -17
- package/dist/plugins/microsoftCalendar/src/index.js +2 -1
- package/dist/plugins/microsoftFiles/src/index.js +10 -24
- package/dist/plugins/microsoftMail/src/index.js +2 -1
- package/dist/plugins/openai/src/index.js +13 -8
- package/dist/plugins/paypal/src/index.js +14 -29
- package/dist/plugins/plaud/src/index.js +210 -0
- package/dist/plugins/plaud/src/shared.js +221 -0
- package/dist/plugins/salesforce/src/shared.js +17 -32
- package/dist/sdk.d.ts +13 -5
- package/dist/sdk.js +35 -29
- package/dist/utils/observer.d.ts +2 -0
- package/dist/utils/observer.js +11 -0
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as t from "typebox";
|
|
2
|
-
import { assertIssueInScope, buildConnArgs,
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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`
|
|
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
|
-
|
|
2
|
-
function getConn(
|
|
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
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
+
}
|