@amerged/ohmyhost-mcp 0.1.43 → 0.1.45

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/index.js CHANGED
@@ -2,7 +2,7 @@ import { createRequire as __ohmyhostCreateRequire } from "node:module"; const re
2
2
  import {
3
3
  createOhmyhostMcpServer,
4
4
  ohmyhostMcpHandler
5
- } from "./chunk-ACMEMS6N.js";
5
+ } from "./chunk-AJV4T4LM.js";
6
6
  export {
7
7
  createOhmyhostMcpServer,
8
8
  ohmyhostMcpHandler
@@ -30,7 +30,7 @@ import {
30
30
  serverIdentityOf,
31
31
  setNegotiatedProtocolVersion,
32
32
  validateEnvelopeMeta
33
- } from "./chunk-ACMEMS6N.js";
33
+ } from "./chunk-AJV4T4LM.js";
34
34
 
35
35
  // node_modules/.pnpm/ignore@7.0.12/node_modules/ignore/index.js
36
36
  var require_ignore = __commonJS({
@@ -24381,12 +24381,12 @@ var failures = {
24381
24381
  "Do not repeat the write with a new key. Inspect the target data with a read query and retain this operation ID. The same key only reads this receipt."
24382
24382
  ],
24383
24383
  database_compute_failed: [
24384
- "The database size change failed at the provider.",
24385
- "Read the current database compute and report this operation ID through feedback. Preserve data; do not reset or redeploy the database to repair its size."
24384
+ "The database provider refused the size change or had no room for it. The database keeps its previous size and keeps running, and no credits are charged for the failed change.",
24385
+ "Read the current size with 'ohmyhost database compute get --project PROJECT_ULID --environment dev|prod --json' or MCP database_compute_get, then repeat the change with a new idempotency key; the old key only returns this failure. If it fails again after a few minutes, start a new deployment of the project, repeat the change and report this operation ID through feedback. Never reset the database to change its size."
24386
24386
  ],
24387
24387
  database_compute_plan_changed: [
24388
- "Paid access ended before the database size change started.",
24389
- "Read the organization's plan and current database compute, then confirm a new standard-size request. This operation did not resize the database."
24388
+ "Paid access ended before the database size change started, so it was not sent to the database provider. The database keeps its previous size and keeps running, and no credits are charged for the change.",
24389
+ "Read the organization's plan with 'ohmyhost credits account --organization ORGANIZATION_ULID --json' or MCP organization_account_get. Sizes above 256 MiB need active Paid: ask the organization Owner to activate Paid through billing checkout, then read the current size with database compute get and repeat the change with a new idempotency key."
24390
24390
  ],
24391
24391
  database_compute_rejected: [
24392
24392
  "The database size change could not be started.",
@@ -25572,6 +25572,36 @@ var PROBLEM_POLICY = {
25572
25572
  action: "The platform is still retrying this webhook delivery. Read the delivery in the delivery list; replay it only if it ends dead_lettered or the receiver needs the event again.",
25573
25573
  title: "Webhook delivery still pending"
25574
25574
  },
25575
+ database_watch_limit: {
25576
+ status: 409,
25577
+ retryable: false,
25578
+ action: "List the project's database watches for this environment and delete one you no longer need, then create the watch again.",
25579
+ title: "Database watch limit reached"
25580
+ },
25581
+ database_watch_table_unavailable: {
25582
+ status: 409,
25583
+ retryable: false,
25584
+ action: "The table must exist as an ordinary table owned by the application, created by a deployed migration; views, partitions, temporary tables and the schemas pgcf, ohmyhost, information_schema and pg_* cannot be watched. Fix the name or deploy the migration, then create the watch again.",
25585
+ title: "Table cannot be watched"
25586
+ },
25587
+ database_watch_column_unavailable: {
25588
+ status: 409,
25589
+ retryable: false,
25590
+ action: "List only existing column names exactly as written, names are case-sensitive. Delete the watch before a migration drops a published column, because the write would fail.",
25591
+ title: "Watched column does not exist"
25592
+ },
25593
+ database_watch_events_unavailable: {
25594
+ status: 409,
25595
+ retryable: false,
25596
+ action: "The platform event schema is installed when the database wakes: run any query against that environment, wait for the answer, then create the watch again. If it still fails, report the request_id through feedback.",
25597
+ title: "Database events not installed yet"
25598
+ },
25599
+ database_watch_conflict: {
25600
+ status: 409,
25601
+ retryable: false,
25602
+ action: "A migration already attaches a capture trigger to this table: keep it and skip the watch, or remove it from the migrations, deploy and create the watch again. A table carries one capture trigger.",
25603
+ title: "Table already has a capture trigger"
25604
+ },
25575
25605
  project_notes_conflict: {
25576
25606
  status: 409,
25577
25607
  retryable: false,
@@ -25836,7 +25866,7 @@ var PROBLEM_POLICY = {
25836
25866
  api_key_permissions_unavailable: {
25837
25867
  status: 503,
25838
25868
  retryable: false,
25839
- action: "Report the platform user-key permission configuration through feedback. Repeated login or creation will not fix it.",
25869
+ action: "Report the platform user-key permission configuration through feedback with this request_id; it affects every token the same way, whatever the integration. Repeated login does not fix it. After the platform confirms the fix, repeat the same create with the same name and Idempotency-Key.",
25840
25870
  title: "Token permissions unavailable"
25841
25871
  },
25842
25872
  reconciliation_exhausted: {
@@ -27093,6 +27123,9 @@ var DATABASE_WEBHOOK_EVENT_TYPES = [
27093
27123
  "database.resized",
27094
27124
  "database.deleted"
27095
27125
  ];
27126
+ var DATABASE_WATCH_OPERATIONS = ["insert", "update", "delete"];
27127
+ var DATABASE_WATCH_MAX_COLUMNS = 32;
27128
+ var DATABASE_WATCH_DEFAULT_SCHEMA = "public";
27096
27129
  var DATABASE_WEBHOOK_MAX_EVENT_TYPES = 16;
27097
27130
  var DATABASE_WEBHOOK_MAX_HEADERS = 8;
27098
27131
  var DATABASE_WEBHOOK_URL_MAX_LENGTH = 2048;
@@ -27135,15 +27168,61 @@ var USAGE_MAX_RANGE_MS = 31 * 24 * USAGE_HOUR_MS;
27135
27168
  var WEBHOOK_KEYS = ["id", "environment", "url", "event_types", "header_names", "created_at"];
27136
27169
  var WEBHOOK_ID_PATTERN = /^whk_[a-z0-9]{20}$/u;
27137
27170
  var DELIVERY_ID_PATTERN = /^dlv_[0-9a-f]{20}$/u;
27138
- var EVENT_ID_PATTERN = /^evt_[0-9a-f]{20}$/u;
27171
+ var EVENT_ID_PATTERN = /^(?:evt_[0-9a-f]{20}|[a-z][a-z0-9]{19}:[1-9][0-9]{0,15}:[1-9][0-9]{0,15})$/u;
27172
+ var DATA_SUBSCRIPTION_PATTERN = /^data\.(?:\*|[a-z][a-z0-9_]{0,47})$/u;
27173
+ var DATA_EVENT_TYPE_PATTERN = /^data\.[a-z][a-z0-9_]{0,47}$/u;
27174
+ var DATABASE_WATCH_ID_PATTERN = /^pgcf_watch_[0-9a-f]{8}$/u;
27175
+ var DATABASE_WATCH_IDENTIFIER_PATTERN = /^[A-Za-z_][A-Za-z0-9_]{0,62}$/u;
27176
+ var DATABASE_WATCH_FORBIDDEN_SCHEMAS = ["information_schema", "pgcf", "ohmyhost"];
27177
+ var DATABASE_WATCH_KEYS = [
27178
+ "id",
27179
+ "environment",
27180
+ "schema",
27181
+ "table",
27182
+ "columns",
27183
+ "operations",
27184
+ "topic",
27185
+ "event_type"
27186
+ ];
27139
27187
  var SCHEDULE_NAME_PATTERN = /^[a-z][a-z0-9_-]{0,62}$/u;
27140
27188
  var CURSOR_PATTERN = /^[A-Za-z0-9._|:=+/-]{1,200}$/u;
27141
27189
  var HEADER_NAME_PATTERN = /^[A-Za-z][A-Za-z0-9-]{0,63}$/u;
27190
+ function isDatabaseWatchIdentifier(value) {
27191
+ return matches(value, DATABASE_WATCH_IDENTIFIER_PATTERN);
27192
+ }
27193
+ function isDatabaseWatchSchema(value) {
27194
+ if (!isDatabaseWatchIdentifier(value))
27195
+ return false;
27196
+ const lower = value.toLowerCase();
27197
+ return !lower.startsWith("pg_") && !DATABASE_WATCH_FORBIDDEN_SCHEMAS.includes(lower);
27198
+ }
27199
+ function dataEventTopic(tableName) {
27200
+ const mapped = Array.from(tableName, (character) => {
27201
+ const lower = Array.from(character.toLowerCase())[0] ?? "_";
27202
+ return /^[a-z0-9_]$/u.test(lower) ? lower : "_";
27203
+ }).join("");
27204
+ const topic = /^[a-z]/u.test(mapped) ? mapped : `t_${mapped}`;
27205
+ return topic.slice(0, 48);
27206
+ }
27142
27207
  function assertDatabaseWebhook(value) {
27143
27208
  if (!record4(value) || !exact4(value, WEBHOOK_KEYS))
27144
27209
  throw new TypeError("Invalid database webhook");
27145
27210
  assertWebhookFields(value);
27146
27211
  }
27212
+ function assertDatabaseWatch(value) {
27213
+ if (!record4(value) || !exact4(value, DATABASE_WATCH_KEYS) || !matches(value["id"], DATABASE_WATCH_ID_PATTERN) || !oneOf(["dev", "prod"], value["environment"]) || !isDatabaseWatchSchema(value["schema"]) || !isDatabaseWatchIdentifier(value["table"]) || !Array.isArray(value["columns"]) || value["columns"].length < 1 || value["columns"].length > DATABASE_WATCH_MAX_COLUMNS || !value["columns"].every(isDatabaseWatchIdentifier) || !isCanonicalOperations(value["operations"]) || value["topic"] !== dataEventTopic(value["table"]) || value["event_type"] !== `data.${value["topic"]}`)
27214
+ throw new TypeError("Invalid database watch");
27215
+ }
27216
+ function assertDatabaseWatchPage(value) {
27217
+ if (!record4(value) || !exact4(value, ["items"]) || !pageItems(value["items"]))
27218
+ throw new TypeError("Invalid database watch page");
27219
+ for (const item of value["items"])
27220
+ assertDatabaseWatch(item);
27221
+ }
27222
+ function assertDatabaseWatchDeleted(value) {
27223
+ if (!record4(value) || !exact4(value, ["id", "deleted"]) || !matches(value["id"], DATABASE_WATCH_ID_PATTERN) || value["deleted"] !== true)
27224
+ throw new TypeError("Invalid database watch deletion");
27225
+ }
27147
27226
  function assertDatabaseWebhookCreated(value) {
27148
27227
  if (!record4(value) || !exact4(value, [...WEBHOOK_KEYS, "secret"]) || typeof value["secret"] !== "string" || value["secret"].length === 0)
27149
27228
  throw new TypeError("Invalid database webhook");
@@ -27172,7 +27251,7 @@ function assertDatabaseWebhookDelivery(value) {
27172
27251
  "delivered_at",
27173
27252
  "last_status",
27174
27253
  "last_error"
27175
- ]) || !matches(value["id"], DELIVERY_ID_PATTERN) || !matches(value["event_id"], EVENT_ID_PATTERN) || !oneOf(DATABASE_WEBHOOK_EVENT_TYPES, value["event_type"]) || !matches(value["webhook_id"], WEBHOOK_ID_PATTERN) || !oneOf(["pending", "delivered", "dead_lettered"], value["state"]) || !nonNegativeInteger(value["attempts"]) || !timestamp3(value["created_at"]) || !nullable(value["next_attempt_at"], timestamp3) || !nullable(value["delivered_at"], timestamp3) || !nullable(value["last_status"], Number.isSafeInteger) || !nullable(value["last_error"], (error) => oneOf(["timeout", "network", "http", "expired", "permanent"], error)))
27254
+ ]) || !matches(value["id"], DELIVERY_ID_PATTERN) || !matches(value["event_id"], EVENT_ID_PATTERN) || !isDeliveredEventType(value["event_type"]) || !matches(value["webhook_id"], WEBHOOK_ID_PATTERN) || !oneOf(["pending", "delivered", "dead_lettered"], value["state"]) || !nonNegativeInteger(value["attempts"]) || !timestamp3(value["created_at"]) || !nullable(value["next_attempt_at"], timestamp3) || !nullable(value["delivered_at"], timestamp3) || !nullable(value["last_status"], Number.isSafeInteger) || !nullable(value["last_error"], (error) => oneOf(["timeout", "network", "http", "expired", "permanent"], error)))
27176
27255
  throw new TypeError("Invalid database webhook delivery");
27177
27256
  }
27178
27257
  function assertDatabaseWebhookDeliveryPage(value) {
@@ -27255,7 +27334,7 @@ function assertUsageRow(value) {
27255
27334
  throw new TypeError("Invalid database execution usage row");
27256
27335
  }
27257
27336
  function assertWebhookFields(value) {
27258
- if (!matches(value["id"], WEBHOOK_ID_PATTERN) || !oneOf(["dev", "prod"], value["environment"]) || !isHttpsUrl2(value["url"]) || !Array.isArray(value["event_types"]) || value["event_types"].length < 1 || value["event_types"].length > DATABASE_WEBHOOK_MAX_EVENT_TYPES || !value["event_types"].every((type) => oneOf(DATABASE_WEBHOOK_EVENT_TYPES, type)) || !Array.isArray(value["header_names"]) || value["header_names"].length > DATABASE_WEBHOOK_MAX_HEADERS || !value["header_names"].every((name) => matches(name, HEADER_NAME_PATTERN)) || !timestamp3(value["created_at"]))
27337
+ if (!matches(value["id"], WEBHOOK_ID_PATTERN) || !oneOf(["dev", "prod"], value["environment"]) || !isHttpsUrl2(value["url"]) || !Array.isArray(value["event_types"]) || value["event_types"].length < 1 || value["event_types"].length > DATABASE_WEBHOOK_MAX_EVENT_TYPES || !value["event_types"].every(isDatabaseWebhookEventPattern) || !Array.isArray(value["header_names"]) || value["header_names"].length > DATABASE_WEBHOOK_MAX_HEADERS || !value["header_names"].every((name) => matches(name, HEADER_NAME_PATTERN)) || !timestamp3(value["created_at"]))
27259
27338
  throw new TypeError("Invalid database webhook");
27260
27339
  }
27261
27340
  function isHttpsUrl2(value) {
@@ -27267,6 +27346,18 @@ function isHttpsUrl2(value) {
27267
27346
  return false;
27268
27347
  }
27269
27348
  }
27349
+ function isDatabaseWebhookEventPattern(value) {
27350
+ return oneOf(DATABASE_WEBHOOK_EVENT_TYPES, value) || matches(value, DATA_SUBSCRIPTION_PATTERN);
27351
+ }
27352
+ function isDeliveredEventType(value) {
27353
+ return oneOf(DATABASE_WEBHOOK_EVENT_TYPES, value) || matches(value, DATA_EVENT_TYPE_PATTERN);
27354
+ }
27355
+ function isCanonicalOperations(value) {
27356
+ if (!Array.isArray(value) || value.length < 1)
27357
+ return false;
27358
+ const canonical = DATABASE_WATCH_OPERATIONS.filter((operation) => value.includes(operation));
27359
+ return canonical.length === value.length && canonical.every((op, index) => op === value[index]);
27360
+ }
27270
27361
  function isCronText(value) {
27271
27362
  return typeof value === "string" && value.length >= DATABASE_SCHEDULE_CRON_MIN_LENGTH && value.length <= DATABASE_SCHEDULE_CRON_MAX_LENGTH;
27272
27363
  }
@@ -27310,7 +27401,7 @@ function exact4(value, keys) {
27310
27401
  // apps/product-cli/package.json
27311
27402
  var package_default2 = {
27312
27403
  name: "@ohmyhost/product-cli",
27313
- version: "0.1.43",
27404
+ version: "0.1.45",
27314
27405
  private: true,
27315
27406
  ohmyhost: {
27316
27407
  deployment: "production",
@@ -28424,20 +28515,23 @@ var HELP_USAGE = Object.freeze({
28424
28515
  "database access create": "ohmyhost database access create --project ULID --environment dev|prod [--mode read|write] [--ttl 5m|1h|24h|SECONDS] [--label TEXT] --yes --json\n The connection URI is shown once. Use it immediately, never store it in files, notes or source, and revoke it when finished.",
28425
28516
  "database access list": "ohmyhost database access list --project ULID [--environment dev|prod] --json",
28426
28517
  "database access revoke": "ohmyhost database access revoke --project ULID --access ULID --yes --json",
28427
- "database webhook create": "ohmyhost database webhook create --project ULID --environment dev|prod --url HTTPS_URL --event-types TYPE[,TYPE] [--headers-file PATH] --idempotency-key KEY --yes --json (the signing secret is shown once in this answer; types: database.created, database.ready, database.woke, database.slept, database.suspended, database.resized, database.deleted; the headers file is a UTF-8 JSON object of at most 8 KiB)",
28518
+ "database watch create": "ohmyhost database watch create --project ULID --environment dev|prod --table NAME [--schema public] --columns COL[,COL] [--operations insert,update,delete] --idempotency-key KEY --yes --json (publishes only the listed columns, at most 32, of each row change as the event type data.<topic> printed in this answer; the table must exist as an ordinary table of the application; a sleeping database wakes; next, subscribe with 'ohmyhost database webhook create ... --event-types data.<topic>')",
28519
+ "database watch list": "ohmyhost database watch list --project ULID [--environment dev|prod] --json (dev when omitted; a sleeping database wakes)",
28520
+ "database watch delete": "ohmyhost database watch delete --project ULID --environment dev|prod --watch ID --idempotency-key KEY --yes --json",
28521
+ "database webhook create": "ohmyhost database webhook create --project ULID --environment dev|prod --url HTTPS_URL --event-types TYPE[,TYPE] [--headers-file PATH] --idempotency-key KEY --yes --json (the signing secret is shown once in this answer; types: database.created, database.ready, database.woke, database.slept, database.suspended, database.resized, database.deleted, data.<topic> for the rows of one watched table printed by database watch create, or data.* for every watched table; a bare * is not accepted and does not include data events; the headers file is a UTF-8 JSON object of at most 8 KiB)",
28428
28522
  "database webhook list": "ohmyhost database webhook list --project ULID [--environment dev|prod] --json (dev when omitted; the secret is never listed)",
28429
28523
  "database webhook get": "ohmyhost database webhook get --project ULID [--environment dev|prod] --webhook ID --json",
28430
28524
  "database webhook delete": "ohmyhost database webhook delete --project ULID --environment dev|prod --webhook ID --yes --json",
28431
28525
  "database webhook test": "ohmyhost database webhook test --project ULID --environment dev|prod --webhook ID --yes --json (sends one signed test request to the receiver; nothing is stored)",
28432
28526
  "database webhook deliveries": "ohmyhost database webhook deliveries --project ULID [--environment dev|prod] [--webhook ID] [--limit 1..100] [--cursor CURSOR] --json",
28433
28527
  "database webhook replay": "ohmyhost database webhook replay --project ULID --environment dev|prod --delivery ID --yes --json",
28434
- "database schedule set": "ohmyhost database schedule set --project ULID --environment dev|prod --name NAME (--cron EXPR | --every-seconds N) --statements-file PATH [--timeout-ms N] [--enabled true|false] --yes --json (runs the statements in the file, a UTF-8 JSON array of 1 to 20 SQL strings, as the runtime role of the application; cron has five fields in UTC; every-seconds is 60 to 2678400; timeout-ms is 1000 to 30000, default 10000)",
28528
+ "database schedule set": "ohmyhost database schedule set --project ULID --environment dev|prod --name NAME (--cron EXPR | --every-seconds N) --statements-file PATH [--timeout-ms N] [--enabled true|false] --yes --json (runs the statements in the file, a UTF-8 JSON array of 1 to 20 SQL strings, as the runtime role of the application; cron has five fields in UTC; every-seconds is 60 to 2678400; timeout-ms is 1000 to 30000, default 10000). A project whose Stop budget is reached has its schedules disabled automatically, within about an hour of the billed usage reaching the budget: the credit service does this after its hourly pgcf pass, and Continue budgets are never touched. While the budget is reached, schedule set with enabled true and schedule run are refused with project_budget_exceeded (HTTP 402). Re-enabling is manual: raise the budget (or wait for the next UTC month) and run database schedule set --enabled true again with the schedule body (read it with schedule get).",
28435
28529
  "database schedule list": "ohmyhost database schedule list --project ULID [--environment dev|prod] --json (dev when omitted)",
28436
28530
  "database schedule get": "ohmyhost database schedule get --project ULID [--environment dev|prod] --name NAME --json",
28437
28531
  "database schedule delete": "ohmyhost database schedule delete --project ULID --environment dev|prod --name NAME --yes --json",
28438
28532
  "database schedule runs": "ohmyhost database schedule runs --project ULID [--environment dev|prod] --name NAME [--limit 1..50] --json",
28439
- "database schedule run": "ohmyhost database schedule run --project ULID --environment dev|prod --name NAME --yes --json (queues one run now, in addition to the timer)",
28440
- "database executions": "ohmyhost database executions --project ULID [--environment dev|prod] --from ISO --to ISO --granularity hour|day [--limit 1..100] [--cursor CURSOR] --json (UTC timestamps like 2026-10-30T00:00:00.000Z aligned to the granularity, at most 31 days; counts schedule runs, webhook attempts and events per hour or day; usage counters only, nothing is charged for them until the operator prices them)",
28533
+ "database schedule run": "ohmyhost database schedule run --project ULID --environment dev|prod --name NAME --yes --json (queues one run now, in addition to the timer; refused with project_budget_exceeded (HTTP 402) while the project's Stop budget is reached)",
28534
+ "database executions": "ohmyhost database executions --project ULID [--environment dev|prod] --from ISO --to ISO --granularity hour|day [--limit 1..100] [--cursor CURSOR] --json (UTC timestamps like 2026-10-30T00:00:00.000Z aligned to the granularity, at most 31 days; counts schedule runs, webhook attempts and events per hour or day; these are counters only, executions have no price of their own and the awake compute time of the database is what is billed)",
28441
28535
  "database psql": "ohmyhost database psql --project ULID --environment dev|prod [--mode read|write] [--ttl 5m|1h|24h|SECONDS] [--json] (starts local psql with a temporary credential and revokes it on exit)",
28442
28536
  link: "ohmyhost link --project ULID --repository-owner OWNER --repository-name REPOSITORY --idempotency-key KEY --json (uses the workspace GitHub connection and waits for the source-link operation)",
28443
28537
  "source auto-deploy set": "ohmyhost source auto-deploy set --project ULID --branch BRANCH --enabled true|false --idempotency-key KEY --json",
@@ -31218,6 +31312,68 @@ var revokeProjectDatabaseAccess = (parameters, options) => {
31218
31312
  ...params
31219
31313
  });
31220
31314
  };
31315
+ var listProjectDatabaseWatches = (parameters, options) => {
31316
+ const params = buildClientParams([parameters], [
31317
+ {
31318
+ args: [
31319
+ { in: "headers", key: "X-Request-Id" },
31320
+ { in: "path", key: "project_id" },
31321
+ { in: "query", key: "environment" }
31322
+ ]
31323
+ }
31324
+ ]);
31325
+ return (options?.client ?? client).get({
31326
+ responseStyle: "data",
31327
+ security: [{ scheme: "bearer", type: "http" }],
31328
+ url: "/v1/projects/{project_id}/database/watches",
31329
+ ...options,
31330
+ ...params
31331
+ });
31332
+ };
31333
+ var createProjectDatabaseWatch = (parameters, options) => {
31334
+ const params = buildClientParams([parameters], [
31335
+ {
31336
+ args: [
31337
+ { in: "headers", key: "X-Request-Id" },
31338
+ { in: "headers", key: "Idempotency-Key" },
31339
+ { in: "path", key: "project_id" },
31340
+ { key: "databaseWatchCreateRequest", map: "body" }
31341
+ ]
31342
+ }
31343
+ ]);
31344
+ return (options?.client ?? client).post({
31345
+ responseStyle: "data",
31346
+ security: [{ scheme: "bearer", type: "http" }],
31347
+ url: "/v1/projects/{project_id}/database/watches",
31348
+ ...options,
31349
+ ...params,
31350
+ headers: {
31351
+ "Content-Type": "application/json",
31352
+ ...options?.headers,
31353
+ ...params.headers
31354
+ }
31355
+ });
31356
+ };
31357
+ var deleteProjectDatabaseWatch = (parameters, options) => {
31358
+ const params = buildClientParams([parameters], [
31359
+ {
31360
+ args: [
31361
+ { in: "headers", key: "X-Request-Id" },
31362
+ { in: "headers", key: "Idempotency-Key" },
31363
+ { in: "path", key: "project_id" },
31364
+ { in: "path", key: "watch_id" },
31365
+ { in: "query", key: "environment" }
31366
+ ]
31367
+ }
31368
+ ]);
31369
+ return (options?.client ?? client).delete({
31370
+ responseStyle: "data",
31371
+ security: [{ scheme: "bearer", type: "http" }],
31372
+ url: "/v1/projects/{project_id}/database/watches/{watch_id}",
31373
+ ...options,
31374
+ ...params
31375
+ });
31376
+ };
31221
31377
  var listProjectDatabaseWebhooks = (parameters, options) => {
31222
31378
  const params = buildClientParams([parameters], [
31223
31379
  {
@@ -32622,6 +32778,7 @@ function checkedResponse(kind, value) {
32622
32778
 
32623
32779
  // apps/product-cli/dist/product-api.js
32624
32780
  var WEBHOOK_SCOPE_MISMATCH = "Webhook response scope mismatch";
32781
+ var WATCH_SCOPE_MISMATCH = "Watch response scope mismatch";
32625
32782
  var SCHEDULE_SCOPE_MISMATCH = "Schedule response scope mismatch";
32626
32783
  var GeneratedSdkProductApi = class {
32627
32784
  #client;
@@ -32819,6 +32976,42 @@ var GeneratedSdkProductApi = class {
32819
32976
  assertProjectDatabaseAccess(value);
32820
32977
  return value;
32821
32978
  }
32979
+ async createProjectDatabaseWatch(input) {
32980
+ const value = await createProjectDatabaseWatch({
32981
+ project_id: input.projectId,
32982
+ "Idempotency-Key": input.idempotencyKey,
32983
+ databaseWatchCreateRequest: {
32984
+ environment: input.environment,
32985
+ schema: input.request.schema,
32986
+ table: input.request.table,
32987
+ columns: [...input.request.columns],
32988
+ operations: [...input.request.operations]
32989
+ }
32990
+ }, { client: this.#client });
32991
+ assertDatabaseWatch(value);
32992
+ if (value.environment !== input.environment || value.schema !== input.request.schema || value.table !== input.request.table)
32993
+ throw new TypeError(WATCH_SCOPE_MISMATCH);
32994
+ return value;
32995
+ }
32996
+ async listProjectDatabaseWatches(input) {
32997
+ const value = await listProjectDatabaseWatches({ project_id: input.projectId, environment: input.environment }, { client: this.#client });
32998
+ assertDatabaseWatchPage(value);
32999
+ if (value.items.some((item) => item.environment !== input.environment))
33000
+ throw new TypeError(WATCH_SCOPE_MISMATCH);
33001
+ return value;
33002
+ }
33003
+ async deleteProjectDatabaseWatch(input) {
33004
+ const value = await deleteProjectDatabaseWatch({
33005
+ project_id: input.projectId,
33006
+ environment: input.environment,
33007
+ "Idempotency-Key": input.idempotencyKey,
33008
+ watch_id: input.watchId
33009
+ }, { client: this.#client });
33010
+ assertDatabaseWatchDeleted(value);
33011
+ if (value.id !== input.watchId)
33012
+ throw new TypeError(WATCH_SCOPE_MISMATCH);
33013
+ return value;
33014
+ }
32822
33015
  /** The signing secret exists only in this answer; the CLI prints it once and stores nothing. */
32823
33016
  async createProjectDatabaseWebhook(input) {
32824
33017
  const value = await createProjectDatabaseWebhook({
@@ -34096,6 +34289,40 @@ var GeneratedSdkLocalMcpClient = class {
34096
34289
  requireScope(value.items.every((webhook) => webhook.environment === environment2));
34097
34290
  return value;
34098
34291
  }
34292
+ async listDatabaseWatches(input) {
34293
+ const environment2 = input.environment ?? "dev";
34294
+ const value = await listProjectDatabaseWatches({ project_id: input.projectId, environment: environment2 }, { client: this.#client });
34295
+ assertDatabaseWatchPage(value);
34296
+ requireScope(value.items.every((watch) => watch.environment === environment2));
34297
+ return value;
34298
+ }
34299
+ async createDatabaseWatch(input) {
34300
+ const value = await createProjectDatabaseWatch({
34301
+ project_id: input.projectId,
34302
+ "Idempotency-Key": input.idempotencyKey,
34303
+ databaseWatchCreateRequest: {
34304
+ environment: input.environment,
34305
+ ...input.schema === void 0 ? {} : { schema: input.schema },
34306
+ table: input.table,
34307
+ columns: [...input.columns],
34308
+ ...input.operations === void 0 ? {} : { operations: [...input.operations] }
34309
+ }
34310
+ }, { client: this.#client });
34311
+ assertDatabaseWatch(value);
34312
+ requireScope(value.environment === input.environment && value.table === input.table && value.schema === (input.schema ?? DATABASE_WATCH_DEFAULT_SCHEMA));
34313
+ return value;
34314
+ }
34315
+ async deleteDatabaseWatch(input) {
34316
+ const value = await deleteProjectDatabaseWatch({
34317
+ project_id: input.projectId,
34318
+ environment: input.environment,
34319
+ "Idempotency-Key": input.idempotencyKey,
34320
+ watch_id: input.watchId
34321
+ }, { client: this.#client });
34322
+ assertDatabaseWatchDeleted(value);
34323
+ requireScope(value.id === input.watchId);
34324
+ return value;
34325
+ }
34099
34326
  /** The signing secret exists only in this response and is never stored by the client. */
34100
34327
  async createDatabaseWebhook(input) {
34101
34328
  const value = await createProjectDatabaseWebhook({
@@ -34523,6 +34750,8 @@ function invalid7() {
34523
34750
  var identifier2 = external_exports.string().min(1).max(256);
34524
34751
  var databaseEnvironment = external_exports.enum(["dev", "prod"]);
34525
34752
  var databaseWebhookId = external_exports.string().regex(/^whk_[a-z0-9]{20}$/u);
34753
+ var databaseWatchId = external_exports.string().regex(/^pgcf_watch_[0-9a-f]{8}$/u);
34754
+ var databaseIdentifier = external_exports.string().regex(/^[A-Za-z_][A-Za-z0-9_]{0,62}$/u);
34526
34755
  var databaseDeliveryId = external_exports.string().regex(/^dlv_[0-9a-f]{20}$/u);
34527
34756
  var databaseScheduleName = external_exports.string().regex(/^[a-z][a-z0-9_-]{0,62}$/u);
34528
34757
  var profileName = external_exports.string().regex(/^[a-z0-9][a-z0-9_-]{0,62}$/u).optional().describe("Saved login to act as (profile_list shows them). Needed only when several logins could run this call; OHMYHOST_PROFILE of this server must match it.");
@@ -34774,15 +35003,52 @@ function createLocalOhmyhostMcpServer(dependencies) {
34774
35003
  ...input.environment === void 0 ? {} : { environment: input.environment }
34775
35004
  }));
34776
35005
  registerProductTool(server, dependencies, "database_access_revoke", "Revoke one issued database credential immediately: open sessions end and its PostgreSQL role is removed. Revocation is idempotent and never touches application roles or data.", external_exports.object({ project_id: identifier2, access_id: identifier2 }), (client2, input) => client2.revokeDatabaseAccess({ projectId: input.project_id, accessId: input.access_id }), destructiveAnnotations());
35006
+ registerProductTool(server, dependencies, "database_watch_list", "List this project's database watches of one Dev or Prod environment (dev when omitted). Each watch shows its table, columns, operations and the event type data.<topic> it publishes. Listing wakes a sleeping database.", external_exports.object({ project_id: identifier2, environment: databaseEnvironment.optional() }), (client2, input) => client2.listDatabaseWatches({
35007
+ projectId: input.project_id,
35008
+ ...input.environment === void 0 ? {} : { environment: input.environment }
35009
+ }));
35010
+ registerProductTool(server, dependencies, "database_watch_create", "Publish row changes of one table of a Dev or Prod database as data events: only the listed columns (at most 32) of each insert, update or delete are published, with the event type data.<topic> returned by this call. Pass that event type to database_webhook_create. The table must exist as an ordinary table of the application, created by a deployed migration; a sleeping database wakes. Rows written before the watch exists are not published, and a published column must not be dropped while the watch exists. Requires confirmed: true and an idempotency key. Refused with project_budget_exceeded while the Stop budget is reached.", external_exports.object({
35011
+ project_id: identifier2,
35012
+ environment: databaseEnvironment,
35013
+ schema: databaseIdentifier.refine(isDatabaseWatchSchema).optional(),
35014
+ table: databaseIdentifier,
35015
+ columns: external_exports.array(databaseIdentifier).min(1).max(32).refine((columns) => new Set(columns).size === columns.length),
35016
+ operations: external_exports.array(external_exports.enum(DATABASE_WATCH_OPERATIONS)).min(1).max(3).refine((operations) => new Set(operations).size === operations.length).optional(),
35017
+ idempotency_key: idempotencyKey,
35018
+ confirmed: external_exports.literal(true)
35019
+ }), (client2, input) => client2.createDatabaseWatch({
35020
+ projectId: input.project_id,
35021
+ environment: input.environment,
35022
+ ...input.schema === void 0 ? {} : { schema: input.schema },
35023
+ table: input.table,
35024
+ columns: input.columns,
35025
+ ...input.operations === void 0 ? {} : { operations: input.operations },
35026
+ idempotencyKey: input.idempotency_key
35027
+ }), mutationAnnotations());
35028
+ registerProductTool(server, dependencies, "database_watch_delete", "Remove the capture trigger of one database watch: its table publishes no more data events. Destructive: requires confirmed: true and an idempotency key. Deleting an already deleted watch answers not found.", external_exports.object({
35029
+ project_id: identifier2,
35030
+ environment: databaseEnvironment,
35031
+ watch_id: databaseWatchId,
35032
+ idempotency_key: idempotencyKey,
35033
+ confirmed: external_exports.literal(true)
35034
+ }), (client2, input) => client2.deleteDatabaseWatch({
35035
+ projectId: input.project_id,
35036
+ environment: input.environment,
35037
+ watchId: input.watch_id,
35038
+ idempotencyKey: input.idempotency_key
35039
+ }), destructiveAnnotations());
34777
35040
  registerProductTool(server, dependencies, "database_webhook_list", "List this project's database webhooks of one Dev or Prod environment (dev when omitted), without signing secrets. Each webhook lists its event types and header names only.", external_exports.object({ project_id: identifier2, environment: databaseEnvironment.optional() }), (client2, input) => client2.listDatabaseWebhooks({
34778
35041
  projectId: input.project_id,
34779
35042
  ...input.environment === void 0 ? {} : { environment: input.environment }
34780
35043
  }));
34781
- registerProductTool(server, dependencies, "database_webhook_create", "Subscribe one HTTPS receiver to database lifecycle events (database.created, ready, woke, slept, suspended, resized, deleted) of one Dev or Prod database. The signing secret is RETURNED EXACTLY ONCE: store it in a secret manager and never write it into files, notes, source, commit messages or later messages. Deliveries are signed, retried and dead-lettered when retries stop. At most five webhooks exist per project environment. Requires confirmed: true and an idempotency key; the same key with the same request returns the stored answer.", external_exports.object({
35044
+ registerProductTool(server, dependencies, "database_webhook_create", "Subscribe one HTTPS receiver to database lifecycle events (database.created, ready, woke, slept, suspended, resized, deleted) of one Dev or Prod database, or to data events: data.<topic> for the rows of one watched table (the event type database_watch_create returns) or data.* for every watched table; a bare * never matches data events. The signing secret is RETURNED EXACTLY ONCE: store it in a secret manager and never write it into files, notes, source, commit messages or later messages. Deliveries are signed, retried and dead-lettered when retries stop. At most five webhooks exist per project environment. Requires confirmed: true and an idempotency key; the same key with the same request returns the stored answer.", external_exports.object({
34782
35045
  project_id: identifier2,
34783
35046
  environment: databaseEnvironment,
34784
35047
  url: external_exports.string().max(2048).regex(/^https:\/\//u),
34785
- event_types: external_exports.array(external_exports.enum(DATABASE_WEBHOOK_EVENT_TYPES)).min(1).max(16),
35048
+ event_types: external_exports.array(external_exports.union([
35049
+ external_exports.enum(DATABASE_WEBHOOK_EVENT_TYPES),
35050
+ external_exports.string().regex(/^data\.(?:\*|[a-z][a-z0-9_]{0,47})$/u).refine(isDatabaseWebhookEventPattern)
35051
+ ])).min(1).max(16),
34786
35052
  headers: external_exports.record(external_exports.string(), external_exports.string()).optional(),
34787
35053
  idempotency_key: idempotencyKey,
34788
35054
  confirmed: external_exports.literal(true)
@@ -34839,7 +35105,7 @@ function createLocalOhmyhostMcpServer(dependencies) {
34839
35105
  projectId: input.project_id,
34840
35106
  ...input.environment === void 0 ? {} : { environment: input.environment }
34841
35107
  }));
34842
- registerProductTool(server, dependencies, "database_schedule_set", "Create or replace a named database schedule that runs SQL statements repeatedly on a timer, as the application's own runtime role, so row-level security applies. Send exactly one of cron (five-field UTC expression) or every_seconds (60 to 2678400). Each run is cancelled after timeout_ms (1000 to 30000, default 10000). Statements run in order and must be safe to repeat: write idempotent statements. Destructive: requires confirmed: true.", external_exports.object({
35108
+ registerProductTool(server, dependencies, "database_schedule_set", "Create or replace a named database schedule that runs SQL statements repeatedly on a timer, as the application's own runtime role, so row-level security applies. Send exactly one of cron (five-field UTC expression) or every_seconds (60 to 2678400). Each run is cancelled after timeout_ms (1000 to 30000, default 10000). Statements run in order and must be safe to repeat: write idempotent statements. Destructive: requires confirmed: true. A project whose Stop budget is reached has its schedules disabled automatically, within about an hour of the billed usage reaching the budget: the credit service does this after its hourly pgcf pass, and Continue budgets are never touched. While the budget is reached, schedule set with enabled true and schedule run are refused with project_budget_exceeded (HTTP 402). Re-enabling is manual: raise the budget (or wait for the next UTC month) and run database schedule set --enabled true again with the schedule body (read it with schedule get).", external_exports.object({
34843
35109
  project_id: identifier2,
34844
35110
  environment: databaseEnvironment,
34845
35111
  name: databaseScheduleName,
@@ -34883,7 +35149,7 @@ function createLocalOhmyhostMcpServer(dependencies) {
34883
35149
  name: input.name,
34884
35150
  ...input.limit === void 0 ? {} : { limit: input.limit }
34885
35151
  }));
34886
- registerProductTool(server, dependencies, "database_schedule_run_now", "Queue one immediate run of a database schedule. Refused while the schedule is disabled or a run is already queued. Destructive: the statements execute against the database; requires confirmed: true. If the answer is lost, read database_schedule_run_list before calling again; a second call queues another run.", external_exports.object({
35152
+ registerProductTool(server, dependencies, "database_schedule_run_now", "Queue one immediate run of a database schedule. Refused while the schedule is disabled or a run is already queued. Destructive: the statements execute against the database; requires confirmed: true. If the answer is lost, read database_schedule_run_list before calling again; a second call queues another run. Refused with project_budget_exceeded while the project's Stop budget is reached.", external_exports.object({
34887
35153
  project_id: identifier2,
34888
35154
  environment: databaseEnvironment,
34889
35155
  name: databaseScheduleName,
@@ -34893,7 +35159,7 @@ function createLocalOhmyhostMcpServer(dependencies) {
34893
35159
  environment: input.environment,
34894
35160
  name: input.name
34895
35161
  }), { ...destructiveAnnotations(), idempotentHint: false });
34896
- registerProductTool(server, dependencies, "database_execution_usage_get", "Read metered database execution usage: schedule runs and webhook attempts per UTC hour or day window. from and to are UTC timestamps with milliseconds (Z), aligned to the granularity, at most 31 days apart. Only completed windows are final. Rejected, skipped, event and delivery counts are reported but not billed.", external_exports.object({
35162
+ registerProductTool(server, dependencies, "database_execution_usage_get", "Read metered database execution usage: schedule runs and webhook attempts per UTC hour or day window. from and to are UTC timestamps with milliseconds (Z), aligned to the granularity, at most 31 days apart. Only completed windows are final. The counts are counters only: executions have no price of their own, and the awake compute time of the database is what is billed.", external_exports.object({
34897
35163
  project_id: identifier2,
34898
35164
  environment: databaseEnvironment.optional(),
34899
35165
  from: external_exports.string().regex(/^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3}Z$/u),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amerged/ohmyhost-mcp",
3
- "version": "0.1.43",
3
+ "version": "0.1.45",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "node": ">=22"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ohmyhost/mcp",
3
- "version": "0.1.43",
3
+ "version": "0.1.45",
4
4
  "private": true,
5
5
  "ohmyhost": {
6
6
  "deployment": "production",
@@ -20,6 +20,10 @@ import {
20
20
  assertDatabaseScheduleRunAccepted,
21
21
  assertDatabaseScheduleRunPage,
22
22
  assertDatabaseWebhookCreated,
23
+ assertDatabaseWatch,
24
+ assertDatabaseWatchDeleted,
25
+ assertDatabaseWatchPage,
26
+ DATABASE_WATCH_DEFAULT_SCHEMA,
23
27
  assertDatabaseWebhookDeleted,
24
28
  assertDatabaseWebhookDeliveryPage,
25
29
  assertDatabaseWebhookPage,
@@ -115,6 +119,9 @@ import {
115
119
  createProjectDatabaseWebhook,
116
120
  listProjectDatabaseWebhooks,
117
121
  deleteProjectDatabaseWebhook,
122
+ createProjectDatabaseWatch,
123
+ listProjectDatabaseWatches,
124
+ deleteProjectDatabaseWatch,
118
125
  testProjectDatabaseWebhook,
119
126
  listProjectDatabaseWebhookDeliveries,
120
127
  replayProjectDatabaseWebhookDelivery,
@@ -849,6 +856,59 @@ export class GeneratedSdkLocalMcpClient implements LocalMcpProductClient {
849
856
  requireScope(value.items.every((webhook) => webhook.environment === environment));
850
857
  return value;
851
858
  }
859
+ public async listDatabaseWatches(
860
+ input: Parameters<LocalMcpProductClient["listDatabaseWatches"]>[0],
861
+ ) {
862
+ const environment = input.environment ?? "dev";
863
+ const value = await listProjectDatabaseWatches(
864
+ { project_id: input.projectId, environment },
865
+ { client: this.#client },
866
+ );
867
+ assertDatabaseWatchPage(value);
868
+ requireScope(value.items.every((watch) => watch.environment === environment));
869
+ return value;
870
+ }
871
+ public async createDatabaseWatch(
872
+ input: Parameters<LocalMcpProductClient["createDatabaseWatch"]>[0],
873
+ ) {
874
+ const value = await createProjectDatabaseWatch(
875
+ {
876
+ project_id: input.projectId,
877
+ "Idempotency-Key": input.idempotencyKey,
878
+ databaseWatchCreateRequest: {
879
+ environment: input.environment,
880
+ ...(input.schema === undefined ? {} : { schema: input.schema }),
881
+ table: input.table,
882
+ columns: [...input.columns],
883
+ ...(input.operations === undefined ? {} : { operations: [...input.operations] }),
884
+ },
885
+ },
886
+ { client: this.#client },
887
+ );
888
+ assertDatabaseWatch(value);
889
+ requireScope(
890
+ value.environment === input.environment &&
891
+ value.table === input.table &&
892
+ value.schema === (input.schema ?? DATABASE_WATCH_DEFAULT_SCHEMA),
893
+ );
894
+ return value;
895
+ }
896
+ public async deleteDatabaseWatch(
897
+ input: Parameters<LocalMcpProductClient["deleteDatabaseWatch"]>[0],
898
+ ) {
899
+ const value = await deleteProjectDatabaseWatch(
900
+ {
901
+ project_id: input.projectId,
902
+ environment: input.environment,
903
+ "Idempotency-Key": input.idempotencyKey,
904
+ watch_id: input.watchId,
905
+ },
906
+ { client: this.#client },
907
+ );
908
+ assertDatabaseWatchDeleted(value);
909
+ requireScope(value.id === input.watchId);
910
+ return value;
911
+ }
852
912
  /** The signing secret exists only in this response and is never stored by the client. */
853
913
  public async createDatabaseWebhook(
854
914
  input: Parameters<LocalMcpProductClient["createDatabaseWebhook"]>[0],