@amerged/ohmyhost-mcp 0.1.45 → 0.1.46

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.
@@ -30,7 +30,7 @@ import {
30
30
  serverIdentityOf,
31
31
  setNegotiatedProtocolVersion,
32
32
  validateEnvelopeMeta
33
- } from "./chunk-AJV4T4LM.js";
33
+ } from "./chunk-2GRH5YCK.js";
34
34
 
35
35
  // node_modules/.pnpm/ignore@7.0.12/node_modules/ignore/index.js
36
36
  var require_ignore = __commonJS({
@@ -24293,14 +24293,14 @@ function isFrameworkConversionDiagnosticCode(value) {
24293
24293
  function parseFrameworkConversionDiagnostic(value) {
24294
24294
  if (typeof value !== "object" || value === null || Array.isArray(value))
24295
24295
  return null;
24296
- const record8 = value;
24297
- const code = Object.hasOwn(record8, "code") ? record8["code"] : void 0;
24298
- const path2 = Object.hasOwn(record8, "path") ? record8["path"] : void 0;
24296
+ const record9 = value;
24297
+ const code = Object.hasOwn(record9, "code") ? record9["code"] : void 0;
24298
+ const path2 = Object.hasOwn(record9, "path") ? record9["path"] : void 0;
24299
24299
  if (!isFrameworkConversionDiagnosticCode(code) || typeof path2 !== "string")
24300
24300
  return null;
24301
24301
  const definition = DETAIL_DEFINITIONS[code];
24302
- const fields = Object.entries(definition.fields ?? {}).map(([name, grammar]) => [name, grammar, Object.hasOwn(record8, name) ? record8[name] : void 0]);
24303
- if (Object.keys(record8).length !== fields.length + 2 || pathClassOf(definition, path2) === null || fields.some(([, grammar, field]) => !fieldMatches(grammar, field)))
24302
+ const fields = Object.entries(definition.fields ?? {}).map(([name, grammar]) => [name, grammar, Object.hasOwn(record9, name) ? record9[name] : void 0]);
24303
+ if (Object.keys(record9).length !== fields.length + 2 || pathClassOf(definition, path2) === null || fields.some(([, grammar, field]) => !fieldMatches(grammar, field)))
24304
24304
  return null;
24305
24305
  return Object.freeze(Object.fromEntries([
24306
24306
  ["code", code],
@@ -25602,6 +25602,18 @@ var PROBLEM_POLICY = {
25602
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
25603
  title: "Table already has a capture trigger"
25604
25604
  },
25605
+ database_table_conflict: {
25606
+ status: 409,
25607
+ retryable: false,
25608
+ action: "A table with this name already exists with another definition. Choose another table name, or create the table again with the same columns, types, nullability, identity, defaults and primary key it has.",
25609
+ title: "Table already exists with another definition"
25610
+ },
25611
+ database_table_limit: {
25612
+ status: 409,
25613
+ retryable: false,
25614
+ action: "This database already holds the maximum number of tables in its public schema, so no table can be added. Use an existing table, or remove a table you no longer need through a deployed migration, then create the table again.",
25615
+ title: "Database table limit reached"
25616
+ },
25605
25617
  project_notes_conflict: {
25606
25618
  status: 409,
25607
25619
  retryable: false,
@@ -26285,8 +26297,8 @@ var isPaidValidationRecord = (value) => {
26285
26297
  if (typeof value !== "object" || value === null || Array.isArray(value) || Object.keys(value).sort().join(",") !== "content,name,purpose,type") {
26286
26298
  return false;
26287
26299
  }
26288
- const record8 = value;
26289
- return (record8["type"] === "CNAME" || record8["type"] === "TXT") && boundedString(record8["name"], 1, 253) && boundedString(record8["content"], 1, 2048) && ["traffic", "ownership", "ssl", "dcv_delegation"].includes(String(record8["purpose"]));
26300
+ const record9 = value;
26301
+ return (record9["type"] === "CNAME" || record9["type"] === "TXT") && boundedString(record9["name"], 1, 253) && boundedString(record9["content"], 1, 2048) && ["traffic", "ownership", "ssl", "dcv_delegation"].includes(String(record9["purpose"]));
26290
26302
  };
26291
26303
 
26292
26304
  // apps/product-cli/dist/source-publisher.js
@@ -26556,7 +26568,7 @@ async function prepareLocalSourcePublication(input) {
26556
26568
  request = { ...request, expected_commit_sha: link2.source.last_confirmed_commit_sha };
26557
26569
  }
26558
26570
  parseManagedSourceRequest("upload", request);
26559
- const record8 = {
26571
+ const record9 = {
26560
26572
  version: 1,
26561
26573
  ...scope,
26562
26574
  operation_id: null,
@@ -26565,16 +26577,16 @@ async function prepareLocalSourcePublication(input) {
26565
26577
  request
26566
26578
  };
26567
26579
  if (original !== null) {
26568
- assertSameAttempt(original, record8);
26580
+ assertSameAttempt(original, record9);
26569
26581
  } else
26570
- await writePending(record8, true);
26582
+ await writePending(record9, true);
26571
26583
  return {
26572
26584
  request,
26573
26585
  accepted: async (operation) => {
26574
26586
  const current = await readPending(scope, replay);
26575
26587
  if (current === null)
26576
26588
  throw new LocalSourcePublicationError("source_publication_missing");
26577
- assertSameAttempt(current, record8);
26589
+ assertSameAttempt(current, record9);
26578
26590
  if (current.operation_id !== null && current.operation_id !== operation.id)
26579
26591
  throw new ResponseContractError();
26580
26592
  if (!replay)
@@ -26593,7 +26605,7 @@ async function prepareLocalSourcePublication(input) {
26593
26605
  const current = await readPending(scope);
26594
26606
  if (current === null || current.operation_id !== null)
26595
26607
  return;
26596
- assertSameAttempt(current, record8);
26608
+ assertSameAttempt(current, record9);
26597
26609
  } catch (error2) {
26598
26610
  if (error2 instanceof LocalSourcePublicationError && (error2.code === "source_publication_scope_mismatch" || error2.code === "source_publication_pending"))
26599
26611
  return;
@@ -26796,12 +26808,12 @@ async function readRecord(path2) {
26796
26808
  await handle?.close();
26797
26809
  }
26798
26810
  }
26799
- async function writePending(record8, create, completed = false) {
26800
- const parent = join3(record8.directory, ".ohmyhost");
26811
+ async function writePending(record9, create, completed = false) {
26812
+ const parent = join3(record9.directory, ".ohmyhost");
26801
26813
  for (const directory of [
26802
26814
  parent,
26803
- record8.metadata_directory,
26804
- join3(record8.metadata_directory, "publications")
26815
+ record9.metadata_directory,
26816
+ join3(record9.metadata_directory, "publications")
26805
26817
  ]) {
26806
26818
  try {
26807
26819
  await fs2.mkdir(directory, { mode: 448 });
@@ -26812,10 +26824,10 @@ async function writePending(record8, create, completed = false) {
26812
26824
  await checkedDirectory(directory);
26813
26825
  await fs2.chmod(directory, 448);
26814
26826
  }
26815
- const target = completed ? completedPath(record8) : pendingPath(record8);
26827
+ const target = completed ? completedPath(record9) : pendingPath(record9);
26816
26828
  const temporary = create ? target : `${target}.${randomUUID()}.tmp`;
26817
26829
  try {
26818
- await fs2.writeFile(temporary, `${JSON.stringify(record8)}
26830
+ await fs2.writeFile(temporary, `${JSON.stringify(record9)}
26819
26831
  `, { mode: 384, flag: "wx" });
26820
26832
  if (!create)
26821
26833
  await fs2.rename(temporary, target);
@@ -26945,7 +26957,7 @@ function exact2(value, keys) {
26945
26957
 
26946
26958
  // packages/contracts/dist/credit-pricing.js
26947
26959
  function assertOrganizationCreditUsage(value) {
26948
- const record8 = (input, keys) => {
26960
+ const record9 = (input, keys) => {
26949
26961
  if (!input || typeof input !== "object" || Array.isArray(input) || Object.keys(input).sort().join() !== keys.split(",").sort().join())
26950
26962
  throw new TypeError("Invalid usage fields");
26951
26963
  return input;
@@ -26953,30 +26965,30 @@ function assertOrganizationCreditUsage(value) {
26953
26965
  const identifier3 = (input) => typeof input === "string" && /^[0-7][0-9A-HJKMNP-TV-Z]{25}$/u.test(input);
26954
26966
  const amount = (input) => typeof input === "string" && /^(0|[1-9][0-9]{0,37})$/u.test(input);
26955
26967
  const instant2 = (input) => typeof input === "string" && Number.isFinite(Date.parse(input)) && new Date(input).toISOString() === input;
26956
- const report = record8(value, "organization_id,as_of,unit,period,from,projects,days");
26968
+ const report = record9(value, "organization_id,as_of,unit,period,from,projects,days");
26957
26969
  if (!identifier3(report["organization_id"]) || !instant2(report["as_of"]) || !instant2(report["from"]) || report["unit"] !== "microcredits" || typeof report["period"] !== "string" || !/^20[0-9]{2}-(0[1-9]|1[0-2])$/u.test(report["period"]) || !Array.isArray(report["projects"]) || report["projects"].length > 1e3 || !Array.isArray(report["days"]) || report["days"].length > 15)
26958
26970
  throw new TypeError("Invalid usage report");
26959
26971
  let previous = "";
26960
26972
  for (const input of report["projects"]) {
26961
- const project = record8(input, "project_id,used_micros");
26973
+ const project = record9(input, "project_id,used_micros");
26962
26974
  if (!identifier3(project["project_id"]) || String(project["project_id"]) <= previous || !amount(project["used_micros"]))
26963
26975
  throw new TypeError("Invalid usage project");
26964
26976
  previous = String(project["project_id"]);
26965
26977
  }
26966
26978
  let previousDay = "";
26967
26979
  for (const input of report["days"]) {
26968
- const day = record8(input, "day,projects");
26980
+ const day = record9(input, "day,projects");
26969
26981
  if (typeof day["day"] !== "string" || !/^20[0-9]{2}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$/u.test(day["day"]) || day["day"] <= previousDay || !Array.isArray(day["projects"]) || day["projects"].length > 1e3)
26970
26982
  throw new TypeError("Invalid usage day");
26971
26983
  previousDay = day["day"];
26972
26984
  previous = "";
26973
26985
  for (const projectInput of day["projects"]) {
26974
- const project = record8(projectInput, "project_id,meters");
26986
+ const project = record9(projectInput, "project_id,meters");
26975
26987
  if (!identifier3(project["project_id"]) || String(project["project_id"]) <= previous || !Array.isArray(project["meters"]) || project["meters"].length > 1e3)
26976
26988
  throw new TypeError("Invalid usage project");
26977
26989
  previous = String(project["project_id"]);
26978
26990
  for (const meterInput of project["meters"]) {
26979
- const meter = record8(meterInput, "environment_id,meter,unit,rate_card_id,quantity,charged_micros");
26991
+ const meter = record9(meterInput, "environment_id,meter,unit,rate_card_id,quantity,charged_micros");
26980
26992
  if (meter["environment_id"] !== null && !identifier3(meter["environment_id"]))
26981
26993
  throw new TypeError("Invalid usage environment");
26982
26994
  for (const key of ["meter", "unit", "rate_card_id"])
@@ -27401,7 +27413,7 @@ function exact4(value, keys) {
27401
27413
  // apps/product-cli/package.json
27402
27414
  var package_default2 = {
27403
27415
  name: "@ohmyhost/product-cli",
27404
- version: "0.1.45",
27416
+ version: "0.1.46",
27405
27417
  private: true,
27406
27418
  ohmyhost: {
27407
27419
  deployment: "production",
@@ -27850,6 +27862,170 @@ function expectPositiveInteger(value, field, options = {}) {
27850
27862
  return value;
27851
27863
  }
27852
27864
 
27865
+ // packages/contracts/dist/database-tables.js
27866
+ var DATABASE_TABLE_COLUMN_TYPES = [
27867
+ "text",
27868
+ "integer",
27869
+ "bigint",
27870
+ "boolean",
27871
+ "numeric",
27872
+ "double precision",
27873
+ "timestamptz",
27874
+ "date",
27875
+ "uuid",
27876
+ "jsonb"
27877
+ ];
27878
+ var DATABASE_TABLE_DEFAULTS = ["now", "uuid"];
27879
+ var DATABASE_TABLE_MAX_COLUMNS = 64;
27880
+ var DATABASE_TABLE_MAX_PRIMARY_KEY_COLUMNS = 8;
27881
+ var DATABASE_TABLE_IDENTIFIER_PATTERN = /^[a-z_][a-z0-9_]{0,62}$/u;
27882
+ var RESERVED_WORDS = /* @__PURE__ */ new Set([
27883
+ "all",
27884
+ "analyse",
27885
+ "analyze",
27886
+ "and",
27887
+ "any",
27888
+ "array",
27889
+ "as",
27890
+ "asc",
27891
+ "asymmetric",
27892
+ "both",
27893
+ "case",
27894
+ "cast",
27895
+ "check",
27896
+ "collate",
27897
+ "column",
27898
+ "constraint",
27899
+ "create",
27900
+ "current_catalog",
27901
+ "current_date",
27902
+ "current_role",
27903
+ "current_time",
27904
+ "current_timestamp",
27905
+ "current_user",
27906
+ "default",
27907
+ "deferrable",
27908
+ "desc",
27909
+ "distinct",
27910
+ "do",
27911
+ "else",
27912
+ "end",
27913
+ "except",
27914
+ "false",
27915
+ "fetch",
27916
+ "for",
27917
+ "foreign",
27918
+ "from",
27919
+ "grant",
27920
+ "group",
27921
+ "having",
27922
+ "in",
27923
+ "initially",
27924
+ "intersect",
27925
+ "into",
27926
+ "lateral",
27927
+ "leading",
27928
+ "limit",
27929
+ "localtime",
27930
+ "localtimestamp",
27931
+ "not",
27932
+ "null",
27933
+ "offset",
27934
+ "on",
27935
+ "only",
27936
+ "or",
27937
+ "order",
27938
+ "placing",
27939
+ "primary",
27940
+ "references",
27941
+ "returning",
27942
+ "select",
27943
+ "session_user",
27944
+ "some",
27945
+ "symmetric",
27946
+ "table",
27947
+ "then",
27948
+ "to",
27949
+ "trailing",
27950
+ "true",
27951
+ "union",
27952
+ "unique",
27953
+ "user",
27954
+ "using",
27955
+ "variadic",
27956
+ "when",
27957
+ "where",
27958
+ "window",
27959
+ "with"
27960
+ ]);
27961
+ function isDatabaseTableIdentifier(value) {
27962
+ return typeof value === "string" && DATABASE_TABLE_IDENTIFIER_PATTERN.test(value) && !value.startsWith("pg_") && !RESERVED_WORDS.has(value);
27963
+ }
27964
+ function parseDatabaseTableCreateRequest(value) {
27965
+ if (!record7(value) || !onlyKeys(value, ["table", "columns", "primaryKey"]) || !isDatabaseTableIdentifier(value["table"]) || !Array.isArray(value["columns"]) || value["columns"].length < 1 || value["columns"].length > DATABASE_TABLE_MAX_COLUMNS)
27966
+ throw new TypeError("Invalid database table request");
27967
+ const inputs = value["columns"].map(parseColumn);
27968
+ const names = inputs.map((column) => column.name);
27969
+ if (new Set(names).size !== names.length || inputs.filter((column) => column.identity).length > 1)
27970
+ throw new TypeError("Invalid database table request");
27971
+ const primaryKey = parsePrimaryKey(value["primaryKey"], names);
27972
+ return {
27973
+ table: value["table"],
27974
+ columns: inputs.map((column) => completeColumn(column, primaryKey.includes(column.name))),
27975
+ primaryKey
27976
+ };
27977
+ }
27978
+ function parseColumn(value) {
27979
+ if (!record7(value) || !onlyKeys(value, ["name", "type", "nullable", "identity", "default"]) || !isDatabaseTableIdentifier(value["name"]) || !oneOf2(DATABASE_TABLE_COLUMN_TYPES, value["type"]) || value["nullable"] !== void 0 && typeof value["nullable"] !== "boolean" || value["identity"] !== void 0 && typeof value["identity"] !== "boolean" || value["default"] !== void 0 && !oneOf2(DATABASE_TABLE_DEFAULTS, value["default"]))
27980
+ throw new TypeError("Invalid database table request");
27981
+ const type = value["type"];
27982
+ const identity = value["identity"] === true;
27983
+ const defaultValue = value["default"] ?? null;
27984
+ if (identity && type !== "integer" && type !== "bigint")
27985
+ throw new TypeError("Invalid database table request");
27986
+ if (identity && defaultValue !== null)
27987
+ throw new TypeError("Invalid database table request");
27988
+ if (defaultValue === "now" && type !== "timestamptz")
27989
+ throw new TypeError("Invalid database table request");
27990
+ if (defaultValue === "uuid" && type !== "uuid")
27991
+ throw new TypeError("Invalid database table request");
27992
+ return {
27993
+ name: value["name"],
27994
+ type,
27995
+ nullable: value["nullable"],
27996
+ identity,
27997
+ default: defaultValue
27998
+ };
27999
+ }
28000
+ function parsePrimaryKey(value, names) {
28001
+ if (value === void 0)
28002
+ return [];
28003
+ if (!Array.isArray(value) || value.length < 1 || value.length > DATABASE_TABLE_MAX_PRIMARY_KEY_COLUMNS || new Set(value).size !== value.length || !value.every((name) => typeof name === "string" && names.includes(name)))
28004
+ throw new TypeError("Invalid database table request");
28005
+ return value;
28006
+ }
28007
+ function completeColumn(column, keyed) {
28008
+ const required = column.identity || keyed;
28009
+ if (required && column.nullable === true)
28010
+ throw new TypeError("Invalid database table request");
28011
+ return {
28012
+ name: column.name,
28013
+ type: column.type,
28014
+ nullable: required ? false : column.nullable ?? true,
28015
+ identity: column.identity,
28016
+ default: column.default
28017
+ };
28018
+ }
28019
+ function oneOf2(allowed, value) {
28020
+ return typeof value === "string" && allowed.includes(value);
28021
+ }
28022
+ function record7(value) {
28023
+ return typeof value === "object" && value !== null && !Array.isArray(value);
28024
+ }
28025
+ function onlyKeys(value, allowed) {
28026
+ return Object.keys(value).every((key) => allowed.includes(key));
28027
+ }
28028
+
27853
28029
  // packages/workos-auth-contracts/dist/user-api-keys.js
27854
28030
  var WORKOS_USER_API_KEY_PATTERN = /^sk_[A-Za-z0-9_-]{20,128}$/u;
27855
28031
 
@@ -28518,6 +28694,7 @@ var HELP_USAGE = Object.freeze({
28518
28694
  "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
28695
  "database watch list": "ohmyhost database watch list --project ULID [--environment dev|prod] --json (dev when omitted; a sleeping database wakes)",
28520
28696
  "database watch delete": "ohmyhost database watch delete --project ULID --environment dev|prod --watch ID --idempotency-key KEY --yes --json",
28697
+ "database table create": "ohmyhost database table create --project ULID --environment dev|prod --table NAME --columns-file PATH [--primary-key COL[,COL]] --idempotency-key KEY --yes --json (the table is created in the public schema as the migration role, with the same access as migration tables, so a database watch can attach later; the columns file is a UTF-8 JSON array of 1 to 64 column objects, at most 64 KiB, each with name, type and optional nullable, identity and default; types: text, integer, bigint, boolean, numeric, double precision, timestamptz, date, uuid, jsonb; identity needs integer or bigint; default now needs timestamptz and default uuid needs uuid; identical repeats answer created false; at most 100 tables, 64 columns per table)",
28521
28698
  "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)",
28522
28699
  "database webhook list": "ohmyhost database webhook list --project ULID [--environment dev|prod] --json (dev when omitted; the secret is never listed)",
28523
28700
  "database webhook get": "ohmyhost database webhook get --project ULID [--environment dev|prod] --webhook ID --json",
@@ -28660,12 +28837,12 @@ function buildClientParams(args, fields) {
28660
28837
  };
28661
28838
  const map = buildKeyMap(fields);
28662
28839
  function writeSlot(slot, key, value) {
28663
- let record8 = params[slot];
28664
- if (record8 === void 0) {
28665
- record8 = /* @__PURE__ */ Object.create(null);
28666
- params[slot] = record8;
28840
+ let record9 = params[slot];
28841
+ if (record9 === void 0) {
28842
+ record9 = /* @__PURE__ */ Object.create(null);
28843
+ params[slot] = record9;
28667
28844
  }
28668
- record8[key] = value;
28845
+ record9[key] = value;
28669
28846
  }
28670
28847
  let config;
28671
28848
  for (const [index, arg] of args.entries()) {
@@ -31312,6 +31489,30 @@ var revokeProjectDatabaseAccess = (parameters, options) => {
31312
31489
  ...params
31313
31490
  });
31314
31491
  };
31492
+ var createProjectDatabaseTable = (parameters, options) => {
31493
+ const params = buildClientParams([parameters], [
31494
+ {
31495
+ args: [
31496
+ { in: "headers", key: "X-Request-Id" },
31497
+ { in: "headers", key: "Idempotency-Key" },
31498
+ { in: "path", key: "project_id" },
31499
+ { key: "databaseTableCreateRequest", map: "body" }
31500
+ ]
31501
+ }
31502
+ ]);
31503
+ return (options?.client ?? client).post({
31504
+ responseStyle: "data",
31505
+ security: [{ scheme: "bearer", type: "http" }],
31506
+ url: "/v1/projects/{project_id}/database/tables",
31507
+ ...options,
31508
+ ...params,
31509
+ headers: {
31510
+ "Content-Type": "application/json",
31511
+ ...options?.headers,
31512
+ ...params.headers
31513
+ }
31514
+ });
31515
+ };
31315
31516
  var listProjectDatabaseWatches = (parameters, options) => {
31316
31517
  const params = buildClientParams([parameters], [
31317
31518
  {
@@ -32779,7 +32980,24 @@ function checkedResponse(kind, value) {
32779
32980
  // apps/product-cli/dist/product-api.js
32780
32981
  var WEBHOOK_SCOPE_MISMATCH = "Webhook response scope mismatch";
32781
32982
  var WATCH_SCOPE_MISMATCH = "Watch response scope mismatch";
32983
+ var TABLE_SCOPE_MISMATCH = "Table response scope mismatch";
32782
32984
  var SCHEDULE_SCOPE_MISMATCH = "Schedule response scope mismatch";
32985
+ function assertDatabaseTableAnswer(value) {
32986
+ const table = isRecordValue(value) ? value["table"] : void 0;
32987
+ const columns = isRecordValue(table) ? table["columns"] : void 0;
32988
+ const primaryKey = isRecordValue(table) ? table["primary_key"] : void 0;
32989
+ if (!isRecordValue(table) || !hasExactKeys(table, ["environment", "schema", "table", "columns", "primary_key", "created"]) || table["environment"] !== "dev" && table["environment"] !== "prod" || table["schema"] !== "public" || !isDatabaseTableIdentifier(table["table"]) || typeof table["created"] !== "boolean" || !Array.isArray(columns) || columns.length < 1 || !columns.every(isDatabaseTableAnswerColumn) || !Array.isArray(primaryKey) || !primaryKey.every((name) => columns.some((column) => column["name"] === name)))
32990
+ throw new TypeError("Invalid database table answer");
32991
+ }
32992
+ function isDatabaseTableAnswerColumn(value) {
32993
+ return isRecordValue(value) && hasExactKeys(value, ["name", "type", "nullable", "identity", "default"]) && isDatabaseTableIdentifier(value["name"]) && DATABASE_TABLE_COLUMN_TYPES.includes(value["type"]) && typeof value["nullable"] === "boolean" && typeof value["identity"] === "boolean" && (value["default"] === null || DATABASE_TABLE_DEFAULTS.includes(value["default"]));
32994
+ }
32995
+ function isRecordValue(value) {
32996
+ return typeof value === "object" && value !== null && !Array.isArray(value);
32997
+ }
32998
+ function hasExactKeys(value, keys) {
32999
+ return Object.keys(value).sort().join(",") === [...keys].sort().join(",");
33000
+ }
32783
33001
  var GeneratedSdkProductApi = class {
32784
33002
  #client;
32785
33003
  managedSource;
@@ -33012,6 +33230,22 @@ var GeneratedSdkProductApi = class {
33012
33230
  throw new TypeError(WATCH_SCOPE_MISMATCH);
33013
33231
  return value;
33014
33232
  }
33233
+ async createProjectDatabaseTable(input) {
33234
+ const value = await createProjectDatabaseTable({
33235
+ project_id: input.projectId,
33236
+ "Idempotency-Key": input.idempotencyKey,
33237
+ databaseTableCreateRequest: {
33238
+ environment: input.environment,
33239
+ table: input.request.table,
33240
+ columns: [...input.request.columns],
33241
+ ...input.request.primaryKey.length === 0 ? {} : { primary_key: [...input.request.primaryKey] }
33242
+ }
33243
+ }, { client: this.#client });
33244
+ assertDatabaseTableAnswer(value);
33245
+ if (value.table.environment !== input.environment || value.table.table !== input.request.table)
33246
+ throw new TypeError(TABLE_SCOPE_MISMATCH);
33247
+ return value;
33248
+ }
33015
33249
  /** The signing secret exists only in this answer; the CLI prints it once and stores nothing. */
33016
33250
  async createProjectDatabaseWebhook(input) {
33017
33251
  const value = await createProjectDatabaseWebhook({
@@ -34312,6 +34546,21 @@ var GeneratedSdkLocalMcpClient = class {
34312
34546
  requireScope(value.environment === input.environment && value.table === input.table && value.schema === (input.schema ?? DATABASE_WATCH_DEFAULT_SCHEMA));
34313
34547
  return value;
34314
34548
  }
34549
+ async createDatabaseTable(input) {
34550
+ const value = await createProjectDatabaseTable({
34551
+ project_id: input.projectId,
34552
+ "Idempotency-Key": input.idempotencyKey,
34553
+ databaseTableCreateRequest: {
34554
+ environment: input.environment,
34555
+ table: input.table,
34556
+ columns: [...input.columns],
34557
+ ...input.primaryKey === void 0 || input.primaryKey.length === 0 ? {} : { primary_key: [...input.primaryKey] }
34558
+ }
34559
+ }, { client: this.#client });
34560
+ assertDatabaseTableAnswer(value);
34561
+ requireScope(value.table.environment === input.environment && value.table.table === input.table);
34562
+ return value;
34563
+ }
34315
34564
  async deleteDatabaseWatch(input) {
34316
34565
  const value = await deleteProjectDatabaseWatch({
34317
34566
  project_id: input.projectId,
@@ -34672,13 +34921,13 @@ function normalizeMcpSourceChanges(value) {
34672
34921
  }
34673
34922
  }
34674
34923
  function normalizeSourceChanges(value) {
34675
- const body = record7(value);
34924
+ const body = record8(value);
34676
34925
  const changes = body["changes"];
34677
34926
  if (!Array.isArray(changes) || changes.length < 1 || changes.length > MANAGED_SOURCE_LIMITS.files)
34678
34927
  invalid7();
34679
34928
  let textBytes = 0;
34680
34929
  const normalized = changes.map((change) => {
34681
- const entry = record7(change);
34930
+ const entry = record8(change);
34682
34931
  const hasText = Object.hasOwn(entry, "content_text");
34683
34932
  if (hasText === Object.hasOwn(entry, "content_base64"))
34684
34933
  invalid7();
@@ -34737,7 +34986,7 @@ function encodeBase64(bytes) {
34737
34986
  binary += String.fromCharCode(...bytes.subarray(offset, offset + 32768));
34738
34987
  return btoa(binary);
34739
34988
  }
34740
- function record7(value) {
34989
+ function record8(value) {
34741
34990
  if (typeof value !== "object" || value === null || Array.isArray(value))
34742
34991
  invalid7();
34743
34992
  return value;
@@ -34753,6 +35002,7 @@ var databaseWebhookId = external_exports.string().regex(/^whk_[a-z0-9]{20}$/u);
34753
35002
  var databaseWatchId = external_exports.string().regex(/^pgcf_watch_[0-9a-f]{8}$/u);
34754
35003
  var databaseIdentifier = external_exports.string().regex(/^[A-Za-z_][A-Za-z0-9_]{0,62}$/u);
34755
35004
  var databaseDeliveryId = external_exports.string().regex(/^dlv_[0-9a-f]{20}$/u);
35005
+ var databaseTableIdentifier = external_exports.string().refine(isDatabaseTableIdentifier);
34756
35006
  var databaseScheduleName = external_exports.string().regex(/^[a-z][a-z0-9_-]{0,62}$/u);
34757
35007
  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.");
34758
35008
  var idempotencyKey = external_exports.string().min(1).max(128);
@@ -35037,6 +35287,35 @@ function createLocalOhmyhostMcpServer(dependencies) {
35037
35287
  watchId: input.watch_id,
35038
35288
  idempotencyKey: input.idempotency_key
35039
35289
  }), destructiveAnnotations());
35290
+ registerProductTool(server, dependencies, "database_table_create", "Create one ordinary table in the public schema of a Dev or Prod database, as the migration role with the same access as migration tables, so a database_watch_create can attach later. Each column has a lower-case name, a type (text, integer, bigint, boolean, numeric, double precision, timestamptz, date, uuid or jsonb) and optional nullable, identity (integer or bigint only) and default (now for timestamptz, uuid for uuid); at most 64 columns, 8 primary-key columns and 100 tables per database. An identical repeat answers created: false; a table of the same name with another definition is refused with database_table_conflict. Tables created this way are not in the repository's migration files: record them in project notes and add a migration when the source should reproduce the database. Requires confirmed: true and an idempotency key. Refused with project_budget_exceeded while the Stop budget is reached.", external_exports.object({
35291
+ project_id: identifier2,
35292
+ environment: databaseEnvironment,
35293
+ table: databaseTableIdentifier,
35294
+ columns: external_exports.array(external_exports.object({
35295
+ name: databaseTableIdentifier,
35296
+ type: external_exports.enum(DATABASE_TABLE_COLUMN_TYPES),
35297
+ nullable: external_exports.boolean().optional(),
35298
+ identity: external_exports.boolean().optional(),
35299
+ default: external_exports.enum(DATABASE_TABLE_DEFAULTS).optional()
35300
+ })).min(1).max(DATABASE_TABLE_MAX_COLUMNS),
35301
+ primary_key: external_exports.array(databaseTableIdentifier).min(1).max(DATABASE_TABLE_MAX_PRIMARY_KEY_COLUMNS).optional(),
35302
+ idempotency_key: idempotencyKey,
35303
+ confirmed: external_exports.literal(true)
35304
+ }), (client2, input) => {
35305
+ const request = parseDatabaseTableCreateRequest({
35306
+ table: input.table,
35307
+ columns: input.columns,
35308
+ ...input.primary_key === void 0 ? {} : { primaryKey: input.primary_key }
35309
+ });
35310
+ return client2.createDatabaseTable({
35311
+ projectId: input.project_id,
35312
+ environment: input.environment,
35313
+ table: request.table,
35314
+ columns: input.columns,
35315
+ ...request.primaryKey.length === 0 ? {} : { primaryKey: request.primaryKey },
35316
+ idempotencyKey: input.idempotency_key
35317
+ });
35318
+ }, mutationAnnotations());
35040
35319
  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({
35041
35320
  projectId: input.project_id,
35042
35321
  ...input.environment === void 0 ? {} : { environment: input.environment }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amerged/ohmyhost-mcp",
3
- "version": "0.1.45",
3
+ "version": "0.1.46",
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.45",
3
+ "version": "0.1.46",
4
4
  "private": true,
5
5
  "ohmyhost": {
6
6
  "deployment": "production",
@@ -119,6 +119,7 @@ import {
119
119
  createProjectDatabaseWebhook,
120
120
  listProjectDatabaseWebhooks,
121
121
  deleteProjectDatabaseWebhook,
122
+ createProjectDatabaseTable,
122
123
  createProjectDatabaseWatch,
123
124
  listProjectDatabaseWatches,
124
125
  deleteProjectDatabaseWatch,
@@ -184,6 +185,8 @@ import {
184
185
  type LocalProfile,
185
186
  type ProfileRegistry,
186
187
  type WorkspaceSession,
188
+ type DatabaseTableAnswer,
189
+ assertDatabaseTableAnswer,
187
190
  } from "@ohmyhost/product-cli";
188
191
 
189
192
  export class OperationEventsUnavailableError extends Error {
@@ -893,6 +896,30 @@ export class GeneratedSdkLocalMcpClient implements LocalMcpProductClient {
893
896
  );
894
897
  return value;
895
898
  }
899
+ public async createDatabaseTable(
900
+ input: Parameters<LocalMcpProductClient["createDatabaseTable"]>[0],
901
+ ): Promise<DatabaseTableAnswer> {
902
+ const value = await createProjectDatabaseTable(
903
+ {
904
+ project_id: input.projectId,
905
+ "Idempotency-Key": input.idempotencyKey,
906
+ databaseTableCreateRequest: {
907
+ environment: input.environment,
908
+ table: input.table,
909
+ columns: [...input.columns],
910
+ ...(input.primaryKey === undefined || input.primaryKey.length === 0
911
+ ? {}
912
+ : { primary_key: [...input.primaryKey] }),
913
+ },
914
+ },
915
+ { client: this.#client },
916
+ );
917
+ assertDatabaseTableAnswer(value);
918
+ requireScope(
919
+ value.table.environment === input.environment && value.table.table === input.table,
920
+ );
921
+ return value;
922
+ }
896
923
  public async deleteDatabaseWatch(
897
924
  input: Parameters<LocalMcpProductClient["deleteDatabaseWatch"]>[0],
898
925
  ) {