runline 0.1.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 (230) hide show
  1. package/.pi/extensions/runline-context/index.ts +135 -0
  2. package/.pi/extensions/runline-context/package.json +17 -0
  3. package/README.md +273 -0
  4. package/dist/commands/actions.d.ts +3 -0
  5. package/dist/commands/actions.js +43 -0
  6. package/dist/commands/connection.d.ts +11 -0
  7. package/dist/commands/connection.js +56 -0
  8. package/dist/commands/exec.d.ts +5 -0
  9. package/dist/commands/exec.js +46 -0
  10. package/dist/commands/init.d.ts +4 -0
  11. package/dist/commands/init.js +26 -0
  12. package/dist/commands/plugin.d.ts +10 -0
  13. package/dist/commands/plugin.js +57 -0
  14. package/dist/config/index.d.ts +3 -0
  15. package/dist/config/index.js +2 -0
  16. package/dist/config/loader.d.ts +11 -0
  17. package/dist/config/loader.js +82 -0
  18. package/dist/config/types.d.ts +9 -0
  19. package/dist/config/types.js +5 -0
  20. package/dist/core/engine.d.ts +21 -0
  21. package/dist/core/engine.js +280 -0
  22. package/dist/index.d.ts +16 -0
  23. package/dist/index.js +9 -0
  24. package/dist/main.d.ts +2 -0
  25. package/dist/main.js +127 -0
  26. package/dist/plugin/api.d.ts +32 -0
  27. package/dist/plugin/api.js +68 -0
  28. package/dist/plugin/installer.d.ts +27 -0
  29. package/dist/plugin/installer.js +181 -0
  30. package/dist/plugin/loader.d.ts +13 -0
  31. package/dist/plugin/loader.js +164 -0
  32. package/dist/plugin/registry.d.ts +18 -0
  33. package/dist/plugin/registry.js +43 -0
  34. package/dist/plugin/types.d.ts +40 -0
  35. package/dist/plugin/types.js +1 -0
  36. package/dist/plugins/actionNetwork/src/index.js +353 -0
  37. package/dist/plugins/activeCampaign/src/index.js +711 -0
  38. package/dist/plugins/adalo/src/index.js +131 -0
  39. package/dist/plugins/affinity/src/index.js +279 -0
  40. package/dist/plugins/agileCrm/src/index.js +415 -0
  41. package/dist/plugins/airtable/src/index.js +280 -0
  42. package/dist/plugins/airtop/src/index.js +527 -0
  43. package/dist/plugins/apiTemplateIo/src/index.js +86 -0
  44. package/dist/plugins/asana/src/index.js +413 -0
  45. package/dist/plugins/autopilot/src/index.js +203 -0
  46. package/dist/plugins/bambooHr/src/index.js +252 -0
  47. package/dist/plugins/bannerbear/src/index.js +100 -0
  48. package/dist/plugins/baserow/src/index.js +180 -0
  49. package/dist/plugins/beeminder/src/index.js +298 -0
  50. package/dist/plugins/bitly/src/index.js +107 -0
  51. package/dist/plugins/bitwarden/src/index.js +383 -0
  52. package/dist/plugins/box/src/index.js +300 -0
  53. package/dist/plugins/brandfetch/src/index.js +80 -0
  54. package/dist/plugins/brevo/src/index.js +305 -0
  55. package/dist/plugins/bubble/src/index.js +181 -0
  56. package/dist/plugins/chargebee/src/index.js +126 -0
  57. package/dist/plugins/circleci/src/index.js +111 -0
  58. package/dist/plugins/ciscoWebex/src/index.js +245 -0
  59. package/dist/plugins/clearbit/src/index.js +103 -0
  60. package/dist/plugins/clickup/src/index.js +1043 -0
  61. package/dist/plugins/clockify/src/index.js +443 -0
  62. package/dist/plugins/cloudflare/src/index.js +93 -0
  63. package/dist/plugins/cockpit/src/index.js +131 -0
  64. package/dist/plugins/coda/src/index.js +327 -0
  65. package/dist/plugins/coingecko/src/index.js +244 -0
  66. package/dist/plugins/contentful/src/index.js +146 -0
  67. package/dist/plugins/convertkit/src/index.js +270 -0
  68. package/dist/plugins/copper/src/index.js +140 -0
  69. package/dist/plugins/cortex/src/index.js +147 -0
  70. package/dist/plugins/currents/src/index.js +405 -0
  71. package/dist/plugins/customerIo/src/index.js +184 -0
  72. package/dist/plugins/databricks/src/index.js +342 -0
  73. package/dist/plugins/deepl/src/index.js +87 -0
  74. package/dist/plugins/demio/src/index.js +111 -0
  75. package/dist/plugins/dhl/src/index.js +40 -0
  76. package/dist/plugins/discord/src/index.js +275 -0
  77. package/dist/plugins/discourse/src/index.js +273 -0
  78. package/dist/plugins/disqus/src/index.js +145 -0
  79. package/dist/plugins/docker/src/index.js +76 -0
  80. package/dist/plugins/drift/src/index.js +89 -0
  81. package/dist/plugins/dropbox/src/index.js +159 -0
  82. package/dist/plugins/dropcontact/src/index.js +59 -0
  83. package/dist/plugins/egoi/src/index.js +151 -0
  84. package/dist/plugins/elasticsearch/src/index.js +157 -0
  85. package/dist/plugins/emelia/src/index.js +174 -0
  86. package/dist/plugins/erpnext/src/index.js +121 -0
  87. package/dist/plugins/facebookGraph/src/index.js +57 -0
  88. package/dist/plugins/freshdesk/src/index.js +320 -0
  89. package/dist/plugins/freshservice/src/index.js +146 -0
  90. package/dist/plugins/freshworksCrm/src/index.js +149 -0
  91. package/dist/plugins/getresponse/src/index.js +140 -0
  92. package/dist/plugins/ghost/src/index.js +192 -0
  93. package/dist/plugins/github/src/index.js +630 -0
  94. package/dist/plugins/gitlab/src/index.js +358 -0
  95. package/dist/plugins/gong/src/index.js +126 -0
  96. package/dist/plugins/gotify/src/index.js +77 -0
  97. package/dist/plugins/gotowebinar/src/index.js +316 -0
  98. package/dist/plugins/grafana/src/index.js +250 -0
  99. package/dist/plugins/graphql/src/index.js +78 -0
  100. package/dist/plugins/grist/src/index.js +106 -0
  101. package/dist/plugins/hackernews/src/index.js +89 -0
  102. package/dist/plugins/halopsa/src/index.js +79 -0
  103. package/dist/plugins/harvest/src/index.js +163 -0
  104. package/dist/plugins/helpscout/src/index.js +176 -0
  105. package/dist/plugins/highlevel/src/index.js +172 -0
  106. package/dist/plugins/homeAssistant/src/index.js +148 -0
  107. package/dist/plugins/hubspot/src/index.js +176 -0
  108. package/dist/plugins/humanticAi/src/index.js +60 -0
  109. package/dist/plugins/hunter/src/index.js +59 -0
  110. package/dist/plugins/intercom/src/index.js +156 -0
  111. package/dist/plugins/iterable/src/index.js +139 -0
  112. package/dist/plugins/jenkins/src/index.js +132 -0
  113. package/dist/plugins/jira/src/index.js +229 -0
  114. package/dist/plugins/keap/src/index.js +502 -0
  115. package/dist/plugins/kobotoolbox/src/index.js +281 -0
  116. package/dist/plugins/lemlist/src/index.js +231 -0
  117. package/dist/plugins/linear/src/index.js +133 -0
  118. package/dist/plugins/lingvanex/src/index.js +31 -0
  119. package/dist/plugins/linkedin/src/index.js +80 -0
  120. package/dist/plugins/lonescale/src/index.js +119 -0
  121. package/dist/plugins/magento/src/index.js +300 -0
  122. package/dist/plugins/mailcheck/src/index.js +27 -0
  123. package/dist/plugins/mailchimp/src/index.js +321 -0
  124. package/dist/plugins/mailerlite/src/index.js +123 -0
  125. package/dist/plugins/mailgun/src/index.js +48 -0
  126. package/dist/plugins/mailjet/src/index.js +155 -0
  127. package/dist/plugins/mandrill/src/index.js +145 -0
  128. package/dist/plugins/marketstack/src/index.js +97 -0
  129. package/dist/plugins/matrix/src/index.js +194 -0
  130. package/dist/plugins/mattermost/src/index.js +331 -0
  131. package/dist/plugins/mautic/src/index.js +311 -0
  132. package/dist/plugins/medium/src/index.js +77 -0
  133. package/dist/plugins/messagebird/src/index.js +57 -0
  134. package/dist/plugins/metabase/src/index.js +130 -0
  135. package/dist/plugins/misp/src/index.js +476 -0
  136. package/dist/plugins/mocean/src/index.js +67 -0
  137. package/dist/plugins/monday/src/index.js +231 -0
  138. package/dist/plugins/monicaCrm/src/index.js +52 -0
  139. package/dist/plugins/msg91/src/index.js +31 -0
  140. package/dist/plugins/nasa/src/index.js +146 -0
  141. package/dist/plugins/netlify/src/index.js +151 -0
  142. package/dist/plugins/netscalerAdc/src/index.js +131 -0
  143. package/dist/plugins/nextcloud/src/index.js +263 -0
  144. package/dist/plugins/nocodb/src/index.js +130 -0
  145. package/dist/plugins/notion/src/index.js +112 -0
  146. package/dist/plugins/npm/src/index.js +104 -0
  147. package/dist/plugins/odoo/src/index.js +157 -0
  148. package/dist/plugins/okta/src/index.js +141 -0
  149. package/dist/plugins/oneSimpleApi/src/index.js +155 -0
  150. package/dist/plugins/onfleet/src/index.js +254 -0
  151. package/dist/plugins/openThesaurus/src/index.js +32 -0
  152. package/dist/plugins/openweathermap/src/index.js +60 -0
  153. package/dist/plugins/oura/src/index.js +62 -0
  154. package/dist/plugins/paddle/src/index.js +247 -0
  155. package/dist/plugins/pagerduty/src/index.js +201 -0
  156. package/dist/plugins/paypal/src/index.js +106 -0
  157. package/dist/plugins/peekalink/src/index.js +35 -0
  158. package/dist/plugins/phantombuster/src/index.js +94 -0
  159. package/dist/plugins/philipsHue/src/index.js +98 -0
  160. package/dist/plugins/pipedrive/src/index.js +169 -0
  161. package/dist/plugins/plivo/src/index.js +66 -0
  162. package/dist/plugins/postbin/src/index.js +93 -0
  163. package/dist/plugins/posthog/src/index.js +113 -0
  164. package/dist/plugins/profitwell/src/index.js +50 -0
  165. package/dist/plugins/pushbullet/src/index.js +102 -0
  166. package/dist/plugins/pushcut/src/index.js +39 -0
  167. package/dist/plugins/pushover/src/index.js +65 -0
  168. package/dist/plugins/quickbase/src/index.js +153 -0
  169. package/dist/plugins/quickbooks/src/index.js +73 -0
  170. package/dist/plugins/quickchart/src/index.js +36 -0
  171. package/dist/plugins/raindrop/src/index.js +209 -0
  172. package/dist/plugins/reddit/src/index.js +185 -0
  173. package/dist/plugins/rocketchat/src/index.js +53 -0
  174. package/dist/plugins/rundeck/src/index.js +62 -0
  175. package/dist/plugins/salesforce/src/index.js +94 -0
  176. package/dist/plugins/salesmate/src/index.js +83 -0
  177. package/dist/plugins/securityScorecard/src/index.js +200 -0
  178. package/dist/plugins/segment/src/index.js +125 -0
  179. package/dist/plugins/sendgrid/src/index.js +187 -0
  180. package/dist/plugins/sendy/src/index.js +138 -0
  181. package/dist/plugins/sentry/src/index.js +233 -0
  182. package/dist/plugins/servicenow/src/index.js +108 -0
  183. package/dist/plugins/shopify/src/index.js +222 -0
  184. package/dist/plugins/signl4/src/index.js +61 -0
  185. package/dist/plugins/slack/src/index.js +236 -0
  186. package/dist/plugins/sms77/src/index.js +63 -0
  187. package/dist/plugins/splunk/src/index.js +207 -0
  188. package/dist/plugins/spotify/src/index.js +188 -0
  189. package/dist/plugins/stackby/src/index.js +82 -0
  190. package/dist/plugins/storyblok/src/index.js +141 -0
  191. package/dist/plugins/strapi/src/index.js +152 -0
  192. package/dist/plugins/strava/src/index.js +137 -0
  193. package/dist/plugins/stripe/src/index.js +222 -0
  194. package/dist/plugins/supabase/src/index.js +121 -0
  195. package/dist/plugins/syncromsp/src/index.js +255 -0
  196. package/dist/plugins/tapfiliate/src/index.js +125 -0
  197. package/dist/plugins/telegram/src/index.js +233 -0
  198. package/dist/plugins/thehive/src/index.js +142 -0
  199. package/dist/plugins/thehiveProject/src/index.js +194 -0
  200. package/dist/plugins/todoist/src/index.js +244 -0
  201. package/dist/plugins/travisci/src/index.js +71 -0
  202. package/dist/plugins/trello/src/index.js +341 -0
  203. package/dist/plugins/twake/src/index.js +40 -0
  204. package/dist/plugins/twilio/src/index.js +75 -0
  205. package/dist/plugins/twist/src/index.js +90 -0
  206. package/dist/plugins/twitter/src/index.js +123 -0
  207. package/dist/plugins/unleashedSoftware/src/index.js +84 -0
  208. package/dist/plugins/uplead/src/index.js +59 -0
  209. package/dist/plugins/uproc/src/index.js +34 -0
  210. package/dist/plugins/uptimerobot/src/index.js +264 -0
  211. package/dist/plugins/urlscanio/src/index.js +64 -0
  212. package/dist/plugins/vero/src/index.js +80 -0
  213. package/dist/plugins/vonage/src/index.js +42 -0
  214. package/dist/plugins/wekan/src/index.js +91 -0
  215. package/dist/plugins/woocommerce/src/index.js +92 -0
  216. package/dist/plugins/wordpress/src/index.js +121 -0
  217. package/dist/plugins/xero/src/index.js +136 -0
  218. package/dist/plugins/yourls/src/index.js +56 -0
  219. package/dist/plugins/zammad/src/index.js +91 -0
  220. package/dist/plugins/zendesk/src/index.js +137 -0
  221. package/dist/plugins/zoho/src/index.js +85 -0
  222. package/dist/plugins/zoom/src/index.js +122 -0
  223. package/dist/plugins/zulip/src/index.js +170 -0
  224. package/dist/sdk.d.ts +38 -0
  225. package/dist/sdk.js +105 -0
  226. package/dist/utils/cli.d.ts +13 -0
  227. package/dist/utils/cli.js +32 -0
  228. package/dist/utils/output.d.ts +4 -0
  229. package/dist/utils/output.js +13 -0
  230. package/package.json +57 -0
@@ -0,0 +1,135 @@
1
+ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
2
+ import { Markdown, Text } from "@mariozechner/pi-tui";
3
+ import { Runline } from "../../../src/sdk.js";
4
+
5
+ function formatActions(
6
+ actions: Array<{
7
+ plugin: string;
8
+ action: string;
9
+ description?: string;
10
+ inputSchema?: Record<string, { type: string; required?: boolean; description?: string }>;
11
+ }>,
12
+ ): string {
13
+ const grouped = new Map<string, typeof actions>();
14
+ for (const a of actions) {
15
+ const list = grouped.get(a.plugin) ?? [];
16
+ list.push(a);
17
+ grouped.set(a.plugin, list);
18
+ }
19
+
20
+ const lines: string[] = [];
21
+ for (const [plugin, entries] of grouped) {
22
+ lines.push(`### ${plugin}`);
23
+ for (const a of entries) {
24
+ const inputs = a.inputSchema
25
+ ? Object.entries(a.inputSchema)
26
+ .map(
27
+ ([k, v]) =>
28
+ `${k}: ${v.type}${v.required ? "" : "?"}`,
29
+ )
30
+ .join(", ")
31
+ : "";
32
+ const sig = inputs
33
+ ? `\`${plugin}.${a.action}({ ${inputs} })\``
34
+ : `\`${plugin}.${a.action}()\``;
35
+ const desc = a.description ? ` — ${a.description}` : "";
36
+ lines.push(`- ${sig}${desc}`);
37
+ }
38
+ lines.push("");
39
+ }
40
+
41
+ return lines.join("\n");
42
+ }
43
+
44
+ export default function (pi: ExtensionAPI) {
45
+ pi.registerMessageRenderer(
46
+ "runline-context",
47
+ (message, { expanded }, theme) => {
48
+ if (!expanded) {
49
+ const label = theme.fg("customMessageLabel", "⚡ runline actions");
50
+ const hint = theme.fg("dim", " — Ctrl+O to expand");
51
+ return new Text(label + hint, 1, 0);
52
+ }
53
+ return new Markdown(
54
+ message.content,
55
+ 1,
56
+ 0,
57
+ {
58
+ heading: (t) => theme.fg("mdHeading", t),
59
+ link: (t) => theme.fg("mdLink", t),
60
+ linkUrl: (t) => theme.fg("mdLinkUrl", t),
61
+ code: (t) => theme.fg("mdCode", t),
62
+ codeBlock: (t) => theme.fg("mdCodeBlock", t),
63
+ codeBlockBorder: (t) => theme.fg("mdCodeBlockBorder", t),
64
+ quote: (t) => theme.fg("mdQuote", t),
65
+ quoteBorder: (t) => theme.fg("mdQuoteBorder", t),
66
+ hr: (t) => theme.fg("mdHr", t),
67
+ listBullet: (t) => theme.fg("mdListBullet", t),
68
+ bold: (t) => theme.bold(t),
69
+ italic: (t) => theme.italic(t),
70
+ strikethrough: (t) => theme.strikethrough(t),
71
+ underline: (t) => theme.underline(t),
72
+ },
73
+ { color: (t) => theme.fg("customMessageText", t) },
74
+ );
75
+ },
76
+ );
77
+
78
+ pi.on("session_start", async (_event, ctx) => {
79
+ const rl = await Runline.fromProject(ctx.cwd);
80
+
81
+ if (!rl) {
82
+ if (ctx.hasUI) {
83
+ ctx.ui.setStatus("runline", ctx.ui.theme.fg("dim", "runline: no .runline/"));
84
+ }
85
+ return;
86
+ }
87
+
88
+ const actions = rl.actions();
89
+ const plugins = rl.plugins();
90
+
91
+ if (actions.length === 0) {
92
+ if (ctx.hasUI) {
93
+ ctx.ui.setStatus("runline", ctx.ui.theme.fg("dim", "runline: no plugins"));
94
+ }
95
+ return;
96
+ }
97
+
98
+ // Check if already injected
99
+ const alreadyInjected = ctx.sessionManager
100
+ .getEntries()
101
+ .some(
102
+ (e: any) =>
103
+ e.type === "message" &&
104
+ e.message.role === "custom" &&
105
+ e.message.customType === "runline-context",
106
+ );
107
+
108
+ if (!alreadyInjected) {
109
+ const header =
110
+ "## Runline actions\n\n" +
111
+ "This project has runline installed. You can execute JavaScript in a sandbox " +
112
+ "where each installed plugin is a top-level global. Chain actions together, " +
113
+ "call `help()` or `pluginName.help()` inside the sandbox for discovery.\n\n" +
114
+ `**${plugins.length} plugins, ${actions.length} actions available.**\n\n` +
115
+ "Use `runline exec '<code>'` to run code. Examples:\n" +
116
+ "```js\n" +
117
+ "return await github.issue.create({ owner: \"acme\", repo: \"api\", title: \"Bug\" })\n" +
118
+ "```\n\n";
119
+
120
+ ctx.sessionManager.appendCustomMessageEntry(
121
+ "runline-context",
122
+ header + formatActions(actions),
123
+ true,
124
+ );
125
+ }
126
+
127
+ if (ctx.hasUI) {
128
+ const theme = ctx.ui.theme;
129
+ ctx.ui.setStatus(
130
+ "runline",
131
+ `⚡${theme.fg("dim", ` runline: ${plugins.length} plugins, ${actions.length} actions`)}`,
132
+ );
133
+ }
134
+ });
135
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "@miclivs/pi-runline-context",
3
+ "version": "0.1.0",
4
+ "description": "Pi extension — injects runline actions into agent context",
5
+ "type": "commonjs",
6
+ "keywords": ["pi-package"],
7
+ "files": ["index.ts", "README.md"],
8
+ "pi": {
9
+ "extensions": ["."]
10
+ },
11
+ "peerDependencies": {
12
+ "@mariozechner/pi-coding-agent": "*",
13
+ "@mariozechner/pi-tui": "*"
14
+ },
15
+ "author": "michaelliv",
16
+ "license": "MIT"
17
+ }
package/README.md ADDED
@@ -0,0 +1,273 @@
1
+ # runline ⚡
2
+
3
+ Code mode for agents.
4
+
5
+ Turn any API into a callable action. Install a plugin, write JavaScript, call actions. The code runs in a QuickJS WASM sandbox — no filesystem, no network, just plugin actions via a proxy.
6
+
7
+ ```bash
8
+ npm install -g runline
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```bash
14
+ runline init
15
+ runline plugin install git:github.com/Michaelliv/runline#plugins/brandfetch
16
+ runline connection add bf --plugin brandfetch --set apiKey=xxx
17
+
18
+ runline exec 'return await brandfetch.brand.getColors({ domain: "nike.com" })'
19
+ # => [{ hex: "#E5E5E5", type: "accent" }, { hex: "#111111", type: "dark" }, ...]
20
+ ```
21
+
22
+ Agent code runs in a QuickJS sandbox. Each installed plugin is a top-level global — no bracket notation, just dot-chain into resource and action. Plugins execute outside the sandbox with full network access; the agent can only reach APIs through the actions you've installed.
23
+
24
+ ```js
25
+ // agent writes this
26
+ const company = await brandfetch.brand.getCompany({ domain: "stripe.com" });
27
+ const deals = await pipedrive.deal.list({ limit: 10 });
28
+ const issue = await github.issue.create({
29
+ owner: "acme", repo: "api",
30
+ title: `New lead: ${company.name}`,
31
+ body: `${deals.length} open deals`
32
+ });
33
+ return { company: company.name, issue: issue.number };
34
+ ```
35
+
36
+ ## Plugins
37
+
38
+ 188 plugins covering popular SaaS, DevOps, and productivity APIs. Each wraps a single service's REST/GraphQL API with typed actions.
39
+
40
+ All plugins install via `runline plugin install git:github.com/Michaelliv/runline#plugins/<name>`.
41
+
42
+ | Plugin | Actions | Auth |
43
+ |--------|---------|------|
44
+ | **github** | file/issue/pr/release/repo/review/user CRUD, search | Bearer token |
45
+ | **gitlab** | issue/merge request/repo/user CRUD | Bearer token |
46
+ | **jira** | issue/project/user CRUD, transitions | Basic auth |
47
+ | **slack** | channel/message/user/reaction/star/file ops | Bearer token |
48
+ | **discord** | channel/message/member CRUD, reactions | Bot token |
49
+ | **notion** | block/database/page/user CRUD, search | Bearer token |
50
+ | **todoist** | task/project/section/comment/label CRUD | Bearer token |
51
+ | **linear** | issue/project/team/comment CRUD (GraphQL) | Bearer token |
52
+ | **hubspot** | contact/company/deal/ticket/engagement CRUD | Bearer token |
53
+ | **pipedrive** | deal/person/org/activity/lead/note/product CRUD, search | API token |
54
+ | **salesforce** | account/contact/lead/opportunity/case/task CRUD | OAuth2 |
55
+ | **shopify** | order/product/customer CRUD | API key |
56
+ | **stripe** | charge/customer/source/coupon CRUD | Bearer token |
57
+ | **airtable** | base/record CRUD, search, upsert | Bearer token |
58
+ | **supabase** | row CRUD | API key |
59
+ | **docker** | container/image/volume/network ops | Unix socket |
60
+ | **telegram** | message/chat/callback/pin ops | Bot token in URL |
61
+ | **twitter** | tweet/user/dm/list ops | OAuth2 Bearer |
62
+ | **clickup** | task/list/folder/space/comment/checklist/team CRUD | Bearer token |
63
+ | **asana** | task/project/section/subtask/tag/user CRUD | Bearer token |
64
+ | **trello** | board/list/card/checklist/attachment/label/member CRUD | API key |
65
+ | **monday** | board/group/item/column/update (GraphQL) | Bearer token |
66
+ | **mailchimp** | list/member/campaign/tag ops | Bearer token |
67
+ | **sendgrid** | contact/list/email ops | Bearer token |
68
+ | **elasticsearch** | document/index CRUD | Basic auth |
69
+ | **cloudflare** | zone/dns/worker/kv/r2/d1/pages/queue CRUD | Bearer token |
70
+ | **databricks** | sql/files/genie/catalog/table/volume/function/vector search | Bearer token |
71
+ | **splunk** | search/alert/report/user CRUD | Bearer token |
72
+ | **home-assistant** | state/service/history/config/template/event ops | Bearer token |
73
+ | **openweathermap** | current/5-day forecast | API key |
74
+ | **brandfetch** | logos/colors/fonts/company/industry lookup | Bearer token |
75
+
76
+ <details>
77
+ <summary>All 188 plugins</summary>
78
+
79
+ actionNetwork, activeCampaign, adalo, affinity, agileCrm, airtable, airtop, apiTemplateIo, asana, autopilot, bambooHr, bannerbear, baserow, beeminder, bitly, bitwarden, box, brandfetch, brevo, bubble, chargebee, circleci, ciscoWebex, clearbit, clickup, clockify, cloudflare, cockpit, coda, coingecko, contentful, convertkit, copper, cortex, currents, customerIo, databricks, deepl, demio, dhl, discord, discourse, disqus, docker, drift, dropbox, dropcontact, egoi, elasticsearch, emelia, erpnext, facebookGraph, freshdesk, freshservice, freshworksCrm, getresponse, ghost, github, gitlab, gong, gotify, gotowebinar, grafana, graphql, grist, hackernews, halopsa, harvest, helpscout, highlevel, homeAssistant, hubspot, humanticAi, hunter, intercom, iterable, jenkins, jira, keap, kobotoolbox, lemlist, line, linear, lingvanex, linkedin, lonescale, magento, mailcheck, mailchimp, mailerlite, mailgun, mailjet, mandrill, marketstack, matrix, mattermost, mautic, medium, messagebird, metabase, misp, mocean, monday, monicaCrm, msg91, nasa, netlify, netscalerAdc, nextcloud, nocodb, notion, npm, odoo, okta, oneSimpleApi, onfleet, openThesaurus, openweathermap, oura, paddle, pagerduty, paypal, peekalink, phantombuster, philipsHue, pipedrive, plivo, postbin, posthog, profitwell, pushbullet, pushcut, pushover, quickbase, quickbooks, quickchart, raindrop, reddit, rocketchat, rundeck, salesforce, salesmate, securityScorecard, segment, sendgrid, sendy, sentry, servicenow, shopify, signl4, slack, sms77, splunk, spotify, stackby, storyblok, strapi, strava, stripe, supabase, syncromsp, tapfiliate, telegram, thehive, thehiveProject, todoist, travisci, trello, twake, twilio, twist, twitter, unleashedSoftware, uplead, uproc, uptimerobot, urlscanio, vero, vonage, wekan, woocommerce, wordpress, xero, yourls, zammad, zendesk, zoho, zoom, zulip
80
+
81
+ </details>
82
+
83
+ ## Examples
84
+
85
+ ```bash
86
+ # List all available actions
87
+ runline actions
88
+
89
+ # Get Nike's brand colors
90
+ runline exec 'return await brandfetch.brand.getColors({ domain: "nike.com" })'
91
+
92
+ # Create a GitHub issue
93
+ runline exec '
94
+ return await github.issue.create({
95
+ owner: "acme", repo: "api",
96
+ title: "Bug: login broken",
97
+ labels: ["bug", "urgent"]
98
+ })
99
+ '
100
+
101
+ # Search Pipedrive deals
102
+ runline exec 'return await pipedrive.deal.search({ term: "Acme" })'
103
+
104
+ # Chain actions together
105
+ runline exec '
106
+ const contact = await hubspot.contact.get({ id: "123" });
107
+ const task = await todoist.task.create({
108
+ content: `Follow up with ${contact.properties.firstname}`,
109
+ priority: 4
110
+ });
111
+ return { contact: contact.properties.email, taskId: task.id };
112
+ '
113
+
114
+ # Discover actions from inside the sandbox
115
+ runline exec 'return brandfetch.help()'
116
+ runline exec 'return help()'
117
+
118
+ # Output as JSON (for agents)
119
+ runline exec 'return await github.repo.list({ owner: "torvalds" })' --json
120
+ ```
121
+
122
+ ## Writing a Plugin
123
+
124
+ Plugins export a function that receives a `RunlinePluginAPI` and registers actions.
125
+
126
+ ```typescript
127
+ import type { RunlinePluginAPI } from "runline";
128
+
129
+ export default function orders(rl: RunlinePluginAPI) {
130
+ rl.setName("orders");
131
+ rl.setVersion("1.0.0");
132
+
133
+ // Connection config — env vars override config.json values
134
+ rl.setConnectionSchema({
135
+ apiKey: { type: "string", required: true, env: "ORDERS_API_KEY" },
136
+ baseUrl: { type: "string", required: true, env: "ORDERS_BASE_URL" },
137
+ });
138
+
139
+ rl.registerAction("list", {
140
+ description: "List orders for an organization",
141
+ inputSchema: {
142
+ orgId: { type: "string", required: true },
143
+ status: { type: "string", required: false, description: "open, closed, or all" },
144
+ limit: { type: "number", required: false },
145
+ },
146
+ async execute(input, ctx) {
147
+ const { orgId, status, limit } = input as Record<string, unknown>;
148
+ const url = new URL(`${ctx.connection.config.baseUrl}/orgs/${orgId}/orders`);
149
+ if (status) url.searchParams.set("status", status as string);
150
+ if (limit) url.searchParams.set("limit", String(limit));
151
+
152
+ const res = await fetch(url.toString(), {
153
+ headers: { Authorization: `Bearer ${ctx.connection.config.apiKey}` },
154
+ });
155
+ if (!res.ok) throw new Error(`Orders API ${res.status}: ${await res.text()}`);
156
+ return res.json();
157
+ },
158
+ });
159
+
160
+ rl.registerAction("create", {
161
+ description: "Create a new order",
162
+ inputSchema: {
163
+ orgId: { type: "string", required: true },
164
+ customer: { type: "string", required: true },
165
+ total: { type: "number", required: true },
166
+ },
167
+ async execute(input, ctx) {
168
+ const res = await fetch(`${ctx.connection.config.baseUrl}/orders`, {
169
+ method: "POST",
170
+ headers: {
171
+ Authorization: `Bearer ${ctx.connection.config.apiKey}`,
172
+ "Content-Type": "application/json",
173
+ },
174
+ body: JSON.stringify(input),
175
+ });
176
+ if (!res.ok) throw new Error(`Orders API ${res.status}: ${await res.text()}`);
177
+ return res.json();
178
+ },
179
+ });
180
+ }
181
+ ```
182
+
183
+ Key points: `execute` runs **outside** the sandbox with full Node.js access (fetch, fs, etc). The sandbox can only call your actions through the proxy. `ctx.connection.config` holds the resolved config with env var overrides applied.
184
+
185
+ See [plugins/](plugins/) for 188 real-world examples.
186
+
187
+ ## Sandbox
188
+
189
+ Agent code runs in a [QuickJS](https://bellard.org/quickjs/) WASM sandbox:
190
+
191
+ - **No `fetch`** — network access is only through plugin actions
192
+ - **No `fs`** — no filesystem access
193
+ - **Timeout** — configurable, kills infinite loops
194
+ - **Memory limit** — configurable, prevents OOM
195
+ - **`console.log`** — captured and returned in `result.logs`
196
+ - **Plugin globals** — each installed plugin is a top-level proxy (e.g. `github`, `slack`, `brandfetch`). Dot-chain into resource and action: `github.issue.create(input)`
197
+
198
+ ## For Agents
199
+
200
+ Every command supports `--json`. Use `runline actions --json` for full schemas with input types.
201
+
202
+ ```bash
203
+ runline actions --json # all actions with schemas
204
+ runline exec '<code>' --json # structured { result, logs } output
205
+ ```
206
+
207
+ ## SDK
208
+
209
+ ```typescript
210
+ import { Runline } from "runline";
211
+ import brandfetch from "runline-plugin-brandfetch";
212
+
213
+ const rl = Runline.create({
214
+ plugins: [brandfetch],
215
+ connections: [{ name: "bf", plugin: "brandfetch", config: { apiKey: "xxx" } }],
216
+ });
217
+
218
+ const result = await rl.execute(`
219
+ const colors = await brandfetch.brand.getColors({ domain: "stripe.com" });
220
+ return colors.filter(c => c.type === "accent");
221
+ `);
222
+
223
+ console.log(result.result); // [{ hex: "#635BFF", type: "accent", brightness: 116 }]
224
+ ```
225
+
226
+ ## CLI Reference
227
+
228
+ ```bash
229
+ runline exec "<code>" # execute JS in sandbox
230
+ runline exec -f ./script.js # execute a file
231
+ runline actions # list all actions
232
+ runline plugin install <source> # install from git/npm/local
233
+ runline plugin list # list installed plugins
234
+ runline plugin remove <name> # remove a plugin
235
+ runline connection add <n> -p <plugin> -s key=val # add connection
236
+ runline connection list # list connections
237
+ runline connection remove <name> # remove a connection
238
+ runline init # create .runline/ directory
239
+ ```
240
+
241
+ ## Configuration
242
+
243
+ `.runline/config.json`:
244
+
245
+ ```json
246
+ {
247
+ "connections": [
248
+ { "name": "gh", "plugin": "github", "config": { "token": "ghp_xxx" } },
249
+ { "name": "bf", "plugin": "brandfetch", "config": { "apiKey": "xxx" } }
250
+ ],
251
+ "timeoutMs": 30000,
252
+ "memoryLimitBytes": 67108864
253
+ }
254
+ ```
255
+
256
+ Env vars override config values. Plugins declare env var names in their connection schema (e.g. `GITHUB_TOKEN`).
257
+
258
+ ## Development
259
+
260
+ ```bash
261
+ npm install
262
+ npm run dev -- exec 'return 1 + 2'
263
+ npm test
264
+ npm run check
265
+ ```
266
+
267
+ ## How It Relates to dripline
268
+
269
+ [dripline](https://github.com/Michaelliv/dripline) is **query mode** — SQL tables over live APIs. runline is **code mode** — JavaScript actions over the same APIs. Same plugin architecture, same connection config, different interface. Use dripline when you want to `SELECT` rows; use runline when you want to `create`, `update`, `delete`, or chain multiple API calls together.
270
+
271
+ ## License
272
+
273
+ MIT
@@ -0,0 +1,3 @@
1
+ export declare function actions(options: {
2
+ json?: boolean;
3
+ }): Promise<void>;
@@ -0,0 +1,43 @@
1
+ import chalk from "chalk";
2
+ import { loadAllPlugins } from "../plugin/loader.js";
3
+ import { registry } from "../plugin/registry.js";
4
+ import { printJson } from "../utils/output.js";
5
+ export async function actions(options) {
6
+ await loadAllPlugins();
7
+ const all = registry.getAllActions();
8
+ if (options.json) {
9
+ printJson(all.map(({ plugin, action }) => ({
10
+ plugin,
11
+ action: action.name,
12
+ description: action.description,
13
+ inputSchema: action.inputSchema,
14
+ })));
15
+ return;
16
+ }
17
+ if (all.length === 0) {
18
+ console.log("No actions registered. Install a plugin first.");
19
+ return;
20
+ }
21
+ const grouped = new Map();
22
+ for (const entry of all) {
23
+ const list = grouped.get(entry.plugin) ?? [];
24
+ list.push(entry);
25
+ grouped.set(entry.plugin, list);
26
+ }
27
+ for (const [plugin, entries] of grouped) {
28
+ console.log(chalk.bold(`\n${plugin}`));
29
+ for (const { action } of entries) {
30
+ const path = chalk.cyan(`${plugin}.${action.name}`);
31
+ const desc = action.description
32
+ ? chalk.dim(` — ${action.description}`)
33
+ : "";
34
+ const schema = action.inputSchema
35
+ ? chalk.dim(` (${Object.entries(action.inputSchema)
36
+ .map(([k, v]) => `${k}: ${v.type}${v.required ? "" : "?"}`)
37
+ .join(", ")})`)
38
+ : "";
39
+ console.log(` ${path}${schema}${desc}`);
40
+ }
41
+ }
42
+ console.log();
43
+ }
@@ -0,0 +1,11 @@
1
+ export declare function connectionAdd(name: string, options: {
2
+ plugin: string;
3
+ set: string[];
4
+ json?: boolean;
5
+ }): Promise<void>;
6
+ export declare function connectionRemove(name: string, options: {
7
+ json?: boolean;
8
+ }): Promise<void>;
9
+ export declare function connectionList(options: {
10
+ json?: boolean;
11
+ }): Promise<void>;
@@ -0,0 +1,56 @@
1
+ import chalk from "chalk";
2
+ import { addConnection, loadConfig, removeConnection, } from "../config/loader.js";
3
+ import { printError, printJson, printSuccess } from "../utils/output.js";
4
+ export async function connectionAdd(name, options) {
5
+ const configValues = {};
6
+ for (const kv of options.set) {
7
+ const eq = kv.indexOf("=");
8
+ if (eq < 0) {
9
+ printError(`Invalid --set value: ${kv} (expected key=value)`);
10
+ process.exit(1);
11
+ }
12
+ configValues[kv.slice(0, eq)] = kv.slice(eq + 1);
13
+ }
14
+ addConnection(name, options.plugin, configValues);
15
+ if (options.json) {
16
+ printJson({ ok: true, name, plugin: options.plugin });
17
+ }
18
+ else {
19
+ printSuccess(`Connection ${chalk.bold(name)} added (plugin: ${options.plugin})`);
20
+ }
21
+ }
22
+ export async function connectionRemove(name, options) {
23
+ const removed = removeConnection(name);
24
+ if (!removed) {
25
+ printError(`Connection "${name}" not found`);
26
+ process.exit(1);
27
+ }
28
+ if (options.json) {
29
+ printJson({ ok: true, removed: name });
30
+ }
31
+ else {
32
+ printSuccess(`Connection ${chalk.bold(name)} removed`);
33
+ }
34
+ }
35
+ export async function connectionList(options) {
36
+ const config = loadConfig();
37
+ const connections = config.connections;
38
+ if (options.json) {
39
+ printJson(connections.map((c) => ({
40
+ name: c.name,
41
+ plugin: c.plugin,
42
+ config: Object.fromEntries(Object.entries(c.config).map(([k, v]) => [
43
+ k,
44
+ typeof v === "string" && v.length > 8 ? `${v.slice(0, 4)}...` : v,
45
+ ])),
46
+ })));
47
+ return;
48
+ }
49
+ if (connections.length === 0) {
50
+ console.log("No connections configured.");
51
+ return;
52
+ }
53
+ for (const c of connections) {
54
+ console.log(` ${chalk.bold(c.name)} (${c.plugin})`);
55
+ }
56
+ }
@@ -0,0 +1,5 @@
1
+ export declare function exec(code: string, options: {
2
+ file?: boolean;
3
+ json?: boolean;
4
+ quiet?: boolean;
5
+ }): Promise<void>;
@@ -0,0 +1,46 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { loadConfig } from "../config/loader.js";
3
+ import { ExecutionEngine } from "../core/engine.js";
4
+ import { loadAllPlugins } from "../plugin/loader.js";
5
+ import { registry } from "../plugin/registry.js";
6
+ import { printError, printJson } from "../utils/output.js";
7
+ export async function exec(code, options) {
8
+ await loadAllPlugins();
9
+ const config = loadConfig();
10
+ const engine = new ExecutionEngine(registry, config);
11
+ if (options.file) {
12
+ if (!existsSync(code)) {
13
+ printError(`File not found: ${code}`);
14
+ process.exit(1);
15
+ }
16
+ code = readFileSync(code, "utf-8");
17
+ }
18
+ const result = await engine.execute(code);
19
+ if (result.error) {
20
+ if (options.json) {
21
+ printJson({ error: result.error, logs: result.logs });
22
+ }
23
+ else {
24
+ if (result.logs.length > 0 && !options.quiet) {
25
+ for (const log of result.logs)
26
+ console.error(log);
27
+ }
28
+ printError(result.error);
29
+ }
30
+ process.exit(1);
31
+ }
32
+ if (options.json) {
33
+ printJson({ result: result.result, logs: result.logs });
34
+ }
35
+ else {
36
+ if (result.logs.length > 0 && !options.quiet) {
37
+ for (const log of result.logs)
38
+ console.error(log);
39
+ }
40
+ if (result.result !== null && result.result !== undefined) {
41
+ console.log(typeof result.result === "string"
42
+ ? result.result
43
+ : JSON.stringify(result.result, null, 2));
44
+ }
45
+ }
46
+ }
@@ -0,0 +1,4 @@
1
+ export declare function init(options: {
2
+ json?: boolean;
3
+ quiet?: boolean;
4
+ }): Promise<void>;
@@ -0,0 +1,26 @@
1
+ import { existsSync, mkdirSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import chalk from "chalk";
4
+ import { DEFAULT_CONFIG } from "../config/types.js";
5
+ import { printJson, printSuccess, printWarn } from "../utils/output.js";
6
+ export async function init(options) {
7
+ const dir = join(process.cwd(), ".runline");
8
+ if (existsSync(dir)) {
9
+ if (options.json) {
10
+ printJson({ ok: true, exists: true, path: dir });
11
+ }
12
+ else if (!options.quiet) {
13
+ printWarn(`${chalk.bold(".runline/")} already exists`);
14
+ }
15
+ return;
16
+ }
17
+ mkdirSync(dir, { recursive: true });
18
+ mkdirSync(join(dir, "plugins"), { recursive: true });
19
+ writeFileSync(join(dir, "config.json"), `${JSON.stringify(DEFAULT_CONFIG, null, 2)}\n`);
20
+ if (options.json) {
21
+ printJson({ ok: true, path: dir });
22
+ }
23
+ else if (!options.quiet) {
24
+ printSuccess(`Created ${chalk.bold(".runline/")} in current directory`);
25
+ }
26
+ }
@@ -0,0 +1,10 @@
1
+ export declare function pluginInstall(source: string, options: {
2
+ global?: boolean;
3
+ json?: boolean;
4
+ }): Promise<void>;
5
+ export declare function pluginRemove(name: string, options: {
6
+ json?: boolean;
7
+ }): Promise<void>;
8
+ export declare function pluginList(options: {
9
+ json?: boolean;
10
+ }): Promise<void>;