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.
- package/.pi/extensions/runline-context/index.ts +135 -0
- package/.pi/extensions/runline-context/package.json +17 -0
- package/README.md +273 -0
- package/dist/commands/actions.d.ts +3 -0
- package/dist/commands/actions.js +43 -0
- package/dist/commands/connection.d.ts +11 -0
- package/dist/commands/connection.js +56 -0
- package/dist/commands/exec.d.ts +5 -0
- package/dist/commands/exec.js +46 -0
- package/dist/commands/init.d.ts +4 -0
- package/dist/commands/init.js +26 -0
- package/dist/commands/plugin.d.ts +10 -0
- package/dist/commands/plugin.js +57 -0
- package/dist/config/index.d.ts +3 -0
- package/dist/config/index.js +2 -0
- package/dist/config/loader.d.ts +11 -0
- package/dist/config/loader.js +82 -0
- package/dist/config/types.d.ts +9 -0
- package/dist/config/types.js +5 -0
- package/dist/core/engine.d.ts +21 -0
- package/dist/core/engine.js +280 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +9 -0
- package/dist/main.d.ts +2 -0
- package/dist/main.js +127 -0
- package/dist/plugin/api.d.ts +32 -0
- package/dist/plugin/api.js +68 -0
- package/dist/plugin/installer.d.ts +27 -0
- package/dist/plugin/installer.js +181 -0
- package/dist/plugin/loader.d.ts +13 -0
- package/dist/plugin/loader.js +164 -0
- package/dist/plugin/registry.d.ts +18 -0
- package/dist/plugin/registry.js +43 -0
- package/dist/plugin/types.d.ts +40 -0
- package/dist/plugin/types.js +1 -0
- package/dist/plugins/actionNetwork/src/index.js +353 -0
- package/dist/plugins/activeCampaign/src/index.js +711 -0
- package/dist/plugins/adalo/src/index.js +131 -0
- package/dist/plugins/affinity/src/index.js +279 -0
- package/dist/plugins/agileCrm/src/index.js +415 -0
- package/dist/plugins/airtable/src/index.js +280 -0
- package/dist/plugins/airtop/src/index.js +527 -0
- package/dist/plugins/apiTemplateIo/src/index.js +86 -0
- package/dist/plugins/asana/src/index.js +413 -0
- package/dist/plugins/autopilot/src/index.js +203 -0
- package/dist/plugins/bambooHr/src/index.js +252 -0
- package/dist/plugins/bannerbear/src/index.js +100 -0
- package/dist/plugins/baserow/src/index.js +180 -0
- package/dist/plugins/beeminder/src/index.js +298 -0
- package/dist/plugins/bitly/src/index.js +107 -0
- package/dist/plugins/bitwarden/src/index.js +383 -0
- package/dist/plugins/box/src/index.js +300 -0
- package/dist/plugins/brandfetch/src/index.js +80 -0
- package/dist/plugins/brevo/src/index.js +305 -0
- package/dist/plugins/bubble/src/index.js +181 -0
- package/dist/plugins/chargebee/src/index.js +126 -0
- package/dist/plugins/circleci/src/index.js +111 -0
- package/dist/plugins/ciscoWebex/src/index.js +245 -0
- package/dist/plugins/clearbit/src/index.js +103 -0
- package/dist/plugins/clickup/src/index.js +1043 -0
- package/dist/plugins/clockify/src/index.js +443 -0
- package/dist/plugins/cloudflare/src/index.js +93 -0
- package/dist/plugins/cockpit/src/index.js +131 -0
- package/dist/plugins/coda/src/index.js +327 -0
- package/dist/plugins/coingecko/src/index.js +244 -0
- package/dist/plugins/contentful/src/index.js +146 -0
- package/dist/plugins/convertkit/src/index.js +270 -0
- package/dist/plugins/copper/src/index.js +140 -0
- package/dist/plugins/cortex/src/index.js +147 -0
- package/dist/plugins/currents/src/index.js +405 -0
- package/dist/plugins/customerIo/src/index.js +184 -0
- package/dist/plugins/databricks/src/index.js +342 -0
- package/dist/plugins/deepl/src/index.js +87 -0
- package/dist/plugins/demio/src/index.js +111 -0
- package/dist/plugins/dhl/src/index.js +40 -0
- package/dist/plugins/discord/src/index.js +275 -0
- package/dist/plugins/discourse/src/index.js +273 -0
- package/dist/plugins/disqus/src/index.js +145 -0
- package/dist/plugins/docker/src/index.js +76 -0
- package/dist/plugins/drift/src/index.js +89 -0
- package/dist/plugins/dropbox/src/index.js +159 -0
- package/dist/plugins/dropcontact/src/index.js +59 -0
- package/dist/plugins/egoi/src/index.js +151 -0
- package/dist/plugins/elasticsearch/src/index.js +157 -0
- package/dist/plugins/emelia/src/index.js +174 -0
- package/dist/plugins/erpnext/src/index.js +121 -0
- package/dist/plugins/facebookGraph/src/index.js +57 -0
- package/dist/plugins/freshdesk/src/index.js +320 -0
- package/dist/plugins/freshservice/src/index.js +146 -0
- package/dist/plugins/freshworksCrm/src/index.js +149 -0
- package/dist/plugins/getresponse/src/index.js +140 -0
- package/dist/plugins/ghost/src/index.js +192 -0
- package/dist/plugins/github/src/index.js +630 -0
- package/dist/plugins/gitlab/src/index.js +358 -0
- package/dist/plugins/gong/src/index.js +126 -0
- package/dist/plugins/gotify/src/index.js +77 -0
- package/dist/plugins/gotowebinar/src/index.js +316 -0
- package/dist/plugins/grafana/src/index.js +250 -0
- package/dist/plugins/graphql/src/index.js +78 -0
- package/dist/plugins/grist/src/index.js +106 -0
- package/dist/plugins/hackernews/src/index.js +89 -0
- package/dist/plugins/halopsa/src/index.js +79 -0
- package/dist/plugins/harvest/src/index.js +163 -0
- package/dist/plugins/helpscout/src/index.js +176 -0
- package/dist/plugins/highlevel/src/index.js +172 -0
- package/dist/plugins/homeAssistant/src/index.js +148 -0
- package/dist/plugins/hubspot/src/index.js +176 -0
- package/dist/plugins/humanticAi/src/index.js +60 -0
- package/dist/plugins/hunter/src/index.js +59 -0
- package/dist/plugins/intercom/src/index.js +156 -0
- package/dist/plugins/iterable/src/index.js +139 -0
- package/dist/plugins/jenkins/src/index.js +132 -0
- package/dist/plugins/jira/src/index.js +229 -0
- package/dist/plugins/keap/src/index.js +502 -0
- package/dist/plugins/kobotoolbox/src/index.js +281 -0
- package/dist/plugins/lemlist/src/index.js +231 -0
- package/dist/plugins/linear/src/index.js +133 -0
- package/dist/plugins/lingvanex/src/index.js +31 -0
- package/dist/plugins/linkedin/src/index.js +80 -0
- package/dist/plugins/lonescale/src/index.js +119 -0
- package/dist/plugins/magento/src/index.js +300 -0
- package/dist/plugins/mailcheck/src/index.js +27 -0
- package/dist/plugins/mailchimp/src/index.js +321 -0
- package/dist/plugins/mailerlite/src/index.js +123 -0
- package/dist/plugins/mailgun/src/index.js +48 -0
- package/dist/plugins/mailjet/src/index.js +155 -0
- package/dist/plugins/mandrill/src/index.js +145 -0
- package/dist/plugins/marketstack/src/index.js +97 -0
- package/dist/plugins/matrix/src/index.js +194 -0
- package/dist/plugins/mattermost/src/index.js +331 -0
- package/dist/plugins/mautic/src/index.js +311 -0
- package/dist/plugins/medium/src/index.js +77 -0
- package/dist/plugins/messagebird/src/index.js +57 -0
- package/dist/plugins/metabase/src/index.js +130 -0
- package/dist/plugins/misp/src/index.js +476 -0
- package/dist/plugins/mocean/src/index.js +67 -0
- package/dist/plugins/monday/src/index.js +231 -0
- package/dist/plugins/monicaCrm/src/index.js +52 -0
- package/dist/plugins/msg91/src/index.js +31 -0
- package/dist/plugins/nasa/src/index.js +146 -0
- package/dist/plugins/netlify/src/index.js +151 -0
- package/dist/plugins/netscalerAdc/src/index.js +131 -0
- package/dist/plugins/nextcloud/src/index.js +263 -0
- package/dist/plugins/nocodb/src/index.js +130 -0
- package/dist/plugins/notion/src/index.js +112 -0
- package/dist/plugins/npm/src/index.js +104 -0
- package/dist/plugins/odoo/src/index.js +157 -0
- package/dist/plugins/okta/src/index.js +141 -0
- package/dist/plugins/oneSimpleApi/src/index.js +155 -0
- package/dist/plugins/onfleet/src/index.js +254 -0
- package/dist/plugins/openThesaurus/src/index.js +32 -0
- package/dist/plugins/openweathermap/src/index.js +60 -0
- package/dist/plugins/oura/src/index.js +62 -0
- package/dist/plugins/paddle/src/index.js +247 -0
- package/dist/plugins/pagerduty/src/index.js +201 -0
- package/dist/plugins/paypal/src/index.js +106 -0
- package/dist/plugins/peekalink/src/index.js +35 -0
- package/dist/plugins/phantombuster/src/index.js +94 -0
- package/dist/plugins/philipsHue/src/index.js +98 -0
- package/dist/plugins/pipedrive/src/index.js +169 -0
- package/dist/plugins/plivo/src/index.js +66 -0
- package/dist/plugins/postbin/src/index.js +93 -0
- package/dist/plugins/posthog/src/index.js +113 -0
- package/dist/plugins/profitwell/src/index.js +50 -0
- package/dist/plugins/pushbullet/src/index.js +102 -0
- package/dist/plugins/pushcut/src/index.js +39 -0
- package/dist/plugins/pushover/src/index.js +65 -0
- package/dist/plugins/quickbase/src/index.js +153 -0
- package/dist/plugins/quickbooks/src/index.js +73 -0
- package/dist/plugins/quickchart/src/index.js +36 -0
- package/dist/plugins/raindrop/src/index.js +209 -0
- package/dist/plugins/reddit/src/index.js +185 -0
- package/dist/plugins/rocketchat/src/index.js +53 -0
- package/dist/plugins/rundeck/src/index.js +62 -0
- package/dist/plugins/salesforce/src/index.js +94 -0
- package/dist/plugins/salesmate/src/index.js +83 -0
- package/dist/plugins/securityScorecard/src/index.js +200 -0
- package/dist/plugins/segment/src/index.js +125 -0
- package/dist/plugins/sendgrid/src/index.js +187 -0
- package/dist/plugins/sendy/src/index.js +138 -0
- package/dist/plugins/sentry/src/index.js +233 -0
- package/dist/plugins/servicenow/src/index.js +108 -0
- package/dist/plugins/shopify/src/index.js +222 -0
- package/dist/plugins/signl4/src/index.js +61 -0
- package/dist/plugins/slack/src/index.js +236 -0
- package/dist/plugins/sms77/src/index.js +63 -0
- package/dist/plugins/splunk/src/index.js +207 -0
- package/dist/plugins/spotify/src/index.js +188 -0
- package/dist/plugins/stackby/src/index.js +82 -0
- package/dist/plugins/storyblok/src/index.js +141 -0
- package/dist/plugins/strapi/src/index.js +152 -0
- package/dist/plugins/strava/src/index.js +137 -0
- package/dist/plugins/stripe/src/index.js +222 -0
- package/dist/plugins/supabase/src/index.js +121 -0
- package/dist/plugins/syncromsp/src/index.js +255 -0
- package/dist/plugins/tapfiliate/src/index.js +125 -0
- package/dist/plugins/telegram/src/index.js +233 -0
- package/dist/plugins/thehive/src/index.js +142 -0
- package/dist/plugins/thehiveProject/src/index.js +194 -0
- package/dist/plugins/todoist/src/index.js +244 -0
- package/dist/plugins/travisci/src/index.js +71 -0
- package/dist/plugins/trello/src/index.js +341 -0
- package/dist/plugins/twake/src/index.js +40 -0
- package/dist/plugins/twilio/src/index.js +75 -0
- package/dist/plugins/twist/src/index.js +90 -0
- package/dist/plugins/twitter/src/index.js +123 -0
- package/dist/plugins/unleashedSoftware/src/index.js +84 -0
- package/dist/plugins/uplead/src/index.js +59 -0
- package/dist/plugins/uproc/src/index.js +34 -0
- package/dist/plugins/uptimerobot/src/index.js +264 -0
- package/dist/plugins/urlscanio/src/index.js +64 -0
- package/dist/plugins/vero/src/index.js +80 -0
- package/dist/plugins/vonage/src/index.js +42 -0
- package/dist/plugins/wekan/src/index.js +91 -0
- package/dist/plugins/woocommerce/src/index.js +92 -0
- package/dist/plugins/wordpress/src/index.js +121 -0
- package/dist/plugins/xero/src/index.js +136 -0
- package/dist/plugins/yourls/src/index.js +56 -0
- package/dist/plugins/zammad/src/index.js +91 -0
- package/dist/plugins/zendesk/src/index.js +137 -0
- package/dist/plugins/zoho/src/index.js +85 -0
- package/dist/plugins/zoom/src/index.js +122 -0
- package/dist/plugins/zulip/src/index.js +170 -0
- package/dist/sdk.d.ts +38 -0
- package/dist/sdk.js +105 -0
- package/dist/utils/cli.d.ts +13 -0
- package/dist/utils/cli.js +32 -0
- package/dist/utils/output.d.ts +4 -0
- package/dist/utils/output.js +13 -0
- package/package.json +57 -0
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
async function apiRequest(host, apiKey, apiUsername, method, endpoint, body, qs) {
|
|
2
|
+
const url = new URL(`${host}${endpoint}`);
|
|
3
|
+
if (qs) {
|
|
4
|
+
for (const [k, v] of Object.entries(qs)) {
|
|
5
|
+
if (v !== undefined && v !== null)
|
|
6
|
+
url.searchParams.set(k, String(v));
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
const opts = {
|
|
10
|
+
method,
|
|
11
|
+
headers: {
|
|
12
|
+
"Content-Type": "application/json",
|
|
13
|
+
"Api-Key": apiKey,
|
|
14
|
+
"Api-Username": apiUsername,
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
if (body && Object.keys(body).length > 0 && method !== "GET") {
|
|
18
|
+
opts.body = JSON.stringify(body);
|
|
19
|
+
}
|
|
20
|
+
const res = await fetch(url.toString(), opts);
|
|
21
|
+
if (!res.ok)
|
|
22
|
+
throw new Error(`Discourse API error ${res.status}: ${await res.text()}`);
|
|
23
|
+
if (res.status === 204)
|
|
24
|
+
return { success: true };
|
|
25
|
+
return res.json();
|
|
26
|
+
}
|
|
27
|
+
function getConn(ctx) {
|
|
28
|
+
const cfg = ctx.connection.config;
|
|
29
|
+
return {
|
|
30
|
+
host: cfg.host.replace(/\/$/, ""),
|
|
31
|
+
apiKey: cfg.apiKey,
|
|
32
|
+
apiUsername: cfg.apiUsername ?? "system",
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function req(ctx, method, endpoint, body, qs) {
|
|
36
|
+
const { host, apiKey, apiUsername } = getConn(ctx);
|
|
37
|
+
return apiRequest(host, apiKey, apiUsername, method, endpoint, body, qs);
|
|
38
|
+
}
|
|
39
|
+
export default function discourse(rl) {
|
|
40
|
+
rl.setName("discourse");
|
|
41
|
+
rl.setVersion("0.1.0");
|
|
42
|
+
rl.setConnectionSchema({
|
|
43
|
+
host: { type: "string", required: true, description: "Discourse instance URL (e.g. https://forum.example.com)", env: "DISCOURSE_HOST" },
|
|
44
|
+
apiKey: { type: "string", required: true, description: "Discourse API key", env: "DISCOURSE_API_KEY" },
|
|
45
|
+
apiUsername: { type: "string", required: false, description: "API username (default: system)", env: "DISCOURSE_API_USERNAME", default: "system" },
|
|
46
|
+
});
|
|
47
|
+
// ── Category ────────────────────────────────────────
|
|
48
|
+
rl.registerAction("category.create", {
|
|
49
|
+
description: "Create a category",
|
|
50
|
+
inputSchema: {
|
|
51
|
+
name: { type: "string", required: true, description: "Category name" },
|
|
52
|
+
color: { type: "string", required: true, description: "Hex color (e.g. 0088CC)" },
|
|
53
|
+
textColor: { type: "string", required: true, description: "Text hex color (e.g. FFFFFF)" },
|
|
54
|
+
},
|
|
55
|
+
async execute(input, ctx) {
|
|
56
|
+
const { name, color, textColor } = input;
|
|
57
|
+
const data = (await req(ctx, "POST", "/categories.json", { name, color, text_color: textColor }));
|
|
58
|
+
return data.category;
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
rl.registerAction("category.list", {
|
|
62
|
+
description: "List all categories",
|
|
63
|
+
inputSchema: { limit: { type: "number", required: false, description: "Max results" } },
|
|
64
|
+
async execute(input, ctx) {
|
|
65
|
+
const { limit } = (input ?? {});
|
|
66
|
+
const data = (await req(ctx, "GET", "/categories.json"));
|
|
67
|
+
const list = data.category_list.categories;
|
|
68
|
+
if (limit)
|
|
69
|
+
return list.slice(0, limit);
|
|
70
|
+
return list;
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
rl.registerAction("category.update", {
|
|
74
|
+
description: "Update a category",
|
|
75
|
+
inputSchema: {
|
|
76
|
+
categoryId: { type: "string", required: true, description: "Category ID" },
|
|
77
|
+
name: { type: "string", required: true, description: "New name" },
|
|
78
|
+
color: { type: "string", required: false, description: "New hex color" },
|
|
79
|
+
textColor: { type: "string", required: false, description: "New text color" },
|
|
80
|
+
},
|
|
81
|
+
async execute(input, ctx) {
|
|
82
|
+
const { categoryId, name, color, textColor } = input;
|
|
83
|
+
const body = { name };
|
|
84
|
+
if (color)
|
|
85
|
+
body.color = color;
|
|
86
|
+
if (textColor)
|
|
87
|
+
body.text_color = textColor;
|
|
88
|
+
const data = (await req(ctx, "PUT", `/categories/${categoryId}.json`, body));
|
|
89
|
+
return data.category;
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
// ── Group ───────────────────────────────────────────
|
|
93
|
+
rl.registerAction("group.create", {
|
|
94
|
+
description: "Create a group",
|
|
95
|
+
inputSchema: { name: { type: "string", required: true, description: "Group name" } },
|
|
96
|
+
async execute(input, ctx) {
|
|
97
|
+
const { name } = input;
|
|
98
|
+
const data = (await req(ctx, "POST", "/admin/groups.json", { group: { name } }));
|
|
99
|
+
return data.basic_group;
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
rl.registerAction("group.get", {
|
|
103
|
+
description: "Get a group by name",
|
|
104
|
+
inputSchema: { name: { type: "string", required: true, description: "Group name" } },
|
|
105
|
+
async execute(input, ctx) {
|
|
106
|
+
const { name } = input;
|
|
107
|
+
const data = (await req(ctx, "GET", `/groups/${name}`));
|
|
108
|
+
return data.group;
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
rl.registerAction("group.list", {
|
|
112
|
+
description: "List all groups",
|
|
113
|
+
inputSchema: { limit: { type: "number", required: false, description: "Max results" } },
|
|
114
|
+
async execute(input, ctx) {
|
|
115
|
+
const { limit } = (input ?? {});
|
|
116
|
+
const data = (await req(ctx, "GET", "/groups.json"));
|
|
117
|
+
const groups = data.groups;
|
|
118
|
+
if (limit)
|
|
119
|
+
return groups.slice(0, limit);
|
|
120
|
+
return groups;
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
rl.registerAction("group.update", {
|
|
124
|
+
description: "Update a group",
|
|
125
|
+
inputSchema: {
|
|
126
|
+
groupId: { type: "string", required: true, description: "Group ID" },
|
|
127
|
+
name: { type: "string", required: true, description: "New group name" },
|
|
128
|
+
},
|
|
129
|
+
async execute(input, ctx) {
|
|
130
|
+
const { groupId, name } = input;
|
|
131
|
+
return req(ctx, "PUT", `/groups/${groupId}.json`, { group: { name } });
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
// ── Post ────────────────────────────────────────────
|
|
135
|
+
rl.registerAction("post.create", {
|
|
136
|
+
description: "Create a post (new topic or reply)",
|
|
137
|
+
inputSchema: {
|
|
138
|
+
title: { type: "string", required: false, description: "Topic title (required for new topics)" },
|
|
139
|
+
content: { type: "string", required: true, description: "Post content (raw markdown)" },
|
|
140
|
+
categoryId: { type: "number", required: false, description: "Category ID (for new topics)" },
|
|
141
|
+
topicId: { type: "number", required: false, description: "Topic ID (for replies)" },
|
|
142
|
+
replyToPostNumber: { type: "number", required: false, description: "Post number to reply to" },
|
|
143
|
+
},
|
|
144
|
+
async execute(input, ctx) {
|
|
145
|
+
const { title, content, categoryId, topicId, replyToPostNumber } = input;
|
|
146
|
+
const body = { raw: content };
|
|
147
|
+
if (title)
|
|
148
|
+
body.title = title;
|
|
149
|
+
if (categoryId)
|
|
150
|
+
body.category = categoryId;
|
|
151
|
+
if (topicId)
|
|
152
|
+
body.topic_id = topicId;
|
|
153
|
+
if (replyToPostNumber)
|
|
154
|
+
body.reply_to_post_number = replyToPostNumber;
|
|
155
|
+
return req(ctx, "POST", "/posts.json", body);
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
rl.registerAction("post.get", {
|
|
159
|
+
description: "Get a post by ID",
|
|
160
|
+
inputSchema: { postId: { type: "string", required: true, description: "Post ID" } },
|
|
161
|
+
async execute(input, ctx) {
|
|
162
|
+
return req(ctx, "GET", `/posts/${input.postId}`);
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
rl.registerAction("post.list", {
|
|
166
|
+
description: "List latest posts",
|
|
167
|
+
inputSchema: { limit: { type: "number", required: false, description: "Max results" } },
|
|
168
|
+
async execute(input, ctx) {
|
|
169
|
+
const { limit } = (input ?? {});
|
|
170
|
+
const data = (await req(ctx, "GET", "/posts.json"));
|
|
171
|
+
const posts = data.latest_posts;
|
|
172
|
+
if (limit)
|
|
173
|
+
return posts.slice(0, limit);
|
|
174
|
+
return posts;
|
|
175
|
+
},
|
|
176
|
+
});
|
|
177
|
+
rl.registerAction("post.update", {
|
|
178
|
+
description: "Update a post",
|
|
179
|
+
inputSchema: {
|
|
180
|
+
postId: { type: "string", required: true, description: "Post ID" },
|
|
181
|
+
content: { type: "string", required: true, description: "New content (raw markdown)" },
|
|
182
|
+
editReason: { type: "string", required: false, description: "Reason for edit" },
|
|
183
|
+
},
|
|
184
|
+
async execute(input, ctx) {
|
|
185
|
+
const { postId, content, editReason } = input;
|
|
186
|
+
const body = { raw: content };
|
|
187
|
+
if (editReason)
|
|
188
|
+
body.edit_reason = editReason;
|
|
189
|
+
const data = (await req(ctx, "PUT", `/posts/${postId}.json`, body));
|
|
190
|
+
return data.post;
|
|
191
|
+
},
|
|
192
|
+
});
|
|
193
|
+
// ── User ────────────────────────────────────────────
|
|
194
|
+
rl.registerAction("user.create", {
|
|
195
|
+
description: "Create a user",
|
|
196
|
+
inputSchema: {
|
|
197
|
+
name: { type: "string", required: true, description: "Full name" },
|
|
198
|
+
email: { type: "string", required: true, description: "Email address" },
|
|
199
|
+
username: { type: "string", required: true, description: "Username" },
|
|
200
|
+
password: { type: "string", required: true, description: "Password" },
|
|
201
|
+
active: { type: "boolean", required: false, description: "Create as active (default: false)" },
|
|
202
|
+
},
|
|
203
|
+
async execute(input, ctx) {
|
|
204
|
+
const { name, email, username, password, active } = input;
|
|
205
|
+
const body = { name, email, username, password };
|
|
206
|
+
if (active !== undefined)
|
|
207
|
+
body.active = active;
|
|
208
|
+
return req(ctx, "POST", "/users.json", body);
|
|
209
|
+
},
|
|
210
|
+
});
|
|
211
|
+
rl.registerAction("user.get", {
|
|
212
|
+
description: "Get a user by username or external ID",
|
|
213
|
+
inputSchema: {
|
|
214
|
+
username: { type: "string", required: false, description: "Username" },
|
|
215
|
+
externalId: { type: "string", required: false, description: "External (SSO) ID" },
|
|
216
|
+
},
|
|
217
|
+
async execute(input, ctx) {
|
|
218
|
+
const { username, externalId } = (input ?? {});
|
|
219
|
+
if (externalId)
|
|
220
|
+
return req(ctx, "GET", `/u/by-external/${externalId}.json`);
|
|
221
|
+
if (username)
|
|
222
|
+
return req(ctx, "GET", `/users/${username}`);
|
|
223
|
+
throw new Error("Provide either username or externalId");
|
|
224
|
+
},
|
|
225
|
+
});
|
|
226
|
+
rl.registerAction("user.list", {
|
|
227
|
+
description: "List users (admin)",
|
|
228
|
+
inputSchema: {
|
|
229
|
+
flag: { type: "string", required: false, description: "Filter: active (default), new, staff, suspended, blocked" },
|
|
230
|
+
limit: { type: "number", required: false, description: "Max results" },
|
|
231
|
+
order: { type: "string", required: false, description: "Order by field" },
|
|
232
|
+
asc: { type: "boolean", required: false, description: "Ascending order" },
|
|
233
|
+
showEmails: { type: "boolean", required: false, description: "Include email addresses" },
|
|
234
|
+
},
|
|
235
|
+
async execute(input, ctx) {
|
|
236
|
+
const { flag = "active", limit, order, asc, showEmails } = (input ?? {});
|
|
237
|
+
const qs = {};
|
|
238
|
+
if (order)
|
|
239
|
+
qs.order = order;
|
|
240
|
+
if (asc !== undefined)
|
|
241
|
+
qs.asc = asc;
|
|
242
|
+
if (showEmails)
|
|
243
|
+
qs.show_emails = true;
|
|
244
|
+
let data = (await req(ctx, "GET", `/admin/users/list/${flag}.json`, undefined, qs));
|
|
245
|
+
if (limit)
|
|
246
|
+
data = data.slice(0, limit);
|
|
247
|
+
return data;
|
|
248
|
+
},
|
|
249
|
+
});
|
|
250
|
+
// ── User Group ──────────────────────────────────────
|
|
251
|
+
rl.registerAction("userGroup.add", {
|
|
252
|
+
description: "Add users to a group",
|
|
253
|
+
inputSchema: {
|
|
254
|
+
groupId: { type: "string", required: true, description: "Group ID" },
|
|
255
|
+
usernames: { type: "string", required: true, description: "Comma-separated usernames" },
|
|
256
|
+
},
|
|
257
|
+
async execute(input, ctx) {
|
|
258
|
+
const { groupId, usernames } = input;
|
|
259
|
+
return req(ctx, "PUT", `/groups/${groupId}/members.json`, { usernames });
|
|
260
|
+
},
|
|
261
|
+
});
|
|
262
|
+
rl.registerAction("userGroup.remove", {
|
|
263
|
+
description: "Remove users from a group",
|
|
264
|
+
inputSchema: {
|
|
265
|
+
groupId: { type: "string", required: true, description: "Group ID" },
|
|
266
|
+
usernames: { type: "string", required: true, description: "Comma-separated usernames" },
|
|
267
|
+
},
|
|
268
|
+
async execute(input, ctx) {
|
|
269
|
+
const { groupId, usernames } = input;
|
|
270
|
+
return req(ctx, "DELETE", `/groups/${groupId}/members.json`, { usernames });
|
|
271
|
+
},
|
|
272
|
+
});
|
|
273
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
const BASE_URL = "https://disqus.com/api/3.0";
|
|
2
|
+
async function apiRequest(apiKey, endpoint, qs) {
|
|
3
|
+
const url = new URL(`${BASE_URL}/${endpoint}`);
|
|
4
|
+
url.searchParams.set("api_key", apiKey);
|
|
5
|
+
if (qs) {
|
|
6
|
+
for (const [k, v] of Object.entries(qs)) {
|
|
7
|
+
if (v === undefined || v === null)
|
|
8
|
+
continue;
|
|
9
|
+
if (Array.isArray(v)) {
|
|
10
|
+
for (const item of v)
|
|
11
|
+
url.searchParams.append(k, String(item));
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
url.searchParams.set(k, String(v));
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
const res = await fetch(url.toString(), { headers: { Accept: "application/json" } });
|
|
19
|
+
if (!res.ok)
|
|
20
|
+
throw new Error(`Disqus API error ${res.status}: ${await res.text()}`);
|
|
21
|
+
return res.json();
|
|
22
|
+
}
|
|
23
|
+
async function paginate(apiKey, endpoint, qs, limit) {
|
|
24
|
+
const results = [];
|
|
25
|
+
let cursor;
|
|
26
|
+
do {
|
|
27
|
+
const q = { ...qs, limit: 100 };
|
|
28
|
+
if (cursor)
|
|
29
|
+
q.cursor = cursor;
|
|
30
|
+
const data = (await apiRequest(apiKey, endpoint, q));
|
|
31
|
+
const items = data.response;
|
|
32
|
+
results.push(...items);
|
|
33
|
+
const c = data.cursor;
|
|
34
|
+
if (c?.more && c?.hasNext) {
|
|
35
|
+
cursor = c.id;
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
if (limit && results.length >= limit)
|
|
41
|
+
break;
|
|
42
|
+
} while (true);
|
|
43
|
+
return limit ? results.slice(0, limit) : results;
|
|
44
|
+
}
|
|
45
|
+
export default function disqus(rl) {
|
|
46
|
+
rl.setName("disqus");
|
|
47
|
+
rl.setVersion("0.1.0");
|
|
48
|
+
rl.setConnectionSchema({
|
|
49
|
+
apiKey: { type: "string", required: true, description: "Disqus API key (access token)", env: "DISQUS_API_KEY" },
|
|
50
|
+
});
|
|
51
|
+
rl.registerAction("forum.get", {
|
|
52
|
+
description: "Get forum details",
|
|
53
|
+
inputSchema: {
|
|
54
|
+
forum: { type: "string", required: true, description: "Forum short name (ID)" },
|
|
55
|
+
related: { type: "array", required: false, description: "Relations to include (e.g. ['author'])" },
|
|
56
|
+
attach: { type: "array", required: false, description: "Attach fields (e.g. ['counters'])" },
|
|
57
|
+
},
|
|
58
|
+
async execute(input, ctx) {
|
|
59
|
+
const { forum, related, attach } = input;
|
|
60
|
+
const apiKey = ctx.connection.config.apiKey;
|
|
61
|
+
const qs = { forum };
|
|
62
|
+
if (related)
|
|
63
|
+
qs.related = related;
|
|
64
|
+
if (attach)
|
|
65
|
+
qs.attach = attach;
|
|
66
|
+
const data = (await apiRequest(apiKey, "forums/details.json", qs));
|
|
67
|
+
return data.response;
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
rl.registerAction("forum.listPosts", {
|
|
71
|
+
description: "List posts in a forum",
|
|
72
|
+
inputSchema: {
|
|
73
|
+
forum: { type: "string", required: true, description: "Forum short name" },
|
|
74
|
+
limit: { type: "number", required: false, description: "Max results" },
|
|
75
|
+
order: { type: "string", required: false, description: "Sort order: asc or desc" },
|
|
76
|
+
query: { type: "string", required: false, description: "Search query" },
|
|
77
|
+
since: { type: "string", required: false, description: "Filter posts since (ISO datetime or unix timestamp)" },
|
|
78
|
+
related: { type: "array", required: false, description: "Relations (e.g. ['thread'])" },
|
|
79
|
+
include: { type: "array", required: false, description: "Include filters (e.g. ['approved'])" },
|
|
80
|
+
filters: { type: "array", required: false, description: "Post filters (e.g. ['Is_Flagged'])" },
|
|
81
|
+
},
|
|
82
|
+
async execute(input, ctx) {
|
|
83
|
+
const { forum, limit, order, query, since, related, include, filters } = (input ?? {});
|
|
84
|
+
const apiKey = ctx.connection.config.apiKey;
|
|
85
|
+
const qs = { forum };
|
|
86
|
+
if (order)
|
|
87
|
+
qs.order = order;
|
|
88
|
+
if (query)
|
|
89
|
+
qs.query = query;
|
|
90
|
+
if (since)
|
|
91
|
+
qs.since = since;
|
|
92
|
+
if (related)
|
|
93
|
+
qs.related = related;
|
|
94
|
+
if (include)
|
|
95
|
+
qs.include = include;
|
|
96
|
+
if (filters)
|
|
97
|
+
qs.filters = filters;
|
|
98
|
+
return paginate(apiKey, "forums/listPosts.json", qs, limit);
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
rl.registerAction("forum.listCategories", {
|
|
102
|
+
description: "List categories in a forum",
|
|
103
|
+
inputSchema: {
|
|
104
|
+
forum: { type: "string", required: true, description: "Forum short name" },
|
|
105
|
+
limit: { type: "number", required: false, description: "Max results" },
|
|
106
|
+
order: { type: "string", required: false, description: "Sort order: asc or desc" },
|
|
107
|
+
},
|
|
108
|
+
async execute(input, ctx) {
|
|
109
|
+
const { forum, limit, order } = (input ?? {});
|
|
110
|
+
const apiKey = ctx.connection.config.apiKey;
|
|
111
|
+
const qs = { forum };
|
|
112
|
+
if (order)
|
|
113
|
+
qs.order = order;
|
|
114
|
+
return paginate(apiKey, "forums/listCategories.json", qs, limit);
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
rl.registerAction("forum.listThreads", {
|
|
118
|
+
description: "List threads in a forum",
|
|
119
|
+
inputSchema: {
|
|
120
|
+
forum: { type: "string", required: true, description: "Forum short name" },
|
|
121
|
+
limit: { type: "number", required: false, description: "Max results" },
|
|
122
|
+
order: { type: "string", required: false, description: "Sort order: asc or desc" },
|
|
123
|
+
since: { type: "string", required: false, description: "Filter since (ISO datetime or unix timestamp)" },
|
|
124
|
+
related: { type: "array", required: false, description: "Relations (e.g. ['author', 'forum'])" },
|
|
125
|
+
include: { type: "array", required: false, description: "Thread states (e.g. ['open', 'closed', 'killed'])" },
|
|
126
|
+
thread: { type: "string", required: false, description: "Look up specific thread by ID or ident" },
|
|
127
|
+
},
|
|
128
|
+
async execute(input, ctx) {
|
|
129
|
+
const { forum, limit, order, since, related, include, thread } = (input ?? {});
|
|
130
|
+
const apiKey = ctx.connection.config.apiKey;
|
|
131
|
+
const qs = { forum };
|
|
132
|
+
if (order)
|
|
133
|
+
qs.order = order;
|
|
134
|
+
if (since)
|
|
135
|
+
qs.since = since;
|
|
136
|
+
if (related)
|
|
137
|
+
qs.related = related;
|
|
138
|
+
if (include)
|
|
139
|
+
qs.include = include;
|
|
140
|
+
if (thread)
|
|
141
|
+
qs.thread = thread;
|
|
142
|
+
return paginate(apiKey, "forums/listThreads.json", qs, limit);
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { commandExists, syncExec } from "runline";
|
|
2
|
+
export default function docker(rl) {
|
|
3
|
+
rl.setName("docker");
|
|
4
|
+
rl.setVersion("0.1.0");
|
|
5
|
+
rl.onInit(() => {
|
|
6
|
+
if (!commandExists("docker")) {
|
|
7
|
+
rl.log.warn("docker not found on PATH — docker actions will be unavailable");
|
|
8
|
+
}
|
|
9
|
+
});
|
|
10
|
+
rl.registerAction("containers.list", {
|
|
11
|
+
description: "List Docker containers",
|
|
12
|
+
inputSchema: {
|
|
13
|
+
all: { type: "boolean", description: "Include stopped containers", required: false },
|
|
14
|
+
},
|
|
15
|
+
execute(input) {
|
|
16
|
+
const opts = input ?? {};
|
|
17
|
+
const args = ["ps", "--format", "{{json .}}", "--no-trunc"];
|
|
18
|
+
if (opts.all)
|
|
19
|
+
args.push("--all");
|
|
20
|
+
const { rows } = syncExec("docker", args, { parser: "jsonlines" });
|
|
21
|
+
return rows.map((r) => ({
|
|
22
|
+
id: r.ID ?? r.id ?? "",
|
|
23
|
+
name: (r.Names ?? r.names ?? "").replace(/^\//, ""),
|
|
24
|
+
image: r.Image ?? r.image ?? "",
|
|
25
|
+
status: r.Status ?? r.status ?? "",
|
|
26
|
+
state: r.State ?? r.state ?? "",
|
|
27
|
+
ports: r.Ports ?? r.ports ?? "",
|
|
28
|
+
}));
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
rl.registerAction("containers.start", {
|
|
32
|
+
description: "Start a Docker container",
|
|
33
|
+
inputSchema: {
|
|
34
|
+
id: { type: "string", description: "Container ID or name", required: true },
|
|
35
|
+
},
|
|
36
|
+
execute(input) {
|
|
37
|
+
const { id } = input;
|
|
38
|
+
syncExec("docker", ["start", id]);
|
|
39
|
+
return { ok: true, id };
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
rl.registerAction("containers.stop", {
|
|
43
|
+
description: "Stop a Docker container",
|
|
44
|
+
inputSchema: {
|
|
45
|
+
id: { type: "string", description: "Container ID or name", required: true },
|
|
46
|
+
},
|
|
47
|
+
execute(input) {
|
|
48
|
+
const { id } = input;
|
|
49
|
+
syncExec("docker", ["stop", id]);
|
|
50
|
+
return { ok: true, id };
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
rl.registerAction("images.list", {
|
|
54
|
+
description: "List Docker images",
|
|
55
|
+
execute() {
|
|
56
|
+
const { rows } = syncExec("docker", ["images", "--format", "{{json .}}", "--no-trunc"], { parser: "jsonlines" });
|
|
57
|
+
return rows.map((r) => ({
|
|
58
|
+
id: r.ID ?? r.id ?? "",
|
|
59
|
+
repository: r.Repository ?? r.repository ?? "",
|
|
60
|
+
tag: r.Tag ?? r.tag ?? "",
|
|
61
|
+
size: r.Size ?? r.size ?? "",
|
|
62
|
+
}));
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
rl.registerAction("images.pull", {
|
|
66
|
+
description: "Pull a Docker image",
|
|
67
|
+
inputSchema: {
|
|
68
|
+
image: { type: "string", description: "Image name (e.g. nginx:latest)", required: true },
|
|
69
|
+
},
|
|
70
|
+
execute(input) {
|
|
71
|
+
const { image } = input;
|
|
72
|
+
syncExec("docker", ["pull", image]);
|
|
73
|
+
return { ok: true, image };
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
const BASE_URL = "https://driftapi.com";
|
|
2
|
+
async function apiRequest(token, method, endpoint, body) {
|
|
3
|
+
const opts = {
|
|
4
|
+
method,
|
|
5
|
+
headers: {
|
|
6
|
+
Authorization: `Bearer ${token}`,
|
|
7
|
+
"Content-Type": "application/json",
|
|
8
|
+
},
|
|
9
|
+
};
|
|
10
|
+
if (body && Object.keys(body).length > 0 && method !== "GET" && method !== "DELETE") {
|
|
11
|
+
opts.body = JSON.stringify(body);
|
|
12
|
+
}
|
|
13
|
+
const res = await fetch(`${BASE_URL}${endpoint}`, opts);
|
|
14
|
+
if (!res.ok)
|
|
15
|
+
throw new Error(`Drift API error ${res.status}: ${await res.text()}`);
|
|
16
|
+
if (res.status === 204)
|
|
17
|
+
return { success: true };
|
|
18
|
+
return res.json();
|
|
19
|
+
}
|
|
20
|
+
export default function drift(rl) {
|
|
21
|
+
rl.setName("drift");
|
|
22
|
+
rl.setVersion("0.1.0");
|
|
23
|
+
rl.setConnectionSchema({
|
|
24
|
+
accessToken: { type: "string", required: true, description: "Drift API access token", env: "DRIFT_ACCESS_TOKEN" },
|
|
25
|
+
});
|
|
26
|
+
const tok = (ctx) => ctx.connection.config.accessToken;
|
|
27
|
+
rl.registerAction("contact.create", {
|
|
28
|
+
description: "Create a contact",
|
|
29
|
+
inputSchema: {
|
|
30
|
+
email: { type: "string", required: true, description: "Email address" },
|
|
31
|
+
name: { type: "string", required: false, description: "Full name" },
|
|
32
|
+
phone: { type: "string", required: false, description: "Phone number" },
|
|
33
|
+
},
|
|
34
|
+
async execute(input, ctx) {
|
|
35
|
+
const { email, name, phone } = input;
|
|
36
|
+
const attrs = { email };
|
|
37
|
+
if (name)
|
|
38
|
+
attrs.name = name;
|
|
39
|
+
if (phone)
|
|
40
|
+
attrs.phone = phone;
|
|
41
|
+
const data = (await apiRequest(tok(ctx), "POST", "/contacts", { attributes: attrs }));
|
|
42
|
+
return data.data;
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
rl.registerAction("contact.get", {
|
|
46
|
+
description: "Get a contact by ID",
|
|
47
|
+
inputSchema: { contactId: { type: "string", required: true, description: "Contact ID" } },
|
|
48
|
+
async execute(input, ctx) {
|
|
49
|
+
const data = (await apiRequest(tok(ctx), "GET", `/contacts/${input.contactId}`));
|
|
50
|
+
return data.data;
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
rl.registerAction("contact.update", {
|
|
54
|
+
description: "Update a contact",
|
|
55
|
+
inputSchema: {
|
|
56
|
+
contactId: { type: "string", required: true, description: "Contact ID" },
|
|
57
|
+
email: { type: "string", required: false, description: "New email" },
|
|
58
|
+
name: { type: "string", required: false, description: "New name" },
|
|
59
|
+
phone: { type: "string", required: false, description: "New phone" },
|
|
60
|
+
},
|
|
61
|
+
async execute(input, ctx) {
|
|
62
|
+
const { contactId, email, name, phone } = input;
|
|
63
|
+
const attrs = {};
|
|
64
|
+
if (email)
|
|
65
|
+
attrs.email = email;
|
|
66
|
+
if (name)
|
|
67
|
+
attrs.name = name;
|
|
68
|
+
if (phone)
|
|
69
|
+
attrs.phone = phone;
|
|
70
|
+
const data = (await apiRequest(tok(ctx), "PATCH", `/contacts/${contactId}`, { attributes: attrs }));
|
|
71
|
+
return data.data;
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
rl.registerAction("contact.delete", {
|
|
75
|
+
description: "Delete a contact",
|
|
76
|
+
inputSchema: { contactId: { type: "string", required: true, description: "Contact ID" } },
|
|
77
|
+
async execute(input, ctx) {
|
|
78
|
+
await apiRequest(tok(ctx), "DELETE", `/contacts/${input.contactId}`);
|
|
79
|
+
return { success: true };
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
rl.registerAction("contact.getCustomAttributes", {
|
|
83
|
+
description: "List all custom contact attributes",
|
|
84
|
+
async execute(_input, ctx) {
|
|
85
|
+
const data = (await apiRequest(tok(ctx), "GET", "/contacts/attributes"));
|
|
86
|
+
return data.data.properties;
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
}
|