@amerged/ohmyhost-cli 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # @amerged/ohmyhost-cli
2
2
 
3
- ohmyho.st cli client, version 0.1.42.
3
+ ohmyho.st cli client, version 0.1.44.
4
4
 
5
5
  ```sh
6
6
  npm install --global @amerged/ohmyhost-cli
package/dist/bin.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import { createRequire as __ohmyhostCreateRequire } from "node:module"; const require = __ohmyhostCreateRequire(import.meta.url);
3
3
  import {
4
4
  runProductionCli
5
- } from "./chunk-HBM2X2MV.js";
5
+ } from "./chunk-VJWPDFZ5.js";
6
6
  import "./chunk-7D2RFLWM.js";
7
7
 
8
8
  // apps/product-cli/dist/bin.js
@@ -38879,12 +38879,12 @@ var failures = {
38879
38879
  "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."
38880
38880
  ],
38881
38881
  database_compute_failed: [
38882
- "The database size change failed at the provider.",
38883
- "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."
38882
+ "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.",
38883
+ "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."
38884
38884
  ],
38885
38885
  database_compute_plan_changed: [
38886
- "Paid access ended before the database size change started.",
38887
- "Read the organization's plan and current database compute, then confirm a new standard-size request. This operation did not resize the database."
38886
+ "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.",
38887
+ "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."
38888
38888
  ],
38889
38889
  database_compute_rejected: [
38890
38890
  "The database size change could not be started.",
@@ -40055,112 +40055,6 @@ function isSaasCnameTarget(value) {
40055
40055
  return value === "customers.omh.st" || value === "customers.ohmyho.st";
40056
40056
  }
40057
40057
 
40058
- // packages/contracts/dist/credit-pricing.js
40059
- function assertOrganizationCreditUsage(value) {
40060
- const record7 = (input, keys) => {
40061
- if (!input || typeof input !== "object" || Array.isArray(input) || Object.keys(input).sort().join() !== keys.split(",").sort().join())
40062
- throw new TypeError("Invalid usage fields");
40063
- return input;
40064
- };
40065
- const identifier2 = (input) => typeof input === "string" && /^[0-7][0-9A-HJKMNP-TV-Z]{25}$/u.test(input);
40066
- const amount = (input) => typeof input === "string" && /^(0|[1-9][0-9]{0,37})$/u.test(input);
40067
- const instant2 = (input) => typeof input === "string" && Number.isFinite(Date.parse(input)) && new Date(input).toISOString() === input;
40068
- const report = record7(value, "organization_id,as_of,unit,period,from,projects,days");
40069
- if (!identifier2(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)
40070
- throw new TypeError("Invalid usage report");
40071
- let previous = "";
40072
- for (const input of report["projects"]) {
40073
- const project = record7(input, "project_id,used_micros");
40074
- if (!identifier2(project["project_id"]) || String(project["project_id"]) <= previous || !amount(project["used_micros"]))
40075
- throw new TypeError("Invalid usage project");
40076
- previous = String(project["project_id"]);
40077
- }
40078
- let previousDay = "";
40079
- for (const input of report["days"]) {
40080
- const day = record7(input, "day,projects");
40081
- 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)
40082
- throw new TypeError("Invalid usage day");
40083
- previousDay = day["day"];
40084
- previous = "";
40085
- for (const projectInput of day["projects"]) {
40086
- const project = record7(projectInput, "project_id,meters");
40087
- if (!identifier2(project["project_id"]) || String(project["project_id"]) <= previous || !Array.isArray(project["meters"]) || project["meters"].length > 1e3)
40088
- throw new TypeError("Invalid usage project");
40089
- previous = String(project["project_id"]);
40090
- for (const meterInput of project["meters"]) {
40091
- const meter = record7(meterInput, "environment_id,meter,unit,rate_card_id,quantity,charged_micros");
40092
- if (meter["environment_id"] !== null && !identifier2(meter["environment_id"]))
40093
- throw new TypeError("Invalid usage environment");
40094
- for (const key of ["meter", "unit", "rate_card_id"])
40095
- if (typeof meter[key] !== "string" || !/^[A-Za-z0-9_.:/-]{1,128}$/u.test(meter[key]))
40096
- throw new TypeError("Invalid usage meter");
40097
- if (typeof meter["quantity"] !== "string" || !/^(0|[1-9][0-9]{0,29})(\.[0-9]{1,12})?$/u.test(meter["quantity"]) || !amount(meter["charged_micros"]))
40098
- throw new TypeError("Invalid usage amount");
40099
- }
40100
- }
40101
- }
40102
- }
40103
- function assertPublishedCreditRateCards(value) {
40104
- if (!Array.isArray(value) || value.length > 4)
40105
- throw new TypeError("Invalid published prices");
40106
- const ids = /* @__PURE__ */ new Set();
40107
- for (const input of value) {
40108
- if (!input || typeof input !== "object" || Array.isArray(input) || Object.keys(input).sort().join() !== "currency,effective_from,id,published_at,rates")
40109
- throw new TypeError("Invalid published price fields");
40110
- const card = input;
40111
- if (typeof card["id"] !== "string" || !/^[a-z0-9_.-]{1,128}$/u.test(card["id"]) || ids.has(card["id"]) || card["currency"] !== "USD")
40112
- throw new TypeError("Invalid price identity");
40113
- ids.add(card["id"]);
40114
- for (const key of ["published_at", "effective_from"]) {
40115
- const date2 = card[key];
40116
- if (typeof date2 !== "string" || !Number.isFinite(Date.parse(date2)) || new Date(date2).toISOString() !== date2)
40117
- throw new TypeError("Invalid price dates");
40118
- }
40119
- if (String(card["published_at"]) > String(card["effective_from"]) || !Array.isArray(card["rates"]) || card["rates"].length < 1 || card["rates"].length > 32)
40120
- throw new TypeError("Invalid price publication");
40121
- const meters = /* @__PURE__ */ new Set();
40122
- for (const item of card["rates"]) {
40123
- if (!item || typeof item !== "object" || Array.isArray(item) || Object.keys(item).sort().join() !== "credit_micros,meter,provider_cost_micros,unit,units_per_charge")
40124
- throw new TypeError("Invalid rate fields");
40125
- const rate = item;
40126
- for (const key of ["meter", "unit"])
40127
- if (typeof rate[key] !== "string" || !/^[a-z0-9_.-]{1,128}$/u.test(String(rate[key])))
40128
- throw new TypeError("Invalid rate unit");
40129
- const identity = String(rate["meter"]) + "/" + String(rate["unit"]);
40130
- if (meters.has(identity))
40131
- throw new TypeError("Duplicate rate");
40132
- meters.add(identity);
40133
- for (const key of ["units_per_charge", "provider_cost_micros", "credit_micros"]) {
40134
- const amount = rate[key];
40135
- if (key === "provider_cost_micros" && amount === null && rate["meter"] === "pgcf.memory.active")
40136
- continue;
40137
- if (typeof amount !== "string" || !/^(0|[1-9][0-9]{0,18})$/u.test(amount) || BigInt(amount) > 9223372036854775807n || key === "units_per_charge" && amount === "0")
40138
- throw new TypeError("Invalid rate amount");
40139
- }
40140
- }
40141
- }
40142
- }
40143
- function assertOrganizationAccount(value) {
40144
- 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")
40145
- throw new TypeError("Invalid account fields");
40146
- const v = value;
40147
- 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"))
40148
- throw new TypeError("Invalid account scope");
40149
- for (const field of ["available_micros", "monthly_micros", "one_time_micros", "reserved_micros"])
40150
- if (typeof v[field] !== "string" || !/^(0|[1-9][0-9]{0,18})$/u.test(v[field]))
40151
- throw new TypeError("Invalid account credits");
40152
- const held = BigInt(String(v["reserved_micros"]));
40153
- const balance = BigInt(String(v["monthly_micros"])) + BigInt(String(v["one_time_micros"]));
40154
- if (BigInt(String(v["available_micros"])) !== (balance > held ? balance - held : 0n))
40155
- throw new TypeError("Account credit buckets do not balance");
40156
- for (const field of ["as_of", "paid_until", "next_expiry"]) {
40157
- if (field !== "as_of" && v[field] === null)
40158
- continue;
40159
- if (typeof v[field] !== "string" || !Number.isFinite(Date.parse(v[field])) || new Date(v[field]).toISOString() !== v[field])
40160
- throw new TypeError("Invalid account timestamp");
40161
- }
40162
- }
40163
-
40164
40058
  // packages/contracts/dist/problem-policy.js
40165
40059
  var PROBLEM_POLICY = {
40166
40060
  source_generation_conflict: {
@@ -40906,19 +40800,12 @@ function parseOrganizationCredits(value) {
40906
40800
  "available_micros",
40907
40801
  "reserved_micros",
40908
40802
  "grace_started_at",
40909
- "grace_expires_at",
40910
- "active_meters",
40911
- "rate_cards"
40803
+ "grace_expires_at"
40912
40804
  ]);
40913
- try {
40914
- assertPublishedCreditRateCards(input["rate_cards"]);
40915
- } catch {
40916
- throw new ResponseContractError();
40917
- }
40918
40805
  const graceStart = input["grace_started_at"], graceEnd = input["grace_expires_at"];
40919
40806
  if (!(graceStart === null && graceEnd === null) && !(isIsoInstant(graceStart) && isIsoInstant(graceEnd) && Date.parse(graceEnd) - Date.parse(graceStart) === 6048e5))
40920
40807
  throw new ResponseContractError();
40921
- if (!isUlid(input["organization_id"]) || input["unit"] !== "microcredits" || !isIsoInstant(input["as_of"]) || !Array.isArray(input["active_meters"]) || input["active_meters"].some((m) => typeof m !== "string" || !/^[a-z0-9_.-]{1,128}$/u.test(m)))
40808
+ if (!isUlid(input["organization_id"]) || input["unit"] !== "microcredits" || !isIsoInstant(input["as_of"]))
40922
40809
  throw new ResponseContractError();
40923
40810
  for (const field of ["available_micros", "reserved_micros"]) {
40924
40811
  const amount = input[field];
@@ -45312,6 +45199,72 @@ function exact3(value, keys) {
45312
45199
  return Object.keys(value).sort().join(",") === [...keys].sort().join(",");
45313
45200
  }
45314
45201
 
45202
+ // packages/contracts/dist/credit-pricing.js
45203
+ function assertOrganizationCreditUsage(value) {
45204
+ const record7 = (input, keys) => {
45205
+ if (!input || typeof input !== "object" || Array.isArray(input) || Object.keys(input).sort().join() !== keys.split(",").sort().join())
45206
+ throw new TypeError("Invalid usage fields");
45207
+ return input;
45208
+ };
45209
+ const identifier2 = (input) => typeof input === "string" && /^[0-7][0-9A-HJKMNP-TV-Z]{25}$/u.test(input);
45210
+ const amount = (input) => typeof input === "string" && /^(0|[1-9][0-9]{0,37})$/u.test(input);
45211
+ const instant2 = (input) => typeof input === "string" && Number.isFinite(Date.parse(input)) && new Date(input).toISOString() === input;
45212
+ const report = record7(value, "organization_id,as_of,unit,period,from,projects,days");
45213
+ if (!identifier2(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)
45214
+ throw new TypeError("Invalid usage report");
45215
+ let previous = "";
45216
+ for (const input of report["projects"]) {
45217
+ const project = record7(input, "project_id,used_micros");
45218
+ if (!identifier2(project["project_id"]) || String(project["project_id"]) <= previous || !amount(project["used_micros"]))
45219
+ throw new TypeError("Invalid usage project");
45220
+ previous = String(project["project_id"]);
45221
+ }
45222
+ let previousDay = "";
45223
+ for (const input of report["days"]) {
45224
+ const day = record7(input, "day,projects");
45225
+ 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)
45226
+ throw new TypeError("Invalid usage day");
45227
+ previousDay = day["day"];
45228
+ previous = "";
45229
+ for (const projectInput of day["projects"]) {
45230
+ const project = record7(projectInput, "project_id,meters");
45231
+ if (!identifier2(project["project_id"]) || String(project["project_id"]) <= previous || !Array.isArray(project["meters"]) || project["meters"].length > 1e3)
45232
+ throw new TypeError("Invalid usage project");
45233
+ previous = String(project["project_id"]);
45234
+ for (const meterInput of project["meters"]) {
45235
+ const meter = record7(meterInput, "environment_id,meter,unit,rate_card_id,quantity,charged_micros");
45236
+ if (meter["environment_id"] !== null && !identifier2(meter["environment_id"]))
45237
+ throw new TypeError("Invalid usage environment");
45238
+ for (const key of ["meter", "unit", "rate_card_id"])
45239
+ if (typeof meter[key] !== "string" || !/^[A-Za-z0-9_.:/-]{1,128}$/u.test(meter[key]))
45240
+ throw new TypeError("Invalid usage meter");
45241
+ if (typeof meter["quantity"] !== "string" || !/^(0|[1-9][0-9]{0,29})(\.[0-9]{1,12})?$/u.test(meter["quantity"]) || !amount(meter["charged_micros"]))
45242
+ throw new TypeError("Invalid usage amount");
45243
+ }
45244
+ }
45245
+ }
45246
+ }
45247
+ function assertOrganizationAccount(value) {
45248
+ 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")
45249
+ throw new TypeError("Invalid account fields");
45250
+ const v = value;
45251
+ 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"))
45252
+ throw new TypeError("Invalid account scope");
45253
+ for (const field of ["available_micros", "monthly_micros", "one_time_micros", "reserved_micros"])
45254
+ if (typeof v[field] !== "string" || !/^(0|[1-9][0-9]{0,18})$/u.test(v[field]))
45255
+ throw new TypeError("Invalid account credits");
45256
+ const held = BigInt(String(v["reserved_micros"]));
45257
+ const balance = BigInt(String(v["monthly_micros"])) + BigInt(String(v["one_time_micros"]));
45258
+ if (BigInt(String(v["available_micros"])) !== (balance > held ? balance - held : 0n))
45259
+ throw new TypeError("Account credit buckets do not balance");
45260
+ for (const field of ["as_of", "paid_until", "next_expiry"]) {
45261
+ if (field !== "as_of" && v[field] === null)
45262
+ continue;
45263
+ if (typeof v[field] !== "string" || !Number.isFinite(Date.parse(v[field])) || new Date(v[field]).toISOString() !== v[field])
45264
+ throw new TypeError("Invalid account timestamp");
45265
+ }
45266
+ }
45267
+
45315
45268
  // packages/contracts/dist/database-compute.js
45316
45269
  function assertDatabaseCompute(value) {
45317
45270
  if (!record4(value) || !exact4(value, ["data_mode", "database", "environment", "observed_at", "project_id"]) || typeof value["project_id"] !== "string" || !/^[0-7][0-9A-HJKMNP-TV-Z]{25}$/u.test(value["project_id"]) || !["dev", "prod"].includes(String(value["environment"])) || !["shared", "isolated"].includes(String(value["data_mode"])) || typeof value["observed_at"] !== "string" || !Number.isFinite(Date.parse(value["observed_at"])) || new Date(value["observed_at"]).toISOString() !== value["observed_at"])
@@ -45364,7 +45317,7 @@ function exact4(v, keys) {
45364
45317
  // apps/product-cli/package.json
45365
45318
  var package_default = {
45366
45319
  name: "@ohmyhost/product-cli",
45367
- version: "0.1.42",
45320
+ version: "0.1.44",
45368
45321
  private: true,
45369
45322
  ohmyhost: {
45370
45323
  deployment: "production",
@@ -45716,13 +45669,13 @@ var HELP_USAGE = Object.freeze({
45716
45669
  "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)",
45717
45670
  "database webhook deliveries": "ohmyhost database webhook deliveries --project ULID [--environment dev|prod] [--webhook ID] [--limit 1..100] [--cursor CURSOR] --json",
45718
45671
  "database webhook replay": "ohmyhost database webhook replay --project ULID --environment dev|prod --delivery ID --yes --json",
45719
- "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)",
45672
+ "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).",
45720
45673
  "database schedule list": "ohmyhost database schedule list --project ULID [--environment dev|prod] --json (dev when omitted)",
45721
45674
  "database schedule get": "ohmyhost database schedule get --project ULID [--environment dev|prod] --name NAME --json",
45722
45675
  "database schedule delete": "ohmyhost database schedule delete --project ULID --environment dev|prod --name NAME --yes --json",
45723
45676
  "database schedule runs": "ohmyhost database schedule runs --project ULID [--environment dev|prod] --name NAME [--limit 1..50] --json",
45724
- "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)",
45725
- "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)",
45677
+ "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)",
45678
+ "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)",
45726
45679
  "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)",
45727
45680
  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)",
45728
45681
  "source auto-deploy set": "ohmyhost source auto-deploy set --project ULID --branch BRANCH --enabled true|false --idempotency-key KEY --json",
@@ -55906,9 +55859,9 @@ async function startBridge(raw, hooks = {}) {
55906
55859
  // apps/product-cli/dist/psql-session.js
55907
55860
  var runLocalPsql = async (input) => {
55908
55861
  input.signal.throwIfAborted();
55909
- if (input.websocketUrl !== void 0 && input.websocketUrl !== `wss://${input.host}/`)
55862
+ if (input.websocketUrl !== `wss://${input.host}/`)
55910
55863
  throw new Error("Invalid PGCF transport");
55911
- const bridge = input.websocketUrl === void 0 ? null : await startBridge({
55864
+ const bridge = await startBridge({
55912
55865
  endpoint: input.websocketUrl,
55913
55866
  database: input.database,
55914
55867
  user: input.user,
@@ -55918,18 +55871,16 @@ var runLocalPsql = async (input) => {
55918
55871
  input.signal.throwIfAborted();
55919
55872
  const environment = Object.fromEntries(Object.entries(process.env).filter(([key]) => !/^PG[A-Z_]+$/iu.test(key)));
55920
55873
  return await new Promise((resolve7, reject) => {
55921
- const child = spawn("psql", ["-h", bridge?.host ?? input.host, "-U", input.user, "-d", input.database], {
55874
+ const child = spawn("psql", ["-h", bridge.host, "-U", input.user, "-d", input.database], {
55922
55875
  stdio: "inherit",
55923
55876
  signal: input.signal,
55924
55877
  env: {
55925
55878
  ...environment,
55926
55879
  PGPASSWORD: input.password,
55927
- PGSSLMODE: bridge ? "disable" : "require",
55928
- ...bridge ? {
55929
- PGPORT: String(bridge.port),
55930
- PGCHANNELBINDING: "disable",
55931
- PGGSSENCMODE: "disable"
55932
- } : {}
55880
+ PGSSLMODE: "disable",
55881
+ PGPORT: String(bridge.port),
55882
+ PGCHANNELBINDING: "disable",
55883
+ PGGSSENCMODE: "disable"
55933
55884
  }
55934
55885
  });
55935
55886
  child.once("error", (error) => {
@@ -55940,7 +55891,7 @@ var runLocalPsql = async (input) => {
55940
55891
  });
55941
55892
  });
55942
55893
  } finally {
55943
- await bridge?.close();
55894
+ await bridge.close();
55944
55895
  }
55945
55896
  };
55946
55897
 
package/dist/index.js CHANGED
@@ -100,7 +100,7 @@ import {
100
100
  selectWorkspace,
101
101
  verifiedUserId,
102
102
  workspaceOf
103
- } from "./chunk-HBM2X2MV.js";
103
+ } from "./chunk-VJWPDFZ5.js";
104
104
  import {
105
105
  CredentialStoreIntegrityError,
106
106
  CredentialStoreUnavailableError,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amerged/ohmyhost-cli",
3
- "version": "0.1.42",
3
+ "version": "0.1.44",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "node": ">=22"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ohmyhost/product-cli",
3
- "version": "0.1.42",
3
+ "version": "0.1.44",
4
4
  "private": true,
5
5
  "ohmyhost": {
6
6
  "deployment": "production",
@@ -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; usage counters only, nothing is charged for them until the operator prices them)",
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?: string;
12
+ readonly websocketUrl: string;
13
13
  readonly signal: AbortSignal;
14
14
  }): Promise<number> => {
15
15
  input.signal.throwIfAborted();
16
- if (input.websocketUrl !== undefined && input.websocketUrl !== `wss://${input.host}/`)
17
- throw new Error("Invalid PGCF transport");
18
- const bridge =
19
- input.websocketUrl === undefined
20
- ? null
21
- : await startBridge({
22
- endpoint: input.websocketUrl,
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
- "psql",
36
- ["-h", bridge?.host ?? input.host, "-U", input.user, "-d", input.database],
37
- {
38
- stdio: "inherit",
39
- signal: input.signal,
40
- env: {
41
- ...environment,
42
- PGPASSWORD: input.password,
43
- PGSSLMODE: bridge ? "disable" : "require",
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?.close();
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"]) {
@@ -116,96 +116,6 @@ export function assertOrganizationCreditUsage(
116
116
  }
117
117
  }
118
118
 
119
- export interface PublishedCreditRateCard {
120
- readonly id: string;
121
- readonly currency: "USD";
122
- readonly published_at: string;
123
- readonly effective_from: string;
124
- readonly rates: readonly {
125
- readonly meter: string;
126
- readonly unit: string;
127
- readonly units_per_charge: string;
128
- readonly provider_cost_micros: string | null;
129
- readonly credit_micros: string;
130
- }[];
131
- }
132
-
133
- /** Shared wire validation only; clients never recompute accounting decisions. */
134
- export function assertPublishedCreditRateCards(
135
- value: unknown,
136
- ): asserts value is readonly PublishedCreditRateCard[] {
137
- if (!Array.isArray(value) || value.length > 4) throw new TypeError("Invalid published prices");
138
- const ids = new Set<string>();
139
- for (const input of value) {
140
- if (
141
- !input ||
142
- typeof input !== "object" ||
143
- Array.isArray(input) ||
144
- Object.keys(input).sort().join() !== "currency,effective_from,id,published_at,rates"
145
- )
146
- throw new TypeError("Invalid published price fields");
147
- const card = input as Record<string, unknown>;
148
- if (
149
- typeof card["id"] !== "string" ||
150
- !/^[a-z0-9_.-]{1,128}$/u.test(card["id"]) ||
151
- ids.has(card["id"]) ||
152
- card["currency"] !== "USD"
153
- )
154
- throw new TypeError("Invalid price identity");
155
- ids.add(card["id"]);
156
- for (const key of ["published_at", "effective_from"]) {
157
- const date = card[key];
158
- if (
159
- typeof date !== "string" ||
160
- !Number.isFinite(Date.parse(date)) ||
161
- new Date(date).toISOString() !== date
162
- )
163
- throw new TypeError("Invalid price dates");
164
- }
165
- if (
166
- String(card["published_at"]) > String(card["effective_from"]) ||
167
- !Array.isArray(card["rates"]) ||
168
- card["rates"].length < 1 ||
169
- card["rates"].length > 32
170
- )
171
- throw new TypeError("Invalid price publication");
172
- const meters = new Set<string>();
173
- for (const item of card["rates"]) {
174
- if (
175
- !item ||
176
- typeof item !== "object" ||
177
- Array.isArray(item) ||
178
- Object.keys(item).sort().join() !==
179
- "credit_micros,meter,provider_cost_micros,unit,units_per_charge"
180
- )
181
- throw new TypeError("Invalid rate fields");
182
- const rate = item as Record<string, unknown>;
183
- for (const key of ["meter", "unit"])
184
- if (typeof rate[key] !== "string" || !/^[a-z0-9_.-]{1,128}$/u.test(String(rate[key])))
185
- throw new TypeError("Invalid rate unit");
186
- const identity = String(rate["meter"]) + "/" + String(rate["unit"]);
187
- if (meters.has(identity)) throw new TypeError("Duplicate rate");
188
- meters.add(identity);
189
- for (const key of ["units_per_charge", "provider_cost_micros", "credit_micros"]) {
190
- const amount = rate[key];
191
- if (
192
- key === "provider_cost_micros" &&
193
- amount === null &&
194
- rate["meter"] === "pgcf.memory.active"
195
- )
196
- continue;
197
- if (
198
- typeof amount !== "string" ||
199
- !/^(0|[1-9][0-9]{0,18})$/u.test(amount) ||
200
- BigInt(amount) > 9223372036854775807n ||
201
- (key === "units_per_charge" && amount === "0")
202
- )
203
- throw new TypeError("Invalid rate amount");
204
- }
205
- }
206
- }
207
- }
208
-
209
119
  /** Effective feature access and the credit account: the monthly and one-time credit and what the open reservations hold. */
210
120
  export function assertOrganizationAccount(value: unknown): void {
211
121
  if (
@@ -34,12 +34,12 @@ const failures = {
34
34
  "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.",
35
35
  ],
36
36
  database_compute_failed: [
37
- "The database size change failed at the provider.",
38
- "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.",
37
+ "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.",
38
+ "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.",
39
39
  ],
40
40
  database_compute_plan_changed: [
41
- "Paid access ended before the database size change started.",
42
- "Read the organization's plan and current database compute, then confirm a new standard-size request. This operation did not resize the database.",
41
+ "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.",
42
+ "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.",
43
43
  ],
44
44
  database_compute_rejected: [
45
45
  "The database size change could not be started.",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ohmyhost/sdk-ts",
3
- "version": "0.1.42",
3
+ "version": "0.1.44",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "exports": {
@@ -829,7 +829,6 @@ export type {
829
829
  PromoteDeploymentResponse,
830
830
  PromoteDeploymentResponses,
831
831
  ProviderReconciliationAttempt,
832
- PublishedCreditRateCard,
833
832
  PutEnvironmentSecretData,
834
833
  PutEnvironmentSecretError,
835
834
  PutEnvironmentSecretErrors,
@@ -2192,7 +2192,7 @@ export const configureBillingRecharge = <ThrowOnError extends boolean = true>(
2192
2192
  /**
2193
2193
  * Read the owner's shared organization credit pool
2194
2194
  *
2195
- * Returns the spendable credit and the open reservations, in microcredits (one credit is 1000000 microcredits). available_micros is the monthly credit plus the one-time credit minus the reservations, never below zero. Only the organization Owner may read this balance. active_meters names the currently billed sources; usage that is not billed yet is not included. When the credit is used up, grace_started_at and grace_expires_at name the seven days that keep existing services running. Remains available at zero credit.
2195
+ * Returns the spendable credit and the open reservations, in microcredits (one credit is 1000000 microcredits). available_micros is the monthly credit plus the one-time credit minus the reservations, never below zero. Only the organization Owner may read this balance. It states what is left and never what the credit was spent on; read the recent usage from getOrganizationCreditUsage. When the credit is used up, grace_started_at and grace_expires_at name the seven days that keep existing services running. Remains available at zero credit.
2196
2196
  */
2197
2197
  export const getOrganizationCredits = <ThrowOnError extends boolean = true>(
2198
2198
  parameters: {
@@ -5227,7 +5227,7 @@ export const getProjectDatabaseSchedule = <ThrowOnError extends boolean = true>(
5227
5227
  /**
5228
5228
  * Create or replace one database schedule
5229
5229
  *
5230
- * Creates the named schedule or replaces it. Each run executes the statements as the application's own runtime role, so row-level security applies as for the application. A run that does not finish within timeout_ms is cancelled.
5230
+ * Creates the named schedule or replaces it. Each run executes the statements as the application's own runtime role, so row-level security applies as for the application. A run that does not finish within timeout_ms is cancelled. A reached Stop budget (or exhausted credit past its grace) refuses an enabled schedule with project_budget_exceeded (402); disabling the schedule is never refused.
5231
5231
  */
5232
5232
  export const setProjectDatabaseSchedule = <ThrowOnError extends boolean = true>(
5233
5233
  parameters: {
@@ -5328,7 +5328,7 @@ export const listProjectDatabaseScheduleRuns = <ThrowOnError extends boolean = t
5328
5328
  /**
5329
5329
  * Queue one immediate run of a database schedule
5330
5330
  *
5331
- * Queues one manual run. The run is refused while the schedule is disabled or while a run is already queued.
5331
+ * Queues one manual run. The run is refused while the schedule is disabled or while a run is already queued. A reached Stop budget (or exhausted credit past its grace) refuses the run with project_budget_exceeded (402).
5332
5332
  */
5333
5333
  export const runProjectDatabaseSchedule = <ThrowOnError extends boolean = true>(
5334
5334
  parameters: {
@@ -5374,7 +5374,7 @@ export const runProjectDatabaseSchedule = <ThrowOnError extends boolean = true>(
5374
5374
  /**
5375
5375
  * Read metered database execution usage
5376
5376
  *
5377
- * Returns schedule runs and webhook attempts per time window. Only completed windows are final; the current window may still change. Windows are UTC and at most 31 days long. Rejected, skipped, event and delivery counts are reported but not billed.
5377
+ * Returns schedule runs and webhook attempts per time window. Only completed windows are final; the current window may still change. Windows are UTC and at most 31 days long. The counts are counters only; executions have no price of their own, and the awake compute time of the database is what is billed.
5378
5378
  */
5379
5379
  export const getProjectDatabaseExecutionUsage = <ThrowOnError extends boolean = true>(
5380
5380
  parameters: {
@@ -329,11 +329,6 @@ export type OrganizationCredits = {
329
329
  * Seven days after the credit was used up. Existing services and domains are retained during this grace; no automatic data deletion. Explicit project stop budgets are separate.
330
330
  */
331
331
  grace_expires_at: string | null;
332
- active_meters: Array<string>;
333
- /**
334
- * Published integer prices, newest first. A published rate is not proof of enabled billing; only active_meters names currently billed sources. Builds retain their accepted quote. Provider cost is a list-price basis before platform allowances, not an invoice.
335
- */
336
- rate_cards: Array<PublishedCreditRateCard>;
337
332
  };
338
333
 
339
334
  /**
@@ -379,23 +374,6 @@ export type OrganizationCreditUsage = {
379
374
  }>;
380
375
  };
381
376
 
382
- export type PublishedCreditRateCard = {
383
- id: string;
384
- currency: "USD";
385
- published_at: string;
386
- effective_from: string;
387
- rates: Array<{
388
- meter: string;
389
- unit: string;
390
- units_per_charge: string;
391
- /**
392
- * Provider list-cost basis for units_per_charge, in USD micro-units (1000000 equals 1 USD), before shared allowances and discounts. Null for the PGCF RAM tariff means node infrastructure costs are tracked separately and are not allocated to this customer rate; null never means zero cost.
393
- */
394
- provider_cost_micros: string | null;
395
- credit_micros: CreditMicros;
396
- }>;
397
- };
398
-
399
377
  export type SetProjectCreditBudgetRequest = {
400
378
  /**
401
379
  * The monthly budget, at most 9007199254740991 microcredits (about 9 billion credits); null removes it.
@@ -8812,6 +8790,10 @@ export type SetProjectDatabaseScheduleErrors = {
8812
8790
  * The request failed.
8813
8791
  */
8814
8792
  401: ProblemDetails;
8793
+ /**
8794
+ * The request failed.
8795
+ */
8796
+ 402: ProblemDetails;
8815
8797
  /**
8816
8798
  * The resource does not exist or is not visible to the authenticated principal.
8817
8799
  */
@@ -8946,6 +8928,10 @@ export type RunProjectDatabaseScheduleErrors = {
8946
8928
  * The request failed.
8947
8929
  */
8948
8930
  401: ProblemDetails;
8931
+ /**
8932
+ * The request failed.
8933
+ */
8934
+ 402: ProblemDetails;
8949
8935
  /**
8950
8936
  * The resource does not exist or is not visible to the authenticated principal.
8951
8937
  */