helipod-mcp 0.5.12 → 0.5.16
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/README.md +2 -2
- package/dist/http.js +316 -26
- package/dist/index.js +316 -26
- package/dist/stdio.js +316 -26
- package/package.json +1 -1
- package/skills/helipod/SKILL.md +7 -4
- package/skills/helipod-deploy/SKILL.md +5 -1
- package/skills/helipod-domains/SKILL.md +2 -2
- package/skills/helipod-email/SKILL.md +20 -2
- package/skills/helipod-launch/SKILL.md +32 -0
- package/skills/helipod-reseller/SKILL.md +36 -0
- package/skills/helipod-vps-deploy/SKILL.md +39 -0
package/README.md
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Use [Helipod](https://helipod.io) from AI coding tools: deploy apps and databases, run VPS servers, buy domains and edit DNS, and send email, by asking in plain language.
|
|
4
4
|
|
|
5
|
-
- **MCP server** (`helipod-mcp`):
|
|
6
|
-
- **Skills** (`skills/`): step-by-step playbooks so the agent deploys, prices and confirms the way Helipod expects (price first, confirm before deleting, read build logs on failure).
|
|
5
|
+
- **MCP server** (`helipod-mcp`): 263 tools over the Container, Database, VPS, Domain, Email, Account and AI APIs. Works in Claude Code, Codex, Cursor, VS Code, Windsurf, Claude Desktop and any MCP client.
|
|
6
|
+
- **Skills** (`skills/`): step-by-step playbooks so the agent deploys, prices and confirms the way Helipod expects (price first, confirm before deleting, read build logs on failure): containers, VPS and apps on a VPS, databases, domains, email, the AI gateway, end-to-end launches (app + database + domain + email) and reselling.
|
|
7
7
|
|
|
8
8
|
Step-by-step guides for each tool (Claude Code, Codex, Cursor, VS Code, Gemini CLI, opencode, Windsurf, Claude Desktop, Cline and others): **https://docs.helipod.io/docs/helipod/ai-agents**
|
|
9
9
|
|
package/dist/http.js
CHANGED
|
@@ -35623,6 +35623,14 @@ class HelipodClient {
|
|
|
35623
35623
|
const headers = !this.cfg.emailApiKey && opts.project ? { "x-helipod-project": opts.project } : undefined;
|
|
35624
35624
|
return this.request(this.emailApiUrl, key, method, path, { ...opts, headers });
|
|
35625
35625
|
}
|
|
35626
|
+
emailWithAccountKey(method, path, opts = {}) {
|
|
35627
|
+
const key = this.cfg.apiKey;
|
|
35628
|
+
if (!key) {
|
|
35629
|
+
throw new HelipodError(401, "NO_API_KEY", "Managing email keys needs HELIPOD_API_KEY (a Helipod API key with the Email API enabled); an email key cannot do it.");
|
|
35630
|
+
}
|
|
35631
|
+
const headers = opts.project ? { "x-helipod-project": opts.project } : undefined;
|
|
35632
|
+
return this.request(this.emailApiUrl, key, method, path, { ...opts, headers });
|
|
35633
|
+
}
|
|
35626
35634
|
async image(link, maxBytes) {
|
|
35627
35635
|
let url2;
|
|
35628
35636
|
try {
|
|
@@ -35686,7 +35694,7 @@ class HelipodClient {
|
|
|
35686
35694
|
const details = json2?.details && typeof json2.details === "object" && !Array.isArray(json2.details) ? json2.details : undefined;
|
|
35687
35695
|
throw new HelipodError(res.status, json2?.code, retry ? `${msg} (retry after ${retry}s)` : String(msg), details);
|
|
35688
35696
|
}
|
|
35689
|
-
return json2 && "data" in json2 ? json2.data : json2;
|
|
35697
|
+
return json2 && "data" in json2 ? json2.data : json2 ?? (text || null);
|
|
35690
35698
|
}
|
|
35691
35699
|
}
|
|
35692
35700
|
|
|
@@ -35753,7 +35761,7 @@ var containerTools = [
|
|
|
35753
35761
|
kind: "read",
|
|
35754
35762
|
description: "Projects this API key can reach, each with its environments (production, staging, ...). Start here to find an environmentId.",
|
|
35755
35763
|
input: {},
|
|
35756
|
-
run: (_a3, api2) => api2.api("GET",
|
|
35764
|
+
run: (_a3, api2) => api2.api("GET", `/projects`)
|
|
35757
35765
|
}),
|
|
35758
35766
|
defineTool({
|
|
35759
35767
|
name: "get_project",
|
|
@@ -35761,7 +35769,7 @@ var containerTools = [
|
|
|
35761
35769
|
kind: "read",
|
|
35762
35770
|
description: "One project with its environments (ids and names). Use it to find an environmentId when you already know the project.",
|
|
35763
35771
|
input: { projectId: id("Project") },
|
|
35764
|
-
run: (a, api2) => api2.api("GET",
|
|
35772
|
+
run: (a, api2) => api2.api("GET", `/projects/${a.projectId}`)
|
|
35765
35773
|
}),
|
|
35766
35774
|
defineTool({
|
|
35767
35775
|
name: "create_project",
|
|
@@ -35769,7 +35777,7 @@ var containerTools = [
|
|
|
35769
35777
|
kind: "write",
|
|
35770
35778
|
description: "Create a project (free) with its default production environment. Needs a whole-account key (a key limited to one project gets 403). Pass an idempotencyKey when retrying.",
|
|
35771
35779
|
input: { name: exports_external.string().min(3).max(100), description: exports_external.string().max(500).optional(), idempotencyKey: idem },
|
|
35772
|
-
run: (a, api2) => api2.api("POST",
|
|
35780
|
+
run: (a, api2) => api2.api("POST", `/projects`, { body: { name: a.name, description: a.description }, idempotencyKey: a.idempotencyKey })
|
|
35773
35781
|
}),
|
|
35774
35782
|
defineTool({
|
|
35775
35783
|
name: "update_project",
|
|
@@ -35778,7 +35786,7 @@ var containerTools = [
|
|
|
35778
35786
|
idempotent: true,
|
|
35779
35787
|
description: "Change a project's name and/or description.",
|
|
35780
35788
|
input: { projectId: id("Project"), name: exports_external.string().min(3).max(100).optional(), description: exports_external.string().max(500).optional() },
|
|
35781
|
-
run: (a, api2) => api2.api("PATCH",
|
|
35789
|
+
run: (a, api2) => api2.api("PATCH", `/projects/${a.projectId}`, { body: { name: a.name, description: a.description } })
|
|
35782
35790
|
}),
|
|
35783
35791
|
defineTool({
|
|
35784
35792
|
name: "delete_project",
|
|
@@ -35786,7 +35794,7 @@ var containerTools = [
|
|
|
35786
35794
|
kind: "destructive",
|
|
35787
35795
|
description: "Delete an EMPTY project and its environments and shared variables. Irreversible. Refused (409 PROJECT_NOT_EMPTY) while it still has services, databases, VPS or other resources: those must be deleted first. Needs a whole-account key. Only after the user confirms; confirm must be the project's exact name.",
|
|
35788
35796
|
input: { projectId: id("Project"), confirm: exports_external.string().max(255).describe("The project name, exactly") },
|
|
35789
|
-
run: (a, api2) => api2.api("DELETE",
|
|
35797
|
+
run: (a, api2) => api2.api("DELETE", `/projects/${a.projectId}`, { body: { confirm: a.confirm } })
|
|
35790
35798
|
}),
|
|
35791
35799
|
defineTool({
|
|
35792
35800
|
name: "create_environment",
|
|
@@ -35794,7 +35802,7 @@ var containerTools = [
|
|
|
35794
35802
|
kind: "write",
|
|
35795
35803
|
description: "Add an environment (e.g. staging) to a project. Free; services in it are billed as usual once deployed.",
|
|
35796
35804
|
input: { projectId: id("Project"), name: exports_external.string().regex(/^[A-Za-z0-9][A-Za-z0-9 _.-]{0,49}$/).describe("1-50 characters: letters, digits, space _ . -") },
|
|
35797
|
-
run: (a, api2) => api2.api("POST",
|
|
35805
|
+
run: (a, api2) => api2.api("POST", `/projects/${a.projectId}/environments`, { body: { name: a.name } })
|
|
35798
35806
|
}),
|
|
35799
35807
|
defineTool({
|
|
35800
35808
|
name: "rename_environment",
|
|
@@ -35803,7 +35811,7 @@ var containerTools = [
|
|
|
35803
35811
|
idempotent: true,
|
|
35804
35812
|
description: "Rename an environment (names are unique within the project).",
|
|
35805
35813
|
input: { environmentId: id("Environment"), name: exports_external.string().regex(/^[A-Za-z0-9][A-Za-z0-9 _.-]{0,49}$/) },
|
|
35806
|
-
run: (a, api2) => api2.api("PATCH",
|
|
35814
|
+
run: (a, api2) => api2.api("PATCH", `/environments/${a.environmentId}`, { body: { name: a.name } })
|
|
35807
35815
|
}),
|
|
35808
35816
|
defineTool({
|
|
35809
35817
|
name: "delete_environment",
|
|
@@ -35811,7 +35819,7 @@ var containerTools = [
|
|
|
35811
35819
|
kind: "destructive",
|
|
35812
35820
|
description: "Delete an EMPTY environment (409 ENVIRONMENT_NOT_EMPTY while it has services: delete them first). Irreversible. Only after the user confirms; confirm must be the environment's exact name.",
|
|
35813
35821
|
input: { environmentId: id("Environment"), confirm: exports_external.string().max(255).describe("The environment name, exactly") },
|
|
35814
|
-
run: (a, api2) => api2.api("DELETE",
|
|
35822
|
+
run: (a, api2) => api2.api("DELETE", `/environments/${a.environmentId}`, { body: { confirm: a.confirm } })
|
|
35815
35823
|
}),
|
|
35816
35824
|
defineTool({
|
|
35817
35825
|
name: "list_project_variables",
|
|
@@ -35910,6 +35918,7 @@ var containerTools = [
|
|
|
35910
35918
|
ram: exports_external.number().int().min(128).max(8192).optional().describe("MB, multiple of 128"),
|
|
35911
35919
|
startCommand: exports_external.string().optional(),
|
|
35912
35920
|
healthcheckPath: exports_external.string().optional().describe("HTTP path answering 2xx when ready, e.g. /health"),
|
|
35921
|
+
healthcheckTimeout: exports_external.number().int().min(1).max(3600).optional().describe("Seconds the first start may take before the health check counts (default from the platform)"),
|
|
35913
35922
|
deploy: exports_external.boolean().optional().describe("false: create without deploying (nothing is charged)"),
|
|
35914
35923
|
dryRun,
|
|
35915
35924
|
idempotencyKey: idem
|
|
@@ -35931,7 +35940,7 @@ var containerTools = [
|
|
|
35931
35940
|
cpu: a.cpu,
|
|
35932
35941
|
ram: a.ram,
|
|
35933
35942
|
startCommand: a.startCommand,
|
|
35934
|
-
healthcheck: a.healthcheckPath ? { path: a.healthcheckPath } : undefined,
|
|
35943
|
+
healthcheck: a.healthcheckPath ? { path: a.healthcheckPath, timeout: a.healthcheckTimeout } : undefined,
|
|
35935
35944
|
deploy: a.deploy
|
|
35936
35945
|
}
|
|
35937
35946
|
});
|
|
@@ -36061,6 +36070,14 @@ var containerTools = [
|
|
|
36061
36070
|
},
|
|
36062
36071
|
run: (a, api2) => api2.api("POST", `${C}/services/${a.serviceId}/domains`, { body: { kind: a.kind, domain: a.domain, port: a.port, protocol: a.protocol } })
|
|
36063
36072
|
}),
|
|
36073
|
+
defineTool({
|
|
36074
|
+
name: "verify_service_domain",
|
|
36075
|
+
title: "Check custom domain DNS",
|
|
36076
|
+
kind: "write",
|
|
36077
|
+
description: "Check a custom domain's DNS now instead of waiting for the automatic check. Answers the domain with its DNS state (verified, or what is still wrong); the certificate follows once the record points to Helipod (the record to add is in list_service_domains).",
|
|
36078
|
+
input: { serviceId: id("Service"), domainId: id("Domain") },
|
|
36079
|
+
run: (a, api2) => api2.api("POST", `${C}/services/${a.serviceId}/domains/${a.domainId}/verify`)
|
|
36080
|
+
}),
|
|
36064
36081
|
defineTool({
|
|
36065
36082
|
name: "remove_service_domain",
|
|
36066
36083
|
title: "Remove domain from service",
|
|
@@ -36777,22 +36794,40 @@ var record3 = exports_external.object({
|
|
|
36777
36794
|
content: exports_external.string().describe('TXT without surrounding quotes (they are added by the DNS server); MX as "10 mail.example.com"'),
|
|
36778
36795
|
ttl: exports_external.number().int().optional()
|
|
36779
36796
|
});
|
|
36797
|
+
var owner = exports_external.object({
|
|
36798
|
+
name: exports_external.string().min(3).max(255),
|
|
36799
|
+
email: exports_external.string().max(255).optional().describe("Gets the registry's verification email; defaults to the account email"),
|
|
36800
|
+
organization: exports_external.string().max(255).optional(),
|
|
36801
|
+
phone: exports_external.string().min(9).max(20).describe("e.g. +6281234567890"),
|
|
36802
|
+
addressLine1: exports_external.string().min(3).max(500),
|
|
36803
|
+
addressLine2: exports_external.string().max(500).optional(),
|
|
36804
|
+
city: exports_external.string().min(3).max(100),
|
|
36805
|
+
state: exports_external.string().min(1).max(100),
|
|
36806
|
+
countryCode: exports_external.string().regex(/^[A-Za-z]{2}$/).describe("ISO 3166-1 alpha-2, e.g. ID"),
|
|
36807
|
+
postalCode: exports_external.string().min(1).max(20),
|
|
36808
|
+
fax: exports_external.string().max(50).optional()
|
|
36809
|
+
});
|
|
36780
36810
|
var domainTools = [
|
|
36781
36811
|
defineTool({
|
|
36782
36812
|
name: "search_domain",
|
|
36783
36813
|
title: "Check domain availability",
|
|
36784
36814
|
kind: "read",
|
|
36785
36815
|
description: "Is a domain name available to register? No key needed.",
|
|
36786
|
-
input: { domain: exports_external.string().describe("e.g. tokosaya.id") },
|
|
36787
|
-
run: (a, api2) => api2.api("GET", "/domains/search", { query: { domain: a.domain }, auth: false })
|
|
36816
|
+
input: { domain: exports_external.string().describe("e.g. tokosaya.id"), includePremium: exports_external.boolean().optional().describe("Also check names the registry sells as premium (is_premium_name). Premium names cannot be bought online: the user must contact support") },
|
|
36817
|
+
run: (a, api2) => api2.api("GET", "/domains/search", { query: { domain: a.domain, includePremium: a.includePremium ? "true" : undefined }, auth: false })
|
|
36788
36818
|
}),
|
|
36789
36819
|
defineTool({
|
|
36790
36820
|
name: "domain_prices",
|
|
36791
36821
|
title: "Domain prices",
|
|
36792
36822
|
kind: "read",
|
|
36793
36823
|
description: "Registration, renewal and transfer prices per extension (IDR, VAT included) and the yearly WHOIS privacy price.",
|
|
36794
|
-
input: {
|
|
36795
|
-
|
|
36824
|
+
input: {
|
|
36825
|
+
extension: exports_external.string().optional().describe("e.g. .id or .com"),
|
|
36826
|
+
promo: exports_external.boolean().optional().describe("true: only extensions on promotion"),
|
|
36827
|
+
limit: exports_external.number().int().min(1).max(100).optional(),
|
|
36828
|
+
page: exports_external.number().int().min(1).optional()
|
|
36829
|
+
},
|
|
36830
|
+
run: (a, api2) => api2.api("GET", "/domains/prices", { query: { extension: a.extension, promo: a.promo === undefined ? undefined : String(a.promo), limit: a.limit ?? 50, page: a.page }, auth: false })
|
|
36796
36831
|
}),
|
|
36797
36832
|
defineTool({
|
|
36798
36833
|
name: "list_my_domains",
|
|
@@ -36851,17 +36886,32 @@ var domainTools = [
|
|
|
36851
36886
|
name: exports_external.string().describe("Full domain, e.g. tokosaya.id"),
|
|
36852
36887
|
nameservers: exports_external.array(exports_external.string()).min(2).max(5).optional(),
|
|
36853
36888
|
whoisProtection: exports_external.boolean().optional(),
|
|
36889
|
+
registrant: owner.optional().describe("Owner of THIS domain instead of the account's owner details (e.g. a reseller's customer). .id domains only: others answer 400 REGISTRANT_NOT_SUPPORTED. Use only details the user gave."),
|
|
36854
36890
|
dryRun: dryRun3
|
|
36855
36891
|
},
|
|
36856
|
-
run: (a, api2) => api2.api("POST", "/domains/register", { body: { name: a.name, period: 1, nameservers: a.nameservers, whoisProtection: a.whoisProtection }, query: { dryRun: a.dryRun ? "true" : undefined } })
|
|
36892
|
+
run: (a, api2) => api2.api("POST", "/domains/register", { body: { name: a.name, period: 1, nameservers: a.nameservers, whoisProtection: a.whoisProtection, registrant: a.registrant }, query: { dryRun: a.dryRun ? "true" : undefined } })
|
|
36857
36893
|
}),
|
|
36858
36894
|
defineTool({
|
|
36859
36895
|
name: "transfer_domain",
|
|
36860
36896
|
title: "Transfer domain in",
|
|
36861
36897
|
kind: "write",
|
|
36862
|
-
description: `Transfer a domain from another registrar (adds 1 year). COSTS MONEY from the wallet. Needs the domain's auth code. ${DRY2}
|
|
36863
|
-
input: {
|
|
36864
|
-
|
|
36898
|
+
description: `Transfer a domain from another registrar (adds 1 year). COSTS MONEY from the wallet. Needs the domain's auth code. ${DRY2} With registrant (.id only) the domain gets that owner when the transfer completes (ownerPending until then); without it, the account's owner details.`,
|
|
36899
|
+
input: {
|
|
36900
|
+
name: exports_external.string(),
|
|
36901
|
+
authCode: exports_external.string(),
|
|
36902
|
+
nameservers: exports_external.array(exports_external.string()).min(2).max(5).optional(),
|
|
36903
|
+
registrant: owner.optional().describe("Owner of THIS domain once the transfer completes (e.g. a reseller's customer). .id domains only: others answer 400 REGISTRANT_NOT_SUPPORTED. Use only details the user gave."),
|
|
36904
|
+
dryRun: dryRun3
|
|
36905
|
+
},
|
|
36906
|
+
run: (a, api2) => api2.api("POST", "/domains/transfer", { body: { name: a.name, authCode: a.authCode, period: 1, nameservers: a.nameservers, registrant: a.registrant }, query: { dryRun: a.dryRun ? "true" : undefined } })
|
|
36907
|
+
}),
|
|
36908
|
+
defineTool({
|
|
36909
|
+
name: "set_domain_owner",
|
|
36910
|
+
title: "Set domain owner",
|
|
36911
|
+
kind: "write",
|
|
36912
|
+
description: "Set a new owner (registrant) for one .id domain of the account, e.g. a reseller's customer or a domain transferred earlier. Free. An active domain gets it now (ownerPending false); a transfer in progress gets it when it completes, and a change the registry does not take yet is retried automatically (ownerPending true, HTTP 202). Other domains keep their other contacts. Use only details the user gave.",
|
|
36913
|
+
input: { domainId, owner },
|
|
36914
|
+
run: (a, api2) => api2.api("PATCH", `/domains/${a.domainId}/registrant`, { body: a.owner })
|
|
36865
36915
|
}),
|
|
36866
36916
|
defineTool({
|
|
36867
36917
|
name: "renew_domain",
|
|
@@ -36908,9 +36958,9 @@ var domainTools = [
|
|
|
36908
36958
|
name: "enable_whois_privacy",
|
|
36909
36959
|
title: "Enable WHOIS privacy",
|
|
36910
36960
|
kind: "write",
|
|
36911
|
-
description:
|
|
36912
|
-
input: { domainId },
|
|
36913
|
-
run: (a, api2) => api2.api("POST", `/domains/${a.domainId}/whois-protection
|
|
36961
|
+
description: `Hide the owner's details in WHOIS. COSTS MONEY (the whois_protection price per year). ${DRY2}`,
|
|
36962
|
+
input: { domainId, dryRun: dryRun3 },
|
|
36963
|
+
run: (a, api2) => api2.api("POST", `/domains/${a.domainId}/whois-protection`, { query: { dryRun: a.dryRun ? "true" : undefined } })
|
|
36914
36964
|
}),
|
|
36915
36965
|
defineTool({
|
|
36916
36966
|
name: "get_domain_price",
|
|
@@ -36953,6 +37003,23 @@ var domainTools = [
|
|
|
36953
37003
|
input: { domainId, emailDomainId: exports_external.string().uuid() },
|
|
36954
37004
|
run: (a, api2) => api2.api("POST", `/domains/${a.domainId}/email-records`, { body: { emailDomainId: a.emailDomainId } })
|
|
36955
37005
|
}),
|
|
37006
|
+
defineTool({
|
|
37007
|
+
name: "get_child_nameserver",
|
|
37008
|
+
title: "Get child nameserver",
|
|
37009
|
+
kind: "read",
|
|
37010
|
+
description: "One child nameserver (glue record) of a registered domain: hostname and IP address.",
|
|
37011
|
+
input: { domainId, hostId },
|
|
37012
|
+
run: (a, api2) => api2.api("GET", `/domains/${a.domainId}/hosts/${a.hostId}`)
|
|
37013
|
+
}),
|
|
37014
|
+
defineTool({
|
|
37015
|
+
name: "set_domain_auto_renew",
|
|
37016
|
+
title: "Set domain auto-renew",
|
|
37017
|
+
kind: "write",
|
|
37018
|
+
idempotent: true,
|
|
37019
|
+
description: "Turn automatic renewal of a domain on or off (sets the value given; calling it twice is safe). On: renewed from the wallet before it expires. Off: it expires unless renewed by hand (renew_domain). Nothing is charged now.",
|
|
37020
|
+
input: { domainId, autoRenew: exports_external.boolean() },
|
|
37021
|
+
run: (a, api2) => api2.api("PATCH", `/domains/${a.domainId}/auto-renew`, { body: { autoRenew: a.autoRenew } })
|
|
37022
|
+
}),
|
|
36956
37023
|
defineTool({
|
|
36957
37024
|
name: "list_child_nameservers",
|
|
36958
37025
|
title: "List child nameservers",
|
|
@@ -37029,7 +37096,10 @@ var message = exports_external.object({
|
|
|
37029
37096
|
templateId: exports_external.string().optional(),
|
|
37030
37097
|
templateData: exports_external.record(exports_external.string(), exports_external.string()).optional(),
|
|
37031
37098
|
variables: exports_external.record(exports_external.string(), exports_external.string()).optional(),
|
|
37032
|
-
idempotencyKey: exports_external.string().max(128).optional()
|
|
37099
|
+
idempotencyKey: exports_external.string().max(128).optional(),
|
|
37100
|
+
headers: exports_external.record(exports_external.string(), exports_external.string()).optional().describe("Extra MIME headers (letters, digits, hyphens in names; no line breaks). Not with templateId"),
|
|
37101
|
+
metadata: exports_external.record(exports_external.string(), exports_external.string()).optional().describe("Your own key/value data, stored with the email (get_email returns it)"),
|
|
37102
|
+
sensitive: exports_external.boolean().optional().describe("true when templateData or variables carry a secret (password, one-time code): their values are wiped from the job record after the send")
|
|
37033
37103
|
});
|
|
37034
37104
|
var dryRun4 = exports_external.boolean().optional().describe("true = return the price (IDR) and the wallet balance after it; nothing is charged or changed");
|
|
37035
37105
|
var idem2 = exports_external.string().regex(/^[A-Za-z0-9_.:-]{8,128}$/).optional().describe("Idempotency-Key for the real purchase. Generated when omitted; pass the same value when retrying so it is never charged twice");
|
|
@@ -37120,6 +37190,24 @@ var emailTools = [
|
|
|
37120
37190
|
idempotencyKey: purchaseKey(a.dryRun, a.idempotencyKey)
|
|
37121
37191
|
})
|
|
37122
37192
|
}),
|
|
37193
|
+
defineTool({
|
|
37194
|
+
name: "set_email_auto_renew",
|
|
37195
|
+
title: "Set email auto-renew",
|
|
37196
|
+
kind: "write",
|
|
37197
|
+
idempotent: true,
|
|
37198
|
+
description: "Turn the active email plan's auto-renew on or off (sets the value, safe to repeat). On: the plan renews from the wallet deposit when it ends; off: it ends at its expiry date. get_email_plan and email_status show it as autoRenew. A free plan renews at Rp 0.",
|
|
37199
|
+
input: { projectId, autoRenew: exports_external.boolean() },
|
|
37200
|
+
run: (a, api2) => api2.email("POST", "/plan/auto-renew", { project: a.projectId, body: { enabled: a.autoRenew } })
|
|
37201
|
+
}),
|
|
37202
|
+
defineTool({
|
|
37203
|
+
name: "set_email_payg",
|
|
37204
|
+
title: "Set email pay-as-you-go",
|
|
37205
|
+
kind: "write",
|
|
37206
|
+
idempotent: true,
|
|
37207
|
+
description: "Turn pay-as-you-go on or off for the active paid email plan (sets the value, safe to repeat; off by default). Off: once the monthly quota is used, sends stop with 402 PAYG_DISABLED and nothing is charged. On: sends continue past the quota and blocks of 100 emails are bought from the wallet deposit at the plan's per-email price (unused ones refunded if the plan is not renewed). email_status shows it as paygEnabled. No effect on the free plan.",
|
|
37208
|
+
input: { projectId, paygEnabled: exports_external.boolean() },
|
|
37209
|
+
run: (a, api2) => api2.email("POST", "/plan/payg", { project: a.projectId, body: { enabled: a.paygEnabled } })
|
|
37210
|
+
}),
|
|
37123
37211
|
defineTool({
|
|
37124
37212
|
name: "send_email",
|
|
37125
37213
|
title: "Send email",
|
|
@@ -37138,7 +37226,10 @@ var emailTools = [
|
|
|
37138
37226
|
templateId: exports_external.string().optional(),
|
|
37139
37227
|
templateData: exports_external.record(exports_external.string(), exports_external.string()).optional(),
|
|
37140
37228
|
variables: exports_external.record(exports_external.string(), exports_external.string()).optional(),
|
|
37141
|
-
idempotencyKey: exports_external.string().max(128).optional()
|
|
37229
|
+
idempotencyKey: exports_external.string().max(128).optional(),
|
|
37230
|
+
headers: exports_external.record(exports_external.string(), exports_external.string()).optional().describe("Extra MIME headers (letters, digits, hyphens in names; no line breaks). Not with templateId"),
|
|
37231
|
+
metadata: exports_external.record(exports_external.string(), exports_external.string()).optional().describe("Your own key/value data, stored with the email (get_email returns it)"),
|
|
37232
|
+
sensitive: exports_external.boolean().optional().describe("true when templateData or variables carry a secret (password, one-time code): their values are wiped from the job record after the send")
|
|
37142
37233
|
},
|
|
37143
37234
|
run: ({ projectId: project, ...a }, api2) => api2.email("POST", "/send", { body: a, project })
|
|
37144
37235
|
}),
|
|
@@ -37147,7 +37238,15 @@ var emailTools = [
|
|
|
37147
37238
|
title: "List sent emails",
|
|
37148
37239
|
kind: "read",
|
|
37149
37240
|
description: "Email log, newest first, with delivery status.",
|
|
37150
|
-
input: {
|
|
37241
|
+
input: {
|
|
37242
|
+
projectId,
|
|
37243
|
+
status: exports_external.string().optional(),
|
|
37244
|
+
search: exports_external.string().optional(),
|
|
37245
|
+
from: exports_external.string().optional().describe("Sent at or after (ISO 8601), e.g. 2026-10-01T00:00:00Z"),
|
|
37246
|
+
to: exports_external.string().optional().describe("Sent before (ISO 8601)"),
|
|
37247
|
+
limit: exports_external.number().int().min(1).max(100).optional(),
|
|
37248
|
+
cursor: exports_external.string().optional()
|
|
37249
|
+
},
|
|
37151
37250
|
run: ({ projectId: project, ...a }, api2) => api2.email("GET", "/emails", { query: a, project })
|
|
37152
37251
|
}),
|
|
37153
37252
|
defineTool({
|
|
@@ -37182,6 +37281,30 @@ var emailTools = [
|
|
|
37182
37281
|
input: { projectId, domainId: exports_external.string() },
|
|
37183
37282
|
run: (a, api2) => api2.email("POST", `/domains/${a.domainId}/verify`, { project: a.projectId })
|
|
37184
37283
|
}),
|
|
37284
|
+
defineTool({
|
|
37285
|
+
name: "list_email_keys",
|
|
37286
|
+
title: "List email keys",
|
|
37287
|
+
kind: "read",
|
|
37288
|
+
description: "Email API keys of a project (name, prefix, last used, expiry; never the key itself). Needs a Helipod API key with the Email API: an email key or a sign-in token answers 403 KEY_NOT_ALLOWED.",
|
|
37289
|
+
input: { projectId },
|
|
37290
|
+
run: (a, api2) => api2.emailWithAccountKey("GET", "/keys", { project: a.projectId })
|
|
37291
|
+
}),
|
|
37292
|
+
defineTool({
|
|
37293
|
+
name: "create_email_key",
|
|
37294
|
+
title: "Create email key",
|
|
37295
|
+
kind: "write",
|
|
37296
|
+
description: "Make an email API key for a project, e.g. one per customer of a reseller. The key works on the Email API and as the SMTP password (smtp.helipod.io, username apikey) for that project only, and cannot make keys itself. The full key is in the answer ONCE: give it to the user to store; it cannot be shown again. Needs a Helipod API key with the Email API (an email key or a sign-in token answers 403 KEY_NOT_ALLOWED). Per project: 5 active keys on Free/Starter, 10 on Pro, 50 on Business.",
|
|
37297
|
+
input: { projectId, name: exports_external.string().min(1).max(100).describe("To tell keys apart, e.g. the customer's name"), expiresInDays: exports_external.number().int().min(1).max(3650).optional() },
|
|
37298
|
+
run: (a, api2) => api2.emailWithAccountKey("POST", "/keys", { project: a.projectId, body: { name: a.name, expiresInDays: a.expiresInDays } })
|
|
37299
|
+
}),
|
|
37300
|
+
defineTool({
|
|
37301
|
+
name: "revoke_email_key",
|
|
37302
|
+
title: "Revoke email key",
|
|
37303
|
+
kind: "destructive",
|
|
37304
|
+
description: "Revoke an email API key of the project: it stops working at once, also for open SMTP sessions. Apps using it can no longer send. Needs a Helipod API key with the Email API.",
|
|
37305
|
+
input: { projectId, keyId: uid("Email key") },
|
|
37306
|
+
run: (a, api2) => api2.emailWithAccountKey("DELETE", `/keys/${a.keyId}`, { project: a.projectId })
|
|
37307
|
+
}),
|
|
37185
37308
|
defineTool({
|
|
37186
37309
|
name: "list_email_templates",
|
|
37187
37310
|
title: "List email templates",
|
|
@@ -37195,7 +37318,16 @@ var emailTools = [
|
|
|
37195
37318
|
title: "Add contact",
|
|
37196
37319
|
kind: "write",
|
|
37197
37320
|
description: "Add or update an audience contact.",
|
|
37198
|
-
input: {
|
|
37321
|
+
input: {
|
|
37322
|
+
projectId,
|
|
37323
|
+
email: exports_external.string(),
|
|
37324
|
+
firstName: exports_external.string().optional(),
|
|
37325
|
+
lastName: exports_external.string().optional(),
|
|
37326
|
+
status: exports_external.enum(["subscribed", "unsubscribed"]).optional(),
|
|
37327
|
+
properties: exports_external.record(exports_external.string(), exports_external.string()).optional().describe("Custom properties (used by segments and {{placeholders}})"),
|
|
37328
|
+
consentConfirmed: exports_external.boolean().optional().describe("true only when the person agreed to receive email (you have their opt-in)"),
|
|
37329
|
+
consentSource: exports_external.string().max(50).optional().describe("Where the consent came from, e.g. signup_form")
|
|
37330
|
+
},
|
|
37199
37331
|
run: ({ projectId: project, ...a }, api2) => api2.email("POST", "/contacts", { body: a, project })
|
|
37200
37332
|
}),
|
|
37201
37333
|
defineTool({
|
|
@@ -37674,6 +37806,162 @@ var emailTools = [
|
|
|
37674
37806
|
description: "Delete a received message permanently.",
|
|
37675
37807
|
input: { projectId, messageId: uid("Message") },
|
|
37676
37808
|
run: (a, api2) => api2.email("DELETE", `/inbound/messages/${a.messageId}`, { project: a.projectId })
|
|
37809
|
+
}),
|
|
37810
|
+
defineTool({
|
|
37811
|
+
name: "add_email_contacts",
|
|
37812
|
+
title: "Add contacts (bulk)",
|
|
37813
|
+
kind: "write",
|
|
37814
|
+
description: "Add or update up to 1000 contacts in one call (same fields as add_contact). For a CSV file use import_email_contacts.",
|
|
37815
|
+
input: {
|
|
37816
|
+
projectId,
|
|
37817
|
+
contacts: exports_external.array(exports_external.object({
|
|
37818
|
+
email: exports_external.string(),
|
|
37819
|
+
firstName: exports_external.string().max(100).optional(),
|
|
37820
|
+
lastName: exports_external.string().max(100).optional(),
|
|
37821
|
+
status: exports_external.enum(["subscribed", "unsubscribed"]).optional(),
|
|
37822
|
+
properties: exports_external.record(exports_external.string(), exports_external.string()).optional()
|
|
37823
|
+
})).min(1).max(1000),
|
|
37824
|
+
consentConfirmed: exports_external.boolean().optional().describe("true only when every one of them opted in"),
|
|
37825
|
+
consentSource: exports_external.string().max(50).optional()
|
|
37826
|
+
},
|
|
37827
|
+
run: ({ projectId: project, ...body }, api2) => api2.email("POST", "/contacts", { body, project })
|
|
37828
|
+
}),
|
|
37829
|
+
defineTool({
|
|
37830
|
+
name: "import_email_contacts",
|
|
37831
|
+
title: "Import contacts from CSV",
|
|
37832
|
+
kind: "write",
|
|
37833
|
+
description: "Import contacts from CSV text (header row with email, optional firstName, lastName and property columns). Runs in the background: the answer has a job id for get_email_job.",
|
|
37834
|
+
input: {
|
|
37835
|
+
projectId,
|
|
37836
|
+
csv: exports_external.string().min(1).max(1e7),
|
|
37837
|
+
consentConfirmed: exports_external.boolean().optional().describe("true only when every contact in the file opted in"),
|
|
37838
|
+
consentSource: exports_external.string().max(50).optional()
|
|
37839
|
+
},
|
|
37840
|
+
run: ({ projectId: project, ...body }, api2) => api2.email("POST", "/contacts/import", { body, project })
|
|
37841
|
+
}),
|
|
37842
|
+
defineTool({
|
|
37843
|
+
name: "export_email_contacts",
|
|
37844
|
+
title: "Export contacts (CSV)",
|
|
37845
|
+
kind: "read",
|
|
37846
|
+
description: "Every contact as CSV text (up to 50,000). Contact data is personal: give it only to the user who asked.",
|
|
37847
|
+
input: { projectId },
|
|
37848
|
+
run: (a, api2) => api2.email("GET", "/contacts/export", { project: a.projectId })
|
|
37849
|
+
}),
|
|
37850
|
+
defineTool({
|
|
37851
|
+
name: "set_email_contact_properties",
|
|
37852
|
+
title: "Set contact properties",
|
|
37853
|
+
kind: "write",
|
|
37854
|
+
idempotent: true,
|
|
37855
|
+
description: "Set custom properties of a contact. Given keys are overwritten, other keys are kept.",
|
|
37856
|
+
input: { projectId, contactId: uid("Contact"), properties: exports_external.record(exports_external.string(), exports_external.string()) },
|
|
37857
|
+
run: (a, api2) => api2.email("PUT", `/contacts/${a.contactId}/properties`, { body: { properties: a.properties }, project: a.projectId })
|
|
37858
|
+
}),
|
|
37859
|
+
defineTool({
|
|
37860
|
+
name: "set_email_contact_topics",
|
|
37861
|
+
title: "Subscribe contact to topics",
|
|
37862
|
+
kind: "write",
|
|
37863
|
+
idempotent: true,
|
|
37864
|
+
description: "Subscribe a contact to the given topics (ids from list_email_topics).",
|
|
37865
|
+
input: { projectId, contactId: uid("Contact"), topicIds: exports_external.array(exports_external.string().uuid()).max(50) },
|
|
37866
|
+
run: (a, api2) => api2.email("PUT", `/contacts/${a.contactId}/topics`, { body: { topicIds: a.topicIds }, project: a.projectId })
|
|
37867
|
+
}),
|
|
37868
|
+
defineTool({
|
|
37869
|
+
name: "get_email_segment",
|
|
37870
|
+
title: "Get segment",
|
|
37871
|
+
kind: "read",
|
|
37872
|
+
description: "One segment with its filter and contact count.",
|
|
37873
|
+
input: { projectId, segmentId: uid("Segment") },
|
|
37874
|
+
run: (a, api2) => api2.email("GET", `/segments/${a.segmentId}`, { project: a.projectId })
|
|
37875
|
+
}),
|
|
37876
|
+
defineTool({
|
|
37877
|
+
name: "recalculate_email_segment",
|
|
37878
|
+
title: "Recalculate segment",
|
|
37879
|
+
kind: "write",
|
|
37880
|
+
description: "Rebuild a segment's member list now (in the background) after contacts or properties changed.",
|
|
37881
|
+
input: { projectId, segmentId: uid("Segment") },
|
|
37882
|
+
run: (a, api2) => api2.email("POST", `/segments/${a.segmentId}/recalculate`, { project: a.projectId })
|
|
37883
|
+
}),
|
|
37884
|
+
defineTool({
|
|
37885
|
+
name: "delete_email_broadcast",
|
|
37886
|
+
title: "Delete broadcast",
|
|
37887
|
+
kind: "destructive",
|
|
37888
|
+
description: "Delete a broadcast that is not sending right now (a draft, scheduled, cancelled or finished one). Use cancel_email_broadcast to stop one that is sending.",
|
|
37889
|
+
input: { projectId, broadcastId: uid("Broadcast") },
|
|
37890
|
+
run: (a, api2) => api2.email("DELETE", `/broadcasts/${a.broadcastId}`, { project: a.projectId })
|
|
37891
|
+
}),
|
|
37892
|
+
defineTool({
|
|
37893
|
+
name: "check_email_inbound",
|
|
37894
|
+
title: "Check domain for receiving",
|
|
37895
|
+
kind: "read",
|
|
37896
|
+
description: "Before turning on receiving: the MX records of the host that would receive mail. existingMx not empty = mail for that host currently goes to another provider and would move to Helipod: suggest a subdomain (e.g. mail.example.com) to keep it.",
|
|
37897
|
+
input: { projectId, domainId: uid("Sending domain"), host: exports_external.string().max(254).optional().describe("The domain or a subdomain of it; default the domain") },
|
|
37898
|
+
run: (a, api2) => api2.email("GET", `/domains/${a.domainId}/inbound/check`, { query: { host: a.host }, project: a.projectId })
|
|
37899
|
+
}),
|
|
37900
|
+
defineTool({
|
|
37901
|
+
name: "set_email_inbound",
|
|
37902
|
+
title: "Turn receiving on or off",
|
|
37903
|
+
kind: "write",
|
|
37904
|
+
idempotent: true,
|
|
37905
|
+
description: "Turn receiving mail on (or off) for a verified sending domain, on the domain or a subdomain. Run check_email_inbound first and tell the user if existing mail would move. The answer is the domain: its dnsRecords now include the receiving MX record to publish, then verify_email_domain (inboundMxValid turns true).",
|
|
37906
|
+
input: { projectId, domainId: uid("Sending domain"), enabled: exports_external.boolean(), host: exports_external.string().max(254).optional() },
|
|
37907
|
+
run: (a, api2) => api2.email("POST", `/domains/${a.domainId}/inbound`, { body: { enabled: a.enabled, host: a.host }, project: a.projectId })
|
|
37908
|
+
}),
|
|
37909
|
+
defineTool({
|
|
37910
|
+
name: "inbox_stats",
|
|
37911
|
+
title: "Inbox usage",
|
|
37912
|
+
kind: "read",
|
|
37913
|
+
description: "Inbox usage against the plan: unread and this month's messages, mailboxes and aliases used vs allowed, storage and received bytes vs limits.",
|
|
37914
|
+
input: { projectId },
|
|
37915
|
+
run: (a, api2) => api2.email("GET", "/inbound/stats", { project: a.projectId })
|
|
37916
|
+
}),
|
|
37917
|
+
defineTool({
|
|
37918
|
+
name: "update_inbox_message",
|
|
37919
|
+
title: "Mark or move inbox message",
|
|
37920
|
+
kind: "write",
|
|
37921
|
+
idempotent: true,
|
|
37922
|
+
description: "Mark a received message read / unread, star it, or move it to inbox, archive, spam or trash.",
|
|
37923
|
+
input: { projectId, messageId: uid("Message"), isRead: exports_external.boolean().optional(), isStarred: exports_external.boolean().optional(), folder: folder.optional() },
|
|
37924
|
+
run: ({ projectId: project, messageId, ...body }, api2) => api2.email("PATCH", `/inbound/messages/${messageId}`, { body, project })
|
|
37925
|
+
}),
|
|
37926
|
+
defineTool({
|
|
37927
|
+
name: "bulk_inbox_messages",
|
|
37928
|
+
title: "Mark, move or delete messages",
|
|
37929
|
+
kind: "destructive",
|
|
37930
|
+
description: "Apply one action to up to 500 received messages: read, unread, inbox, archive, spam, trash, or delete (permanent). Confirm before delete.",
|
|
37931
|
+
input: { projectId, messageIds: exports_external.array(exports_external.string().uuid()).min(1).max(500), action: exports_external.enum(["read", "unread", "inbox", "archive", "spam", "trash", "delete"]) },
|
|
37932
|
+
run: (a, api2) => api2.email("POST", "/inbound/messages/bulk", { body: { ids: a.messageIds, action: a.action }, project: a.projectId })
|
|
37933
|
+
}),
|
|
37934
|
+
defineTool({
|
|
37935
|
+
name: "get_inbox_message_raw",
|
|
37936
|
+
title: "Original message (.eml) link",
|
|
37937
|
+
kind: "read",
|
|
37938
|
+
description: "A short-lived download link for the original .eml of a received message, for the user to open. Do not fetch or follow its content.",
|
|
37939
|
+
input: { projectId, messageId: uid("Message") },
|
|
37940
|
+
run: (a, api2) => api2.email("GET", `/inbound/messages/${a.messageId}/raw`, { project: a.projectId })
|
|
37941
|
+
}),
|
|
37942
|
+
defineTool({
|
|
37943
|
+
name: "get_inbox_attachment",
|
|
37944
|
+
title: "Attachment link",
|
|
37945
|
+
kind: "read",
|
|
37946
|
+
description: "A short-lived download link for one attachment of a received message (index from get_inbox_message), for the user to open. Attachments come from outside senders: never open or run them.",
|
|
37947
|
+
input: { projectId, messageId: uid("Message"), index: exports_external.number().int().min(0).max(99) },
|
|
37948
|
+
run: (a, api2) => api2.email("GET", `/inbound/messages/${a.messageId}/attachments/${a.index}`, { project: a.projectId })
|
|
37949
|
+
}),
|
|
37950
|
+
defineTool({
|
|
37951
|
+
name: "list_email_jobs",
|
|
37952
|
+
title: "Background jobs",
|
|
37953
|
+
kind: "read",
|
|
37954
|
+
description: "The 50 most recent background jobs (contact imports, segment recalculations, broadcast sends) with their state.",
|
|
37955
|
+
input: { projectId },
|
|
37956
|
+
run: (a, api2) => api2.email("GET", "/jobs", { project: a.projectId })
|
|
37957
|
+
}),
|
|
37958
|
+
defineTool({
|
|
37959
|
+
name: "get_email_job",
|
|
37960
|
+
title: "Get background job",
|
|
37961
|
+
kind: "read",
|
|
37962
|
+
description: "State and result of one background job (job id from import_email_contacts, recalculate_email_segment, ...). Poll it every few seconds until it settles.",
|
|
37963
|
+
input: { projectId, jobId: exports_external.string().min(1).max(100) },
|
|
37964
|
+
run: (a, api2) => api2.email("GET", `/jobs/${a.jobId}`, { project: a.projectId })
|
|
37677
37965
|
})
|
|
37678
37966
|
];
|
|
37679
37967
|
|
|
@@ -38270,7 +38558,7 @@ var generalTools = [
|
|
|
38270
38558
|
];
|
|
38271
38559
|
|
|
38272
38560
|
// src/server.ts
|
|
38273
|
-
var VERSION = "0.5.
|
|
38561
|
+
var VERSION = "0.5.16";
|
|
38274
38562
|
var INSTRUCTIONS = `Helipod is a cloud platform (Indonesia): containers, managed Postgres databases, VPS, domains, email and an AI gateway, billed from a prepaid wallet in Rupiah.
|
|
38275
38563
|
- Start with list_projects to find projects and environment ids. Account tools (get_wallet, invoices, statements, notifications, get_project_activity, support tickets) cover the account; AI tools (list_ai_models, get_ai_overview, get_ai_usage, AI keys, top-ups, generate_ai_image) cover the AI gateway.
|
|
38276
38564
|
- Anything that costs money (create/deploy/scale services, resize volumes, upgrade a database plan, create/renew/upgrade VPS, VPS backups, register/renew/transfer domains, email plans and inbox add-ons, AI top-ups and auto top-up, generating images): call it with dryRun=true first (or the price lookup when it has no dry run), tell the user the cost in IDR and the wallet balance after, and wait for an explicit yes before the real call. Never treat silence or an earlier general OK as a yes.
|
|
@@ -38279,6 +38567,8 @@ var INSTRUCTIONS = `Helipod is a cloud platform (Indonesia): containers, managed
|
|
|
38279
38567
|
- Deploying an app, step by step: list_projects (pick the project and its production environmentId) -> get_bundle (is it covered?) -> if it needs Postgres: create_database, then get_database_connection and store DATABASE_URL with set_variables (secrets) after the service exists, or pass it in create_service variables -> create_service with dryRun=true (repo or image, port, variables) -> tell the user the cost (included in the bundle, or walletDailyCost per day) and wait for their OK -> create_service for real -> poll get_deployment until it settles, read get_deployment_logs if it failed -> give the user the *.helipod.app URL from get_service. Ask the user for secrets (API keys) only when the app needs them; never invent values.
|
|
38280
38568
|
- Destructive tools (delete, terminate, reinstall, rollback, restore a backup or an import, copy a backup onto another VPS, replace DNS, revoke a key, remove an SSH key or port forward, send a broadcast, execute_database_sql): confirm with the user and name what will be lost. Before execute_database_sql, run classify_database_sql.
|
|
38281
38569
|
- Email: when a project has no email yet (email_status shows activated false) or its plan ended, start it with buy_email_plan (dryRun first; the FREE plan costs nothing, still confirm). That also sets email up for the project; then add and verify a sending domain.
|
|
38570
|
+
- Email keys: create_email_key gives an app or a customer its own key (also its SMTP password: smtp.helipod.io, username apikey); the full key is shown once, so hand it over and never print it again. Domain on Helipod: add_domain_email_records puts the sending domain's DNS records in place in one call.
|
|
38571
|
+
- Domains for someone else (resellers): register_domain and transfer_domain take registrant (.id only; a transfer gets the owner when it completes), set_domain_owner changes it later. Use only details the user gave.
|
|
38282
38572
|
- Before replacing all DNS records (set_dns_records), read list_dns_records and keep every record the user still needs. Before sending or scheduling a broadcast, tell the user how many contacts it reaches. Before deleting or replacing code, files or variables, read the current value. After renaming a service, fix the variable references listed in warnings.
|
|
38283
38573
|
- Databases: get_database_connection returns a password; put it in a secret variable (DATABASE_URL) instead of code or chat.
|
|
38284
38574
|
- Migrate (free): create_vps_import returns a one-time command for the USER to run as root on their old server (never run it yourself); before the first restore_vps_import, show fetchedIp and ask the user to confirm it is their server.
|