helipod-mcp 0.5.11 → 0.5.13
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/dist/http.js +259 -24
- package/dist/index.js +259 -24
- package/dist/stdio.js +259 -24
- package/package.json +1 -1
- package/skills/helipod/SKILL.md +2 -2
- package/skills/helipod-deploy/SKILL.md +9 -4
- package/skills/helipod-domains/SKILL.md +2 -2
- package/skills/helipod-email/SKILL.md +12 -0
package/dist/http.js
CHANGED
|
@@ -35686,7 +35686,7 @@ class HelipodClient {
|
|
|
35686
35686
|
const details = json2?.details && typeof json2.details === "object" && !Array.isArray(json2.details) ? json2.details : undefined;
|
|
35687
35687
|
throw new HelipodError(res.status, json2?.code, retry ? `${msg} (retry after ${retry}s)` : String(msg), details);
|
|
35688
35688
|
}
|
|
35689
|
-
return json2 && "data" in json2 ? json2.data : json2;
|
|
35689
|
+
return json2 && "data" in json2 ? json2.data : json2 ?? (text || null);
|
|
35690
35690
|
}
|
|
35691
35691
|
}
|
|
35692
35692
|
|
|
@@ -35730,7 +35730,7 @@ function purchaseKey(dryRun, given) {
|
|
|
35730
35730
|
|
|
35731
35731
|
// src/tools/containers.ts
|
|
35732
35732
|
var id = (what) => exports_external.string().uuid().describe(`${what} id (UUID)`);
|
|
35733
|
-
var dryRun = exports_external.boolean().optional().describe("true: return the
|
|
35733
|
+
var dryRun = exports_external.boolean().optional().describe("true: return the cost only (dailyCost list price, walletDailyCost what the wallet pays after the bundle), change nothing");
|
|
35734
35734
|
var idem = exports_external.string().min(8).max(128).optional().describe("Idempotency-Key: a retry with the same value returns the first result instead of doing it twice");
|
|
35735
35735
|
var C = "/containers";
|
|
35736
35736
|
var port = exports_external.object({
|
|
@@ -35739,6 +35739,14 @@ var port = exports_external.object({
|
|
|
35739
35739
|
public: exports_external.boolean().optional().describe("http ports: give it a *.helipod.app domain (default true)")
|
|
35740
35740
|
});
|
|
35741
35741
|
var containerTools = [
|
|
35742
|
+
defineTool({
|
|
35743
|
+
name: "get_bundle",
|
|
35744
|
+
title: "Container bundle",
|
|
35745
|
+
kind: "read",
|
|
35746
|
+
description: "The account's container bundle: whether one is active, until when, quota used / max (vCPU, GB RAM, GB storage), what it covers and what it does not. Container services within the quota cost the wallet NOTHING: read this before telling the user to top up for a container deploy. The bundle covers containers only (not VPS, databases, email, domains or AI).",
|
|
35747
|
+
input: {},
|
|
35748
|
+
run: (_a3, api2) => api2.api("GET", `${C}/bundle`)
|
|
35749
|
+
}),
|
|
35742
35750
|
defineTool({
|
|
35743
35751
|
name: "list_projects",
|
|
35744
35752
|
title: "List projects",
|
|
@@ -35884,7 +35892,7 @@ var containerTools = [
|
|
|
35884
35892
|
name: "create_service",
|
|
35885
35893
|
title: "Create service",
|
|
35886
35894
|
kind: "write",
|
|
35887
|
-
description: "Create a service from a container image, a Git repository or an inline Dockerfile, and (unless deploy=false) start its first deploy. Private GitHub / GitLab repos work once connected (get_git_connections, list_git_repos); such services also redeploy on every push. A private repo that is not connected is refused (409 GITHUB_NOT_CONNECTED / GITLAB_NOT_CONNECTED / REPO_NOT_ACCESSIBLE, nothing created): give the user details.connectUrl and retry after they connect. COSTS MONEY once deployed
|
|
35895
|
+
description: "Create a service from a container image, a Git repository or an inline Dockerfile, and (unless deploy=false) start its first deploy. Private GitHub / GitLab repos work once connected (get_git_connections, list_git_repos); such services also redeploy on every push. A private repo that is not connected is refused (409 GITHUB_NOT_CONNECTED / GITLAB_NOT_CONNECTED / REPO_NOT_ACCESSIBLE, nothing created): give the user details.connectUrl and retry after they connect. COSTS MONEY once deployed, beyond the account's bundle: billed daily, from the bundle quota first and the wallet only beyond it. Call with dryRun=true first, tell the user what the wallet pays (walletDailyCost) and get their OK. Before saying a price, read walletDailyCost (what the wallet really pays per day) and bundle: with an active bundle that covers it (bundle.coversIt, walletDailyCost 0) nothing is charged and NO top-up is needed, even with a Rp 0 balance.",
|
|
35888
35896
|
input: {
|
|
35889
35897
|
environmentId: id("Environment"),
|
|
35890
35898
|
name: exports_external.string().regex(/^[A-Za-z][A-Za-z0-9_-]{0,39}$/).describe("Service name (letters, digits, _ -; starts with a letter). Also used in references like ${{name.KEY}}"),
|
|
@@ -35902,6 +35910,7 @@ var containerTools = [
|
|
|
35902
35910
|
ram: exports_external.number().int().min(128).max(8192).optional().describe("MB, multiple of 128"),
|
|
35903
35911
|
startCommand: exports_external.string().optional(),
|
|
35904
35912
|
healthcheckPath: exports_external.string().optional().describe("HTTP path answering 2xx when ready, e.g. /health"),
|
|
35913
|
+
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)"),
|
|
35905
35914
|
deploy: exports_external.boolean().optional().describe("false: create without deploying (nothing is charged)"),
|
|
35906
35915
|
dryRun,
|
|
35907
35916
|
idempotencyKey: idem
|
|
@@ -35923,7 +35932,7 @@ var containerTools = [
|
|
|
35923
35932
|
cpu: a.cpu,
|
|
35924
35933
|
ram: a.ram,
|
|
35925
35934
|
startCommand: a.startCommand,
|
|
35926
|
-
healthcheck: a.healthcheckPath ? { path: a.healthcheckPath } : undefined,
|
|
35935
|
+
healthcheck: a.healthcheckPath ? { path: a.healthcheckPath, timeout: a.healthcheckTimeout } : undefined,
|
|
35927
35936
|
deploy: a.deploy
|
|
35928
35937
|
}
|
|
35929
35938
|
});
|
|
@@ -35966,7 +35975,7 @@ var containerTools = [
|
|
|
35966
35975
|
name: "deploy_service",
|
|
35967
35976
|
title: "Deploy service",
|
|
35968
35977
|
kind: "write",
|
|
35969
|
-
description: "Start a new deployment (Git services build the latest commit). COSTS MONEY: charges today's cost when the build starts
|
|
35978
|
+
description: "Start a new deployment (Git services build the latest commit). COSTS MONEY beyond the bundle: charges today's cost when the build starts (refunded if it fails), nothing within an active bundle's quota. dryRun=true returns the cost only. Before saying a price, read walletDailyCost (what the wallet really pays per day) and bundle: with an active bundle that covers it (bundle.coversIt, walletDailyCost 0) nothing is charged and NO top-up is needed, even with a Rp 0 balance. The deployment runs in the background; get_deployment returns its status.",
|
|
35970
35979
|
input: { serviceId: id("Service"), dryRun, idempotencyKey: idem },
|
|
35971
35980
|
run: (a, api2) => api2.api("POST", `${C}/services/${a.serviceId}/deployments`, { query: { dryRun: a.dryRun ? "true" : undefined }, idempotencyKey: a.idempotencyKey })
|
|
35972
35981
|
}),
|
|
@@ -36053,6 +36062,14 @@ var containerTools = [
|
|
|
36053
36062
|
},
|
|
36054
36063
|
run: (a, api2) => api2.api("POST", `${C}/services/${a.serviceId}/domains`, { body: { kind: a.kind, domain: a.domain, port: a.port, protocol: a.protocol } })
|
|
36055
36064
|
}),
|
|
36065
|
+
defineTool({
|
|
36066
|
+
name: "verify_service_domain",
|
|
36067
|
+
title: "Check custom domain DNS",
|
|
36068
|
+
kind: "write",
|
|
36069
|
+
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).",
|
|
36070
|
+
input: { serviceId: id("Service"), domainId: id("Domain") },
|
|
36071
|
+
run: (a, api2) => api2.api("POST", `${C}/services/${a.serviceId}/domains/${a.domainId}/verify`)
|
|
36072
|
+
}),
|
|
36056
36073
|
defineTool({
|
|
36057
36074
|
name: "remove_service_domain",
|
|
36058
36075
|
title: "Remove domain from service",
|
|
@@ -36074,7 +36091,7 @@ var containerTools = [
|
|
|
36074
36091
|
title: "Scale service",
|
|
36075
36092
|
kind: "write",
|
|
36076
36093
|
idempotent: true,
|
|
36077
|
-
description: "Change replicas, CPU or RAM (applies right away). COSTS MONEY
|
|
36094
|
+
description: "Change replicas, CPU or RAM (applies right away). An increase COSTS MONEY unless the bundle covers it: call with dryRun=true first to show currentDailyCost vs newDailyCost and walletDailyCost, and get the user's OK. Before saying a price, read walletDailyCost (what the wallet really pays per day) and bundle: with an active bundle that covers it (bundle.coversIt, walletDailyCost 0) nothing is charged and NO top-up is needed, even with a Rp 0 balance.",
|
|
36078
36095
|
input: {
|
|
36079
36096
|
serviceId: id("Service"),
|
|
36080
36097
|
replicas: exports_external.number().int().min(1).max(20).optional(),
|
|
@@ -36122,7 +36139,7 @@ var containerTools = [
|
|
|
36122
36139
|
name: "resize_volume",
|
|
36123
36140
|
title: "Resize volume",
|
|
36124
36141
|
kind: "write",
|
|
36125
|
-
description: "Change a volume's size (1-50 GB; never below the space already used). A larger volume COSTS MONEY: it raises the daily cost, charged pro rata when the service is redeployed and in full from the next renewal. dryRun=true returns currentDailyCost and
|
|
36142
|
+
description: "Change a volume's size (1-50 GB; never below the space already used). A larger volume COSTS MONEY: it raises the daily cost, charged pro rata when the service is redeployed and in full from the next renewal. dryRun=true returns currentDailyCost, newDailyCost and walletDailyCost (IDR) without changing anything. Applies on the next deploy. Before saying a price, read walletDailyCost (what the wallet really pays per day) and bundle: with an active bundle that covers it (bundle.coversIt, walletDailyCost 0) nothing is charged and NO top-up is needed, even with a Rp 0 balance.",
|
|
36126
36143
|
input: {
|
|
36127
36144
|
serviceId: id("Service"),
|
|
36128
36145
|
volumeId: id("Volume"),
|
|
@@ -36244,20 +36261,21 @@ var containerTools = [
|
|
|
36244
36261
|
name: "deploy_template",
|
|
36245
36262
|
title: "Deploy template",
|
|
36246
36263
|
kind: "write",
|
|
36247
|
-
description: "Create every service of a template in an environment and deploy them. COSTS MONEY
|
|
36264
|
+
description: "Create every service of a template in an environment and deploy them. COSTS MONEY beyond the bundle: each service is billed daily (bundle quota first, then the wallet). Call with dryRun=true first. " + "Before saying a price, read walletDailyCost (what the wallet really pays per day) and bundle: with an active bundle that covers it (bundle.coversIt, walletDailyCost 0) nothing is charged and NO top-up is needed, even with a Rp 0 balance.",
|
|
36248
36265
|
input: {
|
|
36249
36266
|
environmentId: id("Environment"),
|
|
36250
36267
|
slug: exports_external.string().regex(/^[a-z0-9][a-z0-9-]{0,79}$/),
|
|
36251
36268
|
inputs: exports_external.record(exports_external.string(), exports_external.string()).optional().describe("Answers to the template's inputs"),
|
|
36269
|
+
dryRun,
|
|
36252
36270
|
idempotencyKey: idem
|
|
36253
36271
|
},
|
|
36254
|
-
run: (a, api2) => api2.api("POST", `${C}/environments/${a.environmentId}/templates/${a.slug}`, { body: { inputs: a.inputs }, idempotencyKey: a.idempotencyKey })
|
|
36272
|
+
run: (a, api2) => api2.api("POST", `${C}/environments/${a.environmentId}/templates/${a.slug}`, { body: { inputs: a.inputs }, query: { dryRun: a.dryRun ? "true" : undefined }, idempotencyKey: a.idempotencyKey })
|
|
36255
36273
|
}),
|
|
36256
36274
|
defineTool({
|
|
36257
36275
|
name: "create_stack",
|
|
36258
36276
|
title: "Create stack from spec",
|
|
36259
36277
|
kind: "write",
|
|
36260
|
-
description: "Create several connected services from a template spec ({ version: 1, inputs?, services: [...] }) and deploy them in dependency order. COSTS MONEY: dryRun=true first. See https://docs.helipod.io/docs/container-api/templates-and-stacks",
|
|
36278
|
+
description: "Create several connected services from a template spec ({ version: 1, inputs?, services: [...] }) and deploy them in dependency order. COSTS MONEY beyond the bundle: billed daily (bundle quota first, then the wallet). dryRun=true first. Before saying a price, read walletDailyCost (what the wallet really pays per day) and bundle: with an active bundle that covers it (bundle.coversIt, walletDailyCost 0) nothing is charged and NO top-up is needed, even with a Rp 0 balance. See https://docs.helipod.io/docs/container-api/templates-and-stacks",
|
|
36261
36279
|
input: {
|
|
36262
36280
|
environmentId: id("Environment"),
|
|
36263
36281
|
spec: exports_external.record(exports_external.string(), exports_external.any()).describe("Template spec object"),
|
|
@@ -36774,16 +36792,21 @@ var domainTools = [
|
|
|
36774
36792
|
title: "Check domain availability",
|
|
36775
36793
|
kind: "read",
|
|
36776
36794
|
description: "Is a domain name available to register? No key needed.",
|
|
36777
|
-
input: { domain: exports_external.string().describe("e.g. tokosaya.id") },
|
|
36778
|
-
run: (a, api2) => api2.api("GET", "/domains/search", { query: { domain: a.domain }, auth: false })
|
|
36795
|
+
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") },
|
|
36796
|
+
run: (a, api2) => api2.api("GET", "/domains/search", { query: { domain: a.domain, includePremium: a.includePremium ? "true" : undefined }, auth: false })
|
|
36779
36797
|
}),
|
|
36780
36798
|
defineTool({
|
|
36781
36799
|
name: "domain_prices",
|
|
36782
36800
|
title: "Domain prices",
|
|
36783
36801
|
kind: "read",
|
|
36784
36802
|
description: "Registration, renewal and transfer prices per extension (IDR, VAT included) and the yearly WHOIS privacy price.",
|
|
36785
|
-
input: {
|
|
36786
|
-
|
|
36803
|
+
input: {
|
|
36804
|
+
extension: exports_external.string().optional().describe("e.g. .id or .com"),
|
|
36805
|
+
promo: exports_external.boolean().optional().describe("true: only extensions on promotion"),
|
|
36806
|
+
limit: exports_external.number().int().min(1).max(100).optional(),
|
|
36807
|
+
page: exports_external.number().int().min(1).optional()
|
|
36808
|
+
},
|
|
36809
|
+
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 })
|
|
36787
36810
|
}),
|
|
36788
36811
|
defineTool({
|
|
36789
36812
|
name: "list_my_domains",
|
|
@@ -36842,9 +36865,22 @@ var domainTools = [
|
|
|
36842
36865
|
name: exports_external.string().describe("Full domain, e.g. tokosaya.id"),
|
|
36843
36866
|
nameservers: exports_external.array(exports_external.string()).min(2).max(5).optional(),
|
|
36844
36867
|
whoisProtection: exports_external.boolean().optional(),
|
|
36868
|
+
registrant: exports_external.object({
|
|
36869
|
+
name: exports_external.string().min(3).max(255),
|
|
36870
|
+
email: exports_external.string().max(255).optional().describe("Gets the registry's verification email; defaults to the account email"),
|
|
36871
|
+
organization: exports_external.string().max(255).optional(),
|
|
36872
|
+
phone: exports_external.string().min(9).max(20).describe("e.g. +6281234567890"),
|
|
36873
|
+
addressLine1: exports_external.string().min(3).max(500),
|
|
36874
|
+
addressLine2: exports_external.string().max(500).optional(),
|
|
36875
|
+
city: exports_external.string().min(3).max(100),
|
|
36876
|
+
state: exports_external.string().min(1).max(100),
|
|
36877
|
+
countryCode: exports_external.string().regex(/^[A-Za-z]{2}$/).describe("ISO 3166-1 alpha-2, e.g. ID"),
|
|
36878
|
+
postalCode: exports_external.string().min(1).max(20),
|
|
36879
|
+
fax: exports_external.string().max(50).optional()
|
|
36880
|
+
}).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."),
|
|
36845
36881
|
dryRun: dryRun3
|
|
36846
36882
|
},
|
|
36847
|
-
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 } })
|
|
36883
|
+
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 } })
|
|
36848
36884
|
}),
|
|
36849
36885
|
defineTool({
|
|
36850
36886
|
name: "transfer_domain",
|
|
@@ -36899,9 +36935,9 @@ var domainTools = [
|
|
|
36899
36935
|
name: "enable_whois_privacy",
|
|
36900
36936
|
title: "Enable WHOIS privacy",
|
|
36901
36937
|
kind: "write",
|
|
36902
|
-
description:
|
|
36903
|
-
input: { domainId },
|
|
36904
|
-
run: (a, api2) => api2.api("POST", `/domains/${a.domainId}/whois-protection
|
|
36938
|
+
description: `Hide the owner's details in WHOIS. COSTS MONEY (the whois_protection price per year). ${DRY2}`,
|
|
36939
|
+
input: { domainId, dryRun: dryRun3 },
|
|
36940
|
+
run: (a, api2) => api2.api("POST", `/domains/${a.domainId}/whois-protection`, { query: { dryRun: a.dryRun ? "true" : undefined } })
|
|
36905
36941
|
}),
|
|
36906
36942
|
defineTool({
|
|
36907
36943
|
name: "get_domain_price",
|
|
@@ -36944,6 +36980,23 @@ var domainTools = [
|
|
|
36944
36980
|
input: { domainId, emailDomainId: exports_external.string().uuid() },
|
|
36945
36981
|
run: (a, api2) => api2.api("POST", `/domains/${a.domainId}/email-records`, { body: { emailDomainId: a.emailDomainId } })
|
|
36946
36982
|
}),
|
|
36983
|
+
defineTool({
|
|
36984
|
+
name: "get_child_nameserver",
|
|
36985
|
+
title: "Get child nameserver",
|
|
36986
|
+
kind: "read",
|
|
36987
|
+
description: "One child nameserver (glue record) of a registered domain: hostname and IP address.",
|
|
36988
|
+
input: { domainId, hostId },
|
|
36989
|
+
run: (a, api2) => api2.api("GET", `/domains/${a.domainId}/hosts/${a.hostId}`)
|
|
36990
|
+
}),
|
|
36991
|
+
defineTool({
|
|
36992
|
+
name: "set_domain_auto_renew",
|
|
36993
|
+
title: "Set domain auto-renew",
|
|
36994
|
+
kind: "write",
|
|
36995
|
+
idempotent: true,
|
|
36996
|
+
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.",
|
|
36997
|
+
input: { domainId, autoRenew: exports_external.boolean() },
|
|
36998
|
+
run: (a, api2) => api2.api("PATCH", `/domains/${a.domainId}/auto-renew`, { body: { autoRenew: a.autoRenew } })
|
|
36999
|
+
}),
|
|
36947
37000
|
defineTool({
|
|
36948
37001
|
name: "list_child_nameservers",
|
|
36949
37002
|
title: "List child nameservers",
|
|
@@ -37020,7 +37073,10 @@ var message = exports_external.object({
|
|
|
37020
37073
|
templateId: exports_external.string().optional(),
|
|
37021
37074
|
templateData: exports_external.record(exports_external.string(), exports_external.string()).optional(),
|
|
37022
37075
|
variables: exports_external.record(exports_external.string(), exports_external.string()).optional(),
|
|
37023
|
-
idempotencyKey: exports_external.string().max(128).optional()
|
|
37076
|
+
idempotencyKey: exports_external.string().max(128).optional(),
|
|
37077
|
+
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"),
|
|
37078
|
+
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)"),
|
|
37079
|
+
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")
|
|
37024
37080
|
});
|
|
37025
37081
|
var dryRun4 = exports_external.boolean().optional().describe("true = return the price (IDR) and the wallet balance after it; nothing is charged or changed");
|
|
37026
37082
|
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");
|
|
@@ -37129,7 +37185,10 @@ var emailTools = [
|
|
|
37129
37185
|
templateId: exports_external.string().optional(),
|
|
37130
37186
|
templateData: exports_external.record(exports_external.string(), exports_external.string()).optional(),
|
|
37131
37187
|
variables: exports_external.record(exports_external.string(), exports_external.string()).optional(),
|
|
37132
|
-
idempotencyKey: exports_external.string().max(128).optional()
|
|
37188
|
+
idempotencyKey: exports_external.string().max(128).optional(),
|
|
37189
|
+
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"),
|
|
37190
|
+
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)"),
|
|
37191
|
+
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")
|
|
37133
37192
|
},
|
|
37134
37193
|
run: ({ projectId: project, ...a }, api2) => api2.email("POST", "/send", { body: a, project })
|
|
37135
37194
|
}),
|
|
@@ -37138,7 +37197,15 @@ var emailTools = [
|
|
|
37138
37197
|
title: "List sent emails",
|
|
37139
37198
|
kind: "read",
|
|
37140
37199
|
description: "Email log, newest first, with delivery status.",
|
|
37141
|
-
input: {
|
|
37200
|
+
input: {
|
|
37201
|
+
projectId,
|
|
37202
|
+
status: exports_external.string().optional(),
|
|
37203
|
+
search: exports_external.string().optional(),
|
|
37204
|
+
from: exports_external.string().optional().describe("Sent at or after (ISO 8601), e.g. 2026-10-01T00:00:00Z"),
|
|
37205
|
+
to: exports_external.string().optional().describe("Sent before (ISO 8601)"),
|
|
37206
|
+
limit: exports_external.number().int().min(1).max(100).optional(),
|
|
37207
|
+
cursor: exports_external.string().optional()
|
|
37208
|
+
},
|
|
37142
37209
|
run: ({ projectId: project, ...a }, api2) => api2.email("GET", "/emails", { query: a, project })
|
|
37143
37210
|
}),
|
|
37144
37211
|
defineTool({
|
|
@@ -37186,7 +37253,16 @@ var emailTools = [
|
|
|
37186
37253
|
title: "Add contact",
|
|
37187
37254
|
kind: "write",
|
|
37188
37255
|
description: "Add or update an audience contact.",
|
|
37189
|
-
input: {
|
|
37256
|
+
input: {
|
|
37257
|
+
projectId,
|
|
37258
|
+
email: exports_external.string(),
|
|
37259
|
+
firstName: exports_external.string().optional(),
|
|
37260
|
+
lastName: exports_external.string().optional(),
|
|
37261
|
+
status: exports_external.enum(["subscribed", "unsubscribed"]).optional(),
|
|
37262
|
+
properties: exports_external.record(exports_external.string(), exports_external.string()).optional().describe("Custom properties (used by segments and {{placeholders}})"),
|
|
37263
|
+
consentConfirmed: exports_external.boolean().optional().describe("true only when the person agreed to receive email (you have their opt-in)"),
|
|
37264
|
+
consentSource: exports_external.string().max(50).optional().describe("Where the consent came from, e.g. signup_form")
|
|
37265
|
+
},
|
|
37190
37266
|
run: ({ projectId: project, ...a }, api2) => api2.email("POST", "/contacts", { body: a, project })
|
|
37191
37267
|
}),
|
|
37192
37268
|
defineTool({
|
|
@@ -37665,6 +37741,162 @@ var emailTools = [
|
|
|
37665
37741
|
description: "Delete a received message permanently.",
|
|
37666
37742
|
input: { projectId, messageId: uid("Message") },
|
|
37667
37743
|
run: (a, api2) => api2.email("DELETE", `/inbound/messages/${a.messageId}`, { project: a.projectId })
|
|
37744
|
+
}),
|
|
37745
|
+
defineTool({
|
|
37746
|
+
name: "add_email_contacts",
|
|
37747
|
+
title: "Add contacts (bulk)",
|
|
37748
|
+
kind: "write",
|
|
37749
|
+
description: "Add or update up to 1000 contacts in one call (same fields as add_contact). For a CSV file use import_email_contacts.",
|
|
37750
|
+
input: {
|
|
37751
|
+
projectId,
|
|
37752
|
+
contacts: exports_external.array(exports_external.object({
|
|
37753
|
+
email: exports_external.string(),
|
|
37754
|
+
firstName: exports_external.string().max(100).optional(),
|
|
37755
|
+
lastName: exports_external.string().max(100).optional(),
|
|
37756
|
+
status: exports_external.enum(["subscribed", "unsubscribed"]).optional(),
|
|
37757
|
+
properties: exports_external.record(exports_external.string(), exports_external.string()).optional()
|
|
37758
|
+
})).min(1).max(1000),
|
|
37759
|
+
consentConfirmed: exports_external.boolean().optional().describe("true only when every one of them opted in"),
|
|
37760
|
+
consentSource: exports_external.string().max(50).optional()
|
|
37761
|
+
},
|
|
37762
|
+
run: ({ projectId: project, ...body }, api2) => api2.email("POST", "/contacts", { body, project })
|
|
37763
|
+
}),
|
|
37764
|
+
defineTool({
|
|
37765
|
+
name: "import_email_contacts",
|
|
37766
|
+
title: "Import contacts from CSV",
|
|
37767
|
+
kind: "write",
|
|
37768
|
+
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.",
|
|
37769
|
+
input: {
|
|
37770
|
+
projectId,
|
|
37771
|
+
csv: exports_external.string().min(1).max(1e7),
|
|
37772
|
+
consentConfirmed: exports_external.boolean().optional().describe("true only when every contact in the file opted in"),
|
|
37773
|
+
consentSource: exports_external.string().max(50).optional()
|
|
37774
|
+
},
|
|
37775
|
+
run: ({ projectId: project, ...body }, api2) => api2.email("POST", "/contacts/import", { body, project })
|
|
37776
|
+
}),
|
|
37777
|
+
defineTool({
|
|
37778
|
+
name: "export_email_contacts",
|
|
37779
|
+
title: "Export contacts (CSV)",
|
|
37780
|
+
kind: "read",
|
|
37781
|
+
description: "Every contact as CSV text (up to 50,000). Contact data is personal: give it only to the user who asked.",
|
|
37782
|
+
input: { projectId },
|
|
37783
|
+
run: (a, api2) => api2.email("GET", "/contacts/export", { project: a.projectId })
|
|
37784
|
+
}),
|
|
37785
|
+
defineTool({
|
|
37786
|
+
name: "set_email_contact_properties",
|
|
37787
|
+
title: "Set contact properties",
|
|
37788
|
+
kind: "write",
|
|
37789
|
+
idempotent: true,
|
|
37790
|
+
description: "Set custom properties of a contact. Given keys are overwritten, other keys are kept.",
|
|
37791
|
+
input: { projectId, contactId: uid("Contact"), properties: exports_external.record(exports_external.string(), exports_external.string()) },
|
|
37792
|
+
run: (a, api2) => api2.email("PUT", `/contacts/${a.contactId}/properties`, { body: { properties: a.properties }, project: a.projectId })
|
|
37793
|
+
}),
|
|
37794
|
+
defineTool({
|
|
37795
|
+
name: "set_email_contact_topics",
|
|
37796
|
+
title: "Subscribe contact to topics",
|
|
37797
|
+
kind: "write",
|
|
37798
|
+
idempotent: true,
|
|
37799
|
+
description: "Subscribe a contact to the given topics (ids from list_email_topics).",
|
|
37800
|
+
input: { projectId, contactId: uid("Contact"), topicIds: exports_external.array(exports_external.string().uuid()).max(50) },
|
|
37801
|
+
run: (a, api2) => api2.email("PUT", `/contacts/${a.contactId}/topics`, { body: { topicIds: a.topicIds }, project: a.projectId })
|
|
37802
|
+
}),
|
|
37803
|
+
defineTool({
|
|
37804
|
+
name: "get_email_segment",
|
|
37805
|
+
title: "Get segment",
|
|
37806
|
+
kind: "read",
|
|
37807
|
+
description: "One segment with its filter and contact count.",
|
|
37808
|
+
input: { projectId, segmentId: uid("Segment") },
|
|
37809
|
+
run: (a, api2) => api2.email("GET", `/segments/${a.segmentId}`, { project: a.projectId })
|
|
37810
|
+
}),
|
|
37811
|
+
defineTool({
|
|
37812
|
+
name: "recalculate_email_segment",
|
|
37813
|
+
title: "Recalculate segment",
|
|
37814
|
+
kind: "write",
|
|
37815
|
+
description: "Rebuild a segment's member list now (in the background) after contacts or properties changed.",
|
|
37816
|
+
input: { projectId, segmentId: uid("Segment") },
|
|
37817
|
+
run: (a, api2) => api2.email("POST", `/segments/${a.segmentId}/recalculate`, { project: a.projectId })
|
|
37818
|
+
}),
|
|
37819
|
+
defineTool({
|
|
37820
|
+
name: "delete_email_broadcast",
|
|
37821
|
+
title: "Delete broadcast",
|
|
37822
|
+
kind: "destructive",
|
|
37823
|
+
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.",
|
|
37824
|
+
input: { projectId, broadcastId: uid("Broadcast") },
|
|
37825
|
+
run: (a, api2) => api2.email("DELETE", `/broadcasts/${a.broadcastId}`, { project: a.projectId })
|
|
37826
|
+
}),
|
|
37827
|
+
defineTool({
|
|
37828
|
+
name: "check_email_inbound",
|
|
37829
|
+
title: "Check domain for receiving",
|
|
37830
|
+
kind: "read",
|
|
37831
|
+
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.",
|
|
37832
|
+
input: { projectId, domainId: uid("Sending domain"), host: exports_external.string().max(254).optional().describe("The domain or a subdomain of it; default the domain") },
|
|
37833
|
+
run: (a, api2) => api2.email("GET", `/domains/${a.domainId}/inbound/check`, { query: { host: a.host }, project: a.projectId })
|
|
37834
|
+
}),
|
|
37835
|
+
defineTool({
|
|
37836
|
+
name: "set_email_inbound",
|
|
37837
|
+
title: "Turn receiving on or off",
|
|
37838
|
+
kind: "write",
|
|
37839
|
+
idempotent: true,
|
|
37840
|
+
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).",
|
|
37841
|
+
input: { projectId, domainId: uid("Sending domain"), enabled: exports_external.boolean(), host: exports_external.string().max(254).optional() },
|
|
37842
|
+
run: (a, api2) => api2.email("POST", `/domains/${a.domainId}/inbound`, { body: { enabled: a.enabled, host: a.host }, project: a.projectId })
|
|
37843
|
+
}),
|
|
37844
|
+
defineTool({
|
|
37845
|
+
name: "inbox_stats",
|
|
37846
|
+
title: "Inbox usage",
|
|
37847
|
+
kind: "read",
|
|
37848
|
+
description: "Inbox usage against the plan: unread and this month's messages, mailboxes and aliases used vs allowed, storage and received bytes vs limits.",
|
|
37849
|
+
input: { projectId },
|
|
37850
|
+
run: (a, api2) => api2.email("GET", "/inbound/stats", { project: a.projectId })
|
|
37851
|
+
}),
|
|
37852
|
+
defineTool({
|
|
37853
|
+
name: "update_inbox_message",
|
|
37854
|
+
title: "Mark or move inbox message",
|
|
37855
|
+
kind: "write",
|
|
37856
|
+
idempotent: true,
|
|
37857
|
+
description: "Mark a received message read / unread, star it, or move it to inbox, archive, spam or trash.",
|
|
37858
|
+
input: { projectId, messageId: uid("Message"), isRead: exports_external.boolean().optional(), isStarred: exports_external.boolean().optional(), folder: folder.optional() },
|
|
37859
|
+
run: ({ projectId: project, messageId, ...body }, api2) => api2.email("PATCH", `/inbound/messages/${messageId}`, { body, project })
|
|
37860
|
+
}),
|
|
37861
|
+
defineTool({
|
|
37862
|
+
name: "bulk_inbox_messages",
|
|
37863
|
+
title: "Mark, move or delete messages",
|
|
37864
|
+
kind: "destructive",
|
|
37865
|
+
description: "Apply one action to up to 500 received messages: read, unread, inbox, archive, spam, trash, or delete (permanent). Confirm before delete.",
|
|
37866
|
+
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"]) },
|
|
37867
|
+
run: (a, api2) => api2.email("POST", "/inbound/messages/bulk", { body: { ids: a.messageIds, action: a.action }, project: a.projectId })
|
|
37868
|
+
}),
|
|
37869
|
+
defineTool({
|
|
37870
|
+
name: "get_inbox_message_raw",
|
|
37871
|
+
title: "Original message (.eml) link",
|
|
37872
|
+
kind: "read",
|
|
37873
|
+
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.",
|
|
37874
|
+
input: { projectId, messageId: uid("Message") },
|
|
37875
|
+
run: (a, api2) => api2.email("GET", `/inbound/messages/${a.messageId}/raw`, { project: a.projectId })
|
|
37876
|
+
}),
|
|
37877
|
+
defineTool({
|
|
37878
|
+
name: "get_inbox_attachment",
|
|
37879
|
+
title: "Attachment link",
|
|
37880
|
+
kind: "read",
|
|
37881
|
+
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.",
|
|
37882
|
+
input: { projectId, messageId: uid("Message"), index: exports_external.number().int().min(0).max(99) },
|
|
37883
|
+
run: (a, api2) => api2.email("GET", `/inbound/messages/${a.messageId}/attachments/${a.index}`, { project: a.projectId })
|
|
37884
|
+
}),
|
|
37885
|
+
defineTool({
|
|
37886
|
+
name: "list_email_jobs",
|
|
37887
|
+
title: "Background jobs",
|
|
37888
|
+
kind: "read",
|
|
37889
|
+
description: "The 50 most recent background jobs (contact imports, segment recalculations, broadcast sends) with their state.",
|
|
37890
|
+
input: { projectId },
|
|
37891
|
+
run: (a, api2) => api2.email("GET", "/jobs", { project: a.projectId })
|
|
37892
|
+
}),
|
|
37893
|
+
defineTool({
|
|
37894
|
+
name: "get_email_job",
|
|
37895
|
+
title: "Get background job",
|
|
37896
|
+
kind: "read",
|
|
37897
|
+
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.",
|
|
37898
|
+
input: { projectId, jobId: exports_external.string().min(1).max(100) },
|
|
37899
|
+
run: (a, api2) => api2.email("GET", `/jobs/${a.jobId}`, { project: a.projectId })
|
|
37668
37900
|
})
|
|
37669
37901
|
];
|
|
37670
37902
|
|
|
@@ -37776,7 +38008,7 @@ var databaseTools = [
|
|
|
37776
38008
|
name: "upgrade_database",
|
|
37777
38009
|
title: "Upgrade database plan / top up",
|
|
37778
38010
|
kind: "write",
|
|
37779
|
-
description: "Buy a paid plan or top up credits. COSTS MONEY from the wallet: call with dryRun first, show price and wallet balance after, and get the user's explicit OK.",
|
|
38011
|
+
description: "Buy a paid plan or top up credits. Only when the user asks for more than the FREE plan gives (never to get started: create_database is free). COSTS MONEY from the wallet (bundles do not cover it): call with dryRun first, show price and wallet balance after, and get the user's explicit OK.",
|
|
37780
38012
|
input: { databaseId, plan: exports_external.string().describe("Plan code (e.g. STARTER) or id from list_database_plans"), dryRun: dryRun5, idempotencyKey: exports_external.string().max(128).optional() },
|
|
37781
38013
|
run: (a, api2) => api2.api("POST", `/databases/${a.databaseId}/upgrade`, { body: { plan: a.plan }, query: { dryRun: a.dryRun ? "true" : undefined }, idempotencyKey: a.idempotencyKey })
|
|
37782
38014
|
}),
|
|
@@ -38261,10 +38493,13 @@ var generalTools = [
|
|
|
38261
38493
|
];
|
|
38262
38494
|
|
|
38263
38495
|
// src/server.ts
|
|
38264
|
-
var VERSION = "0.5.
|
|
38496
|
+
var VERSION = "0.5.13";
|
|
38265
38497
|
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.
|
|
38266
38498
|
- 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.
|
|
38267
38499
|
- 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.
|
|
38500
|
+
- What the user really pays: container services are paid from the account's bundle first (get_bundle: a monthly bundle with a CPU / RAM / storage quota) and from the wallet only beyond it. Container dry runs return walletDailyCost (what the wallet pays per day) and bundle.coversIt: when the bundle covers it, say it is included in their bundle and deploy after their OK; never ask for a top-up for something walletDailyCost says is 0, whatever the balance. Quote dailyCost only as the price without a bundle. The bundle covers containers only: VPS, domains, AI, paid database plans and paid email plans are paid from the wallet.
|
|
38501
|
+
- Free first: the managed Postgres database (create_database) starts on the FREE plan and email (buy_email_plan with the FREE plan) costs nothing. For a database, use create_database rather than running Postgres as a container service, and do not offer upgrade_database or a paid email plan unless the user asks for more or hits a limit.
|
|
38502
|
+
- 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.
|
|
38268
38503
|
- 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.
|
|
38269
38504
|
- 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.
|
|
38270
38505
|
- 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.
|