@amerged/ohmyhost-mcp 0.1.40 → 0.1.42
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-OW4IDEWK.js → chunk-OD4R3WMQ.js} +6 -6
- package/dist/index.js +1 -1
- package/dist/licenses/pgcf-bridge-NOTICE +1 -1
- package/dist/stdio-main.js +1130 -167
- package/package.json +1 -1
- package/src/apps/mcp/package.json +1 -1
- package/src/apps/mcp/src/local-client.ts +221 -8
- package/src/apps/mcp/src/local-server.ts +366 -25
- package/src/apps/product-cli/package.json +1 -1
- package/src/apps/product-cli/src/cli.ts +440 -40
- package/src/apps/product-cli/src/command.ts +492 -19
- package/src/apps/product-cli/src/composition.ts +6 -3
- package/src/apps/product-cli/src/product-api.ts +366 -13
- package/src/apps/product-cli/src/runtime-contract.ts +1 -10
- package/src/packages/agent-skills/src/generated-skill-resources.ts +5 -5
- package/src/packages/contracts/package.json +5 -0
- package/src/packages/contracts/src/credit-pricing.ts +78 -50
- package/src/packages/contracts/src/database-access.ts +24 -45
- package/src/packages/contracts/src/database-events.ts +697 -0
- package/src/packages/contracts/src/feedback.ts +0 -5
- package/src/packages/contracts/src/problem-policy.ts +28 -0
- package/src/packages/postgres-bridge/NOTICE +1 -1
- package/src/packages/postgres-bridge/src/bridge.ts +0 -4
- package/src/packages/sdk-ts/package.json +1 -1
- package/src/packages/sdk-ts/src/generated/index.ts +109 -0
- package/src/packages/sdk-ts/src/generated/sdk.gen.ts +702 -10
- package/src/packages/sdk-ts/src/generated/types.gen.ts +1205 -49
|
@@ -1,23 +1,28 @@
|
|
|
1
1
|
export interface OrganizationCreditUsage {
|
|
2
2
|
organization_id: string;
|
|
3
|
-
month: string;
|
|
4
3
|
as_of: string;
|
|
5
4
|
unit: "microcredits";
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
5
|
+
/** The UTC month (`YYYY-MM`) that `projects[].used_micros` counts. */
|
|
6
|
+
period: string;
|
|
7
|
+
/** The first instant of the daily lines: the start of the oldest UTC day that is reported. */
|
|
8
|
+
from: string;
|
|
9
|
+
/** What each project has used so far in `period`. */
|
|
10
|
+
projects: { project_id: string; used_micros: string }[];
|
|
11
|
+
/** The credit-log lines of the last up to 14 UTC days, grouped by day, project and meter, oldest day first. */
|
|
12
|
+
days: {
|
|
13
|
+
day: string;
|
|
14
|
+
projects: {
|
|
15
|
+
project_id: string;
|
|
16
|
+
meters: {
|
|
17
|
+
environment_id: string | null;
|
|
18
|
+
meter: string;
|
|
19
|
+
unit: string;
|
|
20
|
+
rate_card_id: string;
|
|
21
|
+
quantity: string;
|
|
22
|
+
charged_micros: string;
|
|
23
|
+
}[];
|
|
18
24
|
}[];
|
|
19
25
|
}[];
|
|
20
|
-
next_cursor: string | null;
|
|
21
26
|
}
|
|
22
27
|
|
|
23
28
|
/** A bounded read model, not a new source of accounting or billing authorization. */
|
|
@@ -38,53 +43,77 @@ export function assertOrganizationCreditUsage(
|
|
|
38
43
|
typeof input === "string" && /^[0-7][0-9A-HJKMNP-TV-Z]{25}$/u.test(input);
|
|
39
44
|
const amount = (input: unknown) =>
|
|
40
45
|
typeof input === "string" && /^(0|[1-9][0-9]{0,37})$/u.test(input);
|
|
41
|
-
const
|
|
46
|
+
const instant = (input: unknown) =>
|
|
47
|
+
typeof input === "string" &&
|
|
48
|
+
Number.isFinite(Date.parse(input)) &&
|
|
49
|
+
new Date(input).toISOString() === input;
|
|
50
|
+
const report = record(value, "organization_id,as_of,unit,period,from,projects,days");
|
|
42
51
|
if (
|
|
43
52
|
!identifier(report["organization_id"]) ||
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
typeof report["as_of"] !== "string" ||
|
|
47
|
-
!Number.isFinite(Date.parse(report["as_of"])) ||
|
|
48
|
-
new Date(report["as_of"]).toISOString() !== report["as_of"] ||
|
|
53
|
+
!instant(report["as_of"]) ||
|
|
54
|
+
!instant(report["from"]) ||
|
|
49
55
|
report["unit"] !== "microcredits" ||
|
|
50
|
-
|
|
51
|
-
report["
|
|
52
|
-
|
|
56
|
+
typeof report["period"] !== "string" ||
|
|
57
|
+
!/^20[0-9]{2}-(0[1-9]|1[0-2])$/u.test(report["period"]) ||
|
|
58
|
+
!Array.isArray(report["projects"]) ||
|
|
59
|
+
report["projects"].length > 1000 ||
|
|
60
|
+
!Array.isArray(report["days"]) ||
|
|
61
|
+
report["days"].length > 15
|
|
53
62
|
)
|
|
54
63
|
throw new TypeError("Invalid usage report");
|
|
55
64
|
let previous = "";
|
|
56
|
-
for (const input of report["
|
|
57
|
-
const project = record(input, "project_id,
|
|
65
|
+
for (const input of report["projects"]) {
|
|
66
|
+
const project = record(input, "project_id,used_micros");
|
|
58
67
|
if (
|
|
59
68
|
!identifier(project["project_id"]) ||
|
|
60
69
|
String(project["project_id"]) <= previous ||
|
|
61
|
-
!amount(project["
|
|
62
|
-
!Array.isArray(project["meters"]) ||
|
|
63
|
-
project["meters"].length > 1000
|
|
70
|
+
!amount(project["used_micros"])
|
|
64
71
|
)
|
|
65
72
|
throw new TypeError("Invalid usage project");
|
|
66
73
|
previous = String(project["project_id"]);
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
74
|
+
}
|
|
75
|
+
let previousDay = "";
|
|
76
|
+
for (const input of report["days"]) {
|
|
77
|
+
const day = record(input, "day,projects");
|
|
78
|
+
if (
|
|
79
|
+
typeof day["day"] !== "string" ||
|
|
80
|
+
!/^20[0-9]{2}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$/u.test(day["day"]) ||
|
|
81
|
+
day["day"] <= previousDay ||
|
|
82
|
+
!Array.isArray(day["projects"]) ||
|
|
83
|
+
day["projects"].length > 1000
|
|
84
|
+
)
|
|
85
|
+
throw new TypeError("Invalid usage day");
|
|
86
|
+
previousDay = day["day"];
|
|
87
|
+
previous = "";
|
|
88
|
+
for (const projectInput of day["projects"]) {
|
|
89
|
+
const project = record(projectInput, "project_id,meters");
|
|
79
90
|
if (
|
|
80
|
-
|
|
81
|
-
|
|
91
|
+
!identifier(project["project_id"]) ||
|
|
92
|
+
String(project["project_id"]) <= previous ||
|
|
93
|
+
!Array.isArray(project["meters"]) ||
|
|
94
|
+
project["meters"].length > 1000
|
|
82
95
|
)
|
|
83
|
-
throw new TypeError("
|
|
96
|
+
throw new TypeError("Invalid usage project");
|
|
97
|
+
previous = String(project["project_id"]);
|
|
98
|
+
for (const meterInput of project["meters"]) {
|
|
99
|
+
const meter = record(
|
|
100
|
+
meterInput,
|
|
101
|
+
"environment_id,meter,unit,rate_card_id,quantity,charged_micros",
|
|
102
|
+
);
|
|
103
|
+
if (meter["environment_id"] !== null && !identifier(meter["environment_id"]))
|
|
104
|
+
throw new TypeError("Invalid usage environment");
|
|
105
|
+
for (const key of ["meter", "unit", "rate_card_id"])
|
|
106
|
+
if (typeof meter[key] !== "string" || !/^[A-Za-z0-9_.:/-]{1,128}$/u.test(meter[key]))
|
|
107
|
+
throw new TypeError("Invalid usage meter");
|
|
108
|
+
if (
|
|
109
|
+
typeof meter["quantity"] !== "string" ||
|
|
110
|
+
!/^(0|[1-9][0-9]{0,29})(\.[0-9]{1,12})?$/u.test(meter["quantity"]) ||
|
|
111
|
+
!amount(meter["charged_micros"])
|
|
112
|
+
)
|
|
113
|
+
throw new TypeError("Invalid usage amount");
|
|
114
|
+
}
|
|
84
115
|
}
|
|
85
116
|
}
|
|
86
|
-
if (report["next_cursor"] !== null && report["next_cursor"] !== previous)
|
|
87
|
-
throw new TypeError("Invalid usage cursor");
|
|
88
117
|
}
|
|
89
118
|
|
|
90
119
|
export interface PublishedCreditRateCard {
|
|
@@ -177,7 +206,7 @@ export function assertPublishedCreditRateCards(
|
|
|
177
206
|
}
|
|
178
207
|
}
|
|
179
208
|
|
|
180
|
-
/** Effective feature access and credit
|
|
209
|
+
/** Effective feature access and the credit account: the monthly and one-time credit and what the open reservations hold. */
|
|
181
210
|
export function assertOrganizationAccount(value: unknown): void {
|
|
182
211
|
if (
|
|
183
212
|
!value ||
|
|
@@ -203,10 +232,9 @@ export function assertOrganizationAccount(value: unknown): void {
|
|
|
203
232
|
for (const field of ["available_micros", "monthly_micros", "one_time_micros", "reserved_micros"])
|
|
204
233
|
if (typeof v[field] !== "string" || !/^(0|[1-9][0-9]{0,18})$/u.test(v[field] as string))
|
|
205
234
|
throw new TypeError("Invalid account credits");
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
)
|
|
235
|
+
const held = BigInt(String(v["reserved_micros"]));
|
|
236
|
+
const balance = BigInt(String(v["monthly_micros"])) + BigInt(String(v["one_time_micros"]));
|
|
237
|
+
if (BigInt(String(v["available_micros"])) !== (balance > held ? balance - held : 0n))
|
|
210
238
|
throw new TypeError("Account credit buckets do not balance");
|
|
211
239
|
for (const field of ["as_of", "paid_until", "next_expiry"]) {
|
|
212
240
|
if (field !== "as_of" && v[field] === null) continue;
|
|
@@ -112,21 +112,12 @@ export interface ProjectDatabaseAccessFields {
|
|
|
112
112
|
| "data_change"
|
|
113
113
|
| null;
|
|
114
114
|
}
|
|
115
|
-
export type ProjectDatabaseAccess = ProjectDatabaseAccessFields &
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
readonly websocket_url?: never;
|
|
122
|
-
}
|
|
123
|
-
| {
|
|
124
|
-
readonly provider: "pgcf";
|
|
125
|
-
readonly transport: "wss";
|
|
126
|
-
readonly websocket_url: string;
|
|
127
|
-
readonly database: string;
|
|
128
|
-
}
|
|
129
|
-
);
|
|
115
|
+
export type ProjectDatabaseAccess = ProjectDatabaseAccessFields & {
|
|
116
|
+
readonly provider: "pgcf";
|
|
117
|
+
readonly transport: "wss";
|
|
118
|
+
readonly websocket_url: string;
|
|
119
|
+
readonly database: string;
|
|
120
|
+
};
|
|
130
121
|
export type ProjectDatabaseAccessCredential = ProjectDatabaseAccess & {
|
|
131
122
|
readonly connection_uri: string;
|
|
132
123
|
readonly psql_command: string;
|
|
@@ -140,6 +131,9 @@ const ACCESS_KEYS = [
|
|
|
140
131
|
"mode",
|
|
141
132
|
"role_name",
|
|
142
133
|
"host",
|
|
134
|
+
"provider",
|
|
135
|
+
"transport",
|
|
136
|
+
"websocket_url",
|
|
143
137
|
"database",
|
|
144
138
|
"label",
|
|
145
139
|
"state",
|
|
@@ -177,26 +171,23 @@ export function parseProjectDatabaseAccessRequest(value: unknown): ProjectDataba
|
|
|
177
171
|
export function assertProjectDatabaseAccess(
|
|
178
172
|
value: unknown,
|
|
179
173
|
): asserts value is ProjectDatabaseAccess {
|
|
180
|
-
if (!record(value) || !exact(value,
|
|
181
|
-
throw new TypeError("Invalid database access");
|
|
174
|
+
if (!record(value) || !exact(value, ACCESS_KEYS)) throw new TypeError("Invalid database access");
|
|
182
175
|
assertAccessFields(value);
|
|
183
176
|
}
|
|
184
177
|
export function assertProjectDatabaseAccessCredential(
|
|
185
178
|
value: unknown,
|
|
186
179
|
): asserts value is ProjectDatabaseAccessCredential {
|
|
187
|
-
if (!record(value) || !exact(value, [...
|
|
180
|
+
if (!record(value) || !exact(value, [...ACCESS_KEYS, "connection_uri", "psql_command"]))
|
|
188
181
|
throw new TypeError("Invalid database access credential");
|
|
189
182
|
assertAccessFields(value);
|
|
190
183
|
const uri = value["connection_uri"];
|
|
191
184
|
if (
|
|
192
185
|
typeof uri !== "string" ||
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
).test(value["psql_command"])
|
|
199
|
-
: value["psql_command"] !== `psql '${uri}'`)
|
|
186
|
+
typeof value["psql_command"] !== "string" ||
|
|
187
|
+
!new RegExp(
|
|
188
|
+
`^ohmyhost database psql --project [0-7][0-9A-HJKMNP-TV-Z]{25} --environment ${value["environment"]} --mode ${value["mode"]}$`,
|
|
189
|
+
"u",
|
|
190
|
+
).test(value["psql_command"])
|
|
200
191
|
)
|
|
201
192
|
throw new TypeError("Invalid database access credential");
|
|
202
193
|
let parsed: URL;
|
|
@@ -206,20 +197,14 @@ export function assertProjectDatabaseAccessCredential(
|
|
|
206
197
|
throw new TypeError("Invalid database access credential");
|
|
207
198
|
}
|
|
208
199
|
if (
|
|
209
|
-
|
|
210
|
-
? !["postgres:", "postgresql:"].includes(parsed.protocol)
|
|
211
|
-
: parsed.protocol !== "postgresql:") ||
|
|
200
|
+
!["postgres:", "postgresql:"].includes(parsed.protocol) ||
|
|
212
201
|
decodeURIComponent(parsed.username) !== value["role_name"] ||
|
|
213
202
|
parsed.password === "" ||
|
|
214
203
|
parsed.hostname !== value["host"] ||
|
|
215
|
-
(
|
|
216
|
-
? parsed.port !== "" && parsed.port !== "5432"
|
|
217
|
-
: parsed.host !== value["host"]) ||
|
|
204
|
+
(parsed.port !== "" && parsed.port !== "5432") ||
|
|
218
205
|
decodeURIComponent(parsed.pathname.slice(1)) !== value["database"] ||
|
|
219
206
|
parsed.hash !== "" ||
|
|
220
|
-
|
|
221
|
-
? !["", "?sslmode=require", "?sslmode=verify-full"].includes(parsed.search)
|
|
222
|
-
: parsed.search !== "?sslmode=require")
|
|
207
|
+
!["", "?sslmode=require", "?sslmode=verify-full"].includes(parsed.search)
|
|
223
208
|
)
|
|
224
209
|
throw new TypeError("Invalid database access credential");
|
|
225
210
|
}
|
|
@@ -235,11 +220,6 @@ export function assertProjectDatabaseAccessPage(
|
|
|
235
220
|
throw new TypeError("Invalid database access page");
|
|
236
221
|
for (const item of value["items"]) assertProjectDatabaseAccess(item);
|
|
237
222
|
}
|
|
238
|
-
function accessKeys(value: Record<string, unknown>): readonly string[] {
|
|
239
|
-
return value["provider"] === "pgcf"
|
|
240
|
-
? [...ACCESS_KEYS, "provider", "transport", "websocket_url"]
|
|
241
|
-
: ACCESS_KEYS;
|
|
242
|
-
}
|
|
243
223
|
function assertAccessFields(value: Record<string, unknown>): void {
|
|
244
224
|
const revoked = value["revoked_at"],
|
|
245
225
|
reason = value["revocation_reason"];
|
|
@@ -252,12 +232,11 @@ function assertAccessFields(value: Record<string, unknown>): void {
|
|
|
252
232
|
!ACCESS_ROLE_PATTERN.test(value["role_name"]) ||
|
|
253
233
|
typeof value["host"] !== "string" ||
|
|
254
234
|
!/^[a-z0-9]([a-z0-9.-]{0,251}[a-z0-9])?$/u.test(value["host"]) ||
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
: value["database"] !== "neondb") ||
|
|
235
|
+
value["provider"] !== "pgcf" ||
|
|
236
|
+
value["transport"] !== "wss" ||
|
|
237
|
+
value["websocket_url"] !== `wss://${value["host"]}/` ||
|
|
238
|
+
typeof value["database"] !== "string" ||
|
|
239
|
+
!/^[a-z][a-z0-9]{19}$/u.test(value["database"]) ||
|
|
261
240
|
(value["label"] !== null && accessLabel(value["label"]) !== value["label"]) ||
|
|
262
241
|
!["active", "expired", "revoked"].includes(String(value["state"])) ||
|
|
263
242
|
!timestamp(value["issued_at"]) ||
|