runline 0.7.2 → 0.7.5
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/commands/exec.js +3 -1
- package/dist/core/engine.js +0 -2
- package/dist/main.js +1 -1
- package/dist/plugin/loader.d.ts +1 -1
- package/dist/plugin/loader.js +22 -7
- package/dist/plugins/gmail/src/index.js +69 -13
- package/dist/plugins/node/src/index.js +544 -0
- package/dist/sdk.js +0 -4
- package/package.json +2 -1
- package/dist/plugin/node-plugin.d.ts +0 -4
- package/dist/plugin/node-plugin.js +0 -546
package/dist/commands/exec.js
CHANGED
|
@@ -5,8 +5,10 @@ import { loadAllPlugins } from "../plugin/loader.js";
|
|
|
5
5
|
import { registry } from "../plugin/registry.js";
|
|
6
6
|
import { printError, printJson } from "../utils/output.js";
|
|
7
7
|
export async function exec(code, options) {
|
|
8
|
-
await loadAllPlugins();
|
|
9
8
|
const config = loadConfig();
|
|
9
|
+
await loadAllPlugins({
|
|
10
|
+
builtinAllowlist: new Set(config.connections.map((c) => c.plugin)),
|
|
11
|
+
});
|
|
10
12
|
const engine = new ExecutionEngine(registry, config);
|
|
11
13
|
if (options.file) {
|
|
12
14
|
if (!existsSync(code)) {
|
package/dist/core/engine.js
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import { getQuickJS, shouldInterruptAfterDeadline, } from "quickjs-emscripten";
|
|
3
3
|
import { applyEnvOverrides, updateConnectionConfig } from "../config/loader.js";
|
|
4
|
-
import { registerNodePlugin } from "../plugin/node-plugin.js";
|
|
5
4
|
export class ExecutionEngine {
|
|
6
5
|
registry;
|
|
7
6
|
config;
|
|
8
7
|
constructor(registry, config) {
|
|
9
8
|
this.registry = registry;
|
|
10
9
|
this.config = config;
|
|
11
|
-
registerNodePlugin(this.registry);
|
|
12
10
|
}
|
|
13
11
|
async execute(code, options) {
|
|
14
12
|
const timeoutMs = options?.timeoutMs ?? this.config.timeoutMs;
|
package/dist/main.js
CHANGED
|
@@ -39,7 +39,7 @@ program
|
|
|
39
39
|
.addHelpText("after", `
|
|
40
40
|
The code runs in a QuickJS runtime with an \`actions\` proxy.
|
|
41
41
|
Each installed plugin is a top-level global. Dot-chain into resource and action.
|
|
42
|
-
|
|
42
|
+
Configure the built-in \`node\` plugin to expose host-backed fs/path/os/process/crypto/fetch actions.
|
|
43
43
|
|
|
44
44
|
Examples:
|
|
45
45
|
$ runline exec 'return await docker.containers.list()'
|
package/dist/plugin/loader.d.ts
CHANGED
|
@@ -32,4 +32,4 @@ export declare function discoverPlugins(configDir?: string | null, options?: Dis
|
|
|
32
32
|
* Load all plugins and register them into the global registry.
|
|
33
33
|
* Used by the CLI.
|
|
34
34
|
*/
|
|
35
|
-
export declare function loadAllPlugins(): Promise<void>;
|
|
35
|
+
export declare function loadAllPlugins(options?: DiscoverOptions): Promise<void>;
|
package/dist/plugin/loader.js
CHANGED
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { dirname, join, resolve } from "node:path";
|
|
4
|
-
import { fileURLToPath
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { createJiti } from "jiti";
|
|
5
6
|
import { findConfigDir } from "../config/loader.js";
|
|
6
7
|
import { resolvePluginExport } from "./api.js";
|
|
7
|
-
import { registerNodePlugin } from "./node-plugin.js";
|
|
8
8
|
import { registry } from "./registry.js";
|
|
9
9
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
const jiti = createJiti(import.meta.url, {
|
|
11
|
+
moduleCache: false,
|
|
12
|
+
interopDefault: true,
|
|
13
|
+
tryNative: false,
|
|
14
|
+
alias: {
|
|
15
|
+
runline: new URL("../index.js", import.meta.url).pathname,
|
|
16
|
+
"runline/utils/cli": new URL("../utils/cli.js", import.meta.url).pathname,
|
|
17
|
+
},
|
|
18
|
+
});
|
|
10
19
|
export async function loadPluginFromPath(path) {
|
|
11
20
|
let absPath = resolve(path);
|
|
12
21
|
if (existsSync(absPath) && statSync(absPath).isDirectory()) {
|
|
@@ -35,9 +44,16 @@ export async function loadPluginFromPath(path) {
|
|
|
35
44
|
throw new Error(`No entry point found in ${absPath}`);
|
|
36
45
|
}
|
|
37
46
|
}
|
|
38
|
-
|
|
47
|
+
// Plugins may live outside the project/package tree. Load them
|
|
48
|
+
// through Runline's module context so documented imports like
|
|
49
|
+
// `from "runline"` work without requiring a node_modules folder next
|
|
50
|
+
// to the plugin file.
|
|
51
|
+
const mod = (await jiti.import(absPath));
|
|
39
52
|
const pluginId = absPath.replace(/.*\//, "").replace(/\.(ts|js)$/, "");
|
|
40
|
-
|
|
53
|
+
const pluginExport = typeof mod === "object" && mod !== null && "default" in mod
|
|
54
|
+
? mod.default
|
|
55
|
+
: mod;
|
|
56
|
+
return resolvePluginExport(pluginExport, pluginId);
|
|
41
57
|
}
|
|
42
58
|
async function loadFromDirectory(dir) {
|
|
43
59
|
const plugins = [];
|
|
@@ -163,11 +179,10 @@ export async function discoverPlugins(configDir, options = {}) {
|
|
|
163
179
|
* Load all plugins and register them into the global registry.
|
|
164
180
|
* Used by the CLI.
|
|
165
181
|
*/
|
|
166
|
-
export async function loadAllPlugins() {
|
|
182
|
+
export async function loadAllPlugins(options = {}) {
|
|
167
183
|
const configDir = findConfigDir();
|
|
168
|
-
const plugins = await discoverPlugins(configDir);
|
|
184
|
+
const plugins = await discoverPlugins(configDir, options);
|
|
169
185
|
for (const p of plugins) {
|
|
170
186
|
registry.register(p);
|
|
171
187
|
}
|
|
172
|
-
registerNodePlugin(registry);
|
|
173
188
|
}
|
|
@@ -77,6 +77,7 @@ async function paginateAll(ctx, path, key, qs) {
|
|
|
77
77
|
}
|
|
78
78
|
// ─── MIME encoding ───────────────────────────────────────────────
|
|
79
79
|
const CRLF = "\r\n";
|
|
80
|
+
const GMAIL_MAX_RAW_BYTES = 35 * 1024 * 1024;
|
|
80
81
|
function base64url(bytes) {
|
|
81
82
|
const buf = typeof bytes === "string" ? Buffer.from(bytes, "utf-8") : Buffer.from(bytes);
|
|
82
83
|
return buf
|
|
@@ -104,21 +105,57 @@ function header(name, value) {
|
|
|
104
105
|
return "";
|
|
105
106
|
return `${name}: ${value}${CRLF}`;
|
|
106
107
|
}
|
|
108
|
+
function foldedBase64ByteLength(length) {
|
|
109
|
+
return length === 0 ? 0 : length + Math.floor((length - 1) / 76) * CRLF.length;
|
|
110
|
+
}
|
|
111
|
+
function foldBase64(encoded) {
|
|
112
|
+
let folded = "";
|
|
113
|
+
for (let i = 0; i < encoded.length; i += 76) {
|
|
114
|
+
if (i > 0)
|
|
115
|
+
folded += CRLF;
|
|
116
|
+
folded += encoded.slice(i, i + 76);
|
|
117
|
+
}
|
|
118
|
+
return folded;
|
|
119
|
+
}
|
|
120
|
+
function assertAttachmentPayloadFits(atts) {
|
|
121
|
+
const attachmentBodyBytes = atts.reduce((sum, att) => sum + foldedBase64ByteLength(att.contentBase64.length), 0);
|
|
122
|
+
if (attachmentBodyBytes > GMAIL_MAX_RAW_BYTES) {
|
|
123
|
+
throw new Error(`gmail: attachment payload is ${attachmentBodyBytes} bytes after MIME folding; Gmail API raw messages must be <= ${GMAIL_MAX_RAW_BYTES} bytes`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
107
126
|
function textPart(body, mimeType) {
|
|
108
127
|
const encoded = Buffer.from(body, "utf-8").toString("base64");
|
|
109
|
-
// Fold base64 to 76 chars per RFC 2045.
|
|
110
|
-
const folded = encoded.match(/.{1,76}/g)?.join(CRLF) ?? encoded;
|
|
111
128
|
return (`Content-Type: ${mimeType}; charset="UTF-8"${CRLF}` +
|
|
112
129
|
`Content-Transfer-Encoding: base64${CRLF}${CRLF}` +
|
|
113
|
-
`${
|
|
130
|
+
`${foldBase64(encoded)}${CRLF}`);
|
|
131
|
+
}
|
|
132
|
+
function normalizeAttachment(input, index) {
|
|
133
|
+
if (!input || typeof input !== "object") {
|
|
134
|
+
throw new Error(`gmail: attachment ${index} must be an object`);
|
|
135
|
+
}
|
|
136
|
+
const content = input.contentBase64;
|
|
137
|
+
const contentBase64 = typeof content === "string"
|
|
138
|
+
? content
|
|
139
|
+
: content &&
|
|
140
|
+
typeof content === "object" &&
|
|
141
|
+
typeof content.contentBase64 === "string"
|
|
142
|
+
? content.contentBase64
|
|
143
|
+
: undefined;
|
|
144
|
+
if (typeof contentBase64 !== "string") {
|
|
145
|
+
throw new Error(`gmail: attachment ${index} contentBase64 must be a base64 string`);
|
|
146
|
+
}
|
|
147
|
+
return {
|
|
148
|
+
name: input.name ?? input.filename ?? `attachment-${index + 1}`,
|
|
149
|
+
mimeType: input.mimeType ?? "application/octet-stream",
|
|
150
|
+
contentBase64,
|
|
151
|
+
};
|
|
114
152
|
}
|
|
115
153
|
function attachmentPart(att) {
|
|
116
154
|
const encodedName = encodeHeaderWord(att.name);
|
|
117
|
-
const folded = att.contentBase64.match(/.{1,76}/g)?.join(CRLF) ?? att.contentBase64;
|
|
118
155
|
return (`Content-Type: ${att.mimeType}; name="${encodedName}"${CRLF}` +
|
|
119
156
|
`Content-Disposition: attachment; filename="${encodedName}"${CRLF}` +
|
|
120
157
|
`Content-Transfer-Encoding: base64${CRLF}${CRLF}` +
|
|
121
|
-
`${
|
|
158
|
+
`${foldBase64(att.contentBase64)}${CRLF}`);
|
|
122
159
|
}
|
|
123
160
|
/**
|
|
124
161
|
* Build a MIME message and return its base64url-encoded form,
|
|
@@ -143,7 +180,8 @@ export function encodeEmail(email) {
|
|
|
143
180
|
headers.push(header("MIME-Version", "1.0"));
|
|
144
181
|
const text = email.text ?? "";
|
|
145
182
|
const html = email.html ?? "";
|
|
146
|
-
const atts = email.attachments ?? [];
|
|
183
|
+
const atts = (email.attachments ?? []).map((att, index) => normalizeAttachment(att, index));
|
|
184
|
+
assertAttachmentPayloadFits(atts);
|
|
147
185
|
const hasAtt = atts.length > 0;
|
|
148
186
|
const hasBoth = text && html;
|
|
149
187
|
let bodyBlock;
|
|
@@ -189,6 +227,10 @@ export function encodeEmail(email) {
|
|
|
189
227
|
`--${mixedBoundary}--${CRLF}`;
|
|
190
228
|
}
|
|
191
229
|
const raw = `${headers.join("")}${rootType}${bodyBlock}`;
|
|
230
|
+
const rawBytes = Buffer.byteLength(raw, "utf-8");
|
|
231
|
+
if (rawBytes > GMAIL_MAX_RAW_BYTES) {
|
|
232
|
+
throw new Error(`gmail: encoded message is ${rawBytes} bytes; Gmail API raw messages must be <= ${GMAIL_MAX_RAW_BYTES} bytes`);
|
|
233
|
+
}
|
|
192
234
|
return base64url(raw);
|
|
193
235
|
}
|
|
194
236
|
// ─── Email address helpers ───────────────────────────────────────
|
|
@@ -351,7 +393,11 @@ function simplifyMessage(raw, labels) {
|
|
|
351
393
|
}
|
|
352
394
|
}
|
|
353
395
|
// Body + attachments only meaningful when format=full was used.
|
|
354
|
-
const acc = {
|
|
396
|
+
const acc = {
|
|
397
|
+
text: [],
|
|
398
|
+
html: [],
|
|
399
|
+
attachments: [],
|
|
400
|
+
};
|
|
355
401
|
walkPayload(payload, acc);
|
|
356
402
|
if (acc.text.length > 0)
|
|
357
403
|
out.text = acc.text.join("\n");
|
|
@@ -412,7 +458,9 @@ async function replyToMessage(ctx, messageId, p) {
|
|
|
412
458
|
to,
|
|
413
459
|
cc: normalizeAddressList(p.cc),
|
|
414
460
|
bcc: normalizeAddressList(p.bcc),
|
|
415
|
-
subject: subject.toLowerCase().startsWith("re:")
|
|
461
|
+
subject: subject.toLowerCase().startsWith("re:")
|
|
462
|
+
? subject
|
|
463
|
+
: `Re: ${subject}`,
|
|
416
464
|
text: p.text,
|
|
417
465
|
html: p.html,
|
|
418
466
|
inReplyTo: messageIdHeader,
|
|
@@ -756,7 +804,11 @@ export default function gmail(rl) {
|
|
|
756
804
|
description: "Get a thread by ID",
|
|
757
805
|
inputSchema: {
|
|
758
806
|
id: { type: "string", required: true },
|
|
759
|
-
format: {
|
|
807
|
+
format: {
|
|
808
|
+
type: "string",
|
|
809
|
+
required: false,
|
|
810
|
+
description: "minimal | full | metadata",
|
|
811
|
+
},
|
|
760
812
|
metadataHeaders: { type: "array", required: false },
|
|
761
813
|
simple: {
|
|
762
814
|
type: "boolean",
|
|
@@ -996,10 +1048,12 @@ export default function gmail(rl) {
|
|
|
996
1048
|
async execute(input, ctx) {
|
|
997
1049
|
const p = (input ?? {});
|
|
998
1050
|
const body = { name: p.name };
|
|
999
|
-
if (p.labelListVisibility)
|
|
1051
|
+
if (p.labelListVisibility) {
|
|
1000
1052
|
body.labelListVisibility = p.labelListVisibility;
|
|
1001
|
-
|
|
1053
|
+
}
|
|
1054
|
+
if (p.messageListVisibility) {
|
|
1002
1055
|
body.messageListVisibility = p.messageListVisibility;
|
|
1056
|
+
}
|
|
1003
1057
|
return gmailRequest(ctx, "POST", "/labels", body);
|
|
1004
1058
|
},
|
|
1005
1059
|
});
|
|
@@ -1039,10 +1093,12 @@ export default function gmail(rl) {
|
|
|
1039
1093
|
const body = {};
|
|
1040
1094
|
if (p.name)
|
|
1041
1095
|
body.name = p.name;
|
|
1042
|
-
if (p.labelListVisibility)
|
|
1096
|
+
if (p.labelListVisibility) {
|
|
1043
1097
|
body.labelListVisibility = p.labelListVisibility;
|
|
1044
|
-
|
|
1098
|
+
}
|
|
1099
|
+
if (p.messageListVisibility) {
|
|
1045
1100
|
body.messageListVisibility = p.messageListVisibility;
|
|
1101
|
+
}
|
|
1046
1102
|
return gmailRequest(ctx, "PATCH", `/labels/${p.id}`, body);
|
|
1047
1103
|
},
|
|
1048
1104
|
});
|