helipod-mcp 0.5.8 → 0.5.10

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 CHANGED
@@ -35582,10 +35582,12 @@ var EMPTY_COMPLETION_RESULT = {
35582
35582
  class HelipodError extends Error {
35583
35583
  status;
35584
35584
  code;
35585
- constructor(status, code, message) {
35585
+ details;
35586
+ constructor(status, code, message, details) {
35586
35587
  super(message);
35587
35588
  this.status = status;
35588
35589
  this.code = code;
35590
+ this.details = details;
35589
35591
  this.name = "HelipodError";
35590
35592
  }
35591
35593
  }
@@ -35681,7 +35683,8 @@ class HelipodClient {
35681
35683
  if (!res.ok || json2?.success === false) {
35682
35684
  const retry = res.headers.get("retry-after");
35683
35685
  const msg = json2?.error ?? (text.slice(0, 300) || res.statusText);
35684
- throw new HelipodError(res.status, json2?.code, retry ? `${msg} (retry after ${retry}s)` : String(msg));
35686
+ const details = json2?.details && typeof json2.details === "object" && !Array.isArray(json2.details) ? json2.details : undefined;
35687
+ throw new HelipodError(res.status, json2?.code, retry ? `${msg} (retry after ${retry}s)` : String(msg), details);
35685
35688
  }
35686
35689
  return json2 && "data" in json2 ? json2.data : json2;
35687
35690
  }
@@ -35713,7 +35716,7 @@ function register(server, api2, defs) {
35713
35716
  const err = e instanceof HelipodError ? e : new HelipodError(0, "CLIENT_ERROR", String(e?.message ?? e));
35714
35717
  return {
35715
35718
  isError: true,
35716
- content: [{ type: "text", text: JSON.stringify({ status: err.status, code: err.code ?? null, error: err.message }, null, 2) }]
35719
+ content: [{ type: "text", text: JSON.stringify({ status: err.status, code: err.code ?? null, error: err.message, ...err.details ? { details: err.details } : {} }, null, 2) }]
35717
35720
  };
35718
35721
  }
35719
35722
  });
@@ -35847,18 +35850,48 @@ var containerTools = [
35847
35850
  input: { serviceId: id("Service") },
35848
35851
  run: (a, api2) => api2.api("GET", `${C}/services/${a.serviceId}`)
35849
35852
  }),
35853
+ defineTool({
35854
+ name: "get_git_connections",
35855
+ title: "Git connections",
35856
+ kind: "read",
35857
+ description: "Whether the user connected GitHub (sign-in + the Helipod GitHub App, with the accounts it is installed on) and GitLab, plus connectUrl: the Helipod page where the user connects them. Check this before deploying from a private repo. Connecting needs the user in a browser: give them connectUrl, never try to connect yourself.",
35858
+ input: {},
35859
+ run: (_a3, api2) => api2.api("GET", `${C}/git/connections`)
35860
+ }),
35861
+ defineTool({
35862
+ name: "list_git_repos",
35863
+ title: "List Git repositories",
35864
+ kind: "read",
35865
+ description: "Repositories the user's connected GitHub App installations (host github, default) or GitLab account (host gitlab) can reach: fullName, url, private, defaultBranch, newest first. Pass one url to create_service as repo. Not connected: 409 with details.connectUrl for the user.",
35866
+ input: {
35867
+ host: exports_external.enum(["github", "gitlab"]).optional().describe("github (default) or gitlab"),
35868
+ installationId: exports_external.number().int().positive().optional().describe("Only this GitHub installation (id from get_git_connections)"),
35869
+ search: exports_external.string().max(200).optional().describe("Filter by part of owner/name"),
35870
+ limit: exports_external.number().int().min(1).max(100).optional().describe("Default 50"),
35871
+ offset: exports_external.number().int().min(0).optional()
35872
+ },
35873
+ run: (a, api2) => api2.api("GET", `${C}/git/repos`, {
35874
+ query: {
35875
+ host: a.host,
35876
+ installationId: a.installationId !== undefined ? String(a.installationId) : undefined,
35877
+ search: a.search,
35878
+ limit: a.limit !== undefined ? String(a.limit) : undefined,
35879
+ offset: a.offset !== undefined ? String(a.offset) : undefined
35880
+ }
35881
+ })
35882
+ }),
35850
35883
  defineTool({
35851
35884
  name: "create_service",
35852
35885
  title: "Create service",
35853
35886
  kind: "write",
35854
- description: "Create a service from a container image, a public Git repository or an inline Dockerfile, and (unless deploy=false) start its first deploy. COSTS MONEY once deployed (billed daily from the wallet): call with dryRun=true first, show the dailyCost to the user and get their OK.",
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 (billed daily from the wallet): call with dryRun=true first, show the dailyCost to the user and get their OK.",
35855
35888
  input: {
35856
35889
  environmentId: id("Environment"),
35857
35890
  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}}"),
35858
35891
  image: exports_external.string().optional().describe("Container image, e.g. nginx:alpine or ghcr.io/acme/app:1.2"),
35859
35892
  registryUsername: exports_external.string().optional().describe("Private registry user (with registryPassword)"),
35860
35893
  registryPassword: exports_external.string().optional().describe("Private registry password or token"),
35861
- repo: exports_external.string().optional().describe("Public HTTPS Git repository URL (instead of image)"),
35894
+ repo: exports_external.string().optional().describe("Git repository: https URL or owner/name for GitHub (instead of image). Private repos need a connected GitHub / GitLab (list_git_repos)"),
35862
35895
  branch: exports_external.string().optional().describe("Git branch (default main)"),
35863
35896
  rootDirectory: exports_external.string().optional().describe("Monorepo: folder to build, e.g. apps/web"),
35864
35897
  dockerfile: exports_external.string().optional().describe("Inline Dockerfile content (instead of image/repo)"),
@@ -36248,6 +36281,10 @@ var period = exports_external.enum(["monthly", "3month", "6month", "yearly"]);
36248
36281
  var pointId = exports_external.string().describe("Restore point id (from list_vps_restore_points)");
36249
36282
  var backupPath = exports_external.string().max(1024).describe("Absolute path inside the backup, e.g. /etc/nginx or /var/www/index.html");
36250
36283
  var migrationId = exports_external.string().describe("Migration id (from list_vps_migrations)");
36284
+ var dryRun2 = exports_external.boolean().optional().describe("true = return the price (IDR) and the wallet balance after it; nothing is charged or changed");
36285
+ var idempotencyKey = exports_external.string().regex(/^[A-Za-z0-9_.:-]{8,128}$/).optional().describe("Idempotency-Key for the real call. Generated when omitted; pass the same value when retrying so it is never charged twice");
36286
+ var DRY = "dryRun=true returns the price and walletBalanceAfter without charging or changing anything.";
36287
+ var dryQuery = (d) => ({ dryRun: d ? "true" : undefined });
36251
36288
  var vpsTools = [
36252
36289
  defineTool({
36253
36290
  name: "list_vps_plans",
@@ -36277,7 +36314,7 @@ var vpsTools = [
36277
36314
  name: "create_vps",
36278
36315
  title: "Create VPS",
36279
36316
  kind: "write",
36280
- description: "Order a server. COSTS MONEY right away (the whole billing period is charged from the wallet; refunded if provisioning fails). Show the plan price from list_vps_plans and get the user's explicit OK first. Provisioning takes a few minutes: poll get_vps.",
36317
+ description: `Order a server. COSTS MONEY right away (the whole billing period is charged from the wallet; refunded if provisioning fails). Call with dryRun=true first, show the price and the wallet after it, and get the user's explicit OK. ${DRY} autoRenew=false creates it with auto-renew off (renew it with renew_vps). Provisioning takes a few minutes: poll get_vps.`,
36281
36318
  input: {
36282
36319
  projectId: exports_external.string().uuid(),
36283
36320
  planId: exports_external.string(),
@@ -36285,9 +36322,12 @@ var vpsTools = [
36285
36322
  osType: exports_external.string().describe("From the plan's osTemplates, e.g. ubuntu"),
36286
36323
  osVersion: exports_external.string().describe("e.g. 24.04"),
36287
36324
  rootPassword: password,
36288
- billingPeriod: period.optional().describe("Default monthly")
36325
+ billingPeriod: period.optional().describe("Default monthly"),
36326
+ autoRenew: exports_external.boolean().optional().describe("false = no automatic renewal from the wallet (default true)"),
36327
+ dryRun: dryRun2,
36328
+ idempotencyKey
36289
36329
  },
36290
- run: (a, api2) => api2.api("POST", "/vps", { body: a })
36330
+ run: ({ dryRun: dryRun3, idempotencyKey: idempotencyKey2, ...body }, api2) => api2.api("POST", "/vps", { body, query: dryQuery(dryRun3), idempotencyKey: purchaseKey(dryRun3, idempotencyKey2) })
36291
36331
  }),
36292
36332
  defineTool({
36293
36333
  name: "vps_power",
@@ -36341,17 +36381,17 @@ var vpsTools = [
36341
36381
  name: "renew_vps",
36342
36382
  title: "Renew VPS",
36343
36383
  kind: "write",
36344
- description: "Extend a server's paid period. COSTS MONEY: show the price from get_vps_renewal_options and get the user's OK.",
36345
- input: { vpsId, billingPeriod: period },
36346
- run: (a, api2) => api2.api("POST", `/vps/${a.vpsId}/renew`, { body: { billingPeriod: a.billingPeriod } })
36384
+ description: `Extend a server's paid period. COSTS MONEY: call with dryRun=true first, show the price and the wallet after it, and get the user's OK. ${DRY}`,
36385
+ input: { vpsId, billingPeriod: period, dryRun: dryRun2, idempotencyKey },
36386
+ run: (a, api2) => api2.api("POST", `/vps/${a.vpsId}/renew`, { body: { billingPeriod: a.billingPeriod }, query: dryQuery(a.dryRun), idempotencyKey: purchaseKey(a.dryRun, a.idempotencyKey) })
36347
36387
  }),
36348
36388
  defineTool({
36349
36389
  name: "upgrade_vps",
36350
36390
  title: "Upgrade VPS plan",
36351
36391
  kind: "write",
36352
- 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. Servers with providerCode 2 only.",
36353
- input: { vpsId, planId: exports_external.string() },
36354
- run: (a, api2) => api2.api("POST", `/vps/${a.vpsId}/upgrade`, { body: { planId: a.planId } })
36392
+ 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. Call with dryRun=true first and get the user's OK. ${DRY} Servers with providerCode 2 only.`,
36393
+ input: { vpsId, planId: exports_external.string(), dryRun: dryRun2, idempotencyKey },
36394
+ run: (a, api2) => api2.api("POST", `/vps/${a.vpsId}/upgrade`, { body: { planId: a.planId }, query: dryQuery(a.dryRun), idempotencyKey: purchaseKey(a.dryRun, a.idempotencyKey) })
36355
36395
  }),
36356
36396
  defineTool({
36357
36397
  name: "get_vps_upgrade_options",
@@ -36378,11 +36418,20 @@ var vpsTools = [
36378
36418
  input: { vpsId, range: exports_external.enum(["1h", "6h", "24h", "7d", "30d"]).optional() },
36379
36419
  run: (a, api2) => api2.api("GET", `/vps/${a.vpsId}/metrics`, { query: { range: a.range } })
36380
36420
  }),
36421
+ defineTool({
36422
+ name: "set_vps_auto_renew",
36423
+ title: "Set VPS auto-renew",
36424
+ kind: "write",
36425
+ idempotent: true,
36426
+ description: "Turn auto-renew on or off (sets the value, safe to repeat). With it off, the server is only renewed by renew_vps and expires otherwise.",
36427
+ input: { vpsId, autoRenew: exports_external.boolean() },
36428
+ run: (a, api2) => api2.api("POST", `/vps/${a.vpsId}/auto-renew`, { body: { autoRenew: a.autoRenew } })
36429
+ }),
36381
36430
  defineTool({
36382
36431
  name: "toggle_vps_auto_renew",
36383
36432
  title: "Toggle VPS auto-renew",
36384
36433
  kind: "write",
36385
- description: "Flip auto-renew on or off; the response gives the new value.",
36434
+ description: "Flip auto-renew to the other value; the response gives the new one. Prefer set_vps_auto_renew to reach a given state.",
36386
36435
  input: { vpsId },
36387
36436
  run: (a, api2) => api2.api("POST", `/vps/${a.vpsId}/auto-renew-toggle`)
36388
36437
  }),
@@ -36711,8 +36760,8 @@ var vpsTools = [
36711
36760
  // src/tools/domains.ts
36712
36761
  var domainId = exports_external.number().int().positive().describe("Domain id (number, from list_my_domains)");
36713
36762
  var hostId = exports_external.number().int().positive().describe("Child nameserver id (from list_child_nameservers)");
36714
- 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");
36715
- var DRY = "dryRun=true returns the price and walletBalanceAfter without charging or changing anything.";
36763
+ var dryRun3 = exports_external.boolean().optional().describe("true = return the exact price (IDR, VAT included) and the wallet balance after it; nothing is charged or registered");
36764
+ var DRY2 = "dryRun=true returns the price and walletBalanceAfter without charging or changing anything.";
36716
36765
  var record3 = exports_external.object({
36717
36766
  name: exports_external.string().describe('"@" for the domain itself, or the part before the domain, e.g. "www" or "_dmarc.mail". A full name like "www.example.com" is accepted and stored as "www"'),
36718
36767
  type: exports_external.enum(["A", "AAAA", "MX", "MXE", "CNAME", "SPF", "TXT"]),
@@ -36788,12 +36837,12 @@ var domainTools = [
36788
36837
  name: "register_domain",
36789
36838
  title: "Register domain",
36790
36839
  kind: "write",
36791
- description: `Register a domain for 1 year. COSTS MONEY from the wallet. Check search_domain, and that the owner details are complete (check_registrant / update_registrant). ${DRY} The dry run includes WHOIS privacy when whoisProtection is true.`,
36840
+ description: `Register a domain for 1 year. COSTS MONEY from the wallet. Check search_domain, and that the owner details are complete (check_registrant / update_registrant). ${DRY2} The dry run includes WHOIS privacy when whoisProtection is true.`,
36792
36841
  input: {
36793
36842
  name: exports_external.string().describe("Full domain, e.g. tokosaya.id"),
36794
36843
  nameservers: exports_external.array(exports_external.string()).min(2).max(5).optional(),
36795
36844
  whoisProtection: exports_external.boolean().optional(),
36796
- dryRun: dryRun2
36845
+ dryRun: dryRun3
36797
36846
  },
36798
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 } })
36799
36848
  }),
@@ -36801,16 +36850,16 @@ var domainTools = [
36801
36850
  name: "transfer_domain",
36802
36851
  title: "Transfer domain in",
36803
36852
  kind: "write",
36804
- description: `Transfer a domain from another registrar (adds 1 year). COSTS MONEY from the wallet. Needs the domain's auth code. ${DRY}`,
36805
- input: { name: exports_external.string(), authCode: exports_external.string(), nameservers: exports_external.array(exports_external.string()).min(2).max(5).optional(), dryRun: dryRun2 },
36853
+ description: `Transfer a domain from another registrar (adds 1 year). COSTS MONEY from the wallet. Needs the domain's auth code. ${DRY2}`,
36854
+ input: { name: exports_external.string(), authCode: exports_external.string(), nameservers: exports_external.array(exports_external.string()).min(2).max(5).optional(), dryRun: dryRun3 },
36806
36855
  run: (a, api2) => api2.api("POST", "/domains/transfer", { body: { name: a.name, authCode: a.authCode, period: 1, nameservers: a.nameservers }, query: { dryRun: a.dryRun ? "true" : undefined } })
36807
36856
  }),
36808
36857
  defineTool({
36809
36858
  name: "renew_domain",
36810
36859
  title: "Renew domain",
36811
36860
  kind: "write",
36812
- description: `Renew a domain for 1 year (plus WHOIS privacy when it is on). COSTS MONEY from the wallet. ${DRY}`,
36813
- input: { domainId, dryRun: dryRun2 },
36861
+ description: `Renew a domain for 1 year (plus WHOIS privacy when it is on). COSTS MONEY from the wallet. ${DRY2}`,
36862
+ input: { domainId, dryRun: dryRun3 },
36814
36863
  run: (a, api2) => api2.api("POST", `/domains/${a.domainId}/renew`, { body: { period: 1 }, query: { dryRun: a.dryRun ? "true" : undefined } })
36815
36864
  }),
36816
36865
  defineTool({
@@ -36973,7 +37022,7 @@ var message = exports_external.object({
36973
37022
  variables: exports_external.record(exports_external.string(), exports_external.string()).optional(),
36974
37023
  idempotencyKey: exports_external.string().max(128).optional()
36975
37024
  });
36976
- var dryRun3 = exports_external.boolean().optional().describe("true = return the price (IDR) and the wallet balance after it; nothing is charged or changed");
37025
+ var dryRun4 = exports_external.boolean().optional().describe("true = return the price (IDR) and the wallet balance after it; nothing is charged or changed");
36977
37026
  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");
36978
37027
  var MONEY = "COSTS MONEY from the wallet. dryRun=true returns the charge (IDR) and walletBalanceAfter without charging or changing anything.";
36979
37028
  var planCode = exports_external.string().min(1).max(50).describe("Plan code from list_email_plans");
@@ -37009,7 +37058,7 @@ var emailTools = [
37009
37058
  title: "Buy email plan",
37010
37059
  kind: "write",
37011
37060
  description: `Start an email plan for the project (a free plan activates without charge). Works for a project with no email yet: the real call also sets email up (result.setup). ${MONEY} The dry run's mode says purchase, activate_free or already_active.`,
37012
- input: { projectId, planCode, months: exports_external.union([exports_external.literal(1), exports_external.literal(3), exports_external.literal(6), exports_external.literal(12)]).optional().describe("Billing cycle, default 1"), dryRun: dryRun3, idempotencyKey: idem2 },
37061
+ input: { projectId, planCode, months: exports_external.union([exports_external.literal(1), exports_external.literal(3), exports_external.literal(6), exports_external.literal(12)]).optional().describe("Billing cycle, default 1"), dryRun: dryRun4, idempotencyKey: idem2 },
37013
37062
  run: (a, api2) => api2.email("POST", "/plan", {
37014
37063
  project: a.projectId,
37015
37064
  body: { planCode: a.planCode, months: a.months },
@@ -37022,7 +37071,7 @@ var emailTools = [
37022
37071
  title: "Renew email plan",
37023
37072
  kind: "write",
37024
37073
  description: `Renew the project's ended or expiring email plan (409 PLAN_ACTIVE while one is still running and cannot be extended). Repeats the last plan and cycle unless planCode / months are given. ${MONEY}`,
37025
- input: { projectId, planCode: planCode.optional(), months: exports_external.union([exports_external.literal(1), exports_external.literal(3), exports_external.literal(6), exports_external.literal(12)]).optional(), dryRun: dryRun3, idempotencyKey: idem2 },
37074
+ input: { projectId, planCode: planCode.optional(), months: exports_external.union([exports_external.literal(1), exports_external.literal(3), exports_external.literal(6), exports_external.literal(12)]).optional(), dryRun: dryRun4, idempotencyKey: idem2 },
37026
37075
  run: (a, api2) => api2.email("POST", "/plan/renew", {
37027
37076
  project: a.projectId,
37028
37077
  body: { planCode: a.planCode, months: a.months },
@@ -37035,7 +37084,7 @@ var emailTools = [
37035
37084
  title: "Change email plan",
37036
37085
  kind: "write",
37037
37086
  description: `Move the active email plan to another plan. An upgrade applies now and charges the prorated difference to the same end date; a downgrade is scheduled for the next cycle (no refund). ${MONEY}`,
37038
- input: { projectId, planCode, dryRun: dryRun3, idempotencyKey: idem2 },
37087
+ input: { projectId, planCode, dryRun: dryRun4, idempotencyKey: idem2 },
37039
37088
  run: (a, api2) => api2.email("POST", "/plan/change", {
37040
37089
  project: a.projectId,
37041
37090
  body: { planCode: a.planCode },
@@ -37052,7 +37101,7 @@ var emailTools = [
37052
37101
  projectId,
37053
37102
  extraMailboxes: exports_external.number().int().min(0).max(50).describe("Total extra mailboxes wanted (0-50)"),
37054
37103
  extraStorageGb: exports_external.number().int().min(0).max(1000).describe("Total extra inbox storage in GB (0-1000)"),
37055
- dryRun: dryRun3,
37104
+ dryRun: dryRun4,
37056
37105
  idempotencyKey: idem2
37057
37106
  },
37058
37107
  run: (a, api2) => api2.email("POST", "/plan/addons", {
@@ -37622,7 +37671,7 @@ var emailTools = [
37622
37671
  // src/tools/databases.ts
37623
37672
  var databaseId = exports_external.string().uuid().describe("Database id (from list_databases)");
37624
37673
  var environmentId = exports_external.string().uuid().describe("Environment id (from list_projects). Omit for the primary (production) branch.");
37625
- var dryRun4 = exports_external.boolean().optional().describe("true = only check and show the result or price; nothing changes");
37674
+ var dryRun5 = exports_external.boolean().optional().describe("true = only check and show the result or price; nothing changes");
37626
37675
  var databaseTools = [
37627
37676
  defineTool({
37628
37677
  name: "list_database_plans",
@@ -37716,7 +37765,7 @@ var databaseTools = [
37716
37765
  minCu: exports_external.number().min(0.25).max(10).optional(),
37717
37766
  maxCu: exports_external.number().min(0.25).max(10).optional(),
37718
37767
  suspendTimeoutSeconds: exports_external.number().int().min(0).max(604800).optional().describe("0 = never suspend, or 60-604800"),
37719
- dryRun: dryRun4
37768
+ dryRun: dryRun5
37720
37769
  },
37721
37770
  run: (a, api2) => api2.api("PATCH", `/databases/${a.databaseId}/settings`, {
37722
37771
  body: { minCu: a.minCu, maxCu: a.maxCu, suspendTimeoutSeconds: a.suspendTimeoutSeconds },
@@ -37728,7 +37777,7 @@ var databaseTools = [
37728
37777
  title: "Upgrade database plan / top up",
37729
37778
  kind: "write",
37730
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.",
37731
- input: { databaseId, plan: exports_external.string().describe("Plan code (e.g. STARTER) or id from list_database_plans"), dryRun: dryRun4, idempotencyKey: exports_external.string().max(128).optional() },
37780
+ 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() },
37732
37781
  run: (a, api2) => api2.api("POST", `/databases/${a.databaseId}/upgrade`, { body: { plan: a.plan }, query: { dryRun: a.dryRun ? "true" : undefined }, idempotencyKey: a.idempotencyKey })
37733
37782
  }),
37734
37783
  defineTool({
@@ -37986,7 +38035,7 @@ var accountTools = [
37986
38035
  var projectId2 = exports_external.string().uuid().optional().describe("Limit to one project (a project key is always limited to its own)");
37987
38036
  var keyId = exports_external.string().uuid().optional().describe("Limit to one AI key (id from list_ai_keys)");
37988
38037
  var model = exports_external.string().max(100).optional().describe("Limit to one model id");
37989
- var dryRun5 = exports_external.boolean().optional().describe("true = only check and show the result; nothing is charged or changed");
38038
+ var dryRun6 = exports_external.boolean().optional().describe("true = only check and show the result; nothing is charged or changed");
37990
38039
  var ACCOUNT_KEY2 = "Needs a whole-account key (a key limited to one project gets 403 ACCOUNT_KEY_REQUIRED).";
37991
38040
  var imageProject = exports_external.string().uuid().optional().describe("Project the images belong to (required with a whole-account key; a project key uses its own)");
37992
38041
  var INLINE_MAX_BYTES = 1500000;
@@ -38071,7 +38120,7 @@ var aiTools = [
38071
38120
  n: exports_external.number().int().min(1).max(10).optional().describe("Images in this request (up to the model's maxN; default 1)"),
38072
38121
  images: exports_external.array(exports_external.string().url().startsWith("https://")).max(10).optional().describe("Reference images as https:// links (only models with refImages > 0)"),
38073
38122
  projectId: imageProject,
38074
- dryRun: dryRun5,
38123
+ dryRun: dryRun6,
38075
38124
  wait: exports_external.number().int().min(0).max(55).optional().describe("Seconds to wait for the result (default 50; 0 = return the id at once)"),
38076
38125
  idempotencyKey: exports_external.string().regex(/^[A-Za-z0-9_.:-]{8,128}$/).optional().describe("Idempotency-Key for the real request. Generated when omitted; pass the same value when retrying so it is never charged twice")
38077
38126
  },
@@ -38121,7 +38170,7 @@ var aiTools = [
38121
38170
  input: {
38122
38171
  packId: exports_external.string().min(1).max(64).optional(),
38123
38172
  amount: exports_external.number().min(1).max(1e9).optional().describe("IDR"),
38124
- dryRun: dryRun5,
38173
+ dryRun: dryRun6,
38125
38174
  idempotencyKey: exports_external.string().regex(/^[A-Za-z0-9_.:-]{8,128}$/).optional().describe("Idempotency-Key for the real top-up. Generated when omitted; pass the same value when retrying so it is never charged twice")
38126
38175
  },
38127
38176
  run: (a, api2) => {
@@ -38144,7 +38193,7 @@ var aiTools = [
38144
38193
  enabled: exports_external.boolean(),
38145
38194
  threshold: exports_external.number().min(1).optional().describe("Top up when the AI balance is below this (IDR)"),
38146
38195
  amount: exports_external.number().min(1).optional().describe("How much to top up each time (IDR)"),
38147
- dryRun: dryRun5
38196
+ dryRun: dryRun6
38148
38197
  },
38149
38198
  run: (a, api2) => api2.api("PUT", "/ai/auto-topup", {
38150
38199
  body: { enabled: a.enabled, threshold: a.threshold, amount: a.amount },
@@ -38180,7 +38229,7 @@ var generalTools = [
38180
38229
  ];
38181
38230
 
38182
38231
  // src/server.ts
38183
- var VERSION = "0.5.8";
38232
+ var VERSION = "0.5.10";
38184
38233
  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.
38185
38234
  - 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.
38186
38235
  - 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.
@@ -38190,9 +38239,10 @@ var INSTRUCTIONS = `Helipod is a cloud platform (Indonesia): containers, managed
38190
38239
  - Databases: get_database_connection returns a password; put it in a secret variable (DATABASE_URL) instead of code or chat.
38191
38240
  - 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.
38192
38241
  - Images from generate_ai_image are deleted after 7 days: tell the user and have them download the ones they keep.
38242
+ - Deploying from Git: public repos need nothing. For a private repo check get_git_connections / list_git_repos first; when GitHub or GitLab is not connected (or a call returns 409 GITHUB_NOT_CONNECTED / GITLAB_NOT_CONNECTED / REPO_NOT_ACCESSIBLE), give the user details.connectUrl to open in their browser and try again after they confirm. Never ask for a GitHub token.
38193
38243
  - Deploys and VPS actions run in the background: poll get_deployment / get_vps until they settle; on a failed deploy read get_deployment_logs.
38194
38244
  - 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.
38195
- - Errors come back as { status, code, error }: 401/403 means the key lacks access (keys choose products and projects in Settings → API keys; wallet, invoices, tickets and AI money need a whole-account key: with a sign-in, reconnect and choose Whole account), 402 means plan limit or low balance.
38245
+ - Errors come back as { status, code, error, details? }: 401/403 means the key lacks access (keys choose products and projects in Settings → API keys; wallet, invoices, tickets and AI money need a whole-account key: with a sign-in, reconnect and choose Whole account), 402 means plan limit or low balance.
38196
38246
  Docs: https://docs.helipod.io`;
38197
38247
  var ALL_TOOLS = [...containerTools, ...databaseTools, ...vpsTools, ...domainTools, ...emailTools, ...accountTools, ...aiTools, ...generalTools];
38198
38248
  function createHelipodServer(config2) {
package/dist/index.js CHANGED
@@ -34727,10 +34727,12 @@ var EMPTY_COMPLETION_RESULT = {
34727
34727
  class HelipodError extends Error {
34728
34728
  status;
34729
34729
  code;
34730
- constructor(status, code, message) {
34730
+ details;
34731
+ constructor(status, code, message, details) {
34731
34732
  super(message);
34732
34733
  this.status = status;
34733
34734
  this.code = code;
34735
+ this.details = details;
34734
34736
  this.name = "HelipodError";
34735
34737
  }
34736
34738
  }
@@ -34826,7 +34828,8 @@ class HelipodClient {
34826
34828
  if (!res.ok || json2?.success === false) {
34827
34829
  const retry = res.headers.get("retry-after");
34828
34830
  const msg = json2?.error ?? (text.slice(0, 300) || res.statusText);
34829
- throw new HelipodError(res.status, json2?.code, retry ? `${msg} (retry after ${retry}s)` : String(msg));
34831
+ const details = json2?.details && typeof json2.details === "object" && !Array.isArray(json2.details) ? json2.details : undefined;
34832
+ throw new HelipodError(res.status, json2?.code, retry ? `${msg} (retry after ${retry}s)` : String(msg), details);
34830
34833
  }
34831
34834
  return json2 && "data" in json2 ? json2.data : json2;
34832
34835
  }
@@ -34858,7 +34861,7 @@ function register(server, api2, defs) {
34858
34861
  const err = e instanceof HelipodError ? e : new HelipodError(0, "CLIENT_ERROR", String(e?.message ?? e));
34859
34862
  return {
34860
34863
  isError: true,
34861
- content: [{ type: "text", text: JSON.stringify({ status: err.status, code: err.code ?? null, error: err.message }, null, 2) }]
34864
+ content: [{ type: "text", text: JSON.stringify({ status: err.status, code: err.code ?? null, error: err.message, ...err.details ? { details: err.details } : {} }, null, 2) }]
34862
34865
  };
34863
34866
  }
34864
34867
  });
@@ -34992,18 +34995,48 @@ var containerTools = [
34992
34995
  input: { serviceId: id("Service") },
34993
34996
  run: (a, api2) => api2.api("GET", `${C}/services/${a.serviceId}`)
34994
34997
  }),
34998
+ defineTool({
34999
+ name: "get_git_connections",
35000
+ title: "Git connections",
35001
+ kind: "read",
35002
+ description: "Whether the user connected GitHub (sign-in + the Helipod GitHub App, with the accounts it is installed on) and GitLab, plus connectUrl: the Helipod page where the user connects them. Check this before deploying from a private repo. Connecting needs the user in a browser: give them connectUrl, never try to connect yourself.",
35003
+ input: {},
35004
+ run: (_a3, api2) => api2.api("GET", `${C}/git/connections`)
35005
+ }),
35006
+ defineTool({
35007
+ name: "list_git_repos",
35008
+ title: "List Git repositories",
35009
+ kind: "read",
35010
+ description: "Repositories the user's connected GitHub App installations (host github, default) or GitLab account (host gitlab) can reach: fullName, url, private, defaultBranch, newest first. Pass one url to create_service as repo. Not connected: 409 with details.connectUrl for the user.",
35011
+ input: {
35012
+ host: exports_external.enum(["github", "gitlab"]).optional().describe("github (default) or gitlab"),
35013
+ installationId: exports_external.number().int().positive().optional().describe("Only this GitHub installation (id from get_git_connections)"),
35014
+ search: exports_external.string().max(200).optional().describe("Filter by part of owner/name"),
35015
+ limit: exports_external.number().int().min(1).max(100).optional().describe("Default 50"),
35016
+ offset: exports_external.number().int().min(0).optional()
35017
+ },
35018
+ run: (a, api2) => api2.api("GET", `${C}/git/repos`, {
35019
+ query: {
35020
+ host: a.host,
35021
+ installationId: a.installationId !== undefined ? String(a.installationId) : undefined,
35022
+ search: a.search,
35023
+ limit: a.limit !== undefined ? String(a.limit) : undefined,
35024
+ offset: a.offset !== undefined ? String(a.offset) : undefined
35025
+ }
35026
+ })
35027
+ }),
34995
35028
  defineTool({
34996
35029
  name: "create_service",
34997
35030
  title: "Create service",
34998
35031
  kind: "write",
34999
- description: "Create a service from a container image, a public Git repository or an inline Dockerfile, and (unless deploy=false) start its first deploy. COSTS MONEY once deployed (billed daily from the wallet): call with dryRun=true first, show the dailyCost to the user and get their OK.",
35032
+ 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 (billed daily from the wallet): call with dryRun=true first, show the dailyCost to the user and get their OK.",
35000
35033
  input: {
35001
35034
  environmentId: id("Environment"),
35002
35035
  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}}"),
35003
35036
  image: exports_external.string().optional().describe("Container image, e.g. nginx:alpine or ghcr.io/acme/app:1.2"),
35004
35037
  registryUsername: exports_external.string().optional().describe("Private registry user (with registryPassword)"),
35005
35038
  registryPassword: exports_external.string().optional().describe("Private registry password or token"),
35006
- repo: exports_external.string().optional().describe("Public HTTPS Git repository URL (instead of image)"),
35039
+ repo: exports_external.string().optional().describe("Git repository: https URL or owner/name for GitHub (instead of image). Private repos need a connected GitHub / GitLab (list_git_repos)"),
35007
35040
  branch: exports_external.string().optional().describe("Git branch (default main)"),
35008
35041
  rootDirectory: exports_external.string().optional().describe("Monorepo: folder to build, e.g. apps/web"),
35009
35042
  dockerfile: exports_external.string().optional().describe("Inline Dockerfile content (instead of image/repo)"),
@@ -35393,6 +35426,10 @@ var period = exports_external.enum(["monthly", "3month", "6month", "yearly"]);
35393
35426
  var pointId = exports_external.string().describe("Restore point id (from list_vps_restore_points)");
35394
35427
  var backupPath = exports_external.string().max(1024).describe("Absolute path inside the backup, e.g. /etc/nginx or /var/www/index.html");
35395
35428
  var migrationId = exports_external.string().describe("Migration id (from list_vps_migrations)");
35429
+ var dryRun2 = exports_external.boolean().optional().describe("true = return the price (IDR) and the wallet balance after it; nothing is charged or changed");
35430
+ var idempotencyKey = exports_external.string().regex(/^[A-Za-z0-9_.:-]{8,128}$/).optional().describe("Idempotency-Key for the real call. Generated when omitted; pass the same value when retrying so it is never charged twice");
35431
+ var DRY = "dryRun=true returns the price and walletBalanceAfter without charging or changing anything.";
35432
+ var dryQuery = (d) => ({ dryRun: d ? "true" : undefined });
35396
35433
  var vpsTools = [
35397
35434
  defineTool({
35398
35435
  name: "list_vps_plans",
@@ -35422,7 +35459,7 @@ var vpsTools = [
35422
35459
  name: "create_vps",
35423
35460
  title: "Create VPS",
35424
35461
  kind: "write",
35425
- description: "Order a server. COSTS MONEY right away (the whole billing period is charged from the wallet; refunded if provisioning fails). Show the plan price from list_vps_plans and get the user's explicit OK first. Provisioning takes a few minutes: poll get_vps.",
35462
+ description: `Order a server. COSTS MONEY right away (the whole billing period is charged from the wallet; refunded if provisioning fails). Call with dryRun=true first, show the price and the wallet after it, and get the user's explicit OK. ${DRY} autoRenew=false creates it with auto-renew off (renew it with renew_vps). Provisioning takes a few minutes: poll get_vps.`,
35426
35463
  input: {
35427
35464
  projectId: exports_external.string().uuid(),
35428
35465
  planId: exports_external.string(),
@@ -35430,9 +35467,12 @@ var vpsTools = [
35430
35467
  osType: exports_external.string().describe("From the plan's osTemplates, e.g. ubuntu"),
35431
35468
  osVersion: exports_external.string().describe("e.g. 24.04"),
35432
35469
  rootPassword: password,
35433
- billingPeriod: period.optional().describe("Default monthly")
35470
+ billingPeriod: period.optional().describe("Default monthly"),
35471
+ autoRenew: exports_external.boolean().optional().describe("false = no automatic renewal from the wallet (default true)"),
35472
+ dryRun: dryRun2,
35473
+ idempotencyKey
35434
35474
  },
35435
- run: (a, api2) => api2.api("POST", "/vps", { body: a })
35475
+ run: ({ dryRun: dryRun3, idempotencyKey: idempotencyKey2, ...body }, api2) => api2.api("POST", "/vps", { body, query: dryQuery(dryRun3), idempotencyKey: purchaseKey(dryRun3, idempotencyKey2) })
35436
35476
  }),
35437
35477
  defineTool({
35438
35478
  name: "vps_power",
@@ -35486,17 +35526,17 @@ var vpsTools = [
35486
35526
  name: "renew_vps",
35487
35527
  title: "Renew VPS",
35488
35528
  kind: "write",
35489
- description: "Extend a server's paid period. COSTS MONEY: show the price from get_vps_renewal_options and get the user's OK.",
35490
- input: { vpsId, billingPeriod: period },
35491
- run: (a, api2) => api2.api("POST", `/vps/${a.vpsId}/renew`, { body: { billingPeriod: a.billingPeriod } })
35529
+ description: `Extend a server's paid period. COSTS MONEY: call with dryRun=true first, show the price and the wallet after it, and get the user's OK. ${DRY}`,
35530
+ input: { vpsId, billingPeriod: period, dryRun: dryRun2, idempotencyKey },
35531
+ run: (a, api2) => api2.api("POST", `/vps/${a.vpsId}/renew`, { body: { billingPeriod: a.billingPeriod }, query: dryQuery(a.dryRun), idempotencyKey: purchaseKey(a.dryRun, a.idempotencyKey) })
35492
35532
  }),
35493
35533
  defineTool({
35494
35534
  name: "upgrade_vps",
35495
35535
  title: "Upgrade VPS plan",
35496
35536
  kind: "write",
35497
- 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. Servers with providerCode 2 only.",
35498
- input: { vpsId, planId: exports_external.string() },
35499
- run: (a, api2) => api2.api("POST", `/vps/${a.vpsId}/upgrade`, { body: { planId: a.planId } })
35537
+ 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. Call with dryRun=true first and get the user's OK. ${DRY} Servers with providerCode 2 only.`,
35538
+ input: { vpsId, planId: exports_external.string(), dryRun: dryRun2, idempotencyKey },
35539
+ run: (a, api2) => api2.api("POST", `/vps/${a.vpsId}/upgrade`, { body: { planId: a.planId }, query: dryQuery(a.dryRun), idempotencyKey: purchaseKey(a.dryRun, a.idempotencyKey) })
35500
35540
  }),
35501
35541
  defineTool({
35502
35542
  name: "get_vps_upgrade_options",
@@ -35523,11 +35563,20 @@ var vpsTools = [
35523
35563
  input: { vpsId, range: exports_external.enum(["1h", "6h", "24h", "7d", "30d"]).optional() },
35524
35564
  run: (a, api2) => api2.api("GET", `/vps/${a.vpsId}/metrics`, { query: { range: a.range } })
35525
35565
  }),
35566
+ defineTool({
35567
+ name: "set_vps_auto_renew",
35568
+ title: "Set VPS auto-renew",
35569
+ kind: "write",
35570
+ idempotent: true,
35571
+ description: "Turn auto-renew on or off (sets the value, safe to repeat). With it off, the server is only renewed by renew_vps and expires otherwise.",
35572
+ input: { vpsId, autoRenew: exports_external.boolean() },
35573
+ run: (a, api2) => api2.api("POST", `/vps/${a.vpsId}/auto-renew`, { body: { autoRenew: a.autoRenew } })
35574
+ }),
35526
35575
  defineTool({
35527
35576
  name: "toggle_vps_auto_renew",
35528
35577
  title: "Toggle VPS auto-renew",
35529
35578
  kind: "write",
35530
- description: "Flip auto-renew on or off; the response gives the new value.",
35579
+ description: "Flip auto-renew to the other value; the response gives the new one. Prefer set_vps_auto_renew to reach a given state.",
35531
35580
  input: { vpsId },
35532
35581
  run: (a, api2) => api2.api("POST", `/vps/${a.vpsId}/auto-renew-toggle`)
35533
35582
  }),
@@ -35856,8 +35905,8 @@ var vpsTools = [
35856
35905
  // src/tools/domains.ts
35857
35906
  var domainId = exports_external.number().int().positive().describe("Domain id (number, from list_my_domains)");
35858
35907
  var hostId = exports_external.number().int().positive().describe("Child nameserver id (from list_child_nameservers)");
35859
- 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");
35860
- var DRY = "dryRun=true returns the price and walletBalanceAfter without charging or changing anything.";
35908
+ var dryRun3 = exports_external.boolean().optional().describe("true = return the exact price (IDR, VAT included) and the wallet balance after it; nothing is charged or registered");
35909
+ var DRY2 = "dryRun=true returns the price and walletBalanceAfter without charging or changing anything.";
35861
35910
  var record3 = exports_external.object({
35862
35911
  name: exports_external.string().describe('"@" for the domain itself, or the part before the domain, e.g. "www" or "_dmarc.mail". A full name like "www.example.com" is accepted and stored as "www"'),
35863
35912
  type: exports_external.enum(["A", "AAAA", "MX", "MXE", "CNAME", "SPF", "TXT"]),
@@ -35933,12 +35982,12 @@ var domainTools = [
35933
35982
  name: "register_domain",
35934
35983
  title: "Register domain",
35935
35984
  kind: "write",
35936
- description: `Register a domain for 1 year. COSTS MONEY from the wallet. Check search_domain, and that the owner details are complete (check_registrant / update_registrant). ${DRY} The dry run includes WHOIS privacy when whoisProtection is true.`,
35985
+ description: `Register a domain for 1 year. COSTS MONEY from the wallet. Check search_domain, and that the owner details are complete (check_registrant / update_registrant). ${DRY2} The dry run includes WHOIS privacy when whoisProtection is true.`,
35937
35986
  input: {
35938
35987
  name: exports_external.string().describe("Full domain, e.g. tokosaya.id"),
35939
35988
  nameservers: exports_external.array(exports_external.string()).min(2).max(5).optional(),
35940
35989
  whoisProtection: exports_external.boolean().optional(),
35941
- dryRun: dryRun2
35990
+ dryRun: dryRun3
35942
35991
  },
35943
35992
  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 } })
35944
35993
  }),
@@ -35946,16 +35995,16 @@ var domainTools = [
35946
35995
  name: "transfer_domain",
35947
35996
  title: "Transfer domain in",
35948
35997
  kind: "write",
35949
- description: `Transfer a domain from another registrar (adds 1 year). COSTS MONEY from the wallet. Needs the domain's auth code. ${DRY}`,
35950
- input: { name: exports_external.string(), authCode: exports_external.string(), nameservers: exports_external.array(exports_external.string()).min(2).max(5).optional(), dryRun: dryRun2 },
35998
+ description: `Transfer a domain from another registrar (adds 1 year). COSTS MONEY from the wallet. Needs the domain's auth code. ${DRY2}`,
35999
+ input: { name: exports_external.string(), authCode: exports_external.string(), nameservers: exports_external.array(exports_external.string()).min(2).max(5).optional(), dryRun: dryRun3 },
35951
36000
  run: (a, api2) => api2.api("POST", "/domains/transfer", { body: { name: a.name, authCode: a.authCode, period: 1, nameservers: a.nameservers }, query: { dryRun: a.dryRun ? "true" : undefined } })
35952
36001
  }),
35953
36002
  defineTool({
35954
36003
  name: "renew_domain",
35955
36004
  title: "Renew domain",
35956
36005
  kind: "write",
35957
- description: `Renew a domain for 1 year (plus WHOIS privacy when it is on). COSTS MONEY from the wallet. ${DRY}`,
35958
- input: { domainId, dryRun: dryRun2 },
36006
+ description: `Renew a domain for 1 year (plus WHOIS privacy when it is on). COSTS MONEY from the wallet. ${DRY2}`,
36007
+ input: { domainId, dryRun: dryRun3 },
35959
36008
  run: (a, api2) => api2.api("POST", `/domains/${a.domainId}/renew`, { body: { period: 1 }, query: { dryRun: a.dryRun ? "true" : undefined } })
35960
36009
  }),
35961
36010
  defineTool({
@@ -36118,7 +36167,7 @@ var message = exports_external.object({
36118
36167
  variables: exports_external.record(exports_external.string(), exports_external.string()).optional(),
36119
36168
  idempotencyKey: exports_external.string().max(128).optional()
36120
36169
  });
36121
- var dryRun3 = exports_external.boolean().optional().describe("true = return the price (IDR) and the wallet balance after it; nothing is charged or changed");
36170
+ var dryRun4 = exports_external.boolean().optional().describe("true = return the price (IDR) and the wallet balance after it; nothing is charged or changed");
36122
36171
  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");
36123
36172
  var MONEY = "COSTS MONEY from the wallet. dryRun=true returns the charge (IDR) and walletBalanceAfter without charging or changing anything.";
36124
36173
  var planCode = exports_external.string().min(1).max(50).describe("Plan code from list_email_plans");
@@ -36154,7 +36203,7 @@ var emailTools = [
36154
36203
  title: "Buy email plan",
36155
36204
  kind: "write",
36156
36205
  description: `Start an email plan for the project (a free plan activates without charge). Works for a project with no email yet: the real call also sets email up (result.setup). ${MONEY} The dry run's mode says purchase, activate_free or already_active.`,
36157
- input: { projectId, planCode, months: exports_external.union([exports_external.literal(1), exports_external.literal(3), exports_external.literal(6), exports_external.literal(12)]).optional().describe("Billing cycle, default 1"), dryRun: dryRun3, idempotencyKey: idem2 },
36206
+ input: { projectId, planCode, months: exports_external.union([exports_external.literal(1), exports_external.literal(3), exports_external.literal(6), exports_external.literal(12)]).optional().describe("Billing cycle, default 1"), dryRun: dryRun4, idempotencyKey: idem2 },
36158
36207
  run: (a, api2) => api2.email("POST", "/plan", {
36159
36208
  project: a.projectId,
36160
36209
  body: { planCode: a.planCode, months: a.months },
@@ -36167,7 +36216,7 @@ var emailTools = [
36167
36216
  title: "Renew email plan",
36168
36217
  kind: "write",
36169
36218
  description: `Renew the project's ended or expiring email plan (409 PLAN_ACTIVE while one is still running and cannot be extended). Repeats the last plan and cycle unless planCode / months are given. ${MONEY}`,
36170
- input: { projectId, planCode: planCode.optional(), months: exports_external.union([exports_external.literal(1), exports_external.literal(3), exports_external.literal(6), exports_external.literal(12)]).optional(), dryRun: dryRun3, idempotencyKey: idem2 },
36219
+ input: { projectId, planCode: planCode.optional(), months: exports_external.union([exports_external.literal(1), exports_external.literal(3), exports_external.literal(6), exports_external.literal(12)]).optional(), dryRun: dryRun4, idempotencyKey: idem2 },
36171
36220
  run: (a, api2) => api2.email("POST", "/plan/renew", {
36172
36221
  project: a.projectId,
36173
36222
  body: { planCode: a.planCode, months: a.months },
@@ -36180,7 +36229,7 @@ var emailTools = [
36180
36229
  title: "Change email plan",
36181
36230
  kind: "write",
36182
36231
  description: `Move the active email plan to another plan. An upgrade applies now and charges the prorated difference to the same end date; a downgrade is scheduled for the next cycle (no refund). ${MONEY}`,
36183
- input: { projectId, planCode, dryRun: dryRun3, idempotencyKey: idem2 },
36232
+ input: { projectId, planCode, dryRun: dryRun4, idempotencyKey: idem2 },
36184
36233
  run: (a, api2) => api2.email("POST", "/plan/change", {
36185
36234
  project: a.projectId,
36186
36235
  body: { planCode: a.planCode },
@@ -36197,7 +36246,7 @@ var emailTools = [
36197
36246
  projectId,
36198
36247
  extraMailboxes: exports_external.number().int().min(0).max(50).describe("Total extra mailboxes wanted (0-50)"),
36199
36248
  extraStorageGb: exports_external.number().int().min(0).max(1000).describe("Total extra inbox storage in GB (0-1000)"),
36200
- dryRun: dryRun3,
36249
+ dryRun: dryRun4,
36201
36250
  idempotencyKey: idem2
36202
36251
  },
36203
36252
  run: (a, api2) => api2.email("POST", "/plan/addons", {
@@ -36767,7 +36816,7 @@ var emailTools = [
36767
36816
  // src/tools/databases.ts
36768
36817
  var databaseId = exports_external.string().uuid().describe("Database id (from list_databases)");
36769
36818
  var environmentId = exports_external.string().uuid().describe("Environment id (from list_projects). Omit for the primary (production) branch.");
36770
- var dryRun4 = exports_external.boolean().optional().describe("true = only check and show the result or price; nothing changes");
36819
+ var dryRun5 = exports_external.boolean().optional().describe("true = only check and show the result or price; nothing changes");
36771
36820
  var databaseTools = [
36772
36821
  defineTool({
36773
36822
  name: "list_database_plans",
@@ -36861,7 +36910,7 @@ var databaseTools = [
36861
36910
  minCu: exports_external.number().min(0.25).max(10).optional(),
36862
36911
  maxCu: exports_external.number().min(0.25).max(10).optional(),
36863
36912
  suspendTimeoutSeconds: exports_external.number().int().min(0).max(604800).optional().describe("0 = never suspend, or 60-604800"),
36864
- dryRun: dryRun4
36913
+ dryRun: dryRun5
36865
36914
  },
36866
36915
  run: (a, api2) => api2.api("PATCH", `/databases/${a.databaseId}/settings`, {
36867
36916
  body: { minCu: a.minCu, maxCu: a.maxCu, suspendTimeoutSeconds: a.suspendTimeoutSeconds },
@@ -36873,7 +36922,7 @@ var databaseTools = [
36873
36922
  title: "Upgrade database plan / top up",
36874
36923
  kind: "write",
36875
36924
  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.",
36876
- input: { databaseId, plan: exports_external.string().describe("Plan code (e.g. STARTER) or id from list_database_plans"), dryRun: dryRun4, idempotencyKey: exports_external.string().max(128).optional() },
36925
+ 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() },
36877
36926
  run: (a, api2) => api2.api("POST", `/databases/${a.databaseId}/upgrade`, { body: { plan: a.plan }, query: { dryRun: a.dryRun ? "true" : undefined }, idempotencyKey: a.idempotencyKey })
36878
36927
  }),
36879
36928
  defineTool({
@@ -37131,7 +37180,7 @@ var accountTools = [
37131
37180
  var projectId2 = exports_external.string().uuid().optional().describe("Limit to one project (a project key is always limited to its own)");
37132
37181
  var keyId = exports_external.string().uuid().optional().describe("Limit to one AI key (id from list_ai_keys)");
37133
37182
  var model = exports_external.string().max(100).optional().describe("Limit to one model id");
37134
- var dryRun5 = exports_external.boolean().optional().describe("true = only check and show the result; nothing is charged or changed");
37183
+ var dryRun6 = exports_external.boolean().optional().describe("true = only check and show the result; nothing is charged or changed");
37135
37184
  var ACCOUNT_KEY2 = "Needs a whole-account key (a key limited to one project gets 403 ACCOUNT_KEY_REQUIRED).";
37136
37185
  var imageProject = exports_external.string().uuid().optional().describe("Project the images belong to (required with a whole-account key; a project key uses its own)");
37137
37186
  var INLINE_MAX_BYTES = 1500000;
@@ -37216,7 +37265,7 @@ var aiTools = [
37216
37265
  n: exports_external.number().int().min(1).max(10).optional().describe("Images in this request (up to the model's maxN; default 1)"),
37217
37266
  images: exports_external.array(exports_external.string().url().startsWith("https://")).max(10).optional().describe("Reference images as https:// links (only models with refImages > 0)"),
37218
37267
  projectId: imageProject,
37219
- dryRun: dryRun5,
37268
+ dryRun: dryRun6,
37220
37269
  wait: exports_external.number().int().min(0).max(55).optional().describe("Seconds to wait for the result (default 50; 0 = return the id at once)"),
37221
37270
  idempotencyKey: exports_external.string().regex(/^[A-Za-z0-9_.:-]{8,128}$/).optional().describe("Idempotency-Key for the real request. Generated when omitted; pass the same value when retrying so it is never charged twice")
37222
37271
  },
@@ -37266,7 +37315,7 @@ var aiTools = [
37266
37315
  input: {
37267
37316
  packId: exports_external.string().min(1).max(64).optional(),
37268
37317
  amount: exports_external.number().min(1).max(1e9).optional().describe("IDR"),
37269
- dryRun: dryRun5,
37318
+ dryRun: dryRun6,
37270
37319
  idempotencyKey: exports_external.string().regex(/^[A-Za-z0-9_.:-]{8,128}$/).optional().describe("Idempotency-Key for the real top-up. Generated when omitted; pass the same value when retrying so it is never charged twice")
37271
37320
  },
37272
37321
  run: (a, api2) => {
@@ -37289,7 +37338,7 @@ var aiTools = [
37289
37338
  enabled: exports_external.boolean(),
37290
37339
  threshold: exports_external.number().min(1).optional().describe("Top up when the AI balance is below this (IDR)"),
37291
37340
  amount: exports_external.number().min(1).optional().describe("How much to top up each time (IDR)"),
37292
- dryRun: dryRun5
37341
+ dryRun: dryRun6
37293
37342
  },
37294
37343
  run: (a, api2) => api2.api("PUT", "/ai/auto-topup", {
37295
37344
  body: { enabled: a.enabled, threshold: a.threshold, amount: a.amount },
@@ -37325,7 +37374,7 @@ var generalTools = [
37325
37374
  ];
37326
37375
 
37327
37376
  // src/server.ts
37328
- var VERSION = "0.5.8";
37377
+ var VERSION = "0.5.10";
37329
37378
  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.
37330
37379
  - 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.
37331
37380
  - 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.
@@ -37335,9 +37384,10 @@ var INSTRUCTIONS = `Helipod is a cloud platform (Indonesia): containers, managed
37335
37384
  - Databases: get_database_connection returns a password; put it in a secret variable (DATABASE_URL) instead of code or chat.
37336
37385
  - 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.
37337
37386
  - Images from generate_ai_image are deleted after 7 days: tell the user and have them download the ones they keep.
37387
+ - Deploying from Git: public repos need nothing. For a private repo check get_git_connections / list_git_repos first; when GitHub or GitLab is not connected (or a call returns 409 GITHUB_NOT_CONNECTED / GITLAB_NOT_CONNECTED / REPO_NOT_ACCESSIBLE), give the user details.connectUrl to open in their browser and try again after they confirm. Never ask for a GitHub token.
37338
37388
  - Deploys and VPS actions run in the background: poll get_deployment / get_vps until they settle; on a failed deploy read get_deployment_logs.
37339
37389
  - 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.
37340
- - Errors come back as { status, code, error }: 401/403 means the key lacks access (keys choose products and projects in Settings → API keys; wallet, invoices, tickets and AI money need a whole-account key: with a sign-in, reconnect and choose Whole account), 402 means plan limit or low balance.
37390
+ - Errors come back as { status, code, error, details? }: 401/403 means the key lacks access (keys choose products and projects in Settings → API keys; wallet, invoices, tickets and AI money need a whole-account key: with a sign-in, reconnect and choose Whole account), 402 means plan limit or low balance.
37341
37391
  Docs: https://docs.helipod.io`;
37342
37392
  var ALL_TOOLS = [...containerTools, ...databaseTools, ...vpsTools, ...domainTools, ...emailTools, ...accountTools, ...aiTools, ...generalTools];
37343
37393
  function createHelipodServer(config2) {
package/dist/stdio.js CHANGED
@@ -34834,10 +34834,12 @@ var EMPTY_COMPLETION_RESULT = {
34834
34834
  class HelipodError extends Error {
34835
34835
  status;
34836
34836
  code;
34837
- constructor(status, code, message) {
34837
+ details;
34838
+ constructor(status, code, message, details) {
34838
34839
  super(message);
34839
34840
  this.status = status;
34840
34841
  this.code = code;
34842
+ this.details = details;
34841
34843
  this.name = "HelipodError";
34842
34844
  }
34843
34845
  }
@@ -34933,7 +34935,8 @@ class HelipodClient {
34933
34935
  if (!res.ok || json2?.success === false) {
34934
34936
  const retry = res.headers.get("retry-after");
34935
34937
  const msg = json2?.error ?? (text.slice(0, 300) || res.statusText);
34936
- throw new HelipodError(res.status, json2?.code, retry ? `${msg} (retry after ${retry}s)` : String(msg));
34938
+ const details = json2?.details && typeof json2.details === "object" && !Array.isArray(json2.details) ? json2.details : undefined;
34939
+ throw new HelipodError(res.status, json2?.code, retry ? `${msg} (retry after ${retry}s)` : String(msg), details);
34937
34940
  }
34938
34941
  return json2 && "data" in json2 ? json2.data : json2;
34939
34942
  }
@@ -34965,7 +34968,7 @@ function register(server, api2, defs) {
34965
34968
  const err = e instanceof HelipodError ? e : new HelipodError(0, "CLIENT_ERROR", String(e?.message ?? e));
34966
34969
  return {
34967
34970
  isError: true,
34968
- content: [{ type: "text", text: JSON.stringify({ status: err.status, code: err.code ?? null, error: err.message }, null, 2) }]
34971
+ content: [{ type: "text", text: JSON.stringify({ status: err.status, code: err.code ?? null, error: err.message, ...err.details ? { details: err.details } : {} }, null, 2) }]
34969
34972
  };
34970
34973
  }
34971
34974
  });
@@ -35099,18 +35102,48 @@ var containerTools = [
35099
35102
  input: { serviceId: id("Service") },
35100
35103
  run: (a, api2) => api2.api("GET", `${C}/services/${a.serviceId}`)
35101
35104
  }),
35105
+ defineTool({
35106
+ name: "get_git_connections",
35107
+ title: "Git connections",
35108
+ kind: "read",
35109
+ description: "Whether the user connected GitHub (sign-in + the Helipod GitHub App, with the accounts it is installed on) and GitLab, plus connectUrl: the Helipod page where the user connects them. Check this before deploying from a private repo. Connecting needs the user in a browser: give them connectUrl, never try to connect yourself.",
35110
+ input: {},
35111
+ run: (_a3, api2) => api2.api("GET", `${C}/git/connections`)
35112
+ }),
35113
+ defineTool({
35114
+ name: "list_git_repos",
35115
+ title: "List Git repositories",
35116
+ kind: "read",
35117
+ description: "Repositories the user's connected GitHub App installations (host github, default) or GitLab account (host gitlab) can reach: fullName, url, private, defaultBranch, newest first. Pass one url to create_service as repo. Not connected: 409 with details.connectUrl for the user.",
35118
+ input: {
35119
+ host: exports_external.enum(["github", "gitlab"]).optional().describe("github (default) or gitlab"),
35120
+ installationId: exports_external.number().int().positive().optional().describe("Only this GitHub installation (id from get_git_connections)"),
35121
+ search: exports_external.string().max(200).optional().describe("Filter by part of owner/name"),
35122
+ limit: exports_external.number().int().min(1).max(100).optional().describe("Default 50"),
35123
+ offset: exports_external.number().int().min(0).optional()
35124
+ },
35125
+ run: (a, api2) => api2.api("GET", `${C}/git/repos`, {
35126
+ query: {
35127
+ host: a.host,
35128
+ installationId: a.installationId !== undefined ? String(a.installationId) : undefined,
35129
+ search: a.search,
35130
+ limit: a.limit !== undefined ? String(a.limit) : undefined,
35131
+ offset: a.offset !== undefined ? String(a.offset) : undefined
35132
+ }
35133
+ })
35134
+ }),
35102
35135
  defineTool({
35103
35136
  name: "create_service",
35104
35137
  title: "Create service",
35105
35138
  kind: "write",
35106
- description: "Create a service from a container image, a public Git repository or an inline Dockerfile, and (unless deploy=false) start its first deploy. COSTS MONEY once deployed (billed daily from the wallet): call with dryRun=true first, show the dailyCost to the user and get their OK.",
35139
+ 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 (billed daily from the wallet): call with dryRun=true first, show the dailyCost to the user and get their OK.",
35107
35140
  input: {
35108
35141
  environmentId: id("Environment"),
35109
35142
  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}}"),
35110
35143
  image: exports_external.string().optional().describe("Container image, e.g. nginx:alpine or ghcr.io/acme/app:1.2"),
35111
35144
  registryUsername: exports_external.string().optional().describe("Private registry user (with registryPassword)"),
35112
35145
  registryPassword: exports_external.string().optional().describe("Private registry password or token"),
35113
- repo: exports_external.string().optional().describe("Public HTTPS Git repository URL (instead of image)"),
35146
+ repo: exports_external.string().optional().describe("Git repository: https URL or owner/name for GitHub (instead of image). Private repos need a connected GitHub / GitLab (list_git_repos)"),
35114
35147
  branch: exports_external.string().optional().describe("Git branch (default main)"),
35115
35148
  rootDirectory: exports_external.string().optional().describe("Monorepo: folder to build, e.g. apps/web"),
35116
35149
  dockerfile: exports_external.string().optional().describe("Inline Dockerfile content (instead of image/repo)"),
@@ -35500,6 +35533,10 @@ var period = exports_external.enum(["monthly", "3month", "6month", "yearly"]);
35500
35533
  var pointId = exports_external.string().describe("Restore point id (from list_vps_restore_points)");
35501
35534
  var backupPath = exports_external.string().max(1024).describe("Absolute path inside the backup, e.g. /etc/nginx or /var/www/index.html");
35502
35535
  var migrationId = exports_external.string().describe("Migration id (from list_vps_migrations)");
35536
+ var dryRun2 = exports_external.boolean().optional().describe("true = return the price (IDR) and the wallet balance after it; nothing is charged or changed");
35537
+ var idempotencyKey = exports_external.string().regex(/^[A-Za-z0-9_.:-]{8,128}$/).optional().describe("Idempotency-Key for the real call. Generated when omitted; pass the same value when retrying so it is never charged twice");
35538
+ var DRY = "dryRun=true returns the price and walletBalanceAfter without charging or changing anything.";
35539
+ var dryQuery = (d) => ({ dryRun: d ? "true" : undefined });
35503
35540
  var vpsTools = [
35504
35541
  defineTool({
35505
35542
  name: "list_vps_plans",
@@ -35529,7 +35566,7 @@ var vpsTools = [
35529
35566
  name: "create_vps",
35530
35567
  title: "Create VPS",
35531
35568
  kind: "write",
35532
- description: "Order a server. COSTS MONEY right away (the whole billing period is charged from the wallet; refunded if provisioning fails). Show the plan price from list_vps_plans and get the user's explicit OK first. Provisioning takes a few minutes: poll get_vps.",
35569
+ description: `Order a server. COSTS MONEY right away (the whole billing period is charged from the wallet; refunded if provisioning fails). Call with dryRun=true first, show the price and the wallet after it, and get the user's explicit OK. ${DRY} autoRenew=false creates it with auto-renew off (renew it with renew_vps). Provisioning takes a few minutes: poll get_vps.`,
35533
35570
  input: {
35534
35571
  projectId: exports_external.string().uuid(),
35535
35572
  planId: exports_external.string(),
@@ -35537,9 +35574,12 @@ var vpsTools = [
35537
35574
  osType: exports_external.string().describe("From the plan's osTemplates, e.g. ubuntu"),
35538
35575
  osVersion: exports_external.string().describe("e.g. 24.04"),
35539
35576
  rootPassword: password,
35540
- billingPeriod: period.optional().describe("Default monthly")
35577
+ billingPeriod: period.optional().describe("Default monthly"),
35578
+ autoRenew: exports_external.boolean().optional().describe("false = no automatic renewal from the wallet (default true)"),
35579
+ dryRun: dryRun2,
35580
+ idempotencyKey
35541
35581
  },
35542
- run: (a, api2) => api2.api("POST", "/vps", { body: a })
35582
+ run: ({ dryRun: dryRun3, idempotencyKey: idempotencyKey2, ...body }, api2) => api2.api("POST", "/vps", { body, query: dryQuery(dryRun3), idempotencyKey: purchaseKey(dryRun3, idempotencyKey2) })
35543
35583
  }),
35544
35584
  defineTool({
35545
35585
  name: "vps_power",
@@ -35593,17 +35633,17 @@ var vpsTools = [
35593
35633
  name: "renew_vps",
35594
35634
  title: "Renew VPS",
35595
35635
  kind: "write",
35596
- description: "Extend a server's paid period. COSTS MONEY: show the price from get_vps_renewal_options and get the user's OK.",
35597
- input: { vpsId, billingPeriod: period },
35598
- run: (a, api2) => api2.api("POST", `/vps/${a.vpsId}/renew`, { body: { billingPeriod: a.billingPeriod } })
35636
+ description: `Extend a server's paid period. COSTS MONEY: call with dryRun=true first, show the price and the wallet after it, and get the user's OK. ${DRY}`,
35637
+ input: { vpsId, billingPeriod: period, dryRun: dryRun2, idempotencyKey },
35638
+ run: (a, api2) => api2.api("POST", `/vps/${a.vpsId}/renew`, { body: { billingPeriod: a.billingPeriod }, query: dryQuery(a.dryRun), idempotencyKey: purchaseKey(a.dryRun, a.idempotencyKey) })
35599
35639
  }),
35600
35640
  defineTool({
35601
35641
  name: "upgrade_vps",
35602
35642
  title: "Upgrade VPS plan",
35603
35643
  kind: "write",
35604
- 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. Servers with providerCode 2 only.",
35605
- input: { vpsId, planId: exports_external.string() },
35606
- run: (a, api2) => api2.api("POST", `/vps/${a.vpsId}/upgrade`, { body: { planId: a.planId } })
35644
+ 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. Call with dryRun=true first and get the user's OK. ${DRY} Servers with providerCode 2 only.`,
35645
+ input: { vpsId, planId: exports_external.string(), dryRun: dryRun2, idempotencyKey },
35646
+ run: (a, api2) => api2.api("POST", `/vps/${a.vpsId}/upgrade`, { body: { planId: a.planId }, query: dryQuery(a.dryRun), idempotencyKey: purchaseKey(a.dryRun, a.idempotencyKey) })
35607
35647
  }),
35608
35648
  defineTool({
35609
35649
  name: "get_vps_upgrade_options",
@@ -35630,11 +35670,20 @@ var vpsTools = [
35630
35670
  input: { vpsId, range: exports_external.enum(["1h", "6h", "24h", "7d", "30d"]).optional() },
35631
35671
  run: (a, api2) => api2.api("GET", `/vps/${a.vpsId}/metrics`, { query: { range: a.range } })
35632
35672
  }),
35673
+ defineTool({
35674
+ name: "set_vps_auto_renew",
35675
+ title: "Set VPS auto-renew",
35676
+ kind: "write",
35677
+ idempotent: true,
35678
+ description: "Turn auto-renew on or off (sets the value, safe to repeat). With it off, the server is only renewed by renew_vps and expires otherwise.",
35679
+ input: { vpsId, autoRenew: exports_external.boolean() },
35680
+ run: (a, api2) => api2.api("POST", `/vps/${a.vpsId}/auto-renew`, { body: { autoRenew: a.autoRenew } })
35681
+ }),
35633
35682
  defineTool({
35634
35683
  name: "toggle_vps_auto_renew",
35635
35684
  title: "Toggle VPS auto-renew",
35636
35685
  kind: "write",
35637
- description: "Flip auto-renew on or off; the response gives the new value.",
35686
+ description: "Flip auto-renew to the other value; the response gives the new one. Prefer set_vps_auto_renew to reach a given state.",
35638
35687
  input: { vpsId },
35639
35688
  run: (a, api2) => api2.api("POST", `/vps/${a.vpsId}/auto-renew-toggle`)
35640
35689
  }),
@@ -35963,8 +36012,8 @@ var vpsTools = [
35963
36012
  // src/tools/domains.ts
35964
36013
  var domainId = exports_external.number().int().positive().describe("Domain id (number, from list_my_domains)");
35965
36014
  var hostId = exports_external.number().int().positive().describe("Child nameserver id (from list_child_nameservers)");
35966
- 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");
35967
- var DRY = "dryRun=true returns the price and walletBalanceAfter without charging or changing anything.";
36015
+ var dryRun3 = exports_external.boolean().optional().describe("true = return the exact price (IDR, VAT included) and the wallet balance after it; nothing is charged or registered");
36016
+ var DRY2 = "dryRun=true returns the price and walletBalanceAfter without charging or changing anything.";
35968
36017
  var record3 = exports_external.object({
35969
36018
  name: exports_external.string().describe('"@" for the domain itself, or the part before the domain, e.g. "www" or "_dmarc.mail". A full name like "www.example.com" is accepted and stored as "www"'),
35970
36019
  type: exports_external.enum(["A", "AAAA", "MX", "MXE", "CNAME", "SPF", "TXT"]),
@@ -36040,12 +36089,12 @@ var domainTools = [
36040
36089
  name: "register_domain",
36041
36090
  title: "Register domain",
36042
36091
  kind: "write",
36043
- description: `Register a domain for 1 year. COSTS MONEY from the wallet. Check search_domain, and that the owner details are complete (check_registrant / update_registrant). ${DRY} The dry run includes WHOIS privacy when whoisProtection is true.`,
36092
+ description: `Register a domain for 1 year. COSTS MONEY from the wallet. Check search_domain, and that the owner details are complete (check_registrant / update_registrant). ${DRY2} The dry run includes WHOIS privacy when whoisProtection is true.`,
36044
36093
  input: {
36045
36094
  name: exports_external.string().describe("Full domain, e.g. tokosaya.id"),
36046
36095
  nameservers: exports_external.array(exports_external.string()).min(2).max(5).optional(),
36047
36096
  whoisProtection: exports_external.boolean().optional(),
36048
- dryRun: dryRun2
36097
+ dryRun: dryRun3
36049
36098
  },
36050
36099
  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 } })
36051
36100
  }),
@@ -36053,16 +36102,16 @@ var domainTools = [
36053
36102
  name: "transfer_domain",
36054
36103
  title: "Transfer domain in",
36055
36104
  kind: "write",
36056
- description: `Transfer a domain from another registrar (adds 1 year). COSTS MONEY from the wallet. Needs the domain's auth code. ${DRY}`,
36057
- input: { name: exports_external.string(), authCode: exports_external.string(), nameservers: exports_external.array(exports_external.string()).min(2).max(5).optional(), dryRun: dryRun2 },
36105
+ description: `Transfer a domain from another registrar (adds 1 year). COSTS MONEY from the wallet. Needs the domain's auth code. ${DRY2}`,
36106
+ input: { name: exports_external.string(), authCode: exports_external.string(), nameservers: exports_external.array(exports_external.string()).min(2).max(5).optional(), dryRun: dryRun3 },
36058
36107
  run: (a, api2) => api2.api("POST", "/domains/transfer", { body: { name: a.name, authCode: a.authCode, period: 1, nameservers: a.nameservers }, query: { dryRun: a.dryRun ? "true" : undefined } })
36059
36108
  }),
36060
36109
  defineTool({
36061
36110
  name: "renew_domain",
36062
36111
  title: "Renew domain",
36063
36112
  kind: "write",
36064
- description: `Renew a domain for 1 year (plus WHOIS privacy when it is on). COSTS MONEY from the wallet. ${DRY}`,
36065
- input: { domainId, dryRun: dryRun2 },
36113
+ description: `Renew a domain for 1 year (plus WHOIS privacy when it is on). COSTS MONEY from the wallet. ${DRY2}`,
36114
+ input: { domainId, dryRun: dryRun3 },
36066
36115
  run: (a, api2) => api2.api("POST", `/domains/${a.domainId}/renew`, { body: { period: 1 }, query: { dryRun: a.dryRun ? "true" : undefined } })
36067
36116
  }),
36068
36117
  defineTool({
@@ -36225,7 +36274,7 @@ var message = exports_external.object({
36225
36274
  variables: exports_external.record(exports_external.string(), exports_external.string()).optional(),
36226
36275
  idempotencyKey: exports_external.string().max(128).optional()
36227
36276
  });
36228
- var dryRun3 = exports_external.boolean().optional().describe("true = return the price (IDR) and the wallet balance after it; nothing is charged or changed");
36277
+ var dryRun4 = exports_external.boolean().optional().describe("true = return the price (IDR) and the wallet balance after it; nothing is charged or changed");
36229
36278
  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");
36230
36279
  var MONEY = "COSTS MONEY from the wallet. dryRun=true returns the charge (IDR) and walletBalanceAfter without charging or changing anything.";
36231
36280
  var planCode = exports_external.string().min(1).max(50).describe("Plan code from list_email_plans");
@@ -36261,7 +36310,7 @@ var emailTools = [
36261
36310
  title: "Buy email plan",
36262
36311
  kind: "write",
36263
36312
  description: `Start an email plan for the project (a free plan activates without charge). Works for a project with no email yet: the real call also sets email up (result.setup). ${MONEY} The dry run's mode says purchase, activate_free or already_active.`,
36264
- input: { projectId, planCode, months: exports_external.union([exports_external.literal(1), exports_external.literal(3), exports_external.literal(6), exports_external.literal(12)]).optional().describe("Billing cycle, default 1"), dryRun: dryRun3, idempotencyKey: idem2 },
36313
+ input: { projectId, planCode, months: exports_external.union([exports_external.literal(1), exports_external.literal(3), exports_external.literal(6), exports_external.literal(12)]).optional().describe("Billing cycle, default 1"), dryRun: dryRun4, idempotencyKey: idem2 },
36265
36314
  run: (a, api2) => api2.email("POST", "/plan", {
36266
36315
  project: a.projectId,
36267
36316
  body: { planCode: a.planCode, months: a.months },
@@ -36274,7 +36323,7 @@ var emailTools = [
36274
36323
  title: "Renew email plan",
36275
36324
  kind: "write",
36276
36325
  description: `Renew the project's ended or expiring email plan (409 PLAN_ACTIVE while one is still running and cannot be extended). Repeats the last plan and cycle unless planCode / months are given. ${MONEY}`,
36277
- input: { projectId, planCode: planCode.optional(), months: exports_external.union([exports_external.literal(1), exports_external.literal(3), exports_external.literal(6), exports_external.literal(12)]).optional(), dryRun: dryRun3, idempotencyKey: idem2 },
36326
+ input: { projectId, planCode: planCode.optional(), months: exports_external.union([exports_external.literal(1), exports_external.literal(3), exports_external.literal(6), exports_external.literal(12)]).optional(), dryRun: dryRun4, idempotencyKey: idem2 },
36278
36327
  run: (a, api2) => api2.email("POST", "/plan/renew", {
36279
36328
  project: a.projectId,
36280
36329
  body: { planCode: a.planCode, months: a.months },
@@ -36287,7 +36336,7 @@ var emailTools = [
36287
36336
  title: "Change email plan",
36288
36337
  kind: "write",
36289
36338
  description: `Move the active email plan to another plan. An upgrade applies now and charges the prorated difference to the same end date; a downgrade is scheduled for the next cycle (no refund). ${MONEY}`,
36290
- input: { projectId, planCode, dryRun: dryRun3, idempotencyKey: idem2 },
36339
+ input: { projectId, planCode, dryRun: dryRun4, idempotencyKey: idem2 },
36291
36340
  run: (a, api2) => api2.email("POST", "/plan/change", {
36292
36341
  project: a.projectId,
36293
36342
  body: { planCode: a.planCode },
@@ -36304,7 +36353,7 @@ var emailTools = [
36304
36353
  projectId,
36305
36354
  extraMailboxes: exports_external.number().int().min(0).max(50).describe("Total extra mailboxes wanted (0-50)"),
36306
36355
  extraStorageGb: exports_external.number().int().min(0).max(1000).describe("Total extra inbox storage in GB (0-1000)"),
36307
- dryRun: dryRun3,
36356
+ dryRun: dryRun4,
36308
36357
  idempotencyKey: idem2
36309
36358
  },
36310
36359
  run: (a, api2) => api2.email("POST", "/plan/addons", {
@@ -36874,7 +36923,7 @@ var emailTools = [
36874
36923
  // src/tools/databases.ts
36875
36924
  var databaseId = exports_external.string().uuid().describe("Database id (from list_databases)");
36876
36925
  var environmentId = exports_external.string().uuid().describe("Environment id (from list_projects). Omit for the primary (production) branch.");
36877
- var dryRun4 = exports_external.boolean().optional().describe("true = only check and show the result or price; nothing changes");
36926
+ var dryRun5 = exports_external.boolean().optional().describe("true = only check and show the result or price; nothing changes");
36878
36927
  var databaseTools = [
36879
36928
  defineTool({
36880
36929
  name: "list_database_plans",
@@ -36968,7 +37017,7 @@ var databaseTools = [
36968
37017
  minCu: exports_external.number().min(0.25).max(10).optional(),
36969
37018
  maxCu: exports_external.number().min(0.25).max(10).optional(),
36970
37019
  suspendTimeoutSeconds: exports_external.number().int().min(0).max(604800).optional().describe("0 = never suspend, or 60-604800"),
36971
- dryRun: dryRun4
37020
+ dryRun: dryRun5
36972
37021
  },
36973
37022
  run: (a, api2) => api2.api("PATCH", `/databases/${a.databaseId}/settings`, {
36974
37023
  body: { minCu: a.minCu, maxCu: a.maxCu, suspendTimeoutSeconds: a.suspendTimeoutSeconds },
@@ -36980,7 +37029,7 @@ var databaseTools = [
36980
37029
  title: "Upgrade database plan / top up",
36981
37030
  kind: "write",
36982
37031
  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.",
36983
- input: { databaseId, plan: exports_external.string().describe("Plan code (e.g. STARTER) or id from list_database_plans"), dryRun: dryRun4, idempotencyKey: exports_external.string().max(128).optional() },
37032
+ 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() },
36984
37033
  run: (a, api2) => api2.api("POST", `/databases/${a.databaseId}/upgrade`, { body: { plan: a.plan }, query: { dryRun: a.dryRun ? "true" : undefined }, idempotencyKey: a.idempotencyKey })
36985
37034
  }),
36986
37035
  defineTool({
@@ -37238,7 +37287,7 @@ var accountTools = [
37238
37287
  var projectId2 = exports_external.string().uuid().optional().describe("Limit to one project (a project key is always limited to its own)");
37239
37288
  var keyId = exports_external.string().uuid().optional().describe("Limit to one AI key (id from list_ai_keys)");
37240
37289
  var model = exports_external.string().max(100).optional().describe("Limit to one model id");
37241
- var dryRun5 = exports_external.boolean().optional().describe("true = only check and show the result; nothing is charged or changed");
37290
+ var dryRun6 = exports_external.boolean().optional().describe("true = only check and show the result; nothing is charged or changed");
37242
37291
  var ACCOUNT_KEY2 = "Needs a whole-account key (a key limited to one project gets 403 ACCOUNT_KEY_REQUIRED).";
37243
37292
  var imageProject = exports_external.string().uuid().optional().describe("Project the images belong to (required with a whole-account key; a project key uses its own)");
37244
37293
  var INLINE_MAX_BYTES = 1500000;
@@ -37323,7 +37372,7 @@ var aiTools = [
37323
37372
  n: exports_external.number().int().min(1).max(10).optional().describe("Images in this request (up to the model's maxN; default 1)"),
37324
37373
  images: exports_external.array(exports_external.string().url().startsWith("https://")).max(10).optional().describe("Reference images as https:// links (only models with refImages > 0)"),
37325
37374
  projectId: imageProject,
37326
- dryRun: dryRun5,
37375
+ dryRun: dryRun6,
37327
37376
  wait: exports_external.number().int().min(0).max(55).optional().describe("Seconds to wait for the result (default 50; 0 = return the id at once)"),
37328
37377
  idempotencyKey: exports_external.string().regex(/^[A-Za-z0-9_.:-]{8,128}$/).optional().describe("Idempotency-Key for the real request. Generated when omitted; pass the same value when retrying so it is never charged twice")
37329
37378
  },
@@ -37373,7 +37422,7 @@ var aiTools = [
37373
37422
  input: {
37374
37423
  packId: exports_external.string().min(1).max(64).optional(),
37375
37424
  amount: exports_external.number().min(1).max(1e9).optional().describe("IDR"),
37376
- dryRun: dryRun5,
37425
+ dryRun: dryRun6,
37377
37426
  idempotencyKey: exports_external.string().regex(/^[A-Za-z0-9_.:-]{8,128}$/).optional().describe("Idempotency-Key for the real top-up. Generated when omitted; pass the same value when retrying so it is never charged twice")
37378
37427
  },
37379
37428
  run: (a, api2) => {
@@ -37396,7 +37445,7 @@ var aiTools = [
37396
37445
  enabled: exports_external.boolean(),
37397
37446
  threshold: exports_external.number().min(1).optional().describe("Top up when the AI balance is below this (IDR)"),
37398
37447
  amount: exports_external.number().min(1).optional().describe("How much to top up each time (IDR)"),
37399
- dryRun: dryRun5
37448
+ dryRun: dryRun6
37400
37449
  },
37401
37450
  run: (a, api2) => api2.api("PUT", "/ai/auto-topup", {
37402
37451
  body: { enabled: a.enabled, threshold: a.threshold, amount: a.amount },
@@ -37432,7 +37481,7 @@ var generalTools = [
37432
37481
  ];
37433
37482
 
37434
37483
  // src/server.ts
37435
- var VERSION = "0.5.8";
37484
+ var VERSION = "0.5.10";
37436
37485
  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.
37437
37486
  - 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.
37438
37487
  - 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.
@@ -37442,9 +37491,10 @@ var INSTRUCTIONS = `Helipod is a cloud platform (Indonesia): containers, managed
37442
37491
  - Databases: get_database_connection returns a password; put it in a secret variable (DATABASE_URL) instead of code or chat.
37443
37492
  - 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.
37444
37493
  - Images from generate_ai_image are deleted after 7 days: tell the user and have them download the ones they keep.
37494
+ - Deploying from Git: public repos need nothing. For a private repo check get_git_connections / list_git_repos first; when GitHub or GitLab is not connected (or a call returns 409 GITHUB_NOT_CONNECTED / GITLAB_NOT_CONNECTED / REPO_NOT_ACCESSIBLE), give the user details.connectUrl to open in their browser and try again after they confirm. Never ask for a GitHub token.
37445
37495
  - Deploys and VPS actions run in the background: poll get_deployment / get_vps until they settle; on a failed deploy read get_deployment_logs.
37446
37496
  - 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.
37447
- - Errors come back as { status, code, error }: 401/403 means the key lacks access (keys choose products and projects in Settings → API keys; wallet, invoices, tickets and AI money need a whole-account key: with a sign-in, reconnect and choose Whole account), 402 means plan limit or low balance.
37497
+ - Errors come back as { status, code, error, details? }: 401/403 means the key lacks access (keys choose products and projects in Settings → API keys; wallet, invoices, tickets and AI money need a whole-account key: with a sign-in, reconnect and choose Whole account), 402 means plan limit or low balance.
37448
37498
  Docs: https://docs.helipod.io`;
37449
37499
  var ALL_TOOLS = [...containerTools, ...databaseTools, ...vpsTools, ...domainTools, ...emailTools, ...accountTools, ...aiTools, ...generalTools];
37450
37500
  function createHelipodServer(config2) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "helipod-mcp",
3
3
  "mcpName": "io.helipod/helipod",
4
- "version": "0.5.8",
4
+ "version": "0.5.10",
5
5
  "description": "MCP server for Helipod: containers, databases, VPS, domains and email from Claude Code, Codex, Cursor and other AI tools.",
6
6
  "type": "module",
7
7
  "bin": {