@amerged/ohmyhost-mcp 0.1.42 → 0.1.44
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/{chunk-OD4R3WMQ.js → chunk-NUWAJ4TH.js} +6 -6
- package/dist/index.js +1 -1
- package/dist/stdio-main.js +79 -79
- package/package.json +1 -1
- package/src/apps/mcp/package.json +1 -1
- package/src/apps/mcp/src/local-server.ts +4 -4
- package/src/apps/product-cli/package.json +1 -1
- package/src/apps/product-cli/src/cli.ts +3 -3
- package/src/apps/product-cli/src/psql-session.ts +20 -32
- package/src/apps/product-cli/src/runtime-contract.ts +1 -13
- package/src/packages/agent-skills/src/generated-skill-resources.ts +5 -5
- package/src/packages/contracts/src/credit-pricing.ts +0 -90
- package/src/packages/contracts/src/operation-failure.ts +4 -4
- package/src/packages/sdk-ts/package.json +1 -1
- package/src/packages/sdk-ts/src/generated/index.ts +0 -1
- package/src/packages/sdk-ts/src/generated/sdk.gen.ts +4 -4
- package/src/packages/sdk-ts/src/generated/types.gen.ts +8 -22
package/dist/index.js
CHANGED
package/dist/stdio-main.js
CHANGED
|
@@ -30,7 +30,7 @@ import {
|
|
|
30
30
|
serverIdentityOf,
|
|
31
31
|
setNegotiatedProtocolVersion,
|
|
32
32
|
validateEnvelopeMeta
|
|
33
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-NUWAJ4TH.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
|
|
24385
|
-
"Read the current database compute and report this operation ID through feedback.
|
|
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
|
|
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.",
|
|
@@ -25408,72 +25408,6 @@ function isSaasCnameTarget(value) {
|
|
|
25408
25408
|
return value === "customers.omh.st" || value === "customers.ohmyho.st";
|
|
25409
25409
|
}
|
|
25410
25410
|
|
|
25411
|
-
// packages/contracts/dist/credit-pricing.js
|
|
25412
|
-
function assertOrganizationCreditUsage(value) {
|
|
25413
|
-
const record8 = (input, keys) => {
|
|
25414
|
-
if (!input || typeof input !== "object" || Array.isArray(input) || Object.keys(input).sort().join() !== keys.split(",").sort().join())
|
|
25415
|
-
throw new TypeError("Invalid usage fields");
|
|
25416
|
-
return input;
|
|
25417
|
-
};
|
|
25418
|
-
const identifier3 = (input) => typeof input === "string" && /^[0-7][0-9A-HJKMNP-TV-Z]{25}$/u.test(input);
|
|
25419
|
-
const amount = (input) => typeof input === "string" && /^(0|[1-9][0-9]{0,37})$/u.test(input);
|
|
25420
|
-
const instant2 = (input) => typeof input === "string" && Number.isFinite(Date.parse(input)) && new Date(input).toISOString() === input;
|
|
25421
|
-
const report = record8(value, "organization_id,as_of,unit,period,from,projects,days");
|
|
25422
|
-
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)
|
|
25423
|
-
throw new TypeError("Invalid usage report");
|
|
25424
|
-
let previous = "";
|
|
25425
|
-
for (const input of report["projects"]) {
|
|
25426
|
-
const project = record8(input, "project_id,used_micros");
|
|
25427
|
-
if (!identifier3(project["project_id"]) || String(project["project_id"]) <= previous || !amount(project["used_micros"]))
|
|
25428
|
-
throw new TypeError("Invalid usage project");
|
|
25429
|
-
previous = String(project["project_id"]);
|
|
25430
|
-
}
|
|
25431
|
-
let previousDay = "";
|
|
25432
|
-
for (const input of report["days"]) {
|
|
25433
|
-
const day = record8(input, "day,projects");
|
|
25434
|
-
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)
|
|
25435
|
-
throw new TypeError("Invalid usage day");
|
|
25436
|
-
previousDay = day["day"];
|
|
25437
|
-
previous = "";
|
|
25438
|
-
for (const projectInput of day["projects"]) {
|
|
25439
|
-
const project = record8(projectInput, "project_id,meters");
|
|
25440
|
-
if (!identifier3(project["project_id"]) || String(project["project_id"]) <= previous || !Array.isArray(project["meters"]) || project["meters"].length > 1e3)
|
|
25441
|
-
throw new TypeError("Invalid usage project");
|
|
25442
|
-
previous = String(project["project_id"]);
|
|
25443
|
-
for (const meterInput of project["meters"]) {
|
|
25444
|
-
const meter = record8(meterInput, "environment_id,meter,unit,rate_card_id,quantity,charged_micros");
|
|
25445
|
-
if (meter["environment_id"] !== null && !identifier3(meter["environment_id"]))
|
|
25446
|
-
throw new TypeError("Invalid usage environment");
|
|
25447
|
-
for (const key of ["meter", "unit", "rate_card_id"])
|
|
25448
|
-
if (typeof meter[key] !== "string" || !/^[A-Za-z0-9_.:/-]{1,128}$/u.test(meter[key]))
|
|
25449
|
-
throw new TypeError("Invalid usage meter");
|
|
25450
|
-
if (typeof meter["quantity"] !== "string" || !/^(0|[1-9][0-9]{0,29})(\.[0-9]{1,12})?$/u.test(meter["quantity"]) || !amount(meter["charged_micros"]))
|
|
25451
|
-
throw new TypeError("Invalid usage amount");
|
|
25452
|
-
}
|
|
25453
|
-
}
|
|
25454
|
-
}
|
|
25455
|
-
}
|
|
25456
|
-
function assertOrganizationAccount(value) {
|
|
25457
|
-
if (!value || typeof value !== "object" || Array.isArray(value) || Object.keys(value).sort().join(",") !== "as_of,available_micros,monthly_micros,name,next_expiry,one_time_micros,organization_id,paid_until,plan,plan_source,reserved_micros,workos_organization_id")
|
|
25458
|
-
throw new TypeError("Invalid account fields");
|
|
25459
|
-
const v = value;
|
|
25460
|
-
if (typeof v["organization_id"] !== "string" || !/^[0-7][0-9A-HJKMNP-TV-Z]{25}$/u.test(v["organization_id"]) || typeof v["name"] !== "string" || v["name"].length > 128 || typeof v["workos_organization_id"] !== "string" || !/^org_[A-Za-z0-9_]+$/u.test(v["workos_organization_id"]) || v["plan"] !== "paid" && v["plan"] !== "free" || !["free", "stripe", "granted", "manual"].includes(String(v["plan_source"])) || v["plan"] === "free" !== (v["plan_source"] === "free"))
|
|
25461
|
-
throw new TypeError("Invalid account scope");
|
|
25462
|
-
for (const field of ["available_micros", "monthly_micros", "one_time_micros", "reserved_micros"])
|
|
25463
|
-
if (typeof v[field] !== "string" || !/^(0|[1-9][0-9]{0,18})$/u.test(v[field]))
|
|
25464
|
-
throw new TypeError("Invalid account credits");
|
|
25465
|
-
const held = BigInt(String(v["reserved_micros"]));
|
|
25466
|
-
const balance = BigInt(String(v["monthly_micros"])) + BigInt(String(v["one_time_micros"]));
|
|
25467
|
-
if (BigInt(String(v["available_micros"])) !== (balance > held ? balance - held : 0n))
|
|
25468
|
-
throw new TypeError("Account credit buckets do not balance");
|
|
25469
|
-
for (const field of ["as_of", "paid_until", "next_expiry"]) {
|
|
25470
|
-
if (field !== "as_of" && v[field] === null)
|
|
25471
|
-
continue;
|
|
25472
|
-
if (typeof v[field] !== "string" || !Number.isFinite(Date.parse(v[field])) || new Date(v[field]).toISOString() !== v[field])
|
|
25473
|
-
throw new TypeError("Invalid account timestamp");
|
|
25474
|
-
}
|
|
25475
|
-
}
|
|
25476
|
-
|
|
25477
25411
|
// packages/contracts/dist/problem-policy.js
|
|
25478
25412
|
var PROBLEM_POLICY = {
|
|
25479
25413
|
source_generation_conflict: {
|
|
@@ -26979,6 +26913,72 @@ function exact2(value, keys) {
|
|
|
26979
26913
|
return Object.keys(value).sort().join(",") === [...keys].sort().join(",");
|
|
26980
26914
|
}
|
|
26981
26915
|
|
|
26916
|
+
// packages/contracts/dist/credit-pricing.js
|
|
26917
|
+
function assertOrganizationCreditUsage(value) {
|
|
26918
|
+
const record8 = (input, keys) => {
|
|
26919
|
+
if (!input || typeof input !== "object" || Array.isArray(input) || Object.keys(input).sort().join() !== keys.split(",").sort().join())
|
|
26920
|
+
throw new TypeError("Invalid usage fields");
|
|
26921
|
+
return input;
|
|
26922
|
+
};
|
|
26923
|
+
const identifier3 = (input) => typeof input === "string" && /^[0-7][0-9A-HJKMNP-TV-Z]{25}$/u.test(input);
|
|
26924
|
+
const amount = (input) => typeof input === "string" && /^(0|[1-9][0-9]{0,37})$/u.test(input);
|
|
26925
|
+
const instant2 = (input) => typeof input === "string" && Number.isFinite(Date.parse(input)) && new Date(input).toISOString() === input;
|
|
26926
|
+
const report = record8(value, "organization_id,as_of,unit,period,from,projects,days");
|
|
26927
|
+
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)
|
|
26928
|
+
throw new TypeError("Invalid usage report");
|
|
26929
|
+
let previous = "";
|
|
26930
|
+
for (const input of report["projects"]) {
|
|
26931
|
+
const project = record8(input, "project_id,used_micros");
|
|
26932
|
+
if (!identifier3(project["project_id"]) || String(project["project_id"]) <= previous || !amount(project["used_micros"]))
|
|
26933
|
+
throw new TypeError("Invalid usage project");
|
|
26934
|
+
previous = String(project["project_id"]);
|
|
26935
|
+
}
|
|
26936
|
+
let previousDay = "";
|
|
26937
|
+
for (const input of report["days"]) {
|
|
26938
|
+
const day = record8(input, "day,projects");
|
|
26939
|
+
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)
|
|
26940
|
+
throw new TypeError("Invalid usage day");
|
|
26941
|
+
previousDay = day["day"];
|
|
26942
|
+
previous = "";
|
|
26943
|
+
for (const projectInput of day["projects"]) {
|
|
26944
|
+
const project = record8(projectInput, "project_id,meters");
|
|
26945
|
+
if (!identifier3(project["project_id"]) || String(project["project_id"]) <= previous || !Array.isArray(project["meters"]) || project["meters"].length > 1e3)
|
|
26946
|
+
throw new TypeError("Invalid usage project");
|
|
26947
|
+
previous = String(project["project_id"]);
|
|
26948
|
+
for (const meterInput of project["meters"]) {
|
|
26949
|
+
const meter = record8(meterInput, "environment_id,meter,unit,rate_card_id,quantity,charged_micros");
|
|
26950
|
+
if (meter["environment_id"] !== null && !identifier3(meter["environment_id"]))
|
|
26951
|
+
throw new TypeError("Invalid usage environment");
|
|
26952
|
+
for (const key of ["meter", "unit", "rate_card_id"])
|
|
26953
|
+
if (typeof meter[key] !== "string" || !/^[A-Za-z0-9_.:/-]{1,128}$/u.test(meter[key]))
|
|
26954
|
+
throw new TypeError("Invalid usage meter");
|
|
26955
|
+
if (typeof meter["quantity"] !== "string" || !/^(0|[1-9][0-9]{0,29})(\.[0-9]{1,12})?$/u.test(meter["quantity"]) || !amount(meter["charged_micros"]))
|
|
26956
|
+
throw new TypeError("Invalid usage amount");
|
|
26957
|
+
}
|
|
26958
|
+
}
|
|
26959
|
+
}
|
|
26960
|
+
}
|
|
26961
|
+
function assertOrganizationAccount(value) {
|
|
26962
|
+
if (!value || typeof value !== "object" || Array.isArray(value) || Object.keys(value).sort().join(",") !== "as_of,available_micros,monthly_micros,name,next_expiry,one_time_micros,organization_id,paid_until,plan,plan_source,reserved_micros,workos_organization_id")
|
|
26963
|
+
throw new TypeError("Invalid account fields");
|
|
26964
|
+
const v = value;
|
|
26965
|
+
if (typeof v["organization_id"] !== "string" || !/^[0-7][0-9A-HJKMNP-TV-Z]{25}$/u.test(v["organization_id"]) || typeof v["name"] !== "string" || v["name"].length > 128 || typeof v["workos_organization_id"] !== "string" || !/^org_[A-Za-z0-9_]+$/u.test(v["workos_organization_id"]) || v["plan"] !== "paid" && v["plan"] !== "free" || !["free", "stripe", "granted", "manual"].includes(String(v["plan_source"])) || v["plan"] === "free" !== (v["plan_source"] === "free"))
|
|
26966
|
+
throw new TypeError("Invalid account scope");
|
|
26967
|
+
for (const field of ["available_micros", "monthly_micros", "one_time_micros", "reserved_micros"])
|
|
26968
|
+
if (typeof v[field] !== "string" || !/^(0|[1-9][0-9]{0,18})$/u.test(v[field]))
|
|
26969
|
+
throw new TypeError("Invalid account credits");
|
|
26970
|
+
const held = BigInt(String(v["reserved_micros"]));
|
|
26971
|
+
const balance = BigInt(String(v["monthly_micros"])) + BigInt(String(v["one_time_micros"]));
|
|
26972
|
+
if (BigInt(String(v["available_micros"])) !== (balance > held ? balance - held : 0n))
|
|
26973
|
+
throw new TypeError("Account credit buckets do not balance");
|
|
26974
|
+
for (const field of ["as_of", "paid_until", "next_expiry"]) {
|
|
26975
|
+
if (field !== "as_of" && v[field] === null)
|
|
26976
|
+
continue;
|
|
26977
|
+
if (typeof v[field] !== "string" || !Number.isFinite(Date.parse(v[field])) || new Date(v[field]).toISOString() !== v[field])
|
|
26978
|
+
throw new TypeError("Invalid account timestamp");
|
|
26979
|
+
}
|
|
26980
|
+
}
|
|
26981
|
+
|
|
26982
26982
|
// packages/contracts/dist/database-sizes.js
|
|
26983
26983
|
var DATABASE_SIZES = Object.freeze([
|
|
26984
26984
|
Object.freeze({
|
|
@@ -27310,7 +27310,7 @@ function exact4(value, keys) {
|
|
|
27310
27310
|
// apps/product-cli/package.json
|
|
27311
27311
|
var package_default2 = {
|
|
27312
27312
|
name: "@ohmyhost/product-cli",
|
|
27313
|
-
version: "0.1.
|
|
27313
|
+
version: "0.1.44",
|
|
27314
27314
|
private: true,
|
|
27315
27315
|
ohmyhost: {
|
|
27316
27316
|
deployment: "production",
|
|
@@ -28431,13 +28431,13 @@ var HELP_USAGE = Object.freeze({
|
|
|
28431
28431
|
"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
28432
|
"database webhook deliveries": "ohmyhost database webhook deliveries --project ULID [--environment dev|prod] [--webhook ID] [--limit 1..100] [--cursor CURSOR] --json",
|
|
28433
28433
|
"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)",
|
|
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). 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
28435
|
"database schedule list": "ohmyhost database schedule list --project ULID [--environment dev|prod] --json (dev when omitted)",
|
|
28436
28436
|
"database schedule get": "ohmyhost database schedule get --project ULID [--environment dev|prod] --name NAME --json",
|
|
28437
28437
|
"database schedule delete": "ohmyhost database schedule delete --project ULID --environment dev|prod --name NAME --yes --json",
|
|
28438
28438
|
"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;
|
|
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; refused with project_budget_exceeded (HTTP 402) while the project's Stop budget is reached)",
|
|
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; these are counters only, executions have no price of their own and the awake compute time of the database is what is billed)",
|
|
28441
28441
|
"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
28442
|
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
28443
|
"source auto-deploy set": "ohmyhost source auto-deploy set --project ULID --branch BRANCH --enabled true|false --idempotency-key KEY --json",
|
|
@@ -34684,7 +34684,7 @@ function createLocalOhmyhostMcpServer(dependencies) {
|
|
|
34684
34684
|
registerProductTool(server, dependencies, "organization_usage_get", "Read the billed usage of the last up to 14 UTC days (today included) grouped by day, project and meter, and what each project has used so far this UTC month. One credit is 1000000 microcredits. Null environment means shared project cost. Charges can arrive late and unmetered usage is not zero consumption. Owner-only; works at zero credit without new charges.", external_exports.object({ organization_id: identifier2 }), (client2, input) => client2.getOrganizationCreditUsage(input.organization_id));
|
|
34685
34685
|
registerProductTool(server, dependencies, "organization_account_get", "Owner-only: read the effective Free/Paid plan, its Stripe or granted source, the monthly credits that expire at next_expiry, the top-up and bonus credits that never expire, the reservations that hold part of them, and available_micros (monthly plus one-time minus reservations). Granted Paid features do not imply a Stripe subscription or extra monthly Paid allowance. paid_until is the end of the paid coverage, which includes the 14-day renewal grace of a Stripe subscription; null on Free. This read grants no access or credits.", external_exports.object({ organization_id: identifier2 }), (client2, input) => client2.getOrganizationAccount(input.organization_id));
|
|
34686
34686
|
registerProductTool(server, dependencies, "referral_link_get", "Read the workspace's referral link to share. A new user whose first workspace comes from it starts with 30 days of Paid and 1,000 credits; that workspace's first payment gives this workspace 1,000 credits, plus 30 days of Paid unless a Stripe subscription or an unbounded grant already covers it. Any member may read it.", external_exports.object({ organization_id: identifier2 }), (client2, input) => client2.getOrganizationReferral(input.organization_id));
|
|
34687
|
-
registerProductTool(server, dependencies, "organization_credits_get", "Read the owner's shared organization credit
|
|
34687
|
+
registerProductTool(server, dependencies, "organization_credits_get", "Read the owner's shared organization credit balance (available_micros is monthly plus one-time credit minus reservations) and the seven-day grace_started_at/grace_expires_at that begins when the credit is used up. It states what is left, not what the credit was spent on; read organization_usage_get for the recent charges. Existing services/domains are not immediately shut down at zero credit; an explicit project stop budget is separate. One credit is 1000000 microcredits. Provider outages are not customer debt. Works at zero credit. Top-ups and enabling auto-recharge require active Paid access.", external_exports.object({ organization_id: identifier2 }), (client2, input) => client2.getOrganizationCredits(input.organization_id));
|
|
34688
34688
|
registerProductTool(server, dependencies, "project_budget_get", "Read the owner's project UTC-month budget, this month's use and open reservations. A budget never allocates a second wallet.", external_exports.object({ project_id: identifier2 }), (client2, input) => client2.getProjectCreditBudget(input.project_id));
|
|
34689
34689
|
registerProductTool(server, dependencies, "project_budget_set", "Set an owner's optional monthly project budget in microcredits (1000000 = one credit). continue is the default and uses the org pool after the threshold; stop blocks new billable work. null clears the budget and requires continue. Changing it never resets usage or adds credits. Replay the same key after uncertainty; read the budget again for current usage.", external_exports.object({
|
|
34690
34690
|
project_id: identifier2,
|
|
@@ -34839,7 +34839,7 @@ function createLocalOhmyhostMcpServer(dependencies) {
|
|
|
34839
34839
|
projectId: input.project_id,
|
|
34840
34840
|
...input.environment === void 0 ? {} : { environment: input.environment }
|
|
34841
34841
|
}));
|
|
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({
|
|
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. 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
34843
|
project_id: identifier2,
|
|
34844
34844
|
environment: databaseEnvironment,
|
|
34845
34845
|
name: databaseScheduleName,
|
|
@@ -34883,7 +34883,7 @@ function createLocalOhmyhostMcpServer(dependencies) {
|
|
|
34883
34883
|
name: input.name,
|
|
34884
34884
|
...input.limit === void 0 ? {} : { limit: input.limit }
|
|
34885
34885
|
}));
|
|
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({
|
|
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. Refused with project_budget_exceeded while the project's Stop budget is reached.", external_exports.object({
|
|
34887
34887
|
project_id: identifier2,
|
|
34888
34888
|
environment: databaseEnvironment,
|
|
34889
34889
|
name: databaseScheduleName,
|
|
@@ -34893,7 +34893,7 @@ function createLocalOhmyhostMcpServer(dependencies) {
|
|
|
34893
34893
|
environment: input.environment,
|
|
34894
34894
|
name: input.name
|
|
34895
34895
|
}), { ...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.
|
|
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. 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
34897
|
project_id: identifier2,
|
|
34898
34898
|
environment: databaseEnvironment.optional(),
|
|
34899
34899
|
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
|
@@ -921,7 +921,7 @@ export function createLocalOhmyhostMcpServer(dependencies: LocalOhmyhostMcpDepen
|
|
|
921
921
|
server,
|
|
922
922
|
dependencies,
|
|
923
923
|
"organization_credits_get",
|
|
924
|
-
"Read the owner's shared organization credit
|
|
924
|
+
"Read the owner's shared organization credit balance (available_micros is monthly plus one-time credit minus reservations) and the seven-day grace_started_at/grace_expires_at that begins when the credit is used up. It states what is left, not what the credit was spent on; read organization_usage_get for the recent charges. Existing services/domains are not immediately shut down at zero credit; an explicit project stop budget is separate. One credit is 1000000 microcredits. Provider outages are not customer debt. Works at zero credit. Top-ups and enabling auto-recharge require active Paid access.",
|
|
925
925
|
z.object({ organization_id: identifier }),
|
|
926
926
|
(client, input) => client.getOrganizationCredits(input.organization_id),
|
|
927
927
|
);
|
|
@@ -1254,7 +1254,7 @@ export function createLocalOhmyhostMcpServer(dependencies: LocalOhmyhostMcpDepen
|
|
|
1254
1254
|
server,
|
|
1255
1255
|
dependencies,
|
|
1256
1256
|
"database_schedule_set",
|
|
1257
|
-
"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.",
|
|
1257
|
+
"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).",
|
|
1258
1258
|
z
|
|
1259
1259
|
.object({
|
|
1260
1260
|
project_id: identifier,
|
|
@@ -1339,7 +1339,7 @@ export function createLocalOhmyhostMcpServer(dependencies: LocalOhmyhostMcpDepen
|
|
|
1339
1339
|
server,
|
|
1340
1340
|
dependencies,
|
|
1341
1341
|
"database_schedule_run_now",
|
|
1342
|
-
"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.",
|
|
1342
|
+
"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.",
|
|
1343
1343
|
z.object({
|
|
1344
1344
|
project_id: identifier,
|
|
1345
1345
|
environment: databaseEnvironment,
|
|
@@ -1358,7 +1358,7 @@ export function createLocalOhmyhostMcpServer(dependencies: LocalOhmyhostMcpDepen
|
|
|
1358
1358
|
server,
|
|
1359
1359
|
dependencies,
|
|
1360
1360
|
"database_execution_usage_get",
|
|
1361
|
-
"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.
|
|
1361
|
+
"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.",
|
|
1362
1362
|
z.object({
|
|
1363
1363
|
project_id: identifier,
|
|
1364
1364
|
environment: databaseEnvironment.optional(),
|
|
@@ -434,7 +434,7 @@ const HELP_USAGE = Object.freeze({
|
|
|
434
434
|
"database webhook replay":
|
|
435
435
|
"ohmyhost database webhook replay --project ULID --environment dev|prod --delivery ID --yes --json",
|
|
436
436
|
"database schedule set":
|
|
437
|
-
"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)",
|
|
437
|
+
"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).",
|
|
438
438
|
"database schedule list":
|
|
439
439
|
"ohmyhost database schedule list --project ULID [--environment dev|prod] --json (dev when omitted)",
|
|
440
440
|
"database schedule get":
|
|
@@ -444,9 +444,9 @@ const HELP_USAGE = Object.freeze({
|
|
|
444
444
|
"database schedule runs":
|
|
445
445
|
"ohmyhost database schedule runs --project ULID [--environment dev|prod] --name NAME [--limit 1..50] --json",
|
|
446
446
|
"database schedule run":
|
|
447
|
-
"ohmyhost database schedule run --project ULID --environment dev|prod --name NAME --yes --json (queues one run now, in addition to the timer)",
|
|
447
|
+
"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)",
|
|
448
448
|
"database executions":
|
|
449
|
-
"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;
|
|
449
|
+
"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)",
|
|
450
450
|
"database psql":
|
|
451
451
|
"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)",
|
|
452
452
|
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)",
|
|
@@ -9,21 +9,17 @@ export const runLocalPsql = async (input: {
|
|
|
9
9
|
readonly user: string;
|
|
10
10
|
readonly password: string;
|
|
11
11
|
readonly database: string;
|
|
12
|
-
readonly websocketUrl
|
|
12
|
+
readonly websocketUrl: string;
|
|
13
13
|
readonly signal: AbortSignal;
|
|
14
14
|
}): Promise<number> => {
|
|
15
15
|
input.signal.throwIfAborted();
|
|
16
|
-
if (input.websocketUrl !==
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
input.
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
database: input.database,
|
|
24
|
-
user: input.user,
|
|
25
|
-
port: 0,
|
|
26
|
-
});
|
|
16
|
+
if (input.websocketUrl !== `wss://${input.host}/`) throw new Error("Invalid PGCF transport");
|
|
17
|
+
const bridge = await startBridge({
|
|
18
|
+
endpoint: input.websocketUrl,
|
|
19
|
+
database: input.database,
|
|
20
|
+
user: input.user,
|
|
21
|
+
port: 0,
|
|
22
|
+
});
|
|
27
23
|
try {
|
|
28
24
|
input.signal.throwIfAborted();
|
|
29
25
|
// A caller's libpq service/hostaddr settings must not redirect this temporary credential.
|
|
@@ -31,26 +27,18 @@ export const runLocalPsql = async (input: {
|
|
|
31
27
|
Object.entries(process.env).filter(([key]) => !/^PG[A-Z_]+$/iu.test(key)),
|
|
32
28
|
);
|
|
33
29
|
return await new Promise<number>((resolve, reject) => {
|
|
34
|
-
const child = spawn(
|
|
35
|
-
"
|
|
36
|
-
|
|
37
|
-
{
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
...(bridge
|
|
45
|
-
? {
|
|
46
|
-
PGPORT: String(bridge.port),
|
|
47
|
-
PGCHANNELBINDING: "disable",
|
|
48
|
-
PGGSSENCMODE: "disable",
|
|
49
|
-
}
|
|
50
|
-
: {}),
|
|
51
|
-
},
|
|
30
|
+
const child = spawn("psql", ["-h", bridge.host, "-U", input.user, "-d", input.database], {
|
|
31
|
+
stdio: "inherit",
|
|
32
|
+
signal: input.signal,
|
|
33
|
+
env: {
|
|
34
|
+
...environment,
|
|
35
|
+
PGPASSWORD: input.password,
|
|
36
|
+
PGSSLMODE: "disable",
|
|
37
|
+
PGPORT: String(bridge.port),
|
|
38
|
+
PGCHANNELBINDING: "disable",
|
|
39
|
+
PGGSSENCMODE: "disable",
|
|
52
40
|
},
|
|
53
|
-
);
|
|
41
|
+
});
|
|
54
42
|
child.once("error", (error: NodeJS.ErrnoException) => {
|
|
55
43
|
reject(error.code === "ENOENT" ? new PsqlUnavailableError() : error);
|
|
56
44
|
});
|
|
@@ -59,6 +47,6 @@ export const runLocalPsql = async (input: {
|
|
|
59
47
|
});
|
|
60
48
|
});
|
|
61
49
|
} finally {
|
|
62
|
-
await bridge
|
|
50
|
+
await bridge.close();
|
|
63
51
|
}
|
|
64
52
|
};
|
|
@@ -23,7 +23,6 @@ import type {
|
|
|
23
23
|
ProjectCreditBudget,
|
|
24
24
|
} from "@ohmyhost/sdk-ts";
|
|
25
25
|
import { isSaasCnameTarget, type SaasCnameTarget } from "@ohmyhost/contracts/platform-origins";
|
|
26
|
-
import { assertPublishedCreditRateCards } from "@ohmyhost/contracts/credit-pricing";
|
|
27
26
|
import {
|
|
28
27
|
publicOperationFailure,
|
|
29
28
|
publicOperationReconciliation,
|
|
@@ -554,14 +553,7 @@ export function parseOrganizationCredits(value: unknown): OrganizationCredits {
|
|
|
554
553
|
"reserved_micros",
|
|
555
554
|
"grace_started_at",
|
|
556
555
|
"grace_expires_at",
|
|
557
|
-
"active_meters",
|
|
558
|
-
"rate_cards",
|
|
559
556
|
]);
|
|
560
|
-
try {
|
|
561
|
-
assertPublishedCreditRateCards(input["rate_cards"]);
|
|
562
|
-
} catch {
|
|
563
|
-
throw new ResponseContractError();
|
|
564
|
-
}
|
|
565
557
|
const graceStart = input["grace_started_at"],
|
|
566
558
|
graceEnd = input["grace_expires_at"];
|
|
567
559
|
if (
|
|
@@ -576,11 +568,7 @@ export function parseOrganizationCredits(value: unknown): OrganizationCredits {
|
|
|
576
568
|
if (
|
|
577
569
|
!isUlid(input["organization_id"]) ||
|
|
578
570
|
input["unit"] !== "microcredits" ||
|
|
579
|
-
!isIsoInstant(input["as_of"])
|
|
580
|
-
!Array.isArray(input["active_meters"]) ||
|
|
581
|
-
input["active_meters"].some(
|
|
582
|
-
(m: unknown) => typeof m !== "string" || !/^[a-z0-9_.-]{1,128}$/u.test(m),
|
|
583
|
-
)
|
|
571
|
+
!isIsoInstant(input["as_of"])
|
|
584
572
|
)
|
|
585
573
|
throw new ResponseContractError();
|
|
586
574
|
for (const field of ["available_micros", "reserved_micros"]) {
|