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.
@@ -0,0 +1,544 @@
1
+ import { exec as execCb, execFile as execFileCb } from "node:child_process";
2
+ import { createHash, randomBytes, randomUUID } from "node:crypto";
3
+ import { existsSync } from "node:fs";
4
+ import { access, appendFile, copyFile, lstat, mkdir, readdir, readFile, rename, rm, stat, unlink, writeFile, } from "node:fs/promises";
5
+ import { arch, cpus, EOL, freemem, homedir, hostname, platform, release, tmpdir, totalmem, type, uptime, userInfo, } from "node:os";
6
+ import { basename, delimiter, dirname, extname, format, isAbsolute, join, normalize, parse, relative, resolve, sep, } from "node:path";
7
+ import { promisify } from "node:util";
8
+ const exec = promisify(execCb);
9
+ const execFile = promisify(execFileCb);
10
+ const pathInput = (description = "Filesystem path") => ({
11
+ path: { type: "string", required: true, description },
12
+ });
13
+ function action(name, def) {
14
+ return { name, ...def };
15
+ }
16
+ export default function node(rl) {
17
+ rl.setName("node");
18
+ rl.setVersion("0.1.0");
19
+ for (const action of nodeActions) {
20
+ rl.registerAction(action.name, action);
21
+ }
22
+ }
23
+ const nodeActions = [
24
+ // fs/promises-shaped actions
25
+ action("fs.readFile", {
26
+ description: "Read a file from the host filesystem",
27
+ inputSchema: {
28
+ path: { type: "string", required: true, description: "File path" },
29
+ encoding: {
30
+ type: "string",
31
+ required: false,
32
+ description: "Text encoding. Defaults to utf8. Use base64 for binary-safe reads.",
33
+ },
34
+ },
35
+ async execute(input) {
36
+ const { path, encoding = "utf8" } = objectInput(input);
37
+ return readFile(path, { encoding });
38
+ },
39
+ }),
40
+ action("fs.writeFile", {
41
+ description: "Write text data to a file on the host filesystem",
42
+ inputSchema: {
43
+ path: { type: "string", required: true, description: "File path" },
44
+ data: { type: "string", required: true, description: "File contents" },
45
+ encoding: {
46
+ type: "string",
47
+ required: false,
48
+ description: "Text encoding. Defaults to utf8.",
49
+ },
50
+ },
51
+ async execute(input) {
52
+ const { path, data, encoding = "utf8", } = objectInput(input);
53
+ await writeFile(path, data, { encoding });
54
+ return ok({ path });
55
+ },
56
+ }),
57
+ action("fs.appendFile", {
58
+ description: "Append text data to a file on the host filesystem",
59
+ inputSchema: {
60
+ path: { type: "string", required: true, description: "File path" },
61
+ data: {
62
+ type: "string",
63
+ required: true,
64
+ description: "File contents to append",
65
+ },
66
+ encoding: {
67
+ type: "string",
68
+ required: false,
69
+ description: "Text encoding. Defaults to utf8.",
70
+ },
71
+ },
72
+ async execute(input) {
73
+ const { path, data, encoding = "utf8", } = objectInput(input);
74
+ await appendFile(path, data, { encoding });
75
+ return ok({ path });
76
+ },
77
+ }),
78
+ action("fs.readdir", {
79
+ description: "List a directory",
80
+ inputSchema: {
81
+ ...pathInput("Directory path"),
82
+ withFileTypes: {
83
+ type: "boolean",
84
+ required: false,
85
+ description: "Return typed entries instead of names",
86
+ },
87
+ },
88
+ async execute(input) {
89
+ const { path, withFileTypes = false } = objectInput(input);
90
+ if (!withFileTypes)
91
+ return readdir(path);
92
+ const entries = await readdir(path, { withFileTypes: true });
93
+ return entries.map((entry) => ({
94
+ name: entry.name,
95
+ isFile: entry.isFile(),
96
+ isDirectory: entry.isDirectory(),
97
+ isSymbolicLink: entry.isSymbolicLink(),
98
+ }));
99
+ },
100
+ }),
101
+ action("fs.stat", {
102
+ description: "Stat a filesystem path",
103
+ inputSchema: pathInput(),
104
+ async execute(input) {
105
+ return serializeStats(await stat(objectInput(input).path));
106
+ },
107
+ }),
108
+ action("fs.lstat", {
109
+ description: "lstat a filesystem path",
110
+ inputSchema: pathInput(),
111
+ async execute(input) {
112
+ return serializeStats(await lstat(objectInput(input).path));
113
+ },
114
+ }),
115
+ action("fs.exists", {
116
+ description: "Check whether a filesystem path exists",
117
+ inputSchema: pathInput(),
118
+ execute(input) {
119
+ return existsSync(objectInput(input).path);
120
+ },
121
+ }),
122
+ action("fs.access", {
123
+ description: "Check file access",
124
+ inputSchema: pathInput(),
125
+ async execute(input) {
126
+ await access(objectInput(input).path);
127
+ return ok();
128
+ },
129
+ }),
130
+ action("fs.mkdir", {
131
+ description: "Create a directory",
132
+ inputSchema: {
133
+ ...pathInput(),
134
+ recursive: {
135
+ type: "boolean",
136
+ required: false,
137
+ description: "Create parent directories. Defaults to true.",
138
+ },
139
+ },
140
+ async execute(input) {
141
+ const { path, recursive = true } = objectInput(input);
142
+ await mkdir(path, { recursive });
143
+ return ok({ path });
144
+ },
145
+ }),
146
+ action("fs.rm", {
147
+ description: "Remove a file or directory",
148
+ inputSchema: {
149
+ ...pathInput(),
150
+ recursive: {
151
+ type: "boolean",
152
+ required: false,
153
+ description: "Remove directories recursively",
154
+ },
155
+ force: {
156
+ type: "boolean",
157
+ required: false,
158
+ description: "Ignore missing paths",
159
+ },
160
+ },
161
+ async execute(input) {
162
+ const { path, recursive = false, force = false, } = objectInput(input);
163
+ await rm(path, { recursive, force });
164
+ return ok({ path });
165
+ },
166
+ }),
167
+ action("fs.unlink", {
168
+ description: "Remove a file",
169
+ inputSchema: pathInput(),
170
+ async execute(input) {
171
+ const { path } = objectInput(input);
172
+ await unlink(path);
173
+ return ok({ path });
174
+ },
175
+ }),
176
+ action("fs.rename", {
177
+ description: "Rename a file or directory",
178
+ inputSchema: {
179
+ from: { type: "string", required: true, description: "Source path" },
180
+ to: { type: "string", required: true, description: "Destination path" },
181
+ },
182
+ async execute(input) {
183
+ const { from, to } = objectInput(input);
184
+ await rename(from, to);
185
+ return ok({ from, to });
186
+ },
187
+ }),
188
+ action("fs.copyFile", {
189
+ description: "Copy a file",
190
+ inputSchema: {
191
+ from: { type: "string", required: true, description: "Source path" },
192
+ to: { type: "string", required: true, description: "Destination path" },
193
+ },
194
+ async execute(input) {
195
+ const { from, to } = objectInput(input);
196
+ await copyFile(from, to);
197
+ return ok({ from, to });
198
+ },
199
+ }),
200
+ // path-shaped actions. Most accept either an array or { segments: string[] }.
201
+ action("path.join", {
202
+ description: "Join path segments",
203
+ inputSchema: {
204
+ segments: {
205
+ type: "array",
206
+ required: true,
207
+ description: "Path segments",
208
+ },
209
+ },
210
+ execute(input) {
211
+ return join(...stringArrayInput(input));
212
+ },
213
+ }),
214
+ action("path.resolve", {
215
+ description: "Resolve path segments",
216
+ inputSchema: {
217
+ segments: {
218
+ type: "array",
219
+ required: true,
220
+ description: "Path segments",
221
+ },
222
+ },
223
+ execute(input) {
224
+ return resolve(...stringArrayInput(input));
225
+ },
226
+ }),
227
+ action("path.normalize", {
228
+ description: "Normalize a path",
229
+ inputSchema: pathInput(),
230
+ execute(input) {
231
+ return normalize(objectInput(input).path);
232
+ },
233
+ }),
234
+ action("path.dirname", {
235
+ description: "Get a path dirname",
236
+ inputSchema: pathInput(),
237
+ execute(input) {
238
+ return dirname(objectInput(input).path);
239
+ },
240
+ }),
241
+ action("path.basename", {
242
+ description: "Get a path basename",
243
+ inputSchema: {
244
+ ...pathInput(),
245
+ suffix: {
246
+ type: "string",
247
+ required: false,
248
+ description: "Optional suffix to remove",
249
+ },
250
+ },
251
+ execute(input) {
252
+ const { path, suffix } = objectInput(input);
253
+ return basename(path, suffix);
254
+ },
255
+ }),
256
+ action("path.extname", {
257
+ description: "Get a path extension",
258
+ inputSchema: pathInput(),
259
+ execute(input) {
260
+ return extname(objectInput(input).path);
261
+ },
262
+ }),
263
+ action("path.relative", {
264
+ description: "Get a relative path",
265
+ inputSchema: {
266
+ from: { type: "string", required: true, description: "Source path" },
267
+ to: { type: "string", required: true, description: "Destination path" },
268
+ },
269
+ execute(input) {
270
+ const { from, to } = objectInput(input);
271
+ return relative(from, to);
272
+ },
273
+ }),
274
+ action("path.isAbsolute", {
275
+ description: "Check whether a path is absolute",
276
+ inputSchema: pathInput(),
277
+ execute(input) {
278
+ return isAbsolute(objectInput(input).path);
279
+ },
280
+ }),
281
+ action("path.parse", {
282
+ description: "Parse a path into components",
283
+ inputSchema: pathInput(),
284
+ execute(input) {
285
+ return parse(objectInput(input).path);
286
+ },
287
+ }),
288
+ action("path.format", {
289
+ description: "Format a path object into a path string",
290
+ inputSchema: {
291
+ pathObject: {
292
+ type: "object",
293
+ required: true,
294
+ description: "Node path object",
295
+ },
296
+ },
297
+ execute(input) {
298
+ return format(objectInput(input)
299
+ .pathObject);
300
+ },
301
+ }),
302
+ action("path.constants", {
303
+ description: "Get path separator constants",
304
+ execute() {
305
+ return { sep, delimiter };
306
+ },
307
+ }),
308
+ // os/process/shell actions
309
+ action("os.info", {
310
+ description: "Get useful host OS information",
311
+ execute() {
312
+ return {
313
+ platform: platform(),
314
+ arch: arch(),
315
+ type: type(),
316
+ release: release(),
317
+ hostname: hostname(),
318
+ homedir: homedir(),
319
+ tmpdir: tmpdir(),
320
+ uptime: uptime(),
321
+ totalmem: totalmem(),
322
+ freemem: freemem(),
323
+ eol: EOL,
324
+ cpus: cpus().map((cpu) => ({ model: cpu.model, speed: cpu.speed })),
325
+ };
326
+ },
327
+ }),
328
+ action("os.platform", {
329
+ description: "Get OS platform",
330
+ execute: () => platform(),
331
+ }),
332
+ action("os.arch", {
333
+ description: "Get OS architecture",
334
+ execute: () => arch(),
335
+ }),
336
+ action("os.homedir", {
337
+ description: "Get home directory",
338
+ execute: () => homedir(),
339
+ }),
340
+ action("os.tmpdir", {
341
+ description: "Get temp directory",
342
+ execute: () => tmpdir(),
343
+ }),
344
+ action("os.userInfo", {
345
+ description: "Get current user information",
346
+ execute() {
347
+ const info = userInfo();
348
+ return {
349
+ username: info.username,
350
+ uid: info.uid,
351
+ gid: info.gid,
352
+ shell: info.shell,
353
+ homedir: info.homedir,
354
+ };
355
+ },
356
+ }),
357
+ action("process.cwd", {
358
+ description: "Get the current working directory",
359
+ execute: () => process.cwd(),
360
+ }),
361
+ action("process.env", {
362
+ description: "Read environment variables from the host process",
363
+ inputSchema: {
364
+ name: {
365
+ type: "string",
366
+ required: false,
367
+ description: "Optional variable name. Omit to return all env vars.",
368
+ },
369
+ },
370
+ execute(input) {
371
+ const name = input?.name;
372
+ return name ? process.env[name] : { ...process.env };
373
+ },
374
+ }),
375
+ action("process.exec", {
376
+ description: "Run a shell command on the host",
377
+ inputSchema: {
378
+ command: {
379
+ type: "string",
380
+ required: true,
381
+ description: "Shell command",
382
+ },
383
+ cwd: {
384
+ type: "string",
385
+ required: false,
386
+ description: "Working directory",
387
+ },
388
+ timeout: {
389
+ type: "number",
390
+ required: false,
391
+ description: "Timeout in milliseconds",
392
+ },
393
+ },
394
+ async execute(input) {
395
+ const { command, cwd, timeout } = objectInput(input);
396
+ const { stdout, stderr } = await exec(command, {
397
+ cwd,
398
+ timeout,
399
+ maxBuffer: 10 * 1024 * 1024,
400
+ });
401
+ return { stdout, stderr };
402
+ },
403
+ }),
404
+ action("process.execFile", {
405
+ description: "Run a host executable without a shell",
406
+ inputSchema: {
407
+ file: {
408
+ type: "string",
409
+ required: true,
410
+ description: "Executable path or name",
411
+ },
412
+ args: { type: "array", required: false, description: "Arguments" },
413
+ cwd: {
414
+ type: "string",
415
+ required: false,
416
+ description: "Working directory",
417
+ },
418
+ timeout: {
419
+ type: "number",
420
+ required: false,
421
+ description: "Timeout in milliseconds",
422
+ },
423
+ },
424
+ async execute(input) {
425
+ const { file, args = [], cwd, timeout, } = objectInput(input);
426
+ const { stdout, stderr } = await execFile(file, args, {
427
+ cwd,
428
+ timeout,
429
+ maxBuffer: 10 * 1024 * 1024,
430
+ });
431
+ return { stdout, stderr };
432
+ },
433
+ }),
434
+ action("crypto.randomUUID", {
435
+ description: "Generate a random UUID using the host crypto runtime",
436
+ execute() {
437
+ return randomUUID();
438
+ },
439
+ }),
440
+ action("crypto.randomBytes", {
441
+ description: "Generate cryptographically strong random bytes as hex or base64",
442
+ inputSchema: {
443
+ size: {
444
+ type: "number",
445
+ required: true,
446
+ description: "Number of bytes",
447
+ },
448
+ encoding: {
449
+ type: "string",
450
+ required: false,
451
+ description: "hex or base64. Defaults to hex.",
452
+ },
453
+ },
454
+ execute(input) {
455
+ const { size, encoding = "hex" } = objectInput(input);
456
+ return randomBytes(size).toString(encoding);
457
+ },
458
+ }),
459
+ action("crypto.hash", {
460
+ description: "Hash text data with a Node crypto digest algorithm",
461
+ inputSchema: {
462
+ algorithm: {
463
+ type: "string",
464
+ required: false,
465
+ description: "Digest algorithm. Defaults to sha256.",
466
+ },
467
+ data: { type: "string", required: true, description: "Data to hash" },
468
+ encoding: {
469
+ type: "string",
470
+ required: false,
471
+ description: "Digest encoding. Defaults to hex.",
472
+ },
473
+ },
474
+ execute(input) {
475
+ const { algorithm = "sha256", data, encoding = "hex", } = objectInput(input);
476
+ return createHash(algorithm).update(data).digest(encoding);
477
+ },
478
+ }),
479
+ action("fetch", {
480
+ description: "Perform an HTTP fetch from the host runtime",
481
+ inputSchema: {
482
+ url: { type: "string", required: true, description: "Request URL" },
483
+ method: { type: "string", required: false, description: "HTTP method" },
484
+ headers: {
485
+ type: "object",
486
+ required: false,
487
+ description: "Request headers",
488
+ },
489
+ body: { type: "string", required: false, description: "Request body" },
490
+ },
491
+ async execute(input) {
492
+ const { url, method, headers, body } = objectInput(input);
493
+ const res = await fetch(url, { method, headers, body });
494
+ const contentType = res.headers.get("content-type") ?? "";
495
+ const text = await res.text();
496
+ return {
497
+ ok: res.ok,
498
+ status: res.status,
499
+ statusText: res.statusText,
500
+ headers: Object.fromEntries(res.headers.entries()),
501
+ body: contentType.includes("application/json") ? safeJson(text) : text,
502
+ };
503
+ },
504
+ }),
505
+ ];
506
+ function objectInput(input) {
507
+ return (input && typeof input === "object" ? input : {});
508
+ }
509
+ function stringArrayInput(input) {
510
+ if (Array.isArray(input))
511
+ return input.map(String);
512
+ const { segments } = objectInput(input);
513
+ return Array.isArray(segments) ? segments.map(String) : [];
514
+ }
515
+ function ok(extra = {}) {
516
+ return { ok: true, ...extra };
517
+ }
518
+ function safeJson(text) {
519
+ try {
520
+ return JSON.parse(text);
521
+ }
522
+ catch {
523
+ return text;
524
+ }
525
+ }
526
+ function serializeStats(s) {
527
+ return {
528
+ size: s.size,
529
+ mode: s.mode,
530
+ uid: s.uid,
531
+ gid: s.gid,
532
+ atimeMs: s.atimeMs,
533
+ mtimeMs: s.mtimeMs,
534
+ ctimeMs: s.ctimeMs,
535
+ birthtimeMs: s.birthtimeMs,
536
+ isFile: s.isFile(),
537
+ isDirectory: s.isDirectory(),
538
+ isSymbolicLink: s.isSymbolicLink(),
539
+ isBlockDevice: s.isBlockDevice(),
540
+ isCharacterDevice: s.isCharacterDevice(),
541
+ isFIFO: s.isFIFO(),
542
+ isSocket: s.isSocket(),
543
+ };
544
+ }
package/dist/sdk.js CHANGED
@@ -4,7 +4,6 @@ import { DEFAULT_CONFIG } from "./config/types.js";
4
4
  import { ExecutionEngine } from "./core/engine.js";
5
5
  import { resolvePluginExport } from "./plugin/api.js";
6
6
  import { discoverPlugins } from "./plugin/loader.js";
7
- import { registerNodePlugin } from "./plugin/node-plugin.js";
8
7
  import { PluginRegistry } from "./plugin/registry.js";
9
8
  export class Runline {
10
9
  _registry;
@@ -15,7 +14,6 @@ export class Runline {
15
14
  const plugin = resolvePluginExport(pluginOrFn, "unknown");
16
15
  this._registry.register(plugin);
17
16
  }
18
- registerNodePlugin(this._registry);
19
17
  this._config = {
20
18
  connections: options.connections ?? [],
21
19
  timeoutMs: options.timeoutMs ?? DEFAULT_CONFIG.timeoutMs,
@@ -34,7 +32,6 @@ export class Runline {
34
32
  addPlugin(pluginOrFn, connections) {
35
33
  const plugin = resolvePluginExport(pluginOrFn, "unknown");
36
34
  this._registry.register(plugin);
37
- registerNodePlugin(this._registry);
38
35
  if (connections) {
39
36
  this._config = {
40
37
  ...this._config,
@@ -104,7 +101,6 @@ export class Runline {
104
101
  for (const plugin of plugins) {
105
102
  rl._registry.register(plugin);
106
103
  }
107
- registerNodePlugin(rl._registry);
108
104
  return rl;
109
105
  }
110
106
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "runline",
3
- "version": "0.7.2",
3
+ "version": "0.7.5",
4
4
  "description": "Code mode for agents — turn any API or command into a callable action",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -65,6 +65,7 @@
65
65
  "dependencies": {
66
66
  "chalk": "^5.6.2",
67
67
  "commander": "^14.0.3",
68
+ "jiti": "^2.7.0",
68
69
  "proper-lockfile": "^4.1.2",
69
70
  "quickjs-emscripten": "^0.32.0",
70
71
  "rrule": "^2.8.1"
@@ -1,4 +0,0 @@
1
- import type { PluginRegistry } from "./registry.js";
2
- import type { PluginDef } from "./types.js";
3
- export declare function registerNodePlugin(registry: PluginRegistry): void;
4
- export declare const nodePlugin: PluginDef;