@amerged/ohmyhost-mcp 0.1.10

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.
Files changed (72) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +4 -0
  3. package/README.md +11 -0
  4. package/THIRD_PARTY_NOTICES.md +474 -0
  5. package/dist/chunk-7MV2EVXV.js +30779 -0
  6. package/dist/index.js +9 -0
  7. package/dist/stdio-main.js +5607 -0
  8. package/package.json +32 -0
  9. package/src/apps/mcp/index.ts +30 -0
  10. package/src/apps/mcp/local-client.ts +891 -0
  11. package/src/apps/mcp/local-server.ts +1512 -0
  12. package/src/apps/mcp/stdio-main.ts +26 -0
  13. package/src/apps/product-cli/bin.ts +24 -0
  14. package/src/apps/product-cli/cli.ts +2271 -0
  15. package/src/apps/product-cli/command.ts +1811 -0
  16. package/src/apps/product-cli/composition.ts +823 -0
  17. package/src/apps/product-cli/credential-store.ts +149 -0
  18. package/src/apps/product-cli/device-token-expiry.ts +89 -0
  19. package/src/apps/product-cli/file-credential-store.ts +166 -0
  20. package/src/apps/product-cli/index.ts +23 -0
  21. package/src/apps/product-cli/product-api.ts +1082 -0
  22. package/src/apps/product-cli/project-link-store.ts +30 -0
  23. package/src/apps/product-cli/psql-session.ts +29 -0
  24. package/src/apps/product-cli/repository-init.ts +1622 -0
  25. package/src/apps/product-cli/runtime-contract.ts +1747 -0
  26. package/src/apps/product-cli/user-token-file.ts +214 -0
  27. package/src/apps/product-cli/workspace.ts +92 -0
  28. package/src/packages/agent-skills/generated-skill-resources.ts +147 -0
  29. package/src/packages/agent-skills/index.ts +37 -0
  30. package/src/packages/contracts/application-root.ts +80 -0
  31. package/src/packages/contracts/billing.ts +172 -0
  32. package/src/packages/contracts/credit-pricing.ts +214 -0
  33. package/src/packages/contracts/database-access.ts +257 -0
  34. package/src/packages/contracts/database-compute.ts +72 -0
  35. package/src/packages/contracts/database-migration-admission.ts +235 -0
  36. package/src/packages/contracts/feedback.ts +96 -0
  37. package/src/packages/contracts/framework-admission.ts +280 -0
  38. package/src/packages/contracts/framework-family.ts +18 -0
  39. package/src/packages/contracts/functions.ts +76 -0
  40. package/src/packages/contracts/index.ts +245 -0
  41. package/src/packages/contracts/operation-deployment-result.ts +165 -0
  42. package/src/packages/contracts/operation-failure.ts +150 -0
  43. package/src/packages/contracts/platform-origins.ts +5 -0
  44. package/src/packages/contracts/project-context.ts +59 -0
  45. package/src/packages/contracts/project-exports.ts +102 -0
  46. package/src/packages/contracts/user-api-keys.ts +98 -0
  47. package/src/packages/sdk-ts/generated/client/client.gen.ts +277 -0
  48. package/src/packages/sdk-ts/generated/client/index.ts +27 -0
  49. package/src/packages/sdk-ts/generated/client/types.gen.ts +217 -0
  50. package/src/packages/sdk-ts/generated/client/utils.gen.ts +316 -0
  51. package/src/packages/sdk-ts/generated/client.gen.ts +24 -0
  52. package/src/packages/sdk-ts/generated/core/auth.gen.ts +48 -0
  53. package/src/packages/sdk-ts/generated/core/bodySerializer.gen.ts +82 -0
  54. package/src/packages/sdk-ts/generated/core/params.gen.ts +178 -0
  55. package/src/packages/sdk-ts/generated/core/pathSerializer.gen.ts +171 -0
  56. package/src/packages/sdk-ts/generated/core/queryKeySerializer.gen.ts +117 -0
  57. package/src/packages/sdk-ts/generated/core/serverSentEvents.gen.ts +242 -0
  58. package/src/packages/sdk-ts/generated/core/types.gen.ts +114 -0
  59. package/src/packages/sdk-ts/generated/core/utils.gen.ts +140 -0
  60. package/src/packages/sdk-ts/generated/index.ts +578 -0
  61. package/src/packages/sdk-ts/generated/sdk.gen.ts +3440 -0
  62. package/src/packages/sdk-ts/generated/types.gen.ts +5790 -0
  63. package/src/packages/sdk-ts/index.ts +7 -0
  64. package/src/packages/workos-auth-contracts/device-flow.ts +271 -0
  65. package/src/packages/workos-auth-contracts/index.ts +8 -0
  66. package/src/packages/workos-auth-contracts/management-rpc.ts +379 -0
  67. package/src/packages/workos-auth-contracts/organizations.ts +113 -0
  68. package/src/packages/workos-auth-contracts/parsing.ts +75 -0
  69. package/src/packages/workos-auth-contracts/session-status.ts +53 -0
  70. package/src/packages/workos-auth-contracts/tokens.ts +216 -0
  71. package/src/packages/workos-auth-contracts/user-api-keys.ts +85 -0
  72. package/src/packages/workos-auth-contracts/webhooks.ts +127 -0
@@ -0,0 +1,891 @@
1
+ import {
2
+ assertProjectDatabaseAccess,
3
+ assertProjectDatabaseAccessCredential,
4
+ assertProjectDatabaseAccessPage,
5
+ assertProjectDatabaseWriteReceipt,
6
+ } from "@ohmyhost/contracts/database-access";
7
+ import { assertOrganizationAccount } from "@ohmyhost/contracts/credit-pricing";
8
+ import { assertDatabaseCompute } from "@ohmyhost/contracts/database-compute";
9
+ import {
10
+ assertProjectContext,
11
+ assertProjectNotesReceipt,
12
+ } from "@ohmyhost/contracts/project-context";
13
+ import { lstat, open } from "node:fs/promises";
14
+ import { isAbsolute } from "node:path";
15
+ import { assertProjectExport } from "@ohmyhost/contracts/project-exports";
16
+ import { parseUserApiKeyPage } from "@ohmyhost/contracts/user-api-keys";
17
+ import { parseFeedbackReceipt } from "@ohmyhost/contracts/feedback";
18
+ import { assertOrganizationCreditUsage } from "@ohmyhost/contracts/credit-pricing";
19
+ import {
20
+ assertBillingCheckout,
21
+ assertBillingPortal,
22
+ assertRecharge,
23
+ } from "@ohmyhost/contracts/billing";
24
+ import {
25
+ createProjectDevAccessTicket,
26
+ createCloudflareDnsAuthorization,
27
+ getAccountProfile,
28
+ getCloudflareDnsAuthorizationStatus,
29
+ getProjectContext,
30
+ setProjectNotes,
31
+ getDatabaseCompute,
32
+ changeDatabaseCompute,
33
+ createClient,
34
+ createProjectExport,
35
+ getProjectExport,
36
+ createUserApiKey,
37
+ listUserApiKeys,
38
+ revokeUserApiKey,
39
+ createBillingCheckout,
40
+ getBillingCheckout,
41
+ createBillingPortal,
42
+ getBillingRecharge,
43
+ configureBillingRecharge,
44
+ planPaidProjectDomain,
45
+ applyPaidProjectDomain,
46
+ getPaidProjectDomain,
47
+ deletePaidProjectDomain,
48
+ getOrganizationCredits,
49
+ getOrganizationAccount,
50
+ getOrganizationCreditUsage,
51
+ getProjectCreditBudget,
52
+ setProjectCreditBudget,
53
+ createDeployment,
54
+ createProject,
55
+ submitFeedback,
56
+ createOrganization,
57
+ configureProjectMailDomain,
58
+ deleteEnvironmentSecret,
59
+ deleteProject,
60
+ getCurrentIdentity,
61
+ getDeployment,
62
+ getOperation,
63
+ getProjectMailDomain,
64
+ getProject,
65
+ getProjectSource,
66
+ getProjectStatus,
67
+ linkProjectSource,
68
+ connectGithubOrganization,
69
+ getGithubOrganizationConnection,
70
+ listDeployments,
71
+ listEnvironmentSecrets,
72
+ listFunctionRuns,
73
+ getDeploymentLogs,
74
+ listProjects,
75
+ planDeployment,
76
+ planDeploymentRollback,
77
+ planDeploymentPromotion,
78
+ promoteDeployment,
79
+ queryProjectDatabase,
80
+ writeProjectDatabase,
81
+ createProjectDatabaseAccess,
82
+ listProjectDatabaseAccess,
83
+ revokeProjectDatabaseAccess,
84
+ planProjectDeletion,
85
+ rollbackDeployment,
86
+ reconcileOperation,
87
+ streamOperationEvents,
88
+ type DevAccessTicket,
89
+ type CloudflareDnsAuthorization,
90
+ type CloudflareDnsAuthorizationStatus,
91
+ type OhMyHostClient,
92
+ type Operation,
93
+ type OperationEvent,
94
+ } from "@ohmyhost/sdk-ts";
95
+
96
+ import type {
97
+ LocalMcpProductClient,
98
+ WorkspaceReport,
99
+ WorkspaceSelectionReport,
100
+ } from "./local-server.js";
101
+ import {
102
+ createAuthenticatedProductTransport,
103
+ prepareUserTokenFile,
104
+ ProductApiAuthenticationError,
105
+ parseDevAccessTicket,
106
+ parseCloudflareDnsAuthorization,
107
+ parseCloudflareDnsAuthorizationStatus,
108
+ parsePaidDomain,
109
+ parsePaidDomainPlan,
110
+ parseOperation,
111
+ parseAccountProfile,
112
+ parseCurrentIdentity,
113
+ chooseWorkspace,
114
+ publicWorkspace,
115
+ selectWorkspace,
116
+ selectedWorkspaceId,
117
+ InteractiveSessionRequiredError,
118
+ type AuthenticatedProductApiOverrides,
119
+ type WorkspaceSession,
120
+ } from "@ohmyhost/product-cli";
121
+
122
+ export class OperationEventsUnavailableError extends Error {
123
+ public constructor() {
124
+ super("Operation events unavailable");
125
+ this.name = "OperationEventsUnavailableError";
126
+ }
127
+ }
128
+
129
+ export async function createAuthenticatedLocalMcpClient(
130
+ signal: AbortSignal,
131
+ overrides: Omit<AuthenticatedProductApiOverrides, "productApiOrigin"> = {},
132
+ ): Promise<LocalMcpProductClient> {
133
+ try {
134
+ const transport = await createAuthenticatedProductTransport(signal, overrides);
135
+ return new GeneratedSdkLocalMcpClient(
136
+ transport.baseUrl,
137
+ transport.accessToken,
138
+ transport.fetch,
139
+ transport.workspace,
140
+ );
141
+ } catch (error) {
142
+ if (!(error instanceof ProductApiAuthenticationError)) throw error;
143
+ if (error.code === "invalid_environment_token")
144
+ throw new Error(
145
+ "invalid_environment_token. Supply a valid OHMYHOST_TOKEN or unset it before interactive login.",
146
+ );
147
+ const environment = overrides.environment?.OHMYHOST_ENVIRONMENT ?? "production";
148
+ throw new Error(
149
+ `${error.code}. Run OHMYHOST_ENVIRONMENT=${environment} ohmyhost login and retry.`,
150
+ );
151
+ }
152
+ }
153
+
154
+ export class GeneratedSdkLocalMcpClient implements LocalMcpProductClient {
155
+ readonly #client: OhMyHostClient;
156
+
157
+ readonly #workspace: WorkspaceSession | undefined;
158
+
159
+ readonly #rescope: (accessToken: string) => OhMyHostClient;
160
+
161
+ public constructor(
162
+ baseUrl: string,
163
+ accessToken: string,
164
+ fetch: typeof globalThis.fetch,
165
+ workspace?: WorkspaceSession,
166
+ ) {
167
+ this.#rescope = (token) => createClient({ auth: token, baseUrl, fetch, throwOnError: true });
168
+ this.#client = this.#rescope(accessToken);
169
+ this.#workspace = workspace;
170
+ }
171
+
172
+ public async changeDatabaseCompute(input: {
173
+ projectId: string;
174
+ environment: "dev" | "prod";
175
+ profile: "standard" | "performance";
176
+ idempotencyKey: string;
177
+ }) {
178
+ return changeDatabaseCompute(
179
+ {
180
+ project_id: input.projectId,
181
+ environment: input.environment,
182
+ profile: input.profile,
183
+ confirm: true,
184
+ "Idempotency-Key": input.idempotencyKey,
185
+ },
186
+ { client: this.#client },
187
+ );
188
+ }
189
+ public async getDatabaseCompute(input: { projectId: string; environment: "dev" | "prod" }) {
190
+ const value = await getDatabaseCompute(
191
+ { project_id: input.projectId, environment: input.environment },
192
+ { client: this.#client },
193
+ );
194
+ assertDatabaseCompute(value);
195
+ if (value.project_id !== input.projectId || value.environment !== input.environment)
196
+ throw new TypeError("Compute response scope mismatch");
197
+ return value;
198
+ }
199
+ public async getProjectContext(projectId: string) {
200
+ const result = await getProjectContext({ project_id: projectId }, { client: this.#client });
201
+ assertProjectContext(result);
202
+ if (result.project_id !== projectId) throw new TypeError("Project context scope mismatch");
203
+ return result;
204
+ }
205
+ public async setProjectNotes(input: {
206
+ projectId: string;
207
+ markdown: string;
208
+ expectedVersion: number;
209
+ idempotencyKey: string;
210
+ }) {
211
+ const result = await setProjectNotes(
212
+ {
213
+ project_id: input.projectId,
214
+ markdown: input.markdown,
215
+ expected_version: input.expectedVersion,
216
+ "Idempotency-Key": input.idempotencyKey,
217
+ },
218
+ { client: this.#client },
219
+ );
220
+ assertProjectNotesReceipt(result);
221
+ return result;
222
+ }
223
+ public async createProjectExport(
224
+ input: Parameters<LocalMcpProductClient["createProjectExport"]>[0],
225
+ ) {
226
+ const password = await readExportPasswordFile(input.passwordFile);
227
+ return createProjectExport(
228
+ { project_id: input.projectId, password, "Idempotency-Key": input.idempotencyKey },
229
+ { client: this.#client },
230
+ );
231
+ }
232
+ public async getProjectExport(input: Parameters<LocalMcpProductClient["getProjectExport"]>[0]) {
233
+ const result = await getProjectExport(
234
+ { project_id: input.projectId, export_id: input.exportId },
235
+ { client: this.#client },
236
+ );
237
+ assertProjectExport(result);
238
+ return result;
239
+ }
240
+ public async createUserApiKey(input: Parameters<LocalMcpProductClient["createUserApiKey"]>[0]) {
241
+ const file = await prepareUserTokenFile(input.outputPath);
242
+ try {
243
+ const result = await createUserApiKey(
244
+ {
245
+ organization_id: input.organizationId,
246
+ name: input.name,
247
+ "Idempotency-Key": input.idempotencyKey,
248
+ },
249
+ { client: this.#client },
250
+ );
251
+ return await file.save(result);
252
+ } finally {
253
+ await file.close();
254
+ }
255
+ }
256
+ public async listUserApiKeys(input: Parameters<LocalMcpProductClient["listUserApiKeys"]>[0]) {
257
+ return parseUserApiKeyPage(
258
+ await listUserApiKeys(
259
+ { organization_id: input.organizationId, ...(input.after ? { after: input.after } : {}) },
260
+ { client: this.#client },
261
+ ),
262
+ );
263
+ }
264
+ public async revokeUserApiKey(input: Parameters<LocalMcpProductClient["revokeUserApiKey"]>[0]) {
265
+ await revokeUserApiKey(
266
+ { organization_id: input.organizationId, key_id: input.keyId },
267
+ { client: this.#client },
268
+ );
269
+ }
270
+ public async getCurrentIdentity(signal: AbortSignal) {
271
+ const identity = await getCurrentIdentity({}, { client: this.#client });
272
+ // A session signed in before a workspace could be selected carries none, and then every
273
+ // project call answers with an empty page that reads like an empty account.
274
+ if (this.#workspace === undefined || parseCurrentIdentity(identity).organization_ids.length > 0)
275
+ return identity;
276
+ const selection = chooseWorkspace(await this.#readWorkspaces(), undefined);
277
+ if (selection.outcome !== "selected") return identity;
278
+ await selectWorkspace(this.#workspace, selection.organization, signal);
279
+ // The workspace lives in the access token, so the repaired identity needs the new one.
280
+ const scoped = (await this.#workspace.store.read())?.accessToken;
281
+ return scoped === undefined
282
+ ? identity
283
+ : getCurrentIdentity({}, { client: this.#rescope(scoped) });
284
+ }
285
+ public async planPaidDomain(input: { projectId: string; hostname: string }) {
286
+ const value = await planPaidProjectDomain(
287
+ { project_id: input.projectId, paidDomainRequest: { hostname: input.hostname } },
288
+ { client: this.#client },
289
+ );
290
+ parsePaidDomainPlan(value);
291
+ return value;
292
+ }
293
+ public async applyPaidDomain(input: {
294
+ projectId: string;
295
+ hostname: string;
296
+ idempotencyKey: string;
297
+ }) {
298
+ const value = await applyPaidProjectDomain(
299
+ {
300
+ project_id: input.projectId,
301
+ paidDomainRequest: { hostname: input.hostname },
302
+ "Idempotency-Key": input.idempotencyKey,
303
+ },
304
+ { client: this.#client },
305
+ );
306
+ parsePaidDomain(value);
307
+ return value;
308
+ }
309
+ public async getPaidDomain(projectId: string) {
310
+ const value = await getPaidProjectDomain({ project_id: projectId }, { client: this.#client });
311
+ parsePaidDomain(value);
312
+ return value;
313
+ }
314
+ public async deletePaidDomain(input: {
315
+ projectId: string;
316
+ hostname: string;
317
+ idempotencyKey: string;
318
+ }) {
319
+ const value = await deletePaidProjectDomain(
320
+ {
321
+ project_id: input.projectId,
322
+ paidDomainRequest: { hostname: input.hostname },
323
+ "Idempotency-Key": input.idempotencyKey,
324
+ },
325
+ { client: this.#client },
326
+ );
327
+ parsePaidDomain(value);
328
+ return value;
329
+ }
330
+ public async getOrganizationAccount(organizationId: string) {
331
+ const account = await getOrganizationAccount(
332
+ { organization_id: organizationId },
333
+ { client: this.#client },
334
+ );
335
+ assertOrganizationAccount(account);
336
+ return account;
337
+ }
338
+ public getOrganizationCredits(organizationId: string) {
339
+ return getOrganizationCredits({ organization_id: organizationId }, { client: this.#client });
340
+ }
341
+ public async createBillingCheckout(
342
+ input: Parameters<LocalMcpProductClient["createBillingCheckout"]>[0],
343
+ ) {
344
+ const value = await createBillingCheckout(
345
+ {
346
+ organization_id: input.organizationId,
347
+ offer: input.offer,
348
+ packs: input.packs,
349
+ "Idempotency-Key": input.idempotencyKey,
350
+ },
351
+ { client: this.#client },
352
+ );
353
+ assertBillingCheckout(value);
354
+ return value;
355
+ }
356
+ public async getBillingCheckout(
357
+ input: Parameters<LocalMcpProductClient["getBillingCheckout"]>[0],
358
+ ) {
359
+ const value = await getBillingCheckout(
360
+ { organization_id: input.organizationId, checkout_id: input.checkoutId },
361
+ { client: this.#client },
362
+ );
363
+ assertBillingCheckout(value);
364
+ return value;
365
+ }
366
+ public async getBillingRecharge(organizationId: string) {
367
+ const value = await getBillingRecharge(
368
+ { organization_id: organizationId },
369
+ { client: this.#client },
370
+ );
371
+ assertRecharge(value);
372
+ return value;
373
+ }
374
+ public async configureBillingRecharge(
375
+ input: Parameters<LocalMcpProductClient["configureBillingRecharge"]>[0],
376
+ ) {
377
+ const value = await configureBillingRecharge(
378
+ {
379
+ organization_id: input.organizationId,
380
+ "Idempotency-Key": input.idempotencyKey,
381
+ enabled: input.enabled,
382
+ monthly_limit_minor: input.monthlyLimitMinor,
383
+ consent: input.consent,
384
+ revision: input.revision,
385
+ },
386
+ { client: this.#client },
387
+ );
388
+ assertRecharge(value);
389
+ return value;
390
+ }
391
+ public async createBillingPortal(organizationId: string) {
392
+ const value = await createBillingPortal(
393
+ { organization_id: organizationId, body: {} },
394
+ { client: this.#client },
395
+ );
396
+ assertBillingPortal(value);
397
+ return value;
398
+ }
399
+ public async getOrganizationCreditUsage(
400
+ input: Parameters<LocalMcpProductClient["getOrganizationCreditUsage"]>[0],
401
+ ) {
402
+ const value = await getOrganizationCreditUsage(
403
+ {
404
+ organization_id: input.organizationId,
405
+ month: input.month,
406
+ ...(input.cursor ? { cursor: input.cursor } : {}),
407
+ },
408
+ { client: this.#client },
409
+ );
410
+ assertOrganizationCreditUsage(value);
411
+ return value;
412
+ }
413
+ public getProjectCreditBudget(projectId: string) {
414
+ return getProjectCreditBudget({ project_id: projectId }, { client: this.#client });
415
+ }
416
+ public setProjectCreditBudget(
417
+ input: Parameters<LocalMcpProductClient["setProjectCreditBudget"]>[0],
418
+ ) {
419
+ return setProjectCreditBudget(
420
+ {
421
+ project_id: input.projectId,
422
+ "Idempotency-Key": input.idempotencyKey,
423
+ setProjectCreditBudgetRequest: { amount_micros: input.amountMicros, mode: input.mode },
424
+ },
425
+ { client: this.#client },
426
+ );
427
+ }
428
+ public async createOrganization(
429
+ input: Parameters<LocalMcpProductClient["createOrganization"]>[0],
430
+ signal: AbortSignal,
431
+ ): Promise<WorkspaceSelectionReport> {
432
+ const created = await createOrganization(
433
+ {
434
+ "Idempotency-Key": input.idempotencyKey,
435
+ createOrganizationRequest: {
436
+ name: input.name,
437
+ ...(input.signupSource === undefined ? {} : { signup_source: input.signupSource }),
438
+ },
439
+ },
440
+ { client: this.#client },
441
+ );
442
+ // A created workspace only becomes usable once the access token carries it.
443
+ return this.selectOrganization(created.id, signal);
444
+ }
445
+
446
+ public async listOrganizations(): Promise<WorkspaceReport> {
447
+ const session = this.#interactiveSession();
448
+ return {
449
+ organizations: (await this.#readWorkspaces()).map(publicWorkspace),
450
+ selected: await selectedWorkspaceId(session),
451
+ };
452
+ }
453
+
454
+ public async selectOrganization(
455
+ organizationId: string,
456
+ signal: AbortSignal,
457
+ ): Promise<WorkspaceSelectionReport> {
458
+ const session = this.#interactiveSession();
459
+ const selection = chooseWorkspace(await this.#readWorkspaces(), organizationId);
460
+ if (selection.outcome !== "selected")
461
+ throw new Error(`ORGANIZATION_NOT_FOUND:${organizationId}`);
462
+ await selectWorkspace(session, selection.organization, signal);
463
+ return {
464
+ organization: publicWorkspace(selection.organization),
465
+ selected: selection.organization.id,
466
+ };
467
+ }
468
+
469
+ async #readWorkspaces() {
470
+ this.#interactiveSession();
471
+ return parseAccountProfile(await getAccountProfile({ client: this.#client }));
472
+ }
473
+
474
+ #interactiveSession(): WorkspaceSession {
475
+ if (this.#workspace === undefined) throw new InteractiveSessionRequiredError();
476
+ return this.#workspace;
477
+ }
478
+ public listProjects(input: Parameters<LocalMcpProductClient["listProjects"]>[0]) {
479
+ return listProjects(
480
+ {
481
+ ...(input.cursor === undefined ? {} : { cursor: input.cursor }),
482
+ ...(input.limit === undefined ? {} : { limit: input.limit }),
483
+ },
484
+ { client: this.#client },
485
+ );
486
+ }
487
+ public getProject(projectId: string) {
488
+ return getProject({ project_id: projectId }, { client: this.#client });
489
+ }
490
+ public async createDevAccessTicket(projectId: string): Promise<DevAccessTicket> {
491
+ const ticket = parseDevAccessTicket(
492
+ await createProjectDevAccessTicket({ project_id: projectId }, { client: this.#client }),
493
+ );
494
+ if (ticket.project_id !== projectId) throw new TypeError("Unexpected Dev access project");
495
+ return ticket;
496
+ }
497
+ public async authorizeCloudflareDns(
498
+ input: Parameters<LocalMcpProductClient["authorizeCloudflareDns"]>[0],
499
+ ): Promise<CloudflareDnsAuthorization> {
500
+ return parseCloudflareDnsAuthorization(
501
+ await createCloudflareDnsAuthorization(
502
+ {
503
+ project_id: input.projectId,
504
+ createCloudflareDnsAuthorizationRequest: { zone: input.zone },
505
+ "Idempotency-Key": input.idempotencyKey,
506
+ },
507
+ { client: this.#client },
508
+ ),
509
+ );
510
+ }
511
+ public async getCloudflareDnsStatus(
512
+ projectId: string,
513
+ ): Promise<CloudflareDnsAuthorizationStatus> {
514
+ const status = parseCloudflareDnsAuthorizationStatus(
515
+ await getCloudflareDnsAuthorizationStatus(
516
+ { project_id: projectId },
517
+ { client: this.#client },
518
+ ),
519
+ );
520
+ return { ...status, scopes: [...status.scopes] };
521
+ }
522
+ public getProjectStatus(projectId: string) {
523
+ return getProjectStatus({ project_id: projectId }, { client: this.#client });
524
+ }
525
+ public async writeDatabase(input: Parameters<LocalMcpProductClient["writeDatabase"]>[0]) {
526
+ const value = await writeProjectDatabase(
527
+ {
528
+ project_id: input.projectId,
529
+ "Idempotency-Key": input.idempotencyKey,
530
+ projectDatabaseWriteRequest: {
531
+ environment: input.environment,
532
+ statement: input.statement,
533
+ parameters: [...input.parameters],
534
+ },
535
+ },
536
+ { client: this.#client },
537
+ );
538
+ assertProjectDatabaseWriteReceipt(value);
539
+ return value;
540
+ }
541
+ public queryDatabase(input: Parameters<LocalMcpProductClient["queryDatabase"]>[0]) {
542
+ return queryProjectDatabase(
543
+ {
544
+ project_id: input.projectId,
545
+ projectDatabaseQueryRequest: {
546
+ environment: input.environment,
547
+ statement: input.statement,
548
+ parameters: [...input.parameters],
549
+ },
550
+ },
551
+ { client: this.#client },
552
+ );
553
+ }
554
+ /** The connection URI exists only in this response and is never stored by the client. */
555
+ public async createDatabaseAccess(
556
+ input: Parameters<LocalMcpProductClient["createDatabaseAccess"]>[0],
557
+ ) {
558
+ const value = await createProjectDatabaseAccess(
559
+ {
560
+ project_id: input.projectId,
561
+ projectDatabaseAccessRequest: {
562
+ environment: input.environment,
563
+ mode: input.mode,
564
+ ttl_seconds: input.ttlSeconds,
565
+ label: input.label,
566
+ },
567
+ },
568
+ { client: this.#client },
569
+ );
570
+ assertProjectDatabaseAccessCredential(value);
571
+ return value;
572
+ }
573
+ public async listDatabaseAccess(
574
+ input: Parameters<LocalMcpProductClient["listDatabaseAccess"]>[0],
575
+ ) {
576
+ const value = await listProjectDatabaseAccess(
577
+ {
578
+ project_id: input.projectId,
579
+ ...(input.environment === undefined ? {} : { environment: input.environment }),
580
+ },
581
+ { client: this.#client },
582
+ );
583
+ assertProjectDatabaseAccessPage(value);
584
+ return value;
585
+ }
586
+ public async revokeDatabaseAccess(
587
+ input: Parameters<LocalMcpProductClient["revokeDatabaseAccess"]>[0],
588
+ ) {
589
+ const value = await revokeProjectDatabaseAccess(
590
+ { project_id: input.projectId, access_id: input.accessId },
591
+ { client: this.#client },
592
+ );
593
+ assertProjectDatabaseAccess(value);
594
+ return value;
595
+ }
596
+ public planPromotion(input: Parameters<LocalMcpProductClient["planPromotion"]>[0]) {
597
+ return planDeploymentPromotion(
598
+ { project_id: input.projectId, source_deployment_id: input.sourceDeploymentId },
599
+ { client: this.#client },
600
+ );
601
+ }
602
+ public promote(input: Parameters<LocalMcpProductClient["promote"]>[0]) {
603
+ return promoteDeployment(
604
+ {
605
+ project_id: input.projectId,
606
+ source_deployment_id: input.sourceDeploymentId,
607
+ "If-Match": input.ifMatch,
608
+ "X-Confirmation-Token": input.confirmationToken,
609
+ "Idempotency-Key": input.idempotencyKey,
610
+ },
611
+ { client: this.#client },
612
+ );
613
+ }
614
+ public getSource(projectId: string) {
615
+ return getProjectSource({ project_id: projectId }, { client: this.#client });
616
+ }
617
+ public async getOperation(operationId: string): Promise<Operation> {
618
+ return parseOperation(
619
+ await getOperation({ operation_id: operationId }, { client: this.#client }),
620
+ );
621
+ }
622
+ public reconcileOperation(input: Parameters<LocalMcpProductClient["reconcileOperation"]>[0]) {
623
+ return reconcileOperation(
624
+ {
625
+ "Idempotency-Key": input.idempotencyKey,
626
+ operation_id: input.operationId,
627
+ },
628
+ { client: this.#client },
629
+ );
630
+ }
631
+ public configureMailDomain(input: Parameters<LocalMcpProductClient["configureMailDomain"]>[0]) {
632
+ return configureProjectMailDomain(
633
+ {
634
+ "Idempotency-Key": input.idempotencyKey,
635
+ project_id: input.projectId,
636
+ configureProjectMailDomainRequest: { domain: input.domain },
637
+ },
638
+ { client: this.#client },
639
+ );
640
+ }
641
+ public getMailDomain(projectId: string) {
642
+ return getProjectMailDomain({ project_id: projectId }, { client: this.#client });
643
+ }
644
+ public planDelete(projectId: string) {
645
+ return planProjectDeletion({ project_id: projectId }, { client: this.#client });
646
+ }
647
+
648
+ public async submitFeedback(input: {
649
+ report: import("@ohmyhost/sdk-ts").FeedbackSubmission;
650
+ idempotencyKey: string;
651
+ }): Promise<import("@ohmyhost/sdk-ts").FeedbackReceipt> {
652
+ return parseFeedbackReceipt(
653
+ await submitFeedback(
654
+ { "Idempotency-Key": input.idempotencyKey, feedbackSubmission: input.report },
655
+ { client: this.#client },
656
+ ),
657
+ );
658
+ }
659
+
660
+ public createProject(input: Parameters<LocalMcpProductClient["createProject"]>[0]) {
661
+ return createProject(
662
+ {
663
+ "Idempotency-Key": input.idempotencyKey,
664
+ createProjectRequest: {
665
+ organization_id: input.organizationId,
666
+ name: input.name,
667
+ ...(input.dataMode === undefined ? {} : { data_mode: input.dataMode }),
668
+ ...(input.region === undefined ? {} : { region: input.region }),
669
+ },
670
+ },
671
+ { client: this.#client },
672
+ );
673
+ }
674
+
675
+ public connectGithub(input: Parameters<LocalMcpProductClient["connectGithub"]>[0]) {
676
+ return connectGithubOrganization(
677
+ {
678
+ organization_id: input.organizationId,
679
+ "Idempotency-Key": input.idempotencyKey,
680
+ connectGithubOrganizationRequest: {},
681
+ },
682
+ { client: this.#client },
683
+ );
684
+ }
685
+
686
+ public getGithubConnection(organizationId: string) {
687
+ return getGithubOrganizationConnection(
688
+ { organization_id: organizationId },
689
+ { client: this.#client },
690
+ );
691
+ }
692
+
693
+ public linkSource(input: Parameters<LocalMcpProductClient["linkSource"]>[0]) {
694
+ return linkProjectSource(
695
+ {
696
+ "Idempotency-Key": input.idempotencyKey,
697
+ project_id: input.projectId,
698
+ linkProjectSourceRequest: {
699
+ repository_owner: input.repositoryOwner,
700
+ repository_name: input.repositoryName,
701
+ },
702
+ },
703
+ { client: this.#client },
704
+ );
705
+ }
706
+
707
+ public planDeployment(input: Parameters<LocalMcpProductClient["planDeployment"]>[0]) {
708
+ return planDeployment(
709
+ { project_id: input.projectId, planDeploymentRequest: { commit_sha: input.commitSha } },
710
+ { client: this.#client },
711
+ );
712
+ }
713
+
714
+ public createDeployment(input: Parameters<LocalMcpProductClient["createDeployment"]>[0]) {
715
+ return createDeployment(
716
+ {
717
+ "Idempotency-Key": input.idempotencyKey,
718
+ project_id: input.projectId,
719
+ createDeploymentRequest: { plan_id: input.planId },
720
+ },
721
+ { client: this.#client },
722
+ );
723
+ }
724
+
725
+ public listDeployments(input: Parameters<LocalMcpProductClient["listDeployments"]>[0]) {
726
+ return listDeployments(
727
+ {
728
+ project_id: input.projectId,
729
+ ...(input.cursor === undefined ? {} : { cursor: input.cursor }),
730
+ ...(input.limit === undefined ? {} : { limit: input.limit }),
731
+ },
732
+ { client: this.#client },
733
+ );
734
+ }
735
+
736
+ public getDeployment(input: Parameters<LocalMcpProductClient["getDeployment"]>[0]) {
737
+ return getDeployment(
738
+ { project_id: input.projectId, deployment_id: input.deploymentId },
739
+ { client: this.#client },
740
+ );
741
+ }
742
+
743
+ public async listOperationEvents(
744
+ operationId: string,
745
+ signal: AbortSignal,
746
+ maxEvents: number,
747
+ ): Promise<readonly OperationEvent[]> {
748
+ const controller = new AbortController();
749
+ const abort = () => controller.abort(signal.reason);
750
+ signal.addEventListener("abort", abort, { once: true });
751
+ if (signal.aborted) abort();
752
+ const timer = setTimeout(() => controller.abort(), 10_000);
753
+ let streamFailed = false;
754
+ try {
755
+ const result = await streamOperationEvents(
756
+ { operation_id: operationId },
757
+ {
758
+ client: this.#client,
759
+ signal: controller.signal,
760
+ sseMaxRetryAttempts: 1,
761
+ onSseError: () => {
762
+ if (!controller.signal.aborted) streamFailed = true;
763
+ },
764
+ headers: { Accept: "text/event-stream" },
765
+ },
766
+ );
767
+ const events: OperationEvent[] = [];
768
+ for await (const event of result.stream) {
769
+ events.push(event);
770
+ if (
771
+ events.length >= maxEvents ||
772
+ ["OperationSucceeded", "OperationFailed", "OperationCancelled"].includes(event.type)
773
+ ) {
774
+ controller.abort();
775
+ break;
776
+ }
777
+ }
778
+ if (streamFailed || events.length === 0) throw new OperationEventsUnavailableError();
779
+ return events;
780
+ } finally {
781
+ clearTimeout(timer);
782
+ controller.abort();
783
+ signal.removeEventListener("abort", abort);
784
+ }
785
+ }
786
+
787
+ public listEnvironmentSecrets(
788
+ input: Parameters<LocalMcpProductClient["listEnvironmentSecrets"]>[0],
789
+ ) {
790
+ return listEnvironmentSecrets(
791
+ { project_id: input.projectId, environment_id: input.environmentId },
792
+ { client: this.#client },
793
+ );
794
+ }
795
+
796
+ public getDeploymentLogs(input: Parameters<LocalMcpProductClient["getDeploymentLogs"]>[0]) {
797
+ return getDeploymentLogs(
798
+ { project_id: input.projectId, deployment_id: input.deploymentId, limit: input.limit },
799
+ { client: this.#client },
800
+ );
801
+ }
802
+
803
+ public listFunctionRuns(input: Parameters<LocalMcpProductClient["listFunctionRuns"]>[0]) {
804
+ return listFunctionRuns(
805
+ { project_id: input.projectId, environment_id: input.environmentId, limit: input.limit },
806
+ { client: this.#client },
807
+ );
808
+ }
809
+
810
+ public deleteEnvironmentSecret(
811
+ input: Parameters<LocalMcpProductClient["deleteEnvironmentSecret"]>[0],
812
+ ) {
813
+ return deleteEnvironmentSecret(
814
+ {
815
+ "Idempotency-Key": input.idempotencyKey,
816
+ project_id: input.projectId,
817
+ environment_id: input.environmentId,
818
+ secret_name: input.name,
819
+ },
820
+ { client: this.#client },
821
+ );
822
+ }
823
+
824
+ public planRollback(input: Parameters<LocalMcpProductClient["planRollback"]>[0]) {
825
+ return planDeploymentRollback(
826
+ { project_id: input.projectId, target_deployment_id: input.deploymentId },
827
+ { client: this.#client },
828
+ );
829
+ }
830
+
831
+ public rollback(input: Parameters<LocalMcpProductClient["rollback"]>[0]) {
832
+ return rollbackDeployment(
833
+ {
834
+ "Idempotency-Key": input.idempotencyKey,
835
+ "If-Match": input.ifMatch,
836
+ "X-Confirmation-Token": input.confirmationToken,
837
+ project_id: input.projectId,
838
+ target_deployment_id: input.deploymentId,
839
+ },
840
+ { client: this.#client },
841
+ );
842
+ }
843
+
844
+ public deleteProject(input: Parameters<LocalMcpProductClient["deleteProject"]>[0]) {
845
+ return deleteProject(
846
+ {
847
+ "Idempotency-Key": input.idempotencyKey,
848
+ "If-Match": input.ifMatch,
849
+ "X-Confirmation-Token": input.confirmationToken,
850
+ project_id: input.projectId,
851
+ },
852
+ { client: this.#client },
853
+ );
854
+ }
855
+ }
856
+
857
+ /** User-controlled password input only; no writes or credential persistence. */
858
+ async function readExportPasswordFile(path: string): Promise<string> {
859
+ const buffer = Buffer.alloc(1025);
860
+ let file: Awaited<ReturnType<typeof open>> | undefined;
861
+ try {
862
+ if (!isAbsolute(path)) throw new Error();
863
+ const before = await lstat(path);
864
+ if (
865
+ !before.isFile() ||
866
+ before.isSymbolicLink() ||
867
+ before.size < 1 ||
868
+ before.size > 1024 ||
869
+ (process.platform !== "win32" && (before.mode & 0o077) !== 0)
870
+ )
871
+ throw new Error();
872
+ file = await open(path, "r");
873
+ const after = await file.stat();
874
+ if (after.ino !== before.ino || after.dev !== before.dev || after.size !== before.size)
875
+ throw new Error();
876
+ const read = await file.read(buffer, 0, 1025, 0);
877
+ if (read.bytesRead < 1 || read.bytesRead > 1024) throw new Error();
878
+ const value = new TextDecoder("utf-8", { fatal: true }).decode(
879
+ buffer.subarray(0, read.bytesRead),
880
+ );
881
+ if (!value.trim()) throw new Error();
882
+ return value;
883
+ } catch {
884
+ throw new Error(
885
+ "EXPORT_PASSWORD_FILE_INVALID: Choose an existing private UTF-8 password file of 1–1024 bytes.",
886
+ );
887
+ } finally {
888
+ buffer.fill(0);
889
+ await file?.close();
890
+ }
891
+ }