helipod-mcp 0.5.11 → 0.5.12

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
@@ -35730,7 +35730,7 @@ function purchaseKey(dryRun, given) {
35730
35730
 
35731
35731
  // src/tools/containers.ts
35732
35732
  var id = (what) => exports_external.string().uuid().describe(`${what} id (UUID)`);
35733
- var dryRun = exports_external.boolean().optional().describe("true: return the daily cost (IDR) only, change nothing");
35733
+ var dryRun = exports_external.boolean().optional().describe("true: return the cost only (dailyCost list price, walletDailyCost what the wallet pays after the bundle), change nothing");
35734
35734
  var idem = exports_external.string().min(8).max(128).optional().describe("Idempotency-Key: a retry with the same value returns the first result instead of doing it twice");
35735
35735
  var C = "/containers";
35736
35736
  var port = exports_external.object({
@@ -35739,6 +35739,14 @@ var port = exports_external.object({
35739
35739
  public: exports_external.boolean().optional().describe("http ports: give it a *.helipod.app domain (default true)")
35740
35740
  });
35741
35741
  var containerTools = [
35742
+ defineTool({
35743
+ name: "get_bundle",
35744
+ title: "Container bundle",
35745
+ kind: "read",
35746
+ description: "The account's container bundle: whether one is active, until when, quota used / max (vCPU, GB RAM, GB storage), what it covers and what it does not. Container services within the quota cost the wallet NOTHING: read this before telling the user to top up for a container deploy. The bundle covers containers only (not VPS, databases, email, domains or AI).",
35747
+ input: {},
35748
+ run: (_a3, api2) => api2.api("GET", `${C}/bundle`)
35749
+ }),
35742
35750
  defineTool({
35743
35751
  name: "list_projects",
35744
35752
  title: "List projects",
@@ -35884,7 +35892,7 @@ var containerTools = [
35884
35892
  name: "create_service",
35885
35893
  title: "Create service",
35886
35894
  kind: "write",
35887
- description: "Create a service from a container image, a Git repository or an inline Dockerfile, and (unless deploy=false) start its first deploy. Private GitHub / GitLab repos work once connected (get_git_connections, list_git_repos); such services also redeploy on every push. A private repo that is not connected is refused (409 GITHUB_NOT_CONNECTED / GITLAB_NOT_CONNECTED / REPO_NOT_ACCESSIBLE, nothing created): give the user details.connectUrl and retry after they connect. COSTS MONEY once deployed (billed daily from the wallet): call with dryRun=true first, show the dailyCost to the user and get their OK.",
35895
+ description: "Create a service from a container image, a Git repository or an inline Dockerfile, and (unless deploy=false) start its first deploy. Private GitHub / GitLab repos work once connected (get_git_connections, list_git_repos); such services also redeploy on every push. A private repo that is not connected is refused (409 GITHUB_NOT_CONNECTED / GITLAB_NOT_CONNECTED / REPO_NOT_ACCESSIBLE, nothing created): give the user details.connectUrl and retry after they connect. COSTS MONEY once deployed, beyond the account's bundle: billed daily, from the bundle quota first and the wallet only beyond it. Call with dryRun=true first, tell the user what the wallet pays (walletDailyCost) and get their OK. Before saying a price, read walletDailyCost (what the wallet really pays per day) and bundle: with an active bundle that covers it (bundle.coversIt, walletDailyCost 0) nothing is charged and NO top-up is needed, even with a Rp 0 balance.",
35888
35896
  input: {
35889
35897
  environmentId: id("Environment"),
35890
35898
  name: exports_external.string().regex(/^[A-Za-z][A-Za-z0-9_-]{0,39}$/).describe("Service name (letters, digits, _ -; starts with a letter). Also used in references like ${{name.KEY}}"),
@@ -35966,7 +35974,7 @@ var containerTools = [
35966
35974
  name: "deploy_service",
35967
35975
  title: "Deploy service",
35968
35976
  kind: "write",
35969
- 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.",
35977
+ description: "Start a new deployment (Git services build the latest commit). COSTS MONEY beyond the bundle: charges today's cost when the build starts (refunded if it fails), nothing within an active bundle's quota. dryRun=true returns the cost only. Before saying a price, read walletDailyCost (what the wallet really pays per day) and bundle: with an active bundle that covers it (bundle.coversIt, walletDailyCost 0) nothing is charged and NO top-up is needed, even with a Rp 0 balance. The deployment runs in the background; get_deployment returns its status.",
35970
35978
  input: { serviceId: id("Service"), dryRun, idempotencyKey: idem },
35971
35979
  run: (a, api2) => api2.api("POST", `${C}/services/${a.serviceId}/deployments`, { query: { dryRun: a.dryRun ? "true" : undefined }, idempotencyKey: a.idempotencyKey })
35972
35980
  }),
@@ -36074,7 +36082,7 @@ var containerTools = [
36074
36082
  title: "Scale service",
36075
36083
  kind: "write",
36076
36084
  idempotent: true,
36077
- description: "Change replicas, CPU or RAM (applies right away). COSTS MONEY on increase: call with dryRun=true first to show currentDailyCost vs newDailyCost and get the user's OK.",
36085
+ description: "Change replicas, CPU or RAM (applies right away). An increase COSTS MONEY unless the bundle covers it: call with dryRun=true first to show currentDailyCost vs newDailyCost and walletDailyCost, and get the user's OK. Before saying a price, read walletDailyCost (what the wallet really pays per day) and bundle: with an active bundle that covers it (bundle.coversIt, walletDailyCost 0) nothing is charged and NO top-up is needed, even with a Rp 0 balance.",
36078
36086
  input: {
36079
36087
  serviceId: id("Service"),
36080
36088
  replicas: exports_external.number().int().min(1).max(20).optional(),
@@ -36122,7 +36130,7 @@ var containerTools = [
36122
36130
  name: "resize_volume",
36123
36131
  title: "Resize volume",
36124
36132
  kind: "write",
36125
- description: "Change a volume's size (1-50 GB; never below the space already used). A larger volume COSTS MONEY: it raises the daily cost, charged pro rata when the service is redeployed and in full from the next renewal. dryRun=true returns currentDailyCost and newDailyCost (IDR) without changing anything. Applies on the next deploy.",
36133
+ description: "Change a volume's size (1-50 GB; never below the space already used). A larger volume COSTS MONEY: it raises the daily cost, charged pro rata when the service is redeployed and in full from the next renewal. dryRun=true returns currentDailyCost, newDailyCost and walletDailyCost (IDR) without changing anything. Applies on the next deploy. Before saying a price, read walletDailyCost (what the wallet really pays per day) and bundle: with an active bundle that covers it (bundle.coversIt, walletDailyCost 0) nothing is charged and NO top-up is needed, even with a Rp 0 balance.",
36126
36134
  input: {
36127
36135
  serviceId: id("Service"),
36128
36136
  volumeId: id("Volume"),
@@ -36244,20 +36252,21 @@ var containerTools = [
36244
36252
  name: "deploy_template",
36245
36253
  title: "Deploy template",
36246
36254
  kind: "write",
36247
- description: "Create every service of a template in an environment and deploy them. COSTS MONEY (each service is billed daily).",
36255
+ description: "Create every service of a template in an environment and deploy them. COSTS MONEY beyond the bundle: each service is billed daily (bundle quota first, then the wallet). Call with dryRun=true first. " + "Before saying a price, read walletDailyCost (what the wallet really pays per day) and bundle: with an active bundle that covers it (bundle.coversIt, walletDailyCost 0) nothing is charged and NO top-up is needed, even with a Rp 0 balance.",
36248
36256
  input: {
36249
36257
  environmentId: id("Environment"),
36250
36258
  slug: exports_external.string().regex(/^[a-z0-9][a-z0-9-]{0,79}$/),
36251
36259
  inputs: exports_external.record(exports_external.string(), exports_external.string()).optional().describe("Answers to the template's inputs"),
36260
+ dryRun,
36252
36261
  idempotencyKey: idem
36253
36262
  },
36254
- run: (a, api2) => api2.api("POST", `${C}/environments/${a.environmentId}/templates/${a.slug}`, { body: { inputs: a.inputs }, idempotencyKey: a.idempotencyKey })
36263
+ run: (a, api2) => api2.api("POST", `${C}/environments/${a.environmentId}/templates/${a.slug}`, { body: { inputs: a.inputs }, query: { dryRun: a.dryRun ? "true" : undefined }, idempotencyKey: a.idempotencyKey })
36255
36264
  }),
36256
36265
  defineTool({
36257
36266
  name: "create_stack",
36258
36267
  title: "Create stack from spec",
36259
36268
  kind: "write",
36260
- description: "Create several connected services from a template spec ({ version: 1, inputs?, services: [...] }) and deploy them in dependency order. COSTS MONEY: dryRun=true first. See https://docs.helipod.io/docs/container-api/templates-and-stacks",
36269
+ description: "Create several connected services from a template spec ({ version: 1, inputs?, services: [...] }) and deploy them in dependency order. COSTS MONEY beyond the bundle: billed daily (bundle quota first, then the wallet). dryRun=true first. Before saying a price, read walletDailyCost (what the wallet really pays per day) and bundle: with an active bundle that covers it (bundle.coversIt, walletDailyCost 0) nothing is charged and NO top-up is needed, even with a Rp 0 balance. See https://docs.helipod.io/docs/container-api/templates-and-stacks",
36261
36270
  input: {
36262
36271
  environmentId: id("Environment"),
36263
36272
  spec: exports_external.record(exports_external.string(), exports_external.any()).describe("Template spec object"),
@@ -37776,7 +37785,7 @@ var databaseTools = [
37776
37785
  name: "upgrade_database",
37777
37786
  title: "Upgrade database plan / top up",
37778
37787
  kind: "write",
37779
- description: "Buy a paid plan or top up credits. COSTS MONEY from the wallet: call with dryRun first, show price and wallet balance after, and get the user's explicit OK.",
37788
+ description: "Buy a paid plan or top up credits. Only when the user asks for more than the FREE plan gives (never to get started: create_database is free). COSTS MONEY from the wallet (bundles do not cover it): call with dryRun first, show price and wallet balance after, and get the user's explicit OK.",
37780
37789
  input: { databaseId, plan: exports_external.string().describe("Plan code (e.g. STARTER) or id from list_database_plans"), dryRun: dryRun5, idempotencyKey: exports_external.string().max(128).optional() },
37781
37790
  run: (a, api2) => api2.api("POST", `/databases/${a.databaseId}/upgrade`, { body: { plan: a.plan }, query: { dryRun: a.dryRun ? "true" : undefined }, idempotencyKey: a.idempotencyKey })
37782
37791
  }),
@@ -38261,10 +38270,13 @@ var generalTools = [
38261
38270
  ];
38262
38271
 
38263
38272
  // src/server.ts
38264
- var VERSION = "0.5.11";
38273
+ var VERSION = "0.5.12";
38265
38274
  var INSTRUCTIONS = `Helipod is a cloud platform (Indonesia): containers, managed Postgres databases, VPS, domains, email and an AI gateway, billed from a prepaid wallet in Rupiah.
38266
38275
  - Start with list_projects to find projects and environment ids. Account tools (get_wallet, invoices, statements, notifications, get_project_activity, support tickets) cover the account; AI tools (list_ai_models, get_ai_overview, get_ai_usage, AI keys, top-ups, generate_ai_image) cover the AI gateway.
38267
38276
  - 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.
38277
+ - What the user really pays: container services are paid from the account's bundle first (get_bundle: a monthly bundle with a CPU / RAM / storage quota) and from the wallet only beyond it. Container dry runs return walletDailyCost (what the wallet pays per day) and bundle.coversIt: when the bundle covers it, say it is included in their bundle and deploy after their OK; never ask for a top-up for something walletDailyCost says is 0, whatever the balance. Quote dailyCost only as the price without a bundle. The bundle covers containers only: VPS, domains, AI, paid database plans and paid email plans are paid from the wallet.
38278
+ - Free first: the managed Postgres database (create_database) starts on the FREE plan and email (buy_email_plan with the FREE plan) costs nothing. For a database, use create_database rather than running Postgres as a container service, and do not offer upgrade_database or a paid email plan unless the user asks for more or hits a limit.
38279
+ - Deploying an app, step by step: list_projects (pick the project and its production environmentId) -> get_bundle (is it covered?) -> if it needs Postgres: create_database, then get_database_connection and store DATABASE_URL with set_variables (secrets) after the service exists, or pass it in create_service variables -> create_service with dryRun=true (repo or image, port, variables) -> tell the user the cost (included in the bundle, or walletDailyCost per day) and wait for their OK -> create_service for real -> poll get_deployment until it settles, read get_deployment_logs if it failed -> give the user the *.helipod.app URL from get_service. Ask the user for secrets (API keys) only when the app needs them; never invent values.
38268
38280
  - Destructive tools (delete, terminate, reinstall, rollback, restore a backup or an import, copy a backup onto another VPS, replace DNS, revoke a key, remove an SSH key or port forward, send a broadcast, execute_database_sql): confirm with the user and name what will be lost. Before execute_database_sql, run classify_database_sql.
38269
38281
  - Email: when a project has no email yet (email_status shows activated false) or its plan ended, start it with buy_email_plan (dryRun first; the FREE plan costs nothing, still confirm). That also sets email up for the project; then add and verify a sending domain.
38270
38282
  - 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.
package/dist/index.js CHANGED
@@ -34875,7 +34875,7 @@ function purchaseKey(dryRun, given) {
34875
34875
 
34876
34876
  // src/tools/containers.ts
34877
34877
  var id = (what) => exports_external.string().uuid().describe(`${what} id (UUID)`);
34878
- var dryRun = exports_external.boolean().optional().describe("true: return the daily cost (IDR) only, change nothing");
34878
+ var dryRun = exports_external.boolean().optional().describe("true: return the cost only (dailyCost list price, walletDailyCost what the wallet pays after the bundle), change nothing");
34879
34879
  var idem = exports_external.string().min(8).max(128).optional().describe("Idempotency-Key: a retry with the same value returns the first result instead of doing it twice");
34880
34880
  var C = "/containers";
34881
34881
  var port = exports_external.object({
@@ -34884,6 +34884,14 @@ var port = exports_external.object({
34884
34884
  public: exports_external.boolean().optional().describe("http ports: give it a *.helipod.app domain (default true)")
34885
34885
  });
34886
34886
  var containerTools = [
34887
+ defineTool({
34888
+ name: "get_bundle",
34889
+ title: "Container bundle",
34890
+ kind: "read",
34891
+ description: "The account's container bundle: whether one is active, until when, quota used / max (vCPU, GB RAM, GB storage), what it covers and what it does not. Container services within the quota cost the wallet NOTHING: read this before telling the user to top up for a container deploy. The bundle covers containers only (not VPS, databases, email, domains or AI).",
34892
+ input: {},
34893
+ run: (_a3, api2) => api2.api("GET", `${C}/bundle`)
34894
+ }),
34887
34895
  defineTool({
34888
34896
  name: "list_projects",
34889
34897
  title: "List projects",
@@ -35029,7 +35037,7 @@ var containerTools = [
35029
35037
  name: "create_service",
35030
35038
  title: "Create service",
35031
35039
  kind: "write",
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.",
35040
+ description: "Create a service from a container image, a Git repository or an inline Dockerfile, and (unless deploy=false) start its first deploy. Private GitHub / GitLab repos work once connected (get_git_connections, list_git_repos); such services also redeploy on every push. A private repo that is not connected is refused (409 GITHUB_NOT_CONNECTED / GITLAB_NOT_CONNECTED / REPO_NOT_ACCESSIBLE, nothing created): give the user details.connectUrl and retry after they connect. COSTS MONEY once deployed, beyond the account's bundle: billed daily, from the bundle quota first and the wallet only beyond it. Call with dryRun=true first, tell the user what the wallet pays (walletDailyCost) and get their OK. Before saying a price, read walletDailyCost (what the wallet really pays per day) and bundle: with an active bundle that covers it (bundle.coversIt, walletDailyCost 0) nothing is charged and NO top-up is needed, even with a Rp 0 balance.",
35033
35041
  input: {
35034
35042
  environmentId: id("Environment"),
35035
35043
  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}}"),
@@ -35111,7 +35119,7 @@ var containerTools = [
35111
35119
  name: "deploy_service",
35112
35120
  title: "Deploy service",
35113
35121
  kind: "write",
35114
- 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.",
35122
+ description: "Start a new deployment (Git services build the latest commit). COSTS MONEY beyond the bundle: charges today's cost when the build starts (refunded if it fails), nothing within an active bundle's quota. dryRun=true returns the cost only. Before saying a price, read walletDailyCost (what the wallet really pays per day) and bundle: with an active bundle that covers it (bundle.coversIt, walletDailyCost 0) nothing is charged and NO top-up is needed, even with a Rp 0 balance. The deployment runs in the background; get_deployment returns its status.",
35115
35123
  input: { serviceId: id("Service"), dryRun, idempotencyKey: idem },
35116
35124
  run: (a, api2) => api2.api("POST", `${C}/services/${a.serviceId}/deployments`, { query: { dryRun: a.dryRun ? "true" : undefined }, idempotencyKey: a.idempotencyKey })
35117
35125
  }),
@@ -35219,7 +35227,7 @@ var containerTools = [
35219
35227
  title: "Scale service",
35220
35228
  kind: "write",
35221
35229
  idempotent: true,
35222
- description: "Change replicas, CPU or RAM (applies right away). COSTS MONEY on increase: call with dryRun=true first to show currentDailyCost vs newDailyCost and get the user's OK.",
35230
+ description: "Change replicas, CPU or RAM (applies right away). An increase COSTS MONEY unless the bundle covers it: call with dryRun=true first to show currentDailyCost vs newDailyCost and walletDailyCost, and get the user's OK. Before saying a price, read walletDailyCost (what the wallet really pays per day) and bundle: with an active bundle that covers it (bundle.coversIt, walletDailyCost 0) nothing is charged and NO top-up is needed, even with a Rp 0 balance.",
35223
35231
  input: {
35224
35232
  serviceId: id("Service"),
35225
35233
  replicas: exports_external.number().int().min(1).max(20).optional(),
@@ -35267,7 +35275,7 @@ var containerTools = [
35267
35275
  name: "resize_volume",
35268
35276
  title: "Resize volume",
35269
35277
  kind: "write",
35270
- 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.",
35278
+ description: "Change a volume's size (1-50 GB; never below the space already used). A larger volume COSTS MONEY: it raises the daily cost, charged pro rata when the service is redeployed and in full from the next renewal. dryRun=true returns currentDailyCost, newDailyCost and walletDailyCost (IDR) without changing anything. Applies on the next deploy. Before saying a price, read walletDailyCost (what the wallet really pays per day) and bundle: with an active bundle that covers it (bundle.coversIt, walletDailyCost 0) nothing is charged and NO top-up is needed, even with a Rp 0 balance.",
35271
35279
  input: {
35272
35280
  serviceId: id("Service"),
35273
35281
  volumeId: id("Volume"),
@@ -35389,20 +35397,21 @@ var containerTools = [
35389
35397
  name: "deploy_template",
35390
35398
  title: "Deploy template",
35391
35399
  kind: "write",
35392
- description: "Create every service of a template in an environment and deploy them. COSTS MONEY (each service is billed daily).",
35400
+ description: "Create every service of a template in an environment and deploy them. COSTS MONEY beyond the bundle: each service is billed daily (bundle quota first, then the wallet). Call with dryRun=true first. " + "Before saying a price, read walletDailyCost (what the wallet really pays per day) and bundle: with an active bundle that covers it (bundle.coversIt, walletDailyCost 0) nothing is charged and NO top-up is needed, even with a Rp 0 balance.",
35393
35401
  input: {
35394
35402
  environmentId: id("Environment"),
35395
35403
  slug: exports_external.string().regex(/^[a-z0-9][a-z0-9-]{0,79}$/),
35396
35404
  inputs: exports_external.record(exports_external.string(), exports_external.string()).optional().describe("Answers to the template's inputs"),
35405
+ dryRun,
35397
35406
  idempotencyKey: idem
35398
35407
  },
35399
- run: (a, api2) => api2.api("POST", `${C}/environments/${a.environmentId}/templates/${a.slug}`, { body: { inputs: a.inputs }, idempotencyKey: a.idempotencyKey })
35408
+ run: (a, api2) => api2.api("POST", `${C}/environments/${a.environmentId}/templates/${a.slug}`, { body: { inputs: a.inputs }, query: { dryRun: a.dryRun ? "true" : undefined }, idempotencyKey: a.idempotencyKey })
35400
35409
  }),
35401
35410
  defineTool({
35402
35411
  name: "create_stack",
35403
35412
  title: "Create stack from spec",
35404
35413
  kind: "write",
35405
- description: "Create several connected services from a template spec ({ version: 1, inputs?, services: [...] }) and deploy them in dependency order. COSTS MONEY: dryRun=true first. See https://docs.helipod.io/docs/container-api/templates-and-stacks",
35414
+ description: "Create several connected services from a template spec ({ version: 1, inputs?, services: [...] }) and deploy them in dependency order. COSTS MONEY beyond the bundle: billed daily (bundle quota first, then the wallet). dryRun=true first. Before saying a price, read walletDailyCost (what the wallet really pays per day) and bundle: with an active bundle that covers it (bundle.coversIt, walletDailyCost 0) nothing is charged and NO top-up is needed, even with a Rp 0 balance. See https://docs.helipod.io/docs/container-api/templates-and-stacks",
35406
35415
  input: {
35407
35416
  environmentId: id("Environment"),
35408
35417
  spec: exports_external.record(exports_external.string(), exports_external.any()).describe("Template spec object"),
@@ -36921,7 +36930,7 @@ var databaseTools = [
36921
36930
  name: "upgrade_database",
36922
36931
  title: "Upgrade database plan / top up",
36923
36932
  kind: "write",
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.",
36933
+ description: "Buy a paid plan or top up credits. Only when the user asks for more than the FREE plan gives (never to get started: create_database is free). COSTS MONEY from the wallet (bundles do not cover it): call with dryRun first, show price and wallet balance after, and get the user's explicit OK.",
36925
36934
  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() },
36926
36935
  run: (a, api2) => api2.api("POST", `/databases/${a.databaseId}/upgrade`, { body: { plan: a.plan }, query: { dryRun: a.dryRun ? "true" : undefined }, idempotencyKey: a.idempotencyKey })
36927
36936
  }),
@@ -37406,10 +37415,13 @@ var generalTools = [
37406
37415
  ];
37407
37416
 
37408
37417
  // src/server.ts
37409
- var VERSION = "0.5.11";
37418
+ var VERSION = "0.5.12";
37410
37419
  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.
37411
37420
  - 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.
37412
37421
  - 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.
37422
+ - What the user really pays: container services are paid from the account's bundle first (get_bundle: a monthly bundle with a CPU / RAM / storage quota) and from the wallet only beyond it. Container dry runs return walletDailyCost (what the wallet pays per day) and bundle.coversIt: when the bundle covers it, say it is included in their bundle and deploy after their OK; never ask for a top-up for something walletDailyCost says is 0, whatever the balance. Quote dailyCost only as the price without a bundle. The bundle covers containers only: VPS, domains, AI, paid database plans and paid email plans are paid from the wallet.
37423
+ - Free first: the managed Postgres database (create_database) starts on the FREE plan and email (buy_email_plan with the FREE plan) costs nothing. For a database, use create_database rather than running Postgres as a container service, and do not offer upgrade_database or a paid email plan unless the user asks for more or hits a limit.
37424
+ - Deploying an app, step by step: list_projects (pick the project and its production environmentId) -> get_bundle (is it covered?) -> if it needs Postgres: create_database, then get_database_connection and store DATABASE_URL with set_variables (secrets) after the service exists, or pass it in create_service variables -> create_service with dryRun=true (repo or image, port, variables) -> tell the user the cost (included in the bundle, or walletDailyCost per day) and wait for their OK -> create_service for real -> poll get_deployment until it settles, read get_deployment_logs if it failed -> give the user the *.helipod.app URL from get_service. Ask the user for secrets (API keys) only when the app needs them; never invent values.
37413
37425
  - Destructive tools (delete, terminate, reinstall, rollback, restore a backup or an import, copy a backup onto another VPS, replace DNS, revoke a key, remove an SSH key or port forward, send a broadcast, execute_database_sql): confirm with the user and name what will be lost. Before execute_database_sql, run classify_database_sql.
37414
37426
  - 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.
37415
37427
  - 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.
package/dist/stdio.js CHANGED
@@ -34982,7 +34982,7 @@ function purchaseKey(dryRun, given) {
34982
34982
 
34983
34983
  // src/tools/containers.ts
34984
34984
  var id = (what) => exports_external.string().uuid().describe(`${what} id (UUID)`);
34985
- var dryRun = exports_external.boolean().optional().describe("true: return the daily cost (IDR) only, change nothing");
34985
+ var dryRun = exports_external.boolean().optional().describe("true: return the cost only (dailyCost list price, walletDailyCost what the wallet pays after the bundle), change nothing");
34986
34986
  var idem = exports_external.string().min(8).max(128).optional().describe("Idempotency-Key: a retry with the same value returns the first result instead of doing it twice");
34987
34987
  var C = "/containers";
34988
34988
  var port = exports_external.object({
@@ -34991,6 +34991,14 @@ var port = exports_external.object({
34991
34991
  public: exports_external.boolean().optional().describe("http ports: give it a *.helipod.app domain (default true)")
34992
34992
  });
34993
34993
  var containerTools = [
34994
+ defineTool({
34995
+ name: "get_bundle",
34996
+ title: "Container bundle",
34997
+ kind: "read",
34998
+ description: "The account's container bundle: whether one is active, until when, quota used / max (vCPU, GB RAM, GB storage), what it covers and what it does not. Container services within the quota cost the wallet NOTHING: read this before telling the user to top up for a container deploy. The bundle covers containers only (not VPS, databases, email, domains or AI).",
34999
+ input: {},
35000
+ run: (_a3, api2) => api2.api("GET", `${C}/bundle`)
35001
+ }),
34994
35002
  defineTool({
34995
35003
  name: "list_projects",
34996
35004
  title: "List projects",
@@ -35136,7 +35144,7 @@ var containerTools = [
35136
35144
  name: "create_service",
35137
35145
  title: "Create service",
35138
35146
  kind: "write",
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.",
35147
+ description: "Create a service from a container image, a Git repository or an inline Dockerfile, and (unless deploy=false) start its first deploy. Private GitHub / GitLab repos work once connected (get_git_connections, list_git_repos); such services also redeploy on every push. A private repo that is not connected is refused (409 GITHUB_NOT_CONNECTED / GITLAB_NOT_CONNECTED / REPO_NOT_ACCESSIBLE, nothing created): give the user details.connectUrl and retry after they connect. COSTS MONEY once deployed, beyond the account's bundle: billed daily, from the bundle quota first and the wallet only beyond it. Call with dryRun=true first, tell the user what the wallet pays (walletDailyCost) and get their OK. Before saying a price, read walletDailyCost (what the wallet really pays per day) and bundle: with an active bundle that covers it (bundle.coversIt, walletDailyCost 0) nothing is charged and NO top-up is needed, even with a Rp 0 balance.",
35140
35148
  input: {
35141
35149
  environmentId: id("Environment"),
35142
35150
  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}}"),
@@ -35218,7 +35226,7 @@ var containerTools = [
35218
35226
  name: "deploy_service",
35219
35227
  title: "Deploy service",
35220
35228
  kind: "write",
35221
- 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.",
35229
+ description: "Start a new deployment (Git services build the latest commit). COSTS MONEY beyond the bundle: charges today's cost when the build starts (refunded if it fails), nothing within an active bundle's quota. dryRun=true returns the cost only. Before saying a price, read walletDailyCost (what the wallet really pays per day) and bundle: with an active bundle that covers it (bundle.coversIt, walletDailyCost 0) nothing is charged and NO top-up is needed, even with a Rp 0 balance. The deployment runs in the background; get_deployment returns its status.",
35222
35230
  input: { serviceId: id("Service"), dryRun, idempotencyKey: idem },
35223
35231
  run: (a, api2) => api2.api("POST", `${C}/services/${a.serviceId}/deployments`, { query: { dryRun: a.dryRun ? "true" : undefined }, idempotencyKey: a.idempotencyKey })
35224
35232
  }),
@@ -35326,7 +35334,7 @@ var containerTools = [
35326
35334
  title: "Scale service",
35327
35335
  kind: "write",
35328
35336
  idempotent: true,
35329
- description: "Change replicas, CPU or RAM (applies right away). COSTS MONEY on increase: call with dryRun=true first to show currentDailyCost vs newDailyCost and get the user's OK.",
35337
+ description: "Change replicas, CPU or RAM (applies right away). An increase COSTS MONEY unless the bundle covers it: call with dryRun=true first to show currentDailyCost vs newDailyCost and walletDailyCost, and get the user's OK. Before saying a price, read walletDailyCost (what the wallet really pays per day) and bundle: with an active bundle that covers it (bundle.coversIt, walletDailyCost 0) nothing is charged and NO top-up is needed, even with a Rp 0 balance.",
35330
35338
  input: {
35331
35339
  serviceId: id("Service"),
35332
35340
  replicas: exports_external.number().int().min(1).max(20).optional(),
@@ -35374,7 +35382,7 @@ var containerTools = [
35374
35382
  name: "resize_volume",
35375
35383
  title: "Resize volume",
35376
35384
  kind: "write",
35377
- 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.",
35385
+ description: "Change a volume's size (1-50 GB; never below the space already used). A larger volume COSTS MONEY: it raises the daily cost, charged pro rata when the service is redeployed and in full from the next renewal. dryRun=true returns currentDailyCost, newDailyCost and walletDailyCost (IDR) without changing anything. Applies on the next deploy. Before saying a price, read walletDailyCost (what the wallet really pays per day) and bundle: with an active bundle that covers it (bundle.coversIt, walletDailyCost 0) nothing is charged and NO top-up is needed, even with a Rp 0 balance.",
35378
35386
  input: {
35379
35387
  serviceId: id("Service"),
35380
35388
  volumeId: id("Volume"),
@@ -35496,20 +35504,21 @@ var containerTools = [
35496
35504
  name: "deploy_template",
35497
35505
  title: "Deploy template",
35498
35506
  kind: "write",
35499
- description: "Create every service of a template in an environment and deploy them. COSTS MONEY (each service is billed daily).",
35507
+ description: "Create every service of a template in an environment and deploy them. COSTS MONEY beyond the bundle: each service is billed daily (bundle quota first, then the wallet). Call with dryRun=true first. " + "Before saying a price, read walletDailyCost (what the wallet really pays per day) and bundle: with an active bundle that covers it (bundle.coversIt, walletDailyCost 0) nothing is charged and NO top-up is needed, even with a Rp 0 balance.",
35500
35508
  input: {
35501
35509
  environmentId: id("Environment"),
35502
35510
  slug: exports_external.string().regex(/^[a-z0-9][a-z0-9-]{0,79}$/),
35503
35511
  inputs: exports_external.record(exports_external.string(), exports_external.string()).optional().describe("Answers to the template's inputs"),
35512
+ dryRun,
35504
35513
  idempotencyKey: idem
35505
35514
  },
35506
- run: (a, api2) => api2.api("POST", `${C}/environments/${a.environmentId}/templates/${a.slug}`, { body: { inputs: a.inputs }, idempotencyKey: a.idempotencyKey })
35515
+ run: (a, api2) => api2.api("POST", `${C}/environments/${a.environmentId}/templates/${a.slug}`, { body: { inputs: a.inputs }, query: { dryRun: a.dryRun ? "true" : undefined }, idempotencyKey: a.idempotencyKey })
35507
35516
  }),
35508
35517
  defineTool({
35509
35518
  name: "create_stack",
35510
35519
  title: "Create stack from spec",
35511
35520
  kind: "write",
35512
- description: "Create several connected services from a template spec ({ version: 1, inputs?, services: [...] }) and deploy them in dependency order. COSTS MONEY: dryRun=true first. See https://docs.helipod.io/docs/container-api/templates-and-stacks",
35521
+ description: "Create several connected services from a template spec ({ version: 1, inputs?, services: [...] }) and deploy them in dependency order. COSTS MONEY beyond the bundle: billed daily (bundle quota first, then the wallet). dryRun=true first. Before saying a price, read walletDailyCost (what the wallet really pays per day) and bundle: with an active bundle that covers it (bundle.coversIt, walletDailyCost 0) nothing is charged and NO top-up is needed, even with a Rp 0 balance. See https://docs.helipod.io/docs/container-api/templates-and-stacks",
35513
35522
  input: {
35514
35523
  environmentId: id("Environment"),
35515
35524
  spec: exports_external.record(exports_external.string(), exports_external.any()).describe("Template spec object"),
@@ -37028,7 +37037,7 @@ var databaseTools = [
37028
37037
  name: "upgrade_database",
37029
37038
  title: "Upgrade database plan / top up",
37030
37039
  kind: "write",
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.",
37040
+ description: "Buy a paid plan or top up credits. Only when the user asks for more than the FREE plan gives (never to get started: create_database is free). COSTS MONEY from the wallet (bundles do not cover it): call with dryRun first, show price and wallet balance after, and get the user's explicit OK.",
37032
37041
  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() },
37033
37042
  run: (a, api2) => api2.api("POST", `/databases/${a.databaseId}/upgrade`, { body: { plan: a.plan }, query: { dryRun: a.dryRun ? "true" : undefined }, idempotencyKey: a.idempotencyKey })
37034
37043
  }),
@@ -37513,10 +37522,13 @@ var generalTools = [
37513
37522
  ];
37514
37523
 
37515
37524
  // src/server.ts
37516
- var VERSION = "0.5.11";
37525
+ var VERSION = "0.5.12";
37517
37526
  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.
37518
37527
  - 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.
37519
37528
  - 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.
37529
+ - What the user really pays: container services are paid from the account's bundle first (get_bundle: a monthly bundle with a CPU / RAM / storage quota) and from the wallet only beyond it. Container dry runs return walletDailyCost (what the wallet pays per day) and bundle.coversIt: when the bundle covers it, say it is included in their bundle and deploy after their OK; never ask for a top-up for something walletDailyCost says is 0, whatever the balance. Quote dailyCost only as the price without a bundle. The bundle covers containers only: VPS, domains, AI, paid database plans and paid email plans are paid from the wallet.
37530
+ - Free first: the managed Postgres database (create_database) starts on the FREE plan and email (buy_email_plan with the FREE plan) costs nothing. For a database, use create_database rather than running Postgres as a container service, and do not offer upgrade_database or a paid email plan unless the user asks for more or hits a limit.
37531
+ - Deploying an app, step by step: list_projects (pick the project and its production environmentId) -> get_bundle (is it covered?) -> if it needs Postgres: create_database, then get_database_connection and store DATABASE_URL with set_variables (secrets) after the service exists, or pass it in create_service variables -> create_service with dryRun=true (repo or image, port, variables) -> tell the user the cost (included in the bundle, or walletDailyCost per day) and wait for their OK -> create_service for real -> poll get_deployment until it settles, read get_deployment_logs if it failed -> give the user the *.helipod.app URL from get_service. Ask the user for secrets (API keys) only when the app needs them; never invent values.
37520
37532
  - Destructive tools (delete, terminate, reinstall, rollback, restore a backup or an import, copy a backup onto another VPS, replace DNS, revoke a key, remove an SSH key or port forward, send a broadcast, execute_database_sql): confirm with the user and name what will be lost. Before execute_database_sql, run classify_database_sql.
37521
37533
  - 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.
37522
37534
  - 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.
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.11",
4
+ "version": "0.5.12",
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": {
@@ -5,7 +5,7 @@ description: Use when the user mentions Helipod (helipod.io) or wants to work wi
5
5
 
6
6
  # Helipod
7
7
 
8
- Helipod is a cloud platform in Indonesia: containers (deploy apps from Git, images or Dockerfiles), managed Postgres databases, VPS, domain registration and DNS, email sending, and an AI gateway. Everything is billed from a prepaid wallet in Rupiah (IDR). When the wallet is too low, `create_deposit` makes a top-up payment link: give the user its `paymentUrl` to pay (QRIS or bank virtual account) and check it with `get_deposit`; never pay it yourself.
8
+ Helipod is a cloud platform in Indonesia: containers (deploy apps from Git, images or Dockerfiles), managed Postgres databases, VPS, domain registration and DNS, email sending, and an AI gateway. Everything is billed from a prepaid wallet in Rupiah (IDR), except container services covered by a monthly **bundle** (`get_bundle`): within its CPU / RAM / storage quota they cost the wallet nothing. The managed Postgres database and email both have a FREE plan. When the wallet is too low, `create_deposit` makes a top-up payment link: give the user its `paymentUrl` to pay (QRIS or bank virtual account) and check it with `get_deposit`; never pay it yourself.
9
9
 
10
10
  ## Access
11
11
 
@@ -19,7 +19,7 @@ Helipod is a cloud platform in Indonesia: containers (deploy apps from Git, imag
19
19
 
20
20
  ## Rules (always)
21
21
 
22
- 1. **Money**: before anything that costs money (create/deploy/scale a service, create/renew/upgrade a VPS, register/renew/transfer a domain, deploy a template), get the price first (`dryRun: true` on container, VPS and domain calls; `domain_prices` and `list_vps_plans` to compare), tell the user the amount in IDR, and wait for a clear yes.
22
+ 1. **Money**: before anything that costs money (create/deploy/scale a service, create/renew/upgrade a VPS, register/renew/transfer a domain, deploy a template), get the price first (`dryRun: true` on container, VPS and domain calls; `domain_prices` and `list_vps_plans` to compare), tell the user the amount in IDR, and wait for a clear yes. For containers the amount is `walletDailyCost` (what the wallet pays after the bundle): when the dry run says `bundle.coversIt`, it is included in the bundle and needs no top-up, whatever the balance. Start with free options (FREE database and email plans); offer paid plans only when the user asks for more.
23
23
  2. **Destructive**: before deleting a service/volume, terminating/reinstalling a VPS, rolling back a snapshot or replacing DNS records, say exactly what will be lost and wait for a clear yes.
24
24
  3. **Background work**: deploys and VPS actions are asynchronous. Poll (`get_deployment`, `get_vps`) every few seconds until they settle; on a failed deploy read `get_deployment_logs` and explain the cause.
25
25
  4. **Secrets**: never print passwords, API keys or secret variable values unless the user asks. Store app secrets as secret variables, never in code.
@@ -11,6 +11,7 @@ Follow the `helipod` skill rules (price first, confirm destructive actions, neve
11
11
 
12
12
  `list_projects` → pick the project and environment (usually `production`). If several fit, ask the user.
13
13
  `list_services` on that environment shows what already runs (avoid duplicate names; reuse an existing database).
14
+ `get_bundle` tells you whether the account has a container bundle: services within its quota cost the wallet nothing, so a Rp 0 balance is fine.
14
15
 
15
16
  ## 2. Choose the source
16
17
 
@@ -19,7 +20,8 @@ Follow the `helipod` skill rules (price first, confirm destructive actions, neve
19
20
  | A published image (Docker Hub, GHCR, private registry) | `image` (fastest, no build). Private: `registryUsername` + `registryPassword` |
20
21
  | A public Git repo | `repo` (+ `branch`, `rootDirectory` for monorepos). Built by Helipod: a `Dockerfile` in the folder is used, else **Helipack** detects Node/Bun, PHP and Python frameworks |
21
22
  | Only local files | Push to a public Git repo first, or build and push an image. A short `dockerfile` can be sent inline |
22
- | A database or known app (Postgres, MySQL, Redis, WordPress, n8n, ...) | `list_templates` → `deploy_template` |
23
+ | Needs Postgres | The managed database: `create_database` (FREE plan, nothing to pay), then `get_database_connection` → `DATABASE_URL` as a secret variable. Not a Postgres container |
24
+ | A known app or other database (MySQL, Redis, WordPress, n8n, ...) | `list_templates` → `deploy_template` (`dryRun: true` first) |
23
25
 
24
26
  Private Git repos are connected in the dashboard (GitHub/GitLab integration), not through the API.
25
27
 
@@ -31,7 +33,10 @@ Before deploying a repo, check it quickly: the port the app listens on (prefer `
31
33
 
32
34
  ## 3. Create (price first)
33
35
 
34
- 1. `create_service` with `dryRun: true` → show `dailyCost` (IDR per day) to the user, wait for yes.
36
+ 1. `create_service` with `dryRun: true`. Read `walletDailyCost` and `bundle`:
37
+ - `bundle.coversIt: true` (`walletDailyCost: 0`): tell the user it is included in their bundle (say which, and until when), no charge and **no top-up needed**, and ask to go ahead.
38
+ - otherwise: tell the user `walletDailyCost` (IDR per day from the wallet; with a bundle it is only the part over the quota). Only if the wallet cannot cover it, offer `create_deposit`.
39
+ - `dailyCost` is the price without a bundle: never present it as what a bundle owner pays.
35
40
  2. `create_service` again without `dryRun`, with an `idempotencyKey`. Give `ports: [{ "port": <app port> }]` so it gets an HTTPS `*.helipod.app` domain.
36
41
  3. Poll `get_deployment` until `success` / `failed`. Builds take 1 to 5 minutes; images are faster.
37
42
  4. On `success`: give the user the domain `url`. On `failed`: `get_deployment_logs`, find the real error (missing env var, wrong port, build error), fix, `deploy_service` again.
@@ -46,7 +51,7 @@ Before deploying a repo, check it quickly: the port the app listens on (prefer `
46
51
 
47
52
  - Logs: `get_service_logs` (`filter: "error"`, `since: "1h"`); metrics: `get_service_metrics`.
48
53
  - Restart without rebuild: `restart_service` (running services only).
49
- - Scale: `scale_service` with `dryRun: true` first, show current vs new daily cost.
54
+ - Scale: `scale_service` with `dryRun: true` first, show current vs new daily cost and `walletDailyCost` (0 when the bundle still covers it).
50
55
  - Custom domain: `add_service_domain` with `kind: "custom"`, then tell the user the DNS record from the response (`CNAME` to `cname.helipod.app`, or the `A` record for a root domain). If the domain is on Helipod, the `helipod-domains` skill can create the record.
51
56
  - Persistent files: `add_volume` (mount path), then redeploy. Without a volume, files are lost on every deploy.
52
57
 
@@ -59,6 +64,6 @@ Before deploying a repo, check it quickly: the port the app listens on (prefer `
59
64
  | Symptom | Fix |
60
65
  |---|---|
61
66
  | Health check / deploy timeout | App must listen on the exposed port, on `0.0.0.0`, not `localhost` |
62
- | `402` | Plan limit (services, CPU, RAM, storage) or low balance: tell the user |
67
+ | `402` | Plan limit (services, CPU, RAM, storage) or low balance: tell the user. Check `get_bundle` first: within a bundle's quota the balance does not matter |
63
68
  | Crash loop on start | Read runtime logs; usually a missing variable or failed DB connection |
64
69
  | Monorepo built from root | Set `rootDirectory` (`update_service`) and redeploy |