helipod-mcp 0.5.1 → 0.5.3

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 CHANGED
@@ -198,7 +198,7 @@ Pick your agents when asked (or pass `-a codex cursor gemini-cli opencode -y`).
198
198
  | Email | status, plan (buy, renew, change, inbox add-ons, with dry runs), send, sent log, sending domains, templates, contacts, segments, topics, broadcasts, suppressions, webhooks, inbox |
199
199
  | Account | wallet and transactions, invoices, monthly statements, notifications, project activity, support tickets |
200
200
  | AI | models and prices, balance, usage, metrics, AI keys (list, revoke), top-up and auto top-up |
201
- | Other | `helipod_request` for any other documented endpoint |
201
+ | Other | `helipod_api_get` to read any other documented endpoint (read only) |
202
202
 
203
203
  Each tool is marked read-only, write or destructive, so clients can ask before running the risky ones. Calls that cost money support a dry run, and the server tells the agent to show the price and wait for your OK.
204
204
 
package/dist/http.js CHANGED
@@ -35673,7 +35673,7 @@ function register(server, api2, defs) {
35673
35673
  annotations: {
35674
35674
  title: d.title,
35675
35675
  readOnlyHint: d.kind === "read",
35676
- destructiveHint: d.kind === "destructive",
35676
+ destructiveHint: d.kind !== "read",
35677
35677
  idempotentHint: d.kind === "read" || !!d.idempotent,
35678
35678
  openWorldHint: true
35679
35679
  }
@@ -35873,7 +35873,7 @@ var containerTools = [
35873
35873
  title: "Update / rename service",
35874
35874
  kind: "write",
35875
35875
  idempotent: true,
35876
- description: "Rename a service or change its start command, root directory, health check or startup dependencies. Settings apply on the next deploy (needsRedeploy). Renaming breaks references like ${{oldName.KEY}} in other services' variables: show the user the returned warnings and fix those variables (set_variables / set_project_variables) before redeploying.",
35876
+ description: "Rename a service or change its start command, root directory, health check or startup dependencies. Settings apply on the next deploy (needsRedeploy). Renaming breaks references like ${{oldName.KEY}} in other services' variables: the response lists them in warnings.",
35877
35877
  input: {
35878
35878
  serviceId: id("Service"),
35879
35879
  name: exports_external.string().regex(/^[A-Za-z][A-Za-z0-9_-]{0,39}$/).optional().describe("New service name (letters, digits, _ -; starts with a letter; unique in the environment)"),
@@ -35897,7 +35897,7 @@ var containerTools = [
35897
35897
  name: "delete_service",
35898
35898
  title: "Delete service",
35899
35899
  kind: "destructive",
35900
- description: "Stop and delete a service with its domains and volumes. Volume data is lost. Confirm with the user first.",
35900
+ description: "Stop and delete a service with its domains and volumes. Volume data is lost.",
35901
35901
  input: { serviceId: id("Service") },
35902
35902
  run: (a, api2) => api2.api("DELETE", `${C}/services/${a.serviceId}`)
35903
35903
  }),
@@ -35905,7 +35905,7 @@ var containerTools = [
35905
35905
  name: "deploy_service",
35906
35906
  title: "Deploy service",
35907
35907
  kind: "write",
35908
- description: "Start a new deployment (Git services build the latest commit). COSTS MONEY: charges today's cost when the build starts, refunded if it fails. dryRun=true returns the cost only. Then poll get_deployment.",
35908
+ description: "Start a new deployment (Git services build the latest commit). COSTS MONEY: charges today's cost when the build starts, refunded if it fails. dryRun=true returns the cost only. The deployment runs in the background; get_deployment returns its status.",
35909
35909
  input: { serviceId: id("Service"), dryRun, idempotencyKey: idem },
35910
35910
  run: (a, api2) => api2.api("POST", `${C}/services/${a.serviceId}/deployments`, { query: { dryRun: a.dryRun ? "true" : undefined }, idempotencyKey: a.idempotencyKey })
35911
35911
  }),
@@ -35982,7 +35982,7 @@ var containerTools = [
35982
35982
  name: "add_service_domain",
35983
35983
  title: "Add domain to service",
35984
35984
  kind: "write",
35985
- description: "Add a free *.helipod.app domain (kind=helipod) or your own domain (kind=custom) to a port. For custom domains, tell the user to create the DNS record from the response's dns field.",
35985
+ description: "Add a free *.helipod.app domain (kind=helipod) or your own domain (kind=custom) to a port. For custom domains, the response's dns field is the DNS record the domain needs.",
35986
35986
  input: {
35987
35987
  serviceId: id("Service"),
35988
35988
  kind: exports_external.enum(["helipod", "custom"]),
@@ -36053,7 +36053,7 @@ var containerTools = [
36053
36053
  name: "delete_volume",
36054
36054
  title: "Delete volume",
36055
36055
  kind: "destructive",
36056
- description: "Remove a volume; its data is lost on the next deploy. Confirm with the user first.",
36056
+ description: "Remove a volume; its data is lost on the next deploy.",
36057
36057
  input: { serviceId: id("Service"), volumeId: id("Volume") },
36058
36058
  run: (a, api2) => api2.api("DELETE", `${C}/services/${a.serviceId}/volumes/${a.volumeId}`)
36059
36059
  }),
@@ -36061,7 +36061,7 @@ var containerTools = [
36061
36061
  name: "resize_volume",
36062
36062
  title: "Resize volume",
36063
36063
  kind: "write",
36064
- 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. Call with dryRun=true first, show currentDailyCost vs newDailyCost (IDR) and wait for the user's yes. Applies on the next deploy.",
36064
+ 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 newDailyCost (IDR) without changing anything. Applies on the next deploy.",
36065
36065
  input: {
36066
36066
  serviceId: id("Service"),
36067
36067
  volumeId: id("Volume"),
@@ -36100,7 +36100,7 @@ var containerTools = [
36100
36100
  title: "Save function source",
36101
36101
  kind: "write",
36102
36102
  idempotent: true,
36103
- description: "Replace the whole source code of a function service (up to 512 KB). The previous code is overwritten: read get_function_code first when editing. Runs after the next deploy (deploy_service).",
36103
+ description: "Replace the whole source code of a function service (up to 512 KB). The previous code is overwritten (get_function_code returns the current code). Runs after the next deploy (deploy_service).",
36104
36104
  input: { serviceId: id("Service"), code: exports_external.string().min(1).max(512 * 1024) },
36105
36105
  run: (a, api2) => api2.api("PUT", `${C}/services/${a.serviceId}/function`, { body: { code: a.code } })
36106
36106
  }),
@@ -36116,7 +36116,7 @@ var containerTools = [
36116
36116
  name: "add_secret_file",
36117
36117
  title: "Add secret file",
36118
36118
  kind: "write",
36119
- description: "Upload a file (certificate, key, credentials JSON, config) that is mounted read-only into the service at mountPath. Content is base64 of the file's bytes, at most 10 MB; it is stored as a secret and can never be read back. Applies on the next deploy. Don't echo the contents in chat.",
36119
+ description: "Upload a file (certificate, key, credentials JSON, config) that is mounted read-only into the service at mountPath. Content is base64 of the file's bytes, at most 10 MB; it is stored as a secret and can never be read back. Applies on the next deploy.",
36120
36120
  input: {
36121
36121
  serviceId: id("Service"),
36122
36122
  fileName: exports_external.string().min(1).max(200).describe("e.g. service-account.json"),
@@ -36146,7 +36146,7 @@ var containerTools = [
36146
36146
  name: "delete_secret_file",
36147
36147
  title: "Delete secret file",
36148
36148
  kind: "destructive",
36149
- description: "Delete a secret file; it is gone from the service after the next deploy and cannot be recovered (contents are never readable). Confirm with the user.",
36149
+ description: "Delete a secret file; it is gone from the service after the next deploy and cannot be recovered (contents are never readable).",
36150
36150
  input: { serviceId: id("Service"), fileId: id("File") },
36151
36151
  run: (a, api2) => api2.api("DELETE", `${C}/services/${a.serviceId}/files/${a.fileId}`)
36152
36152
  }),
@@ -36183,7 +36183,7 @@ var containerTools = [
36183
36183
  name: "deploy_template",
36184
36184
  title: "Deploy template",
36185
36185
  kind: "write",
36186
- description: "Create every service of a template in an environment and deploy them. COSTS MONEY (each service is billed daily). Confirm with the user first.",
36186
+ description: "Create every service of a template in an environment and deploy them. COSTS MONEY (each service is billed daily).",
36187
36187
  input: {
36188
36188
  environmentId: id("Environment"),
36189
36189
  slug: exports_external.string().regex(/^[a-z0-9][a-z0-9-]{0,79}$/),
@@ -36264,7 +36264,7 @@ var vpsTools = [
36264
36264
  name: "vps_power",
36265
36265
  title: "Start / stop / restart VPS",
36266
36266
  kind: "destructive",
36267
- description: "Power action on a server. stop and restart interrupt whatever runs on it: confirm with the user.",
36267
+ description: "Power action on a server. stop and restart interrupt whatever runs on it.",
36268
36268
  input: { vpsId, action: exports_external.enum(["start", "stop", "restart"]) },
36269
36269
  run: (a, api2) => api2.api("POST", `/vps/${a.vpsId}/${a.action}`)
36270
36270
  }),
@@ -36272,7 +36272,7 @@ var vpsTools = [
36272
36272
  name: "reinstall_vps",
36273
36273
  title: "Reinstall VPS",
36274
36274
  kind: "destructive",
36275
- description: "Wipe the disk and install an operating system. ALL DATA ON THE SERVER IS LOST. Get explicit confirmation.",
36275
+ description: "Wipe the disk and install an operating system. ALL DATA ON THE SERVER IS LOST.",
36276
36276
  input: { vpsId, osType: exports_external.string(), osVersion: exports_external.string(), rootPassword: password },
36277
36277
  run: (a, api2) => api2.api("POST", `/vps/${a.vpsId}/reinstall`, { body: { osType: a.osType, osVersion: a.osVersion, rootPassword: a.rootPassword } })
36278
36278
  }),
@@ -36288,7 +36288,7 @@ var vpsTools = [
36288
36288
  name: "get_vps_credentials",
36289
36289
  title: "Get VPS login",
36290
36290
  kind: "read",
36291
- description: "Login details: host, user (root, or Administrator on Windows), port, protocol (ssh or rdp) and password. Treat the password as a secret: don't echo it unless the user asks.",
36291
+ description: "Login details: host, user (root, or Administrator on Windows), port, protocol (ssh or rdp) and password. The response contains the root password.",
36292
36292
  input: { vpsId },
36293
36293
  run: (a, api2) => api2.api("GET", `/vps/${a.vpsId}/credentials`)
36294
36294
  }),
@@ -36296,7 +36296,7 @@ var vpsTools = [
36296
36296
  name: "terminate_vps",
36297
36297
  title: "Terminate VPS",
36298
36298
  kind: "destructive",
36299
- description: "Delete a server permanently. ALL DATA IS LOST and there is no refund for the remaining period. Get explicit confirmation.",
36299
+ description: "Delete a server permanently. ALL DATA IS LOST and there is no refund for the remaining period.",
36300
36300
  input: { vpsId },
36301
36301
  run: (a, api2) => api2.api("DELETE", `/vps/${a.vpsId}`)
36302
36302
  }),
@@ -36320,7 +36320,7 @@ var vpsTools = [
36320
36320
  name: "upgrade_vps",
36321
36321
  title: "Upgrade VPS plan",
36322
36322
  kind: "write",
36323
- description: "Move a server to a larger plan in the same family and region. COSTS MONEY (the difference for the days left) and restarts the server. Call get_vps_upgrade_options first: use only a planId it lists, tell the user its price in IDR and that the server restarts, and wait for an explicit yes.",
36323
+ description: "Move a server to a larger plan in the same family and region. COSTS MONEY (the difference for the days left) and restarts the server. planId must be one listed by get_vps_upgrade_options, which also gives the price in IDR; the server restarts during the upgrade.",
36324
36324
  input: { vpsId, planId: exports_external.string() },
36325
36325
  run: (a, api2) => api2.api("POST", `/vps/${a.vpsId}/upgrade`, { body: { planId: a.planId } })
36326
36326
  }),
@@ -36328,7 +36328,7 @@ var vpsTools = [
36328
36328
  name: "get_vps_upgrade_options",
36329
36329
  title: "VPS upgrade options",
36330
36330
  kind: "read",
36331
- description: "Plans this server can move to (same family and region) with the price to pay now for the days left (IDR). Call it before upgrade_vps.",
36331
+ description: "Plans this server can move to (same family and region) with the price to pay now for the days left (IDR). upgrade_vps accepts only a planId from this list.",
36332
36332
  input: { vpsId },
36333
36333
  run: (a, api2) => api2.api("GET", `/vps/${a.vpsId}/upgrade-options`)
36334
36334
  }),
@@ -36417,7 +36417,7 @@ var vpsTools = [
36417
36417
  name: "remove_vps_port",
36418
36418
  title: "Remove port forward",
36419
36419
  kind: "destructive",
36420
- description: "Remove a port forward (id from list_vps_ports). Whatever reaches the server through it stops working: confirm with the user.",
36420
+ description: "Remove a port forward (id from list_vps_ports). Whatever reaches the server through it stops working.",
36421
36421
  input: { vpsId, allocationId: exports_external.string().uuid().describe("Port forward id from list_vps_ports") },
36422
36422
  run: (a, api2) => api2.api("DELETE", `/vps/${a.vpsId}/ports/${a.allocationId}`)
36423
36423
  }),
@@ -36441,7 +36441,7 @@ var vpsTools = [
36441
36441
  name: "remove_vps_ssh_key",
36442
36442
  title: "Remove SSH key",
36443
36443
  kind: "destructive",
36444
- description: "Remove an SSH key by fingerprint (from list_vps_ssh_keys). Whoever logs in with it loses access; make sure the user keeps another way in (another key or the root password).",
36444
+ description: "Remove an SSH key by fingerprint (from list_vps_ssh_keys). Whoever logs in with it loses access (another key or the root password still works).",
36445
36445
  input: { vpsId, fingerprint: exports_external.string().min(8).max(100).describe("e.g. SHA256:abc…+/= as listed") },
36446
36446
  run: (a, api2) => api2.api("DELETE", `/vps/${a.vpsId}/ssh-keys/${a.fingerprint.trim().replace(/^SHA256:/, "").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "")}`)
36447
36447
  }),
@@ -36450,7 +36450,7 @@ var vpsTools = [
36450
36450
  title: "Turn SSH on / off",
36451
36451
  kind: "write",
36452
36452
  idempotent: true,
36453
- description: "Turn SSH access to the server on or off (no change when it is already in that state). Turning it off locks out every SSH session and key: confirm with the user first. The server must be running.",
36453
+ description: "Turn SSH access to the server on or off (no change when it is already in that state). Turning it off locks out every SSH session and key. The server must be running.",
36454
36454
  input: { vpsId, enabled: exports_external.boolean() },
36455
36455
  run: (a, api2) => api2.api("POST", `/vps/${a.vpsId}/ssh`, { body: { enabled: a.enabled } })
36456
36456
  }),
@@ -36474,7 +36474,7 @@ var vpsTools = [
36474
36474
  name: "apply_snapshot",
36475
36475
  title: "Roll back to snapshot",
36476
36476
  kind: "destructive",
36477
- description: "Roll the server's disk back to a snapshot. Everything written since is lost. Get explicit confirmation.",
36477
+ description: "Roll the server's disk back to a snapshot. Everything written since is lost.",
36478
36478
  input: { vpsId, snapshotId: exports_external.string() },
36479
36479
  run: (a, api2) => api2.api("POST", `/vps/${a.vpsId}/snapshots/${a.snapshotId}/apply`)
36480
36480
  }),
@@ -36506,7 +36506,7 @@ var vpsTools = [
36506
36506
  name: "activate_vps_backup",
36507
36507
  title: "Turn on VPS backups",
36508
36508
  kind: "write",
36509
- description: "Turn on daily backups for a running Linux server. COSTS MONEY right away: the whole period (1 or 12 months) is charged from the wallet and there is no dry run. Get priceMonthly / priceYearly from list_backup_plans, tell the user the price in IDR and wait for an explicit yes. The server must be running.",
36509
+ description: "Turn on daily backups for a running Linux server. COSTS MONEY right away: the whole period (1 or 12 months) is charged from the wallet and there is no dry run. Prices (priceMonthly / priceYearly, IDR) are in list_backup_plans. The server must be running.",
36510
36510
  input: {
36511
36511
  vpsId,
36512
36512
  planCode: exports_external.string().max(30).describe("Plan code from list_backup_plans"),
@@ -36533,7 +36533,7 @@ var vpsTools = [
36533
36533
  name: "cancel_vps_backup",
36534
36534
  title: "Cancel VPS backups",
36535
36535
  kind: "destructive",
36536
- description: "Stop renewing the server's backups. Backups keep running until the paid period ends; after that and a grace period the backups are DELETED and can no longer be restored. Confirm with the user. (update_vps_backup autoRenew=true undoes it before the end.)",
36536
+ description: "Stop renewing the server's backups. Backups keep running until the paid period ends; after that and a grace period the backups are DELETED and can no longer be restored. (update_vps_backup autoRenew=true undoes it before the end.)",
36537
36537
  input: { vpsId },
36538
36538
  run: (a, api2) => api2.api("DELETE", `/vps/${a.vpsId}/backup`)
36539
36539
  }),
@@ -36573,7 +36573,7 @@ var vpsTools = [
36573
36573
  name: "restore_vps_backup",
36574
36574
  title: "Restore VPS from backup",
36575
36575
  kind: "destructive",
36576
- description: "Restore from a restore point. mode=full puts the WHOLE disk back to that moment: everything written since is LOST and the server is offline while it restores. mode=files restores 1-50 paths: target=folder (default) puts them in a separate folder on the server, target=inplace OVERWRITES the current files. Name what will be overwritten and get explicit confirmation. Runs in the background: follow it with list_vps_backup_runs.",
36576
+ description: "Restore from a restore point. mode=full puts the WHOLE disk back to that moment: everything written since is LOST and the server is offline while it restores. mode=files restores 1-50 paths: target=folder (default) puts them in a separate folder on the server, target=inplace OVERWRITES the current files. Files at those paths are overwritten. Runs in the background: follow it with list_vps_backup_runs.",
36577
36577
  input: {
36578
36578
  vpsId,
36579
36579
  pointId,
@@ -36595,7 +36595,7 @@ var vpsTools = [
36595
36595
  name: "get_vps_backup_download",
36596
36596
  title: "Get backup download link",
36597
36597
  kind: "read",
36598
- description: "Status of a prepared backup download; when ready, a short-lived url for the user to open (it expires at expiresAt). Give the user the link; do not download it yourself.",
36598
+ description: "Status of a prepared backup download; when ready, a short-lived url for the user to open (it expires at expiresAt). The link is for the user to download in a browser.",
36599
36599
  input: { vpsId, downloadId: exports_external.string() },
36600
36600
  run: (a, api2) => api2.api("GET", `/vps/${a.vpsId}/backup/downloads/${a.downloadId}`)
36601
36601
  })
@@ -36605,7 +36605,7 @@ var vpsTools = [
36605
36605
  var domainId = exports_external.number().int().positive().describe("Domain id (number, from list_my_domains)");
36606
36606
  var hostId = exports_external.number().int().positive().describe("Child nameserver id (from list_child_nameservers)");
36607
36607
  var dryRun2 = exports_external.boolean().optional().describe("true = return the exact price (IDR, VAT included) and the wallet balance after it; nothing is charged or registered");
36608
- var DRY = "Call with dryRun=true first, tell the user the price and walletBalanceAfter, and wait for an explicit yes before the real call.";
36608
+ var DRY = "dryRun=true returns the price and walletBalanceAfter without charging or changing anything.";
36609
36609
  var record3 = exports_external.object({
36610
36610
  name: exports_external.string().describe('"@" for the domain itself, or a label like "www"'),
36611
36611
  type: exports_external.enum(["A", "AAAA", "MX", "MXE", "CNAME", "SPF", "TXT"]),
@@ -36658,7 +36658,7 @@ var domainTools = [
36658
36658
  title: "Update owner details",
36659
36659
  kind: "write",
36660
36660
  idempotent: true,
36661
- description: "Set the account's domain owner (registrant) details the registrar needs: name, phone, addressLine1, city, state, countryCode and postalCode are required for a complete profile (organization, addressLine2 and fax are optional). Send only fields to change. This is personal data: use only what the user gave you, never invent or guess it, and don't repeat it back needlessly. The answer says whether the details are now complete and which fields are still missing (it never echoes them).",
36661
+ description: "Set the account's domain owner (registrant) details the registrar needs: name, phone, addressLine1, city, state, countryCode and postalCode are required for a complete profile (organization, addressLine2 and fax are optional). Send only fields to change. These are personal details of the domain owner, stored as given. The answer says whether the details are now complete and which fields are still missing (it never echoes them).",
36662
36662
  input: {
36663
36663
  name: exports_external.string().min(1).max(255).optional().describe("Owner's full name"),
36664
36664
  organization: exports_external.string().max(255).optional().describe("Company / organization (optional)"),
@@ -36718,7 +36718,7 @@ var domainTools = [
36718
36718
  name: "set_dns_records",
36719
36719
  title: "Replace DNS records",
36720
36720
  kind: "destructive",
36721
- description: "Replace ALL DNS records of the domain with this list. Read list_dns_records first and include every record to keep.",
36721
+ description: "Replace ALL DNS records of the domain with this list. Records not included are deleted (list_dns_records returns the current ones).",
36722
36722
  input: { domainId, records: exports_external.array(record3).min(1) },
36723
36723
  run: (a, api2) => api2.api("POST", `/domains/${a.domainId}/dns`, { body: { records: a.records } })
36724
36724
  }),
@@ -36743,7 +36743,7 @@ var domainTools = [
36743
36743
  name: "enable_whois_privacy",
36744
36744
  title: "Enable WHOIS privacy",
36745
36745
  kind: "write",
36746
- description: "Hide the owner's details in WHOIS. COSTS MONEY (the whois_protection price per year). Confirm with the user.",
36746
+ description: "Hide the owner's details in WHOIS. COSTS MONEY (the whois_protection price per year).",
36747
36747
  input: { domainId },
36748
36748
  run: (a, api2) => api2.api("POST", `/domains/${a.domainId}/whois-protection`)
36749
36749
  }),
@@ -36759,7 +36759,7 @@ var domainTools = [
36759
36759
  name: "whois_lookup",
36760
36760
  title: "WHOIS lookup",
36761
36761
  kind: "read",
36762
- description: "Public WHOIS record of any domain name: registrar, dates, status, nameservers. Use search_domain to check availability instead.",
36762
+ description: "Public WHOIS record of any domain name: registrar, dates, status, nameservers. Availability is checked by search_domain.",
36763
36763
  input: { domain: exports_external.string().describe("e.g. example.com") },
36764
36764
  run: (a, api2) => api2.api("GET", "/domains/whois", { query: { domain: a.domain } })
36765
36765
  }),
@@ -36768,7 +36768,7 @@ var domainTools = [
36768
36768
  title: "Update one DNS record",
36769
36769
  kind: "write",
36770
36770
  idempotent: true,
36771
- description: "Set one DNS record (by name and type) to new content, leaving the other records as they are. Safer than set_dns_records for a single change; read list_dns_records first.",
36771
+ description: "Set one DNS record (by name and type) to new content, leaving the other records as they are. Changes one record and leaves the others as they are (list_dns_records returns record ids).",
36772
36772
  input: { domainId, ...record3.shape },
36773
36773
  run: (a, api2) => api2.api("PUT", `/domains/${a.domainId}/dns`, { body: { name: a.name, type: a.type, content: a.content, ttl: a.ttl } })
36774
36774
  }),
@@ -36817,7 +36817,7 @@ var domainTools = [
36817
36817
  name: "disable_whois_privacy",
36818
36818
  title: "Disable WHOIS privacy",
36819
36819
  kind: "destructive",
36820
- description: "Turn WHOIS privacy off: the owner's name, address, email and phone become PUBLIC in WHOIS (no refund; turning it back on with enable_whois_privacy costs money). Get explicit confirmation.",
36820
+ description: "Turn WHOIS privacy off: the owner's name, address, email and phone become PUBLIC in WHOIS (no refund; turning it back on with enable_whois_privacy costs money).",
36821
36821
  input: { domainId },
36822
36822
  run: (a, api2) => api2.api("DELETE", `/domains/${a.domainId}/whois-protection`)
36823
36823
  })
@@ -36852,10 +36852,10 @@ var message = exports_external.object({
36852
36852
  });
36853
36853
  var dryRun3 = exports_external.boolean().optional().describe("true = return the price (IDR) and the wallet balance after it; nothing is charged or changed");
36854
36854
  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");
36855
- var MONEY = "COSTS MONEY from the wallet. Call with dryRun=true first, tell the user the charge (IDR) and walletBalanceAfter, and wait for an explicit yes before the real call.";
36855
+ var MONEY = "COSTS MONEY from the wallet. dryRun=true returns the charge (IDR) and walletBalanceAfter without charging or changing anything.";
36856
36856
  var planCode = exports_external.string().min(1).max(50).describe("Plan code from list_email_plans");
36857
36857
  var folder = exports_external.enum(["inbox", "spam", "archive", "trash"]);
36858
- var AUDIENCE = "Before it goes out, tell the user how many people it reaches: get_email_broadcast for its segmentId/topicId, then the segment's contactCount (list_email_segments) or preview_email_segment with no rules for all contacts (unsubscribed and suppressed addresses are skipped).";
36858
+ var AUDIENCE = "It goes to every contact in the broadcast's segment or topic (unsubscribed and suppressed addresses are skipped); the audience size is the segment's contactCount in list_email_segments, or preview_email_segment with no rules for all contacts.";
36859
36859
  var emailTools = [
36860
36860
  defineTool({
36861
36861
  name: "email_status",
@@ -36924,7 +36924,7 @@ var emailTools = [
36924
36924
  name: "buy_email_inbox_addon",
36925
36925
  title: "Set inbox add-ons",
36926
36926
  kind: "write",
36927
- description: `Set the TOTAL extra mailboxes and extra inbox storage on the active paid plan (absolute numbers, not additions: read get_email_plan first and add to the current values). Increases are charged now, prorated to the plan's end date, then renew with the plan; decreases must still fit what is in use. ${MONEY}`,
36927
+ description: `Set the TOTAL extra mailboxes and extra inbox storage on the active paid plan (absolute numbers, not additions; get_email_plan returns the current values). Increases are charged now, prorated to the plan's end date, then renew with the plan; decreases must still fit what is in use. ${MONEY}`,
36928
36928
  input: {
36929
36929
  projectId,
36930
36930
  extraMailboxes: exports_external.number().int().min(0).max(50).describe("Total extra mailboxes wanted (0-50)"),
@@ -36989,7 +36989,7 @@ var emailTools = [
36989
36989
  name: "add_email_domain",
36990
36990
  title: "Add sending domain",
36991
36991
  kind: "write",
36992
- description: "Add a sending domain; the response lists the DNS records the user must create, then call verify_email_domain.",
36992
+ description: "Add a sending domain; the response lists the DNS records the domain needs; verify_email_domain checks them.",
36993
36993
  input: { projectId, domain: exports_external.string() },
36994
36994
  run: (a, api2) => api2.email("POST", "/domains", { body: { domain: a.domain }, project: a.projectId })
36995
36995
  }),
@@ -37053,7 +37053,7 @@ var emailTools = [
37053
37053
  name: "delete_email_domain",
37054
37054
  title: "Delete sending domain",
37055
37055
  kind: "destructive",
37056
- description: "Remove a sending domain: email can no longer be sent from its addresses. Confirm with the user.",
37056
+ description: "Remove a sending domain: email can no longer be sent from its addresses.",
37057
37057
  input: { projectId, domainId: exports_external.string() },
37058
37058
  run: (a, api2) => api2.email("DELETE", `/domains/${a.domainId}`, { project: a.projectId })
37059
37059
  }),
@@ -37099,7 +37099,7 @@ var emailTools = [
37099
37099
  name: "delete_email_template",
37100
37100
  title: "Delete email template",
37101
37101
  kind: "destructive",
37102
- description: "Delete a template. Code that still sends with its templateId will fail. Confirm with the user.",
37102
+ description: "Delete a template. Code that still sends with its templateId will fail.",
37103
37103
  input: { projectId, templateId: exports_external.string() },
37104
37104
  run: (a, api2) => api2.email("DELETE", `/templates/${a.templateId}`, { project: a.projectId })
37105
37105
  }),
@@ -37155,7 +37155,7 @@ var emailTools = [
37155
37155
  name: "delete_email_contact",
37156
37156
  title: "Delete email contact",
37157
37157
  kind: "destructive",
37158
- description: "Delete a contact and its history. To stop emailing someone, prefer unsubscribe_email_contact (keeps the opt-out on record). Confirm with the user.",
37158
+ description: "Delete a contact and its history. unsubscribe_email_contact stops emails and keeps the opt-out on record; this deletes the contact.",
37159
37159
  input: { projectId, contactId: uid("Contact") },
37160
37160
  run: (a, api2) => api2.email("DELETE", `/contacts/${a.contactId}`, { project: a.projectId })
37161
37161
  }),
@@ -37212,7 +37212,7 @@ var emailTools = [
37212
37212
  name: "delete_email_segment",
37213
37213
  title: "Delete segment",
37214
37214
  kind: "destructive",
37215
- description: "Delete a segment (its contacts are kept). Confirm with the user.",
37215
+ description: "Delete a segment (its contacts are kept).",
37216
37216
  input: { projectId, segmentId: uid("Segment") },
37217
37217
  run: (a, api2) => api2.email("DELETE", `/segments/${a.segmentId}`, { project: a.projectId })
37218
37218
  }),
@@ -37252,7 +37252,7 @@ var emailTools = [
37252
37252
  name: "delete_email_topic",
37253
37253
  title: "Delete topic",
37254
37254
  kind: "destructive",
37255
- description: "Delete a topic and every contact's subscription to it. Confirm with the user.",
37255
+ description: "Delete a topic and every contact's subscription to it.",
37256
37256
  input: { projectId, topicId: uid("Topic") },
37257
37257
  run: (a, api2) => api2.email("DELETE", `/topics/${a.topicId}`, { project: a.projectId })
37258
37258
  }),
@@ -37362,7 +37362,7 @@ var emailTools = [
37362
37362
  name: "delete_email_suppression",
37363
37363
  title: "Remove suppression",
37364
37364
  kind: "destructive",
37365
- description: "Allow an address to be emailed again. Only when the person asked for it or the bounce was fixed: emailing addresses that bounced or complained hurts the project's sending reputation. Confirm with the user.",
37365
+ description: "Allow an address to be emailed again. Only when the person asked for it or the bounce was fixed: emailing addresses that bounced or complained hurts the project's sending reputation.",
37366
37366
  input: { projectId, suppressionId: exports_external.string() },
37367
37367
  run: (a, api2) => api2.email("DELETE", `/suppressions/${a.suppressionId}`, { project: a.projectId })
37368
37368
  }),
@@ -37378,7 +37378,7 @@ var emailTools = [
37378
37378
  name: "create_email_webhook",
37379
37379
  title: "Create email webhook",
37380
37380
  kind: "write",
37381
- description: "POST email events to an HTTPS URL. The response holds the signing secret ONCE: tell the user to store it (e.g. as a secret variable), don't print it elsewhere.",
37381
+ description: "POST email events to an HTTPS URL. The response holds the signing secret ONCE: it is not shown again.",
37382
37382
  input: {
37383
37383
  projectId,
37384
37384
  url: exports_external.string().min(8).max(2000),
@@ -37390,7 +37390,7 @@ var emailTools = [
37390
37390
  name: "delete_email_webhook",
37391
37391
  title: "Delete email webhook",
37392
37392
  kind: "destructive",
37393
- description: "Stop sending events to a webhook URL. Confirm with the user.",
37393
+ description: "Stop sending events to a webhook URL.",
37394
37394
  input: { projectId, webhookId: uid("Webhook") },
37395
37395
  run: (a, api2) => api2.email("DELETE", `/webhooks/${a.webhookId}`, { project: a.projectId })
37396
37396
  }),
@@ -37439,7 +37439,7 @@ var emailTools = [
37439
37439
  name: "delete_inbox_address",
37440
37440
  title: "Delete inbox address",
37441
37441
  kind: "destructive",
37442
- description: "Delete a receiving address: mail to it is no longer accepted (messages already received are kept). Confirm with the user.",
37442
+ description: "Delete a receiving address: mail to it is no longer accepted (messages already received are kept).",
37443
37443
  input: { projectId, addressId: uid("Address") },
37444
37444
  run: (a, api2) => api2.email("DELETE", `/inbound/addresses/${a.addressId}`, { project: a.projectId })
37445
37445
  }),
@@ -37475,7 +37475,7 @@ var emailTools = [
37475
37475
  name: "reply_inbox_message",
37476
37476
  title: "Reply to inbox message",
37477
37477
  kind: "write",
37478
- description: "Send a threaded reply to a received message (counts against the sending quota). Show the user the reply text before sending.",
37478
+ description: "Send a threaded reply to a received message (counts against the sending quota). The reply is sent from the inbox address.",
37479
37479
  input: {
37480
37480
  projectId,
37481
37481
  messageId: uid("Message"),
@@ -37490,7 +37490,7 @@ var emailTools = [
37490
37490
  name: "delete_inbox_message",
37491
37491
  title: "Delete inbox message",
37492
37492
  kind: "destructive",
37493
- description: "Delete a received message permanently. Confirm with the user.",
37493
+ description: "Delete a received message permanently.",
37494
37494
  input: { projectId, messageId: uid("Message") },
37495
37495
  run: (a, api2) => api2.email("DELETE", `/inbound/messages/${a.messageId}`, { project: a.projectId })
37496
37496
  })
@@ -37571,7 +37571,7 @@ var databaseTools = [
37571
37571
  name: "delete_database_branch",
37572
37572
  title: "Delete database branch",
37573
37573
  kind: "destructive",
37574
- description: "Delete an environment's branch and its data. The primary (production) branch cannot be deleted. Confirm with the user.",
37574
+ description: "Delete an environment's branch and its data. The primary (production) branch cannot be deleted.",
37575
37575
  input: { databaseId, environmentId: exports_external.string().uuid() },
37576
37576
  run: (a, api2) => api2.api("DELETE", `/databases/${a.databaseId}/branches/${a.environmentId}`)
37577
37577
  }),
@@ -37612,7 +37612,7 @@ var databaseTools = [
37612
37612
  name: "suspend_database",
37613
37613
  title: "Suspend database",
37614
37614
  kind: "destructive",
37615
- description: "Stop the database's compute (connections fail until resumed). Confirm with the user.",
37615
+ description: "Stop the database's compute (connections fail until resumed).",
37616
37616
  input: { databaseId },
37617
37617
  run: (a, api2) => api2.api("POST", `/databases/${a.databaseId}/suspend`)
37618
37618
  }),
@@ -37673,7 +37673,7 @@ var databaseTools = [
37673
37673
  name: "browse_database_table",
37674
37674
  title: "Browse table rows",
37675
37675
  kind: "read",
37676
- description: "Rows of a table, a page at a time (limit up to 200, offset to page). Read-only. Rows are the customer's data: don't repeat sensitive columns (passwords, tokens) unless the user asks.",
37676
+ description: "Rows of a table, a page at a time (limit up to 200, offset to page). Read-only. Rows are returned as stored, including any sensitive columns.",
37677
37677
  input: {
37678
37678
  databaseId,
37679
37679
  table: exports_external.string().min(1).max(63),
@@ -37687,7 +37687,7 @@ var databaseTools = [
37687
37687
  name: "query_database",
37688
37688
  title: "Query database (read-only)",
37689
37689
  kind: "read",
37690
- description: "Run a read-only SQL statement (SELECT, EXPLAIN, SHOW…) in a read-only transaction; up to 200 rows. Use $1, $2… placeholders with params. Statements that change data are refused: use execute_database_sql.",
37690
+ description: "Run a read-only SQL statement (SELECT, EXPLAIN, SHOW…) in a read-only transaction; up to 200 rows. Use $1, $2… placeholders with params. Statements that change data are refused (execute_database_sql runs those).",
37691
37691
  input: { databaseId, sql: exports_external.string().min(1).max(1e4), params: exports_external.array(exports_external.any()).max(100).optional(), environmentId: environmentId.optional() },
37692
37692
  run: (a, api2) => api2.api("POST", `/databases/${a.databaseId}/query`, { body: { sql: a.sql, params: a.params, environmentId: a.environmentId } })
37693
37693
  }),
@@ -37695,7 +37695,7 @@ var databaseTools = [
37695
37695
  name: "classify_database_sql",
37696
37696
  title: "Classify SQL (no run)",
37697
37697
  kind: "read",
37698
- description: "What a SQL statement is (command, e.g. select / update / drop) and whether it writes, WITHOUT running it. Call it before execute_database_sql to tell the user exactly what will change; readOnly statements go to query_database.",
37698
+ description: "What a SQL statement is (command, e.g. select / update / drop) and whether it writes, WITHOUT running it. It reports what a statement would change before execute_database_sql runs it; readOnly statements can run with query_database.",
37699
37699
  input: { databaseId, sql: exports_external.string().min(1).max(1e4) },
37700
37700
  run: (a, api2) => api2.api("POST", `/databases/${a.databaseId}/query/classify`, { body: { sql: a.sql } })
37701
37701
  }),
@@ -37703,7 +37703,7 @@ var databaseTools = [
37703
37703
  name: "execute_database_sql",
37704
37704
  title: "Execute SQL (writes)",
37705
37705
  kind: "destructive",
37706
- description: "Run a statement that changes data or schema (INSERT, UPDATE, DELETE, CREATE, ALTER, DROP…). First call classify_database_sql to see what the statement does (a read-only one belongs in query_database). Then show the statement, what it changes and the target environment to the user and get explicit confirmation; prefer a staging branch for migrations.",
37706
+ description: "Run a statement that changes data or schema (INSERT, UPDATE, DELETE, CREATE, ALTER, DROP…). classify_database_sql reports what a statement does without running it. It runs on the chosen environment's branch.",
37707
37707
  input: { databaseId, sql: exports_external.string().min(1).max(1e4), params: exports_external.array(exports_external.any()).max(100).optional(), environmentId: environmentId.optional() },
37708
37708
  run: (a, api2) => api2.api("POST", `/databases/${a.databaseId}/query`, { body: { sql: a.sql, params: a.params, environmentId: a.environmentId, allowWrite: true } })
37709
37709
  })
@@ -37793,7 +37793,7 @@ var accountTools = [
37793
37793
  title: "Mark all notifications read",
37794
37794
  kind: "write",
37795
37795
  idempotent: true,
37796
- description: "Mark every unread notification as read (a project key: its project's only). Ask the user first: they may want to read them in the dashboard.",
37796
+ description: "Mark every unread notification as read (a project key: its project's only). Marks every notification as read.",
37797
37797
  input: {},
37798
37798
  run: (_a3, api2) => api2.api("POST", "/account/notifications/read-all")
37799
37799
  }),
@@ -37831,7 +37831,7 @@ var accountTools = [
37831
37831
  name: "create_support_ticket",
37832
37832
  title: "Open support ticket",
37833
37833
  kind: "write",
37834
- description: `Open a ticket with Helipod support (at most 5 an hour; reply to an open ticket instead of opening another). Show the user the subject and message before sending, and never include passwords, API keys or other secrets. ${ACCOUNT_KEY}`,
37834
+ description: `Open a ticket with Helipod support (at most 5 an hour; replies go to an open ticket with reply_support_ticket). The subject and message are sent to Helipod support as written. ${ACCOUNT_KEY}`,
37835
37835
  input: {
37836
37836
  subject: exports_external.string().min(3).max(255),
37837
37837
  category: exports_external.enum(["billing", "technical", "feature_request", "other"]),
@@ -37853,7 +37853,7 @@ var accountTools = [
37853
37853
  title: "Close support ticket",
37854
37854
  kind: "write",
37855
37855
  idempotent: true,
37856
- description: `Close a support ticket once the issue is solved (a closed ticket takes no more replies). Ask the user first. ${ACCOUNT_KEY}`,
37856
+ description: `Close a support ticket once the issue is solved (a closed ticket takes no more replies). ${ACCOUNT_KEY}`,
37857
37857
  input: { ticketId: uid2("Ticket") },
37858
37858
  run: (a, api2) => api2.api("POST", `/account/tickets/${a.ticketId}/close`)
37859
37859
  })
@@ -37910,7 +37910,7 @@ var aiTools = [
37910
37910
  name: "revoke_ai_key",
37911
37911
  title: "Revoke AI key",
37912
37912
  kind: "destructive",
37913
- description: "Revoke an AI key permanently: every app using it stops working at once and it cannot be restored (a new key must be made in the dashboard and deployed). Name the key and get explicit confirmation.",
37913
+ description: "Revoke an AI key permanently: every app using it stops working at once and it cannot be restored (a new key must be made in the dashboard and deployed).",
37914
37914
  input: { keyId: exports_external.string().uuid().describe("AI key id from list_ai_keys") },
37915
37915
  run: (a, api2) => api2.api("DELETE", `/ai/keys/${a.keyId}`)
37916
37916
  }),
@@ -37918,7 +37918,7 @@ var aiTools = [
37918
37918
  name: "topup_ai_balance",
37919
37919
  title: "Top up AI balance",
37920
37920
  kind: "write",
37921
- description: `Move money from the wallet to the AI balance, by pack (packId from get_ai_overview, may include a bonus) or by amount (IDR, at least minTopup). COSTS MONEY from the wallet. Call with dryRun=true first, tell the user the amount, bonus and balances after, and wait for an explicit yes before the real call. ${ACCOUNT_KEY2}`,
37921
+ description: `Move money from the wallet to the AI balance, by pack (packId from get_ai_overview, may include a bonus) or by amount (IDR, at least minTopup). COSTS MONEY from the wallet. dryRun=true returns the amount, bonus and balances after without charging. ${ACCOUNT_KEY2}`,
37922
37922
  input: {
37923
37923
  packId: exports_external.string().min(1).max(64).optional(),
37924
37924
  amount: exports_external.number().min(1).max(1e9).optional().describe("IDR"),
@@ -37940,7 +37940,7 @@ var aiTools = [
37940
37940
  title: "Set AI auto top-up",
37941
37941
  kind: "write",
37942
37942
  idempotent: true,
37943
- description: `Automatic top-up: when the AI balance falls below threshold (IDR), amount (IDR) is moved from the wallet. Enabling it COSTS MONEY later, without asking each time: call with dryRun=true first, tell the user the current and new settings, and wait for an explicit yes. enabled=false turns it off. ${ACCOUNT_KEY2}`,
37943
+ description: `Automatic top-up: when the AI balance falls below threshold (IDR), amount (IDR) is moved from the wallet. Enabling it COSTS MONEY later: top-ups then run automatically. dryRun=true returns the current and new settings without changing them. enabled=false turns it off. ${ACCOUNT_KEY2}`,
37944
37944
  input: {
37945
37945
  enabled: exports_external.boolean(),
37946
37946
  threshold: exports_external.number().min(1).optional().describe("Top up when the AI balance is below this (IDR)"),
@@ -37958,16 +37958,14 @@ var aiTools = [
37958
37958
  var API_PREFIXES = ["/containers/", "/databases", "/vps", "/backup/", "/domains", "/account/", "/ai/"];
37959
37959
  var generalTools = [
37960
37960
  defineTool({
37961
- name: "helipod_request",
37962
- title: "Call any Helipod API endpoint",
37963
- kind: "destructive",
37964
- description: "Call an endpoint that has no dedicated tool, as documented at https://docs.helipod.io. api=core for the Container, Database, VPS, Domain, Account and AI APIs (paths like /vps/{id}/backup, /account/wallet, /ai/usage), api=email for the Email API (paths like /broadcasts). Prefer the dedicated tools; ask the user before any call that costs money or deletes something.",
37961
+ name: "helipod_api_get",
37962
+ title: "Read any Helipod API endpoint",
37963
+ kind: "read",
37964
+ description: "GET an endpoint of the Helipod public API that has no dedicated read tool (Helipod API reference: https://docs.helipod.io/docs/helipod). api=core for the Container, Database, VPS, Domain, Account and AI APIs (paths like /vps/{id}/backup/runs, /account/wallet, /ai/usage), api=email for the Email API (paths like /broadcasts). Read only: it never changes anything.",
37965
37965
  input: {
37966
37966
  api: exports_external.enum(["core", "email"]),
37967
- method: exports_external.enum(["GET", "POST", "PUT", "PATCH", "DELETE"]),
37968
- path: exports_external.string().regex(/^\/[A-Za-z0-9/_.\-:]*$/).describe("Path after the base URL, e.g. /vps/abc/backup"),
37969
- query: exports_external.record(exports_external.string(), exports_external.string()).optional(),
37970
- body: exports_external.record(exports_external.string(), exports_external.any()).optional()
37967
+ path: exports_external.string().regex(/^\/[A-Za-z0-9/_.\-:]*$/).describe("Path after the base URL, e.g. /vps/abc/backup/runs"),
37968
+ query: exports_external.record(exports_external.string(), exports_external.string()).optional()
37971
37969
  },
37972
37970
  run: (a, api2) => {
37973
37971
  if (a.path.includes(".."))
@@ -37975,20 +37973,21 @@ var generalTools = [
37975
37973
  if (a.api === "core") {
37976
37974
  if (!API_PREFIXES.some((p) => a.path.startsWith(p)))
37977
37975
  throw new Error(`Core API paths start with one of: ${API_PREFIXES.join(", ")}`);
37978
- return api2.api(a.method, a.path, { query: a.query, body: a.body });
37976
+ return api2.api("GET", a.path, { query: a.query });
37979
37977
  }
37980
- return api2.email(a.method, a.path, { query: a.query, body: a.body });
37978
+ return api2.email("GET", a.path, { query: a.query });
37981
37979
  }
37982
37980
  })
37983
37981
  ];
37984
37982
 
37985
37983
  // src/server.ts
37986
- var VERSION = "0.5.1";
37984
+ var VERSION = "0.5.3";
37987
37985
  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.
37988
37986
  - 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) cover the AI gateway.
37989
37987
  - 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): 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.
37990
37988
  - Destructive tools (delete, terminate, reinstall, rollback, restore a backup, 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.
37991
37989
  - 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.
37990
+ - 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.
37992
37991
  - Databases: get_database_connection returns a password; put it in a secret variable (DATABASE_URL) instead of code or chat.
37993
37992
  - Deploys and VPS actions run in the background: poll get_deployment / get_vps until they settle; on a failed deploy read get_deployment_logs.
37994
37993
  - Never print secrets (passwords, secret variable values, secret files) unless the user asks for them. Domain owner details are personal data: use only what the user gave.