@amerged/ohmyhost-mcp 0.1.15 → 0.1.18

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.
@@ -17,6 +17,51 @@ export interface FeedbackReceipt {
17
17
  readonly submitted_at: string;
18
18
  }
19
19
 
20
+ export type FeedbackState =
21
+ | "received"
22
+ | "in_review"
23
+ | "planned"
24
+ | "in_progress"
25
+ | "resolved"
26
+ | "closed";
27
+
28
+ /** One customer-visible operator update; internal notes never appear here. */
29
+ export interface FeedbackUpdate {
30
+ readonly id: string;
31
+ readonly created_at: string;
32
+ readonly status?: Exclude<FeedbackState, "received">;
33
+ /** The shipped release that contains the fix; present exactly on `resolved`. */
34
+ readonly release?: string;
35
+ readonly reply?: string;
36
+ }
37
+
38
+ export interface FeedbackStatus {
39
+ readonly id: string;
40
+ readonly organization_id: string;
41
+ readonly submitted_at: string;
42
+ readonly status: FeedbackState;
43
+ /** Latest customer-visible update across the whole history, or the submission time. */
44
+ readonly updated_at: string;
45
+ /** One page of customer-visible updates, oldest first. */
46
+ readonly history: readonly FeedbackUpdate[];
47
+ /** Pass as `cursor` to read the next page; null on the last page. */
48
+ readonly next_cursor: string | null;
49
+ }
50
+
51
+ /** Customer-visible updates per history page. */
52
+ export const FEEDBACK_HISTORY_PAGE_SIZE = 25;
53
+ export const FEEDBACK_REPLY_MAX_LENGTH = 2000;
54
+ /** A `resolved` or `closed` update must explain itself in at least this many characters. */
55
+ export const FEEDBACK_EXPLANATION_MIN_LENGTH = 20;
56
+ export const FEEDBACK_UPDATE_STATES: readonly Exclude<FeedbackState, "received">[] = [
57
+ "in_review",
58
+ "planned",
59
+ "in_progress",
60
+ "resolved",
61
+ "closed",
62
+ ];
63
+ const releasePattern = /^[A-Za-z0-9][A-Za-z0-9._:/+-]{0,63}$/u;
64
+
20
65
  const identifier = /^[0-7][0-9A-HJKMNP-TV-Z]{25}$/u;
21
66
  const fields = new Set([
22
67
  "organization_id",
@@ -30,36 +75,99 @@ const fields = new Set([
30
75
  "client_version",
31
76
  ]);
32
77
 
33
- export function parseFeedbackSubmission(value: unknown): FeedbackSubmission {
34
- if (typeof value !== "object" || value === null || Array.isArray(value))
35
- throw new TypeError("Invalid feedback");
36
- const v = value as Record<string, unknown>;
37
- if (
38
- Object.keys(v).some((key) => !fields.has(key)) ||
39
- typeof v["organization_id"] !== "string" ||
40
- !identifier.test(v["organization_id"]) ||
41
- typeof v["kind"] !== "string" ||
42
- !["bug", "issue", "feature_request"].includes(v["kind"]) ||
43
- !validText(v["title"], 160, false) ||
44
- !validText(v["description"], 8000, true)
45
- )
46
- throw new TypeError("Invalid feedback");
47
- for (const key of ["project_id", "environment_id", "operation_id"]) {
48
- if (key in v && (typeof v[key] !== "string" || !identifier.test(v[key])))
49
- throw new TypeError("Invalid feedback scope");
78
+ const metadata = /^[A-Za-z0-9][A-Za-z0-9._:/+-]{0,127}$/u;
79
+ const detailPrefix = "Feedback was not accepted:";
80
+ const detailSuffix = "Nothing was stored; correct these fields and submit again.";
81
+ // Each refusal sentence names a field and a rule, never the submitted value, so a client can show
82
+ // it verbatim: parseFeedbackSubmissionDetail recognizes exactly these sentences.
83
+ const detailSentence = new RegExp(
84
+ "^" +
85
+ detailPrefix +
86
+ "((?: (?:" +
87
+ [
88
+ "Only organization_id, project_id, environment_id, operation_id, kind, title, description, error_code and client_version are accepted; remove other fields such as attachments or logs\\.",
89
+ "organization_id must be the 26-character workspace ULID\\.",
90
+ "(?:project|environment|operation)_id must be a 26-character ULID\\.",
91
+ "environment_id and operation_id require project_id\\.",
92
+ "kind must be bug, issue or feature_request\\.",
93
+ "(?:title|description) must be a string\\.",
94
+ "(?:title|description) must not be empty\\.",
95
+ "(?:title|description) must not start or end with whitespace; trim it\\.",
96
+ "(?:title|description) has [1-9][0-9]{0,8} characters, over the (?:160|8000)-character limit; shorten it, it is never truncated\\.",
97
+ "title must not contain line breaks, tabs or other control characters\\.",
98
+ "description must not contain control characters other than tabs and line breaks, such as terminal color codes\\.",
99
+ "(?:error_code|client_version) must be 1–128 letters, digits and the characters \\._:/\\+- starting with a letter or digit\\.",
100
+ ].join("|") +
101
+ "))+) " +
102
+ detailSuffix.replace(".", "\\.") +
103
+ "$",
104
+ "u",
105
+ );
106
+
107
+ /** A refused submission whose message names each failing field and rule, never its value. */
108
+ export class FeedbackSubmissionError extends TypeError {
109
+ public constructor(problems: readonly string[]) {
110
+ super(`${detailPrefix} ${problems.join(" ")} ${detailSuffix}`);
111
+ this.name = "FeedbackSubmissionError";
50
112
  }
113
+ }
114
+
115
+ /** The refusal detail when it is exactly a feedback validation message, otherwise null. */
116
+ export function parseFeedbackSubmissionDetail(value: unknown): string | null {
117
+ return typeof value === "string" && value.length <= 2000 && detailSentence.test(value)
118
+ ? value
119
+ : null;
120
+ }
121
+
122
+ export function parseFeedbackSubmission(value: unknown): FeedbackSubmission {
123
+ const v = (
124
+ typeof value === "object" && value !== null && !Array.isArray(value) ? value : {}
125
+ ) as Record<string, unknown>;
126
+ const problems: string[] = [];
127
+ if (Object.keys(v).some((key) => !fields.has(key)))
128
+ problems.push(
129
+ "Only organization_id, project_id, environment_id, operation_id, kind, title, description, error_code and client_version are accepted; remove other fields such as attachments or logs.",
130
+ );
131
+ if (!validIdentifier(v["organization_id"]))
132
+ problems.push("organization_id must be the 26-character workspace ULID.");
133
+ for (const key of ["project_id", "environment_id", "operation_id"])
134
+ if (key in v && !validIdentifier(v[key])) problems.push(`${key} must be a 26-character ULID.`);
51
135
  if (("environment_id" in v || "operation_id" in v) && !("project_id" in v))
52
- throw new TypeError("Project scope is required");
53
- for (const key of ["error_code", "client_version"]) {
54
- if (
55
- key in v &&
56
- (typeof v[key] !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._:/+-]{0,127}$/u.test(v[key]))
57
- )
58
- throw new TypeError("Invalid feedback metadata");
59
- }
136
+ problems.push("environment_id and operation_id require project_id.");
137
+ if (typeof v["kind"] !== "string" || !["bug", "issue", "feature_request"].includes(v["kind"]))
138
+ problems.push("kind must be bug, issue or feature_request.");
139
+ problems.push(...textProblems("title", v["title"], 160, false));
140
+ problems.push(...textProblems("description", v["description"], 8000, true));
141
+ for (const key of ["error_code", "client_version"])
142
+ if (key in v && (typeof v[key] !== "string" || !metadata.test(v[key])))
143
+ problems.push(
144
+ `${key} must be 1–128 letters, digits and the characters ._:/+- starting with a letter or digit.`,
145
+ );
146
+ if (problems.length > 0) throw new FeedbackSubmissionError(problems);
60
147
  return v as unknown as FeedbackSubmission;
61
148
  }
62
149
 
150
+ /** The same rules as validText, each failure named for one field. */
151
+ function textProblems(field: string, value: unknown, max: number, multiline: boolean): string[] {
152
+ if (typeof value !== "string") return [`${field} must be a string.`];
153
+ if (value.length === 0) return [`${field} must not be empty.`];
154
+ const problems: string[] = [];
155
+ if (value !== value.trim())
156
+ problems.push(`${field} must not start or end with whitespace; trim it.`);
157
+ const length = Array.from(value).length;
158
+ if (length > max)
159
+ problems.push(
160
+ `${field} has ${String(length)} characters, over the ${String(max)}-character limit; shorten it, it is never truncated.`,
161
+ );
162
+ if (hasControlCharacter(value, multiline))
163
+ problems.push(
164
+ multiline
165
+ ? `${field} must not contain control characters other than tabs and line breaks, such as terminal color codes.`
166
+ : `${field} must not contain line breaks, tabs or other control characters.`,
167
+ );
168
+ return problems;
169
+ }
170
+
63
171
  export function parseFeedbackReceipt(value: unknown): FeedbackReceipt {
64
172
  if (typeof value !== "object" || value === null || Array.isArray(value))
65
173
  throw new TypeError("Feedback was not acknowledged");
@@ -79,18 +187,90 @@ export function parseFeedbackReceipt(value: unknown): FeedbackReceipt {
79
187
  return v as unknown as FeedbackReceipt;
80
188
  }
81
189
 
190
+ export function parseFeedbackStatus(value: unknown): FeedbackStatus {
191
+ if (typeof value !== "object" || value === null || Array.isArray(value))
192
+ throw new TypeError("Feedback status is invalid");
193
+ const v = value as Record<string, unknown>;
194
+ const history = v["history"];
195
+ if (
196
+ Object.keys(v).sort().join(",") !==
197
+ "history,id,next_cursor,organization_id,status,submitted_at,updated_at" ||
198
+ (v["next_cursor"] !== null && !validIdentifier(v["next_cursor"])) ||
199
+ !validIdentifier(v["id"]) ||
200
+ !validIdentifier(v["organization_id"]) ||
201
+ !validTimestamp(v["submitted_at"]) ||
202
+ !validTimestamp(v["updated_at"]) ||
203
+ typeof v["status"] !== "string" ||
204
+ !(
205
+ v["status"] === "received" ||
206
+ (FEEDBACK_UPDATE_STATES as readonly string[]).includes(v["status"])
207
+ ) ||
208
+ !Array.isArray(history) ||
209
+ history.length > FEEDBACK_HISTORY_PAGE_SIZE ||
210
+ (v["next_cursor"] !== null && history.length !== FEEDBACK_HISTORY_PAGE_SIZE) ||
211
+ !history.every(validUpdate)
212
+ )
213
+ throw new TypeError("Feedback status is invalid");
214
+ return v as unknown as FeedbackStatus;
215
+ }
216
+
217
+ /** Bounded, trimmed text without control characters other than tab and line breaks. */
218
+ export function validFeedbackText(value: unknown, max: number): value is string {
219
+ return validText(value, max, true);
220
+ }
221
+
222
+ function validUpdate(value: unknown): boolean {
223
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
224
+ const v = value as Record<string, unknown>;
225
+ const keys = Object.keys(v);
226
+ const status = v["status"];
227
+ return (
228
+ keys.every((key) => ["id", "created_at", "status", "release", "reply"].includes(key)) &&
229
+ validIdentifier(v["id"]) &&
230
+ validTimestamp(v["created_at"]) &&
231
+ (status === undefined ||
232
+ (typeof status === "string" &&
233
+ (FEEDBACK_UPDATE_STATES as readonly string[]).includes(status))) &&
234
+ (status !== undefined || "reply" in v) &&
235
+ ("release" in v
236
+ ? status === "resolved" &&
237
+ typeof v["release"] === "string" &&
238
+ releasePattern.test(v["release"])
239
+ : status !== "resolved") &&
240
+ (!("reply" in v) || validText(v["reply"], FEEDBACK_REPLY_MAX_LENGTH, true)) &&
241
+ ((status !== "resolved" && status !== "closed") || "reply" in v)
242
+ );
243
+ }
244
+
245
+ function validIdentifier(value: unknown): boolean {
246
+ return typeof value === "string" && identifier.test(value);
247
+ }
248
+
249
+ function validTimestamp(value: unknown): boolean {
250
+ return (
251
+ typeof value === "string" &&
252
+ /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/u.test(value) &&
253
+ Number.isFinite(Date.parse(value)) &&
254
+ new Date(value).toISOString() === value
255
+ );
256
+ }
257
+
82
258
  function validText(value: unknown, max: number, multiline: boolean): boolean {
83
259
  return (
84
260
  typeof value === "string" &&
85
261
  value === value.trim() &&
86
262
  Array.from(value).length >= 1 &&
87
263
  Array.from(value).length <= max &&
88
- !Array.from(value).some((character) => {
89
- const code = character.charCodeAt(0);
90
- return (
91
- (code < 32 && !(multiline && (code === 9 || code === 10 || code === 13))) ||
92
- (code >= 127 && code <= 159)
93
- );
94
- })
264
+ !hasControlCharacter(value, multiline)
95
265
  );
96
266
  }
267
+
268
+ function hasControlCharacter(value: string, multiline: boolean): boolean {
269
+ return Array.from(value).some((character) => {
270
+ const code = character.charCodeAt(0);
271
+ return (
272
+ (code < 32 && !(multiline && (code === 9 || code === 10 || code === 13))) ||
273
+ (code >= 127 && code <= 159)
274
+ );
275
+ });
276
+ }
@@ -22,9 +22,11 @@ export {
22
22
  createProjectExport,
23
23
  createUserApiKey,
24
24
  deleteEnvironmentSecret,
25
+ deleteManagedMailDomain,
25
26
  deletePaidProjectDomain,
26
27
  deleteProject,
27
28
  disableManagedMailReceiving,
29
+ ensureProjectDevShareLink,
28
30
  getAccountProfile,
29
31
  getBillingCheckout,
30
32
  getBillingRecharge,
@@ -34,6 +36,7 @@ export {
34
36
  getDeployment,
35
37
  getDeploymentLogs,
36
38
  getFeatureInterests,
39
+ getFeedback,
37
40
  getGithubOrganizationConnection,
38
41
  getManagedMailAttachment,
39
42
  getManagedMailDomain,
@@ -77,9 +80,12 @@ export {
77
80
  retryManagedMailMessage,
78
81
  revokeCurrentSession,
79
82
  revokeProjectDatabaseAccess,
83
+ revokeProjectDevShareLink,
80
84
  revokeUserApiKey,
81
85
  rollbackDeployment,
86
+ rotateProjectDevShareLink,
82
87
  setProjectCreditBudget,
88
+ setProjectDevAccessMode,
83
89
  setProjectNotes,
84
90
  streamDeploymentLogs,
85
91
  streamOperationEvents,
@@ -220,6 +226,11 @@ export type {
220
226
  DeleteEnvironmentSecretResponse,
221
227
  DeleteEnvironmentSecretResponses,
222
228
  DeleteEnvironmentSecretResult,
229
+ DeleteManagedMailDomainData,
230
+ DeleteManagedMailDomainError,
231
+ DeleteManagedMailDomainErrors,
232
+ DeleteManagedMailDomainResponse,
233
+ DeleteManagedMailDomainResponses,
223
234
  DeletePaidProjectDomainData,
224
235
  DeletePaidProjectDomainError,
225
236
  DeletePaidProjectDomainErrors,
@@ -239,19 +250,28 @@ export type {
239
250
  DeploymentPage,
240
251
  DeploymentPlan,
241
252
  DeploymentPlanWritable,
253
+ DevAccessState,
242
254
  DevAccessTicket,
243
255
  DisableManagedMailReceivingData,
244
256
  DisableManagedMailReceivingError,
245
257
  DisableManagedMailReceivingErrors,
246
258
  DisableManagedMailReceivingResponse,
247
259
  DisableManagedMailReceivingResponses,
260
+ EnsureProjectDevShareLinkData,
261
+ EnsureProjectDevShareLinkError,
262
+ EnsureProjectDevShareLinkErrors,
263
+ EnsureProjectDevShareLinkResponse,
264
+ EnsureProjectDevShareLinkResponses,
248
265
  EnvironmentId,
249
266
  EnvironmentSecret,
250
267
  EnvironmentSecretPage,
251
268
  FeatureVoteChoices,
252
269
  FeatureVoteState,
270
+ FeedbackId,
253
271
  FeedbackReceipt,
272
+ FeedbackStatus,
254
273
  FeedbackSubmission,
274
+ FeedbackUpdate,
255
275
  FunctionRun,
256
276
  FunctionRunPage,
257
277
  GetAccountProfileData,
@@ -299,6 +319,11 @@ export type {
299
319
  GetFeatureInterestsErrors,
300
320
  GetFeatureInterestsResponse,
301
321
  GetFeatureInterestsResponses,
322
+ GetFeedbackData,
323
+ GetFeedbackError,
324
+ GetFeedbackErrors,
325
+ GetFeedbackResponse,
326
+ GetFeedbackResponses,
302
327
  GetGithubOrganizationConnectionData,
303
328
  GetGithubOrganizationConnectionError,
304
329
  GetGithubOrganizationConnectionErrors,
@@ -450,6 +475,7 @@ export type {
450
475
  ManagedMailDisabled,
451
476
  ManagedMailDnsRecord,
452
477
  ManagedMailDomain,
478
+ ManagedMailDomainDeleted,
453
479
  ManagedMailDomainRequest,
454
480
  ManagedMailExpired,
455
481
  ManagedMailMessagePage,
@@ -575,6 +601,11 @@ export type {
575
601
  RevokeProjectDatabaseAccessErrors,
576
602
  RevokeProjectDatabaseAccessResponse,
577
603
  RevokeProjectDatabaseAccessResponses,
604
+ RevokeProjectDevShareLinkData,
605
+ RevokeProjectDevShareLinkError,
606
+ RevokeProjectDevShareLinkErrors,
607
+ RevokeProjectDevShareLinkResponse,
608
+ RevokeProjectDevShareLinkResponses,
578
609
  RevokeUserApiKeyData,
579
610
  RevokeUserApiKeyError,
580
611
  RevokeUserApiKeyErrors,
@@ -585,13 +616,24 @@ export type {
585
616
  RollbackDeploymentErrors,
586
617
  RollbackDeploymentResponse,
587
618
  RollbackDeploymentResponses,
619
+ RotateProjectDevShareLinkData,
620
+ RotateProjectDevShareLinkError,
621
+ RotateProjectDevShareLinkErrors,
622
+ RotateProjectDevShareLinkResponse,
623
+ RotateProjectDevShareLinkResponses,
588
624
  SecretName,
625
+ SetDevAccessModeRequest,
589
626
  SetProjectCreditBudgetData,
590
627
  SetProjectCreditBudgetError,
591
628
  SetProjectCreditBudgetErrors,
592
629
  SetProjectCreditBudgetRequest,
593
630
  SetProjectCreditBudgetResponse,
594
631
  SetProjectCreditBudgetResponses,
632
+ SetProjectDevAccessModeData,
633
+ SetProjectDevAccessModeError,
634
+ SetProjectDevAccessModeErrors,
635
+ SetProjectDevAccessModeResponse,
636
+ SetProjectDevAccessModeResponses,
595
637
  SetProjectNotesData,
596
638
  SetProjectNotesError,
597
639
  SetProjectNotesErrors,