runline 0.8.1 → 0.10.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 (32) hide show
  1. package/dist/plugin/loader.js +41 -25
  2. package/dist/plugins/linear/src/attachments.js +29 -7
  3. package/dist/plugins/linear/src/comments.js +20 -6
  4. package/dist/plugins/linear/src/cycles.js +3 -1
  5. package/dist/plugins/linear/src/index.js +4 -0
  6. package/dist/plugins/linear/src/initiatives.js +9 -4
  7. package/dist/plugins/linear/src/issues.js +29 -7
  8. package/dist/plugins/linear/src/labels.js +6 -1
  9. package/dist/plugins/linear/src/organization.js +2 -1
  10. package/dist/plugins/linear/src/projects.js +12 -1
  11. package/dist/plugins/linear/src/shared.js +103 -0
  12. package/dist/plugins/linear/src/states.js +3 -1
  13. package/dist/plugins/linear/src/teams.js +4 -1
  14. package/dist/plugins/linear/src/users.js +2 -1
  15. package/dist/plugins/linear/src/views.js +24 -15
  16. package/dist/plugins/linear/src/webhooks.js +5 -1
  17. package/dist/plugins/steel/src/browser.js +175 -0
  18. package/dist/plugins/steel/src/captchas.js +19 -0
  19. package/dist/plugins/steel/src/credentials.js +38 -0
  20. package/dist/plugins/steel/src/extensions.js +46 -0
  21. package/dist/plugins/steel/src/files.js +96 -0
  22. package/dist/plugins/steel/src/index.js +21 -374
  23. package/dist/plugins/steel/src/profiles.js +55 -0
  24. package/dist/plugins/steel/src/sessions.js +119 -0
  25. package/dist/plugins/steel/src/shared.js +72 -0
  26. package/dist/plugins/vercel/src/account.js +11 -0
  27. package/dist/plugins/vercel/src/deployments.js +79 -0
  28. package/dist/plugins/vercel/src/env.js +101 -0
  29. package/dist/plugins/vercel/src/index.js +27 -0
  30. package/dist/plugins/vercel/src/projects.js +29 -0
  31. package/dist/plugins/vercel/src/shared.js +73 -0
  32. package/package.json +9 -1
@@ -0,0 +1,72 @@
1
+ import * as t from "typebox";
2
+ const BASE_URL = "https://api.steel.dev";
3
+ export function apiKey(ctx) {
4
+ return ctx.connection.config.apiKey;
5
+ }
6
+ export async function api(ctx, path, options = {}) {
7
+ const url = new URL(path, BASE_URL);
8
+ for (const [key, value] of Object.entries(options.query ?? {})) {
9
+ if (value === undefined || value === null || value === "")
10
+ continue;
11
+ if (Array.isArray(value)) {
12
+ for (const item of value)
13
+ url.searchParams.append(key, String(item));
14
+ }
15
+ else {
16
+ url.searchParams.set(key, String(value));
17
+ }
18
+ }
19
+ const headers = {
20
+ "steel-api-key": apiKey(ctx),
21
+ ...(options.headers ?? {}),
22
+ };
23
+ let body;
24
+ if (options.body !== undefined) {
25
+ if (options.body instanceof FormData) {
26
+ body = options.body;
27
+ }
28
+ else {
29
+ headers["Content-Type"] = "application/json";
30
+ body = JSON.stringify(options.body);
31
+ }
32
+ }
33
+ const res = await fetch(url.toString(), {
34
+ method: options.method ?? "GET",
35
+ headers,
36
+ body,
37
+ });
38
+ const text = await res.text();
39
+ if (!res.ok)
40
+ throw new Error(`Steel API error ${res.status}: ${text || res.statusText}`);
41
+ if (!text)
42
+ return {};
43
+ const contentType = res.headers.get("content-type") ?? "";
44
+ if (contentType.includes("application/json") || text.startsWith("{") || text.startsWith("["))
45
+ return JSON.parse(text);
46
+ return text;
47
+ }
48
+ export function compactRecord(input) {
49
+ return Object.fromEntries(Object.entries(input).filter(([, value]) => value !== undefined && value !== null));
50
+ }
51
+ export const LIST_INPUT_SCHEMA = {
52
+ limit: t.Optional(t.Number({ description: "Maximum number of results when supported" })),
53
+ cursor: t.Optional(t.String({ description: "Pagination cursor when supported" })),
54
+ };
55
+ export const SESSION_OPTIONS_SCHEMA = {
56
+ timeout: t.Optional(t.Number({ description: "Session hard timeout in milliseconds" })),
57
+ inactivityTimeout: t.Optional(t.Number({ description: "Release after this many milliseconds of inactivity" })),
58
+ useProxy: t.Optional(t.Any({ description: "true for Steel managed proxy, or proxy config object" })),
59
+ solveCaptcha: t.Optional(t.Boolean({ description: "Enable CAPTCHA detection/solving" })),
60
+ region: t.Optional(t.String({ description: "Steel region, e.g. lax or iad" })),
61
+ namespace: t.Optional(t.String({ description: "Credential namespace to use for this session" })),
62
+ userAgent: t.Optional(t.String({ description: "Custom browser user agent" })),
63
+ dimensions: t.Optional(t.Any({ description: "Viewport dimensions, e.g. { width: 1280, height: 768 }" })),
64
+ stealthConfig: t.Optional(t.Any({ description: "Steel stealth configuration, e.g. { autoCaptchaSolving: false }" })),
65
+ deviceConfig: t.Optional(t.Any({ description: "Device config, e.g. { device: 'mobile' }" })),
66
+ profileId: t.Optional(t.String({ description: "Profile ID to load" })),
67
+ persistProfile: t.Optional(t.Boolean({ description: "Persist profile changes on release" })),
68
+ credentials: t.Optional(t.Any({ description: "Credentials injection options, or {} to enable defaults" })),
69
+ extensionIds: t.Optional(t.Array(t.String(), { description: "Extension IDs to attach, or ['all_ext']" })),
70
+ sessionContext: t.Optional(t.Any({ description: "Captured session context to restore" })),
71
+ isSelenium: t.Optional(t.Boolean({ description: "Provision a Selenium-compatible session" })),
72
+ };
@@ -0,0 +1,11 @@
1
+ import * as t from "typebox";
2
+ import { TEAM_INPUT_SCHEMA, api } from "./shared.js";
3
+ export function registerAccountActions(rl) {
4
+ rl.registerAction("whoami", {
5
+ description: "Validate the Vercel token and return the authenticated user/account context.",
6
+ inputSchema: t.Object(TEAM_INPUT_SCHEMA),
7
+ async execute(input, ctx) {
8
+ return api(ctx, "/v2/user", { query: (input ?? {}) });
9
+ },
10
+ });
11
+ }
@@ -0,0 +1,79 @@
1
+ import * as t from "typebox";
2
+ import { LIST_INPUT_SCHEMA, TEAM_INPUT_SCHEMA, api, bindGetAction } from "./shared.js";
3
+ export function registerDeploymentActions(rl) {
4
+ const getAction = bindGetAction(rl);
5
+ rl.registerAction("deployment.list", {
6
+ description: "List Vercel deployments. Filter by project, state, target, user, or time window.",
7
+ inputSchema: t.Object({
8
+ ...LIST_INPUT_SCHEMA,
9
+ projectId: t.Optional(t.String({ description: "Project ID to filter deployments" })),
10
+ projectIds: t.Optional(t.Array(t.String(), { description: "Project IDs to filter deployments" })),
11
+ app: t.Optional(t.String({ description: "Project name/app filter" })),
12
+ target: t.Optional(t.String({ description: "production, preview, or a custom target" })),
13
+ state: t.Optional(t.String({ description: "Comma-separated deployment states, e.g. BUILDING,READY,ERROR" })),
14
+ users: t.Optional(t.String({ description: "Comma-separated Vercel user IDs" })),
15
+ }),
16
+ async execute(input, ctx) {
17
+ return api(ctx, "/v7/deployments", { query: (input ?? {}) });
18
+ },
19
+ });
20
+ getAction("deployment.get", "Get a Vercel deployment by ID or URL.", (id) => `/v13/deployments/${encodeURIComponent(id)}`);
21
+ rl.registerAction("deployment.logs", {
22
+ description: "Get build/deployment logs/events for a deployment. Use builds=1 for build logs and limit/since/until to avoid huge responses.",
23
+ inputSchema: t.Object({
24
+ ...TEAM_INPUT_SCHEMA,
25
+ idOrUrl: t.String({ description: "Deployment ID or URL" }),
26
+ limit: t.Optional(t.Number({ description: "Maximum events. Vercel supports -1 for all available logs" })),
27
+ direction: t.Optional(t.String({ description: "forward or backward" })),
28
+ follow: t.Optional(t.Number({ description: "0 or 1. Avoid 1 in short-lived agent calls unless intentionally streaming" })),
29
+ name: t.Optional(t.String({ description: "Build ID/name" })),
30
+ since: t.Optional(t.Number({ description: "Start timestamp in milliseconds" })),
31
+ until: t.Optional(t.Number({ description: "End timestamp in milliseconds" })),
32
+ statusCode: t.Optional(t.String({ description: "HTTP status filter such as 5xx" })),
33
+ delimiter: t.Optional(t.Number({ description: "0 or 1" })),
34
+ builds: t.Optional(t.Number({ description: "0 or 1" })),
35
+ }),
36
+ async execute(input, ctx) {
37
+ const { idOrUrl, ...query } = input;
38
+ return api(ctx, `/v3/deployments/${encodeURIComponent(String(idOrUrl))}/events`, { query });
39
+ },
40
+ });
41
+ rl.registerAction("deployment.runtimeLogs", {
42
+ description: "Get runtime logs for a deployment. Requires projectId and deploymentId; use limit/since/until to keep responses bounded.",
43
+ inputSchema: t.Object({
44
+ ...TEAM_INPUT_SCHEMA,
45
+ projectId: t.String({ description: "Project ID" }),
46
+ deploymentId: t.String({ description: "Deployment ID" }),
47
+ limit: t.Optional(t.Number({ description: "Maximum logs when supported by Vercel" })),
48
+ since: t.Optional(t.Number({ description: "Start timestamp in milliseconds" })),
49
+ until: t.Optional(t.Number({ description: "End timestamp in milliseconds" })),
50
+ }),
51
+ async execute(input, ctx) {
52
+ const { projectId, deploymentId, ...query } = input;
53
+ return api(ctx, `/v1/projects/${encodeURIComponent(String(projectId))}/deployments/${encodeURIComponent(String(deploymentId))}/runtime-logs`, { query });
54
+ },
55
+ });
56
+ rl.registerAction("deployment.cancel", {
57
+ description: "Cancel a queued or building Vercel deployment.",
58
+ inputSchema: t.Object({
59
+ ...TEAM_INPUT_SCHEMA,
60
+ id: t.String({ description: "Deployment ID" }),
61
+ }),
62
+ async execute(input, ctx) {
63
+ const { id, ...query } = input;
64
+ return api(ctx, `/v12/deployments/${encodeURIComponent(id)}/cancel`, { method: "PATCH", query });
65
+ },
66
+ });
67
+ rl.registerAction("deployment.promote", {
68
+ description: "Promote an existing deployment to production for a project, where supported by Vercel.",
69
+ inputSchema: t.Object({
70
+ ...TEAM_INPUT_SCHEMA,
71
+ projectId: t.String({ description: "Project ID" }),
72
+ deploymentId: t.String({ description: "Deployment ID to promote" }),
73
+ }),
74
+ async execute(input, ctx) {
75
+ const { projectId, deploymentId, ...query } = input;
76
+ return api(ctx, `/v10/projects/${encodeURIComponent(String(projectId))}/promote/${encodeURIComponent(String(deploymentId))}`, { method: "POST", query });
77
+ },
78
+ });
79
+ }
@@ -0,0 +1,101 @@
1
+ import * as t from "typebox";
2
+ import { TEAM_INPUT_SCHEMA, api } from "./shared.js";
3
+ const targetSchema = t.Union([t.String(), t.Array(t.String())], {
4
+ description: "production, preview, development, custom environment, or array of targets",
5
+ });
6
+ function normalizeEnvBody(body) {
7
+ const normalized = { ...body };
8
+ if (typeof normalized.target === "string")
9
+ normalized.target = [normalized.target];
10
+ return normalized;
11
+ }
12
+ function assertCreateEnvInput(body) {
13
+ if (!body.key || !body.value || !body.type) {
14
+ throw new Error("env.set create requires key, value, and type");
15
+ }
16
+ if (!body.target && !body.customEnvironmentIds) {
17
+ throw new Error("env.set create requires target or customEnvironmentIds");
18
+ }
19
+ }
20
+ export function registerEnvActions(rl) {
21
+ rl.registerAction("env.list", {
22
+ description: "List environment variables for a Vercel project.",
23
+ inputSchema: t.Object({
24
+ ...TEAM_INPUT_SCHEMA,
25
+ projectIdOrName: t.String({ description: "Project ID or name" }),
26
+ target: t.Optional(t.String({ description: "production, preview, development, or custom environment" })),
27
+ gitBranch: t.Optional(t.String({ description: "Git branch filter" })),
28
+ decrypt: t.Optional(t.Boolean({ description: "Ask Vercel to include decrypted values when permitted" })),
29
+ source: t.Optional(t.String({ description: "Environment variable source filter" })),
30
+ }),
31
+ async execute(input, ctx) {
32
+ const { projectIdOrName, ...query } = input;
33
+ return api(ctx, `/v10/projects/${encodeURIComponent(String(projectIdOrName))}/env`, { query });
34
+ },
35
+ });
36
+ rl.registerAction("env.get", {
37
+ description: "Get one environment variable by ID for a Vercel project, including decrypted value when permitted.",
38
+ inputSchema: t.Object({
39
+ ...TEAM_INPUT_SCHEMA,
40
+ projectIdOrName: t.String({ description: "Project ID or name" }),
41
+ id: t.String({ description: "Environment variable ID" }),
42
+ }),
43
+ async execute(input, ctx) {
44
+ const { projectIdOrName, id, ...query } = input;
45
+ return api(ctx, `/v1/projects/${encodeURIComponent(String(projectIdOrName))}/env/${encodeURIComponent(String(id))}`, { query });
46
+ },
47
+ });
48
+ rl.registerAction("env.set", {
49
+ description: "Create or update a Vercel project environment variable. Without id, creates a variable and requires key, value, type, and target/customEnvironmentIds. With id, updates that exact variable. Be explicit about target to avoid changing the wrong environment.",
50
+ inputSchema: t.Object({
51
+ ...TEAM_INPUT_SCHEMA,
52
+ projectIdOrName: t.String({ description: "Project ID or name" }),
53
+ id: t.Optional(t.String({ description: "Environment variable ID. When provided, env.set updates instead of creating." })),
54
+ key: t.Optional(t.String({ description: "Variable name" })),
55
+ value: t.Optional(t.String({ description: "Variable value" })),
56
+ target: t.Optional(targetSchema),
57
+ customEnvironmentIds: t.Optional(t.Array(t.String(), { description: "Custom environment IDs for custom-environment scoped variables" })),
58
+ type: t.Optional(t.String({ description: "Vercel env var type: system, encrypted, plain, or sensitive" })),
59
+ gitBranch: t.Optional(t.String({ description: "Git branch for branch-scoped preview variables" })),
60
+ comment: t.Optional(t.String({ description: "Optional comment" })),
61
+ variables: t.Optional(t.Array(t.Object({}, { description: "Raw Vercel env var objects for batch create" }))),
62
+ }),
63
+ async execute(input, ctx) {
64
+ const { projectIdOrName, id, teamId, slug, variables, ...fields } = input;
65
+ const query = { teamId, slug };
66
+ if (id) {
67
+ return api(ctx, `/v9/projects/${encodeURIComponent(String(projectIdOrName))}/env/${encodeURIComponent(String(id))}`, {
68
+ method: "PATCH",
69
+ query,
70
+ body: normalizeEnvBody(fields),
71
+ });
72
+ }
73
+ if (Array.isArray(variables)) {
74
+ return api(ctx, `/v10/projects/${encodeURIComponent(String(projectIdOrName))}/env`, {
75
+ method: "POST",
76
+ query,
77
+ body: variables.map((item) => normalizeEnvBody(item)),
78
+ });
79
+ }
80
+ const body = normalizeEnvBody(fields);
81
+ assertCreateEnvInput(body);
82
+ return api(ctx, `/v10/projects/${encodeURIComponent(String(projectIdOrName))}/env`, { method: "POST", query, body });
83
+ },
84
+ });
85
+ rl.registerAction("env.delete", {
86
+ description: "Delete an environment variable from a Vercel project. This removes the variable for the specified project/environment scope.",
87
+ inputSchema: t.Object({
88
+ ...TEAM_INPUT_SCHEMA,
89
+ projectIdOrName: t.String({ description: "Project ID or name" }),
90
+ id: t.String({ description: "Environment variable ID" }),
91
+ customEnvironmentId: t.Optional(t.String({ description: "Custom environment ID when required" })),
92
+ }),
93
+ async execute(input, ctx) {
94
+ const { projectIdOrName, id, customEnvironmentId, ...query } = input;
95
+ return api(ctx, `/v9/projects/${encodeURIComponent(String(projectIdOrName))}/env/${encodeURIComponent(String(id))}`, {
96
+ method: "DELETE",
97
+ query: { ...query, customEnvironmentId },
98
+ });
99
+ },
100
+ });
101
+ }
@@ -0,0 +1,27 @@
1
+ import * as t from "typebox";
2
+ import { registerAccountActions } from "./account.js";
3
+ import { registerDeploymentActions } from "./deployments.js";
4
+ import { registerEnvActions } from "./env.js";
5
+ import { registerProjectActions } from "./projects.js";
6
+ export default function vercel(rl) {
7
+ rl.setName("vercel");
8
+ rl.setVersion("0.1.0");
9
+ rl.setConnectionSchema(t.Object({
10
+ token: t.String({
11
+ description: "Vercel access token (https://vercel.com/account/settings/tokens)",
12
+ env: "VERCEL_TOKEN",
13
+ }),
14
+ teamId: t.Optional(t.String({
15
+ description: "Optional Vercel Team ID. Added as teamId to every API request.",
16
+ env: "VERCEL_TEAM_ID",
17
+ })),
18
+ slug: t.Optional(t.String({
19
+ description: "Optional Vercel Team slug. Added as slug to every API request when teamId is not used.",
20
+ env: "VERCEL_TEAM_SLUG",
21
+ })),
22
+ }));
23
+ registerAccountActions(rl);
24
+ registerProjectActions(rl);
25
+ registerDeploymentActions(rl);
26
+ registerEnvActions(rl);
27
+ }
@@ -0,0 +1,29 @@
1
+ import * as t from "typebox";
2
+ import { LIST_INPUT_SCHEMA, TEAM_INPUT_SCHEMA, api, bindGetAction } from "./shared.js";
3
+ export function registerProjectActions(rl) {
4
+ const getAction = bindGetAction(rl);
5
+ rl.registerAction("project.list", {
6
+ description: "List Vercel projects visible to the token.",
7
+ inputSchema: t.Object({
8
+ ...LIST_INPUT_SCHEMA,
9
+ search: t.Optional(t.String({ description: "Search by project name" })),
10
+ }),
11
+ async execute(input, ctx) {
12
+ const opts = (input ?? {});
13
+ return api(ctx, "/v10/projects", { query: opts });
14
+ },
15
+ });
16
+ getAction("project.get", "Get a Vercel project by ID or name.", (id) => `/v9/projects/${encodeURIComponent(id)}`);
17
+ rl.registerAction("project.domains", {
18
+ description: "List domains configured for a Vercel project.",
19
+ inputSchema: t.Object({
20
+ ...TEAM_INPUT_SCHEMA,
21
+ projectIdOrName: t.String({ description: "Project ID or name" }),
22
+ limit: t.Optional(t.Number({ description: "Maximum number of domains" })),
23
+ }),
24
+ async execute(input, ctx) {
25
+ const { projectIdOrName, ...query } = input;
26
+ return api(ctx, `/v9/projects/${encodeURIComponent(String(projectIdOrName))}/domains`, { query });
27
+ },
28
+ });
29
+ }
@@ -0,0 +1,73 @@
1
+ import * as t from "typebox";
2
+ const BASE_URL = "https://api.vercel.com";
3
+ export function token(ctx) {
4
+ return ctx.connection.config.token;
5
+ }
6
+ export async function api(ctx, path, options = {}) {
7
+ const url = new URL(path, BASE_URL);
8
+ const query = {
9
+ teamId: ctx.connection.config.teamId,
10
+ slug: ctx.connection.config.slug,
11
+ ...(options.query ?? {}),
12
+ };
13
+ for (const [key, value] of Object.entries(query)) {
14
+ if (value === undefined || value === null || value === "")
15
+ continue;
16
+ if (Array.isArray(value)) {
17
+ for (const item of value)
18
+ url.searchParams.append(key, String(item));
19
+ }
20
+ else {
21
+ url.searchParams.set(key, String(value));
22
+ }
23
+ }
24
+ const init = {
25
+ method: options.method ?? "GET",
26
+ headers: {
27
+ Authorization: `Bearer ${token(ctx)}`,
28
+ "Content-Type": "application/json",
29
+ ...(options.headers ?? {}),
30
+ },
31
+ };
32
+ if (options.body !== undefined)
33
+ init.body = JSON.stringify(options.body);
34
+ const res = await fetch(url.toString(), init);
35
+ const text = await res.text();
36
+ if (!res.ok) {
37
+ throw new Error(`Vercel API error ${res.status}: ${text || res.statusText}`);
38
+ }
39
+ if (!text)
40
+ return {};
41
+ const contentType = res.headers.get("content-type") ?? "";
42
+ if (contentType.includes("application/json") || text.startsWith("{") || text.startsWith("[")) {
43
+ return JSON.parse(text);
44
+ }
45
+ return text;
46
+ }
47
+ export const TEAM_INPUT_SCHEMA = {
48
+ teamId: t.Optional(t.String({ description: "Override the configured Vercel Team ID for this call" })),
49
+ slug: t.Optional(t.String({ description: "Override the configured Vercel Team slug for this call" })),
50
+ };
51
+ export const LIST_INPUT_SCHEMA = {
52
+ ...TEAM_INPUT_SCHEMA,
53
+ limit: t.Optional(t.Number({ description: "Maximum number of results" })),
54
+ since: t.Optional(t.Number({ description: "Timestamp in milliseconds to start from" })),
55
+ until: t.Optional(t.Number({ description: "Timestamp in milliseconds to end at" })),
56
+ from: t.Optional(t.Number({ description: "Pagination timestamp/cursor supported by Vercel" })),
57
+ to: t.Optional(t.Number({ description: "Pagination timestamp/cursor supported by Vercel" })),
58
+ };
59
+ export function bindGetAction(rl) {
60
+ return (name, description, pathForId) => {
61
+ rl.registerAction(name, {
62
+ description,
63
+ inputSchema: t.Object({
64
+ id: t.String({ description: "Resource ID, name, or URL" }),
65
+ ...TEAM_INPUT_SCHEMA,
66
+ }),
67
+ async execute(input, ctx) {
68
+ const { id, ...query } = input;
69
+ return api(ctx, pathForId(id), { query });
70
+ },
71
+ });
72
+ };
73
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "runline",
3
- "version": "0.8.1",
3
+ "version": "0.10.0",
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",
@@ -62,6 +62,14 @@
62
62
  "minisearch": "^7.2.0",
63
63
  "typescript": "^5.8.0"
64
64
  },
65
+ "peerDependencies": {
66
+ "playwright": "^1.57.0"
67
+ },
68
+ "peerDependenciesMeta": {
69
+ "playwright": {
70
+ "optional": true
71
+ }
72
+ },
65
73
  "dependencies": {
66
74
  "chalk": "^5.6.2",
67
75
  "commander": "^14.0.3",