@amerged/ohmyhost-mcp 0.1.19 → 0.1.21

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.
@@ -1,5 +1,7 @@
1
1
  import type { ManagedMailCommand } from "@ohmyhost/sdk-ts";
2
2
  import { USER_API_KEY_ID } from "@ohmyhost/contracts/user-api-keys";
3
+ import { WORKOS_USER_API_KEY_PATTERN } from "@ohmyhost/workos-auth-contracts/user-api-keys";
4
+ import { isProfileName, isProfileUserId } from "./profile-store.js";
3
5
  export type ProductCliCommand =
4
6
  | {
5
7
  readonly kind: "github-connect";
@@ -125,6 +127,11 @@ export type ProductCliCommand =
125
127
  readonly organizationId: string;
126
128
  readonly credentialStore: "native";
127
129
  }
130
+ | {
131
+ readonly kind: "referral-link";
132
+ readonly organizationId: string;
133
+ readonly credentialStore: "native";
134
+ }
128
135
  | {
129
136
  readonly kind: "credits-balance";
130
137
  readonly organizationId: string;
@@ -182,8 +189,11 @@ export type ProductCliCommand =
182
189
  | {
183
190
  readonly kind: "login";
184
191
  readonly organizationId?: string;
192
+ /** The user the browser must sign in as; a different account stores nothing. */
193
+ readonly userId?: string;
185
194
  readonly credentialStore: "native";
186
195
  }
196
+ | { readonly kind: "profile-list" }
187
197
  | {
188
198
  readonly kind: "logout";
189
199
  readonly revoke: boolean;
@@ -251,6 +261,18 @@ export type ProductCliCommand =
251
261
  readonly projectId: string;
252
262
  readonly credentialStore: "native";
253
263
  }
264
+ | {
265
+ readonly kind: "project-flag-status";
266
+ readonly projectId: string;
267
+ readonly credentialStore: "native";
268
+ }
269
+ | {
270
+ readonly kind: "project-flag-set";
271
+ readonly projectId: string;
272
+ readonly enabled: boolean;
273
+ readonly idempotencyKey: string;
274
+ readonly credentialStore: "native";
275
+ }
254
276
  | {
255
277
  readonly kind: "database-write";
256
278
  readonly projectId: string;
@@ -421,6 +443,10 @@ export type ProductCliCommand =
421
443
  readonly idempotencyKey: string;
422
444
  readonly stdin: true;
423
445
  readonly wait: boolean;
446
+ /** Handed over from an OHMYHOST_TOKEN process: only a key of this account may run it. */
447
+ readonly environmentToken?: HandedAccount;
448
+ /** Handed over for a saved login: the login that runs it must belong to this account. */
449
+ readonly profileAccount?: HandedAccount;
424
450
  readonly credentialStore: "native";
425
451
  }
426
452
  | {
@@ -497,6 +523,25 @@ export type ProductCliCommand =
497
523
  readonly credentialStore: "native";
498
524
  };
499
525
 
526
+ /**
527
+ * Remove the global login selector from argv. Every authenticated command accepts it; `login` uses
528
+ * the same flag to name the login it adds. The value is an alias, never a credential.
529
+ */
530
+ export const extractProfileName = (
531
+ argv: readonly string[],
532
+ ): { readonly argv: readonly string[]; readonly profileName?: string } => {
533
+ const index = argv.indexOf("--profile-name");
534
+ if (index < 0) return { argv };
535
+ const value = argv[index + 1];
536
+ if (
537
+ value === undefined ||
538
+ !isProfileName(value) ||
539
+ argv.indexOf("--profile-name", index + 1) >= 0
540
+ )
541
+ throw new InvalidCommandError();
542
+ return { argv: [...argv.slice(0, index), ...argv.slice(index + 2)], profileName: value };
543
+ };
544
+
500
545
  export class InvalidCommandError extends Error {
501
546
  public constructor() {
502
547
  super("The command arguments are invalid.");
@@ -521,6 +566,7 @@ export type RecognizedCommandLabel =
521
566
  | "version"
522
567
  | "whoami"
523
568
  | "credits account"
569
+ | "referral link"
524
570
  | "credits balance"
525
571
  | "credits usage"
526
572
  | "budget get"
@@ -528,6 +574,7 @@ export type RecognizedCommandLabel =
528
574
  | "init"
529
575
  | "login"
530
576
  | "logout"
577
+ | "profile list"
531
578
  | "operation get"
532
579
  | "operation reconcile"
533
580
  | "token create"
@@ -548,6 +595,8 @@ export type RecognizedCommandLabel =
548
595
  | "project dev-share rotate"
549
596
  | "project dev-share revoke"
550
597
  | "project dev-access mode"
598
+ | "project flag status"
599
+ | "project flag set"
551
600
  | "project handle check"
552
601
  | "project handle set"
553
602
  | "database write"
@@ -598,6 +647,10 @@ export const recognizedCommandLabel = (argv: readonly string[]): RecognizedComma
598
647
  if (values.includes("--version")) return "version";
599
648
  while (values.length > 0) {
600
649
  const value = values.shift();
650
+ if (value === "--profile-name") {
651
+ values.shift();
652
+ continue;
653
+ }
601
654
  if (
602
655
  value === "--json" ||
603
656
  value === "--dry-run" ||
@@ -618,10 +671,12 @@ export const recognizedCommandLabel = (argv: readonly string[]): RecognizedComma
618
671
  value === "logout"
619
672
  )
620
673
  return value;
674
+ if (value === "profile" && values[0] === "list") return "profile list";
621
675
  if (value === "github" && values[0] === "connect") return "github connect";
622
676
  if (value === "github" && values[0] === "status") return "github status";
623
677
  if (value === "operation" && values[0] === "get") return "operation get";
624
678
  if (value === "credits" && values[0] === "account") return "credits account";
679
+ if (value === "referral" && values[0] === "link") return "referral link";
625
680
  if (value === "credits" && values[0] === "balance") return "credits balance";
626
681
  if (value === "billing" && values[0] === "recharge" && values[1] === "get")
627
682
  return "billing recharge get";
@@ -667,6 +722,10 @@ export const recognizedCommandLabel = (argv: readonly string[]): RecognizedComma
667
722
  return "project dev-access mode";
668
723
  if (value === "project" && values[0] === "dev-access" && values[1] === "create")
669
724
  return "project dev-access create";
725
+ if (value === "project" && values[0] === "flag" && values[1] === "status")
726
+ return "project flag status";
727
+ if (value === "project" && values[0] === "flag" && values[1] === "set")
728
+ return "project flag set";
670
729
  if (value === "database" && values[0] === "compute" && values[1] === "set")
671
730
  return "database compute set";
672
731
  if (value === "database" && values[0] === "compute" && values[1] === "get")
@@ -788,16 +847,32 @@ export const parseProductCliCommand = (argv: readonly string[]): ProductCliComma
788
847
  }
789
848
  if (dryRun) throw new InvalidCommandError();
790
849
  if (first === "login" && !revoke && !yes && !wait && !follow && !stdin) {
791
- const options = parseOptionalOptions(values, ["--organization"]);
850
+ const options = parseOptionalOptions(values, ["--organization", "--user"]);
792
851
  const organizationId = options["--organization"];
793
- if (organizationId !== undefined && !ORGANIZATION_ULID.test(organizationId))
852
+ const userId = options["--user"];
853
+ if (
854
+ (organizationId !== undefined && !ORGANIZATION_ULID.test(organizationId)) ||
855
+ (userId !== undefined && !isProfileUserId(userId))
856
+ )
794
857
  throw new InvalidCommandError();
795
858
  return {
796
859
  kind: "login",
797
860
  ...(organizationId === undefined ? {} : { organizationId }),
861
+ ...(userId === undefined ? {} : { userId }),
798
862
  credentialStore,
799
863
  };
800
864
  }
865
+ if (
866
+ first === "profile" &&
867
+ values.length === 1 &&
868
+ values[0] === "list" &&
869
+ !revoke &&
870
+ !yes &&
871
+ !wait &&
872
+ !follow &&
873
+ !stdin
874
+ )
875
+ return { kind: "profile-list" };
801
876
  if (first === "logout") {
802
877
  if (!yes && !wait && !follow && !stdin && values.length === 0)
803
878
  return { kind: "logout", revoke, credentialStore };
@@ -1030,6 +1105,12 @@ export const parseProductCliCommand = (argv: readonly string[]): ProductCliComma
1030
1105
  const options = parseOptions(values, ["--organization"]);
1031
1106
  return { kind: "credits-account", organizationId: options["--organization"], credentialStore };
1032
1107
  }
1108
+ if (first === "referral" && values[0] === "link") {
1109
+ values.shift();
1110
+ if (yes || wait || follow || stdin) throw new InvalidCommandError();
1111
+ const options = parseOptions(values, ["--organization"]);
1112
+ return { kind: "referral-link", organizationId: options["--organization"], credentialStore };
1113
+ }
1033
1114
  if (first === "credits" && values.shift() === "balance") {
1034
1115
  if (yes || wait || follow || stdin) throw new InvalidCommandError();
1035
1116
  const options = parseOptions(values, ["--organization"]);
@@ -1216,6 +1297,27 @@ export const parseProductCliCommand = (argv: readonly string[]): ProductCliComma
1216
1297
  credentialStore,
1217
1298
  };
1218
1299
  }
1300
+ if (subcommand === "flag") {
1301
+ const action = values.shift();
1302
+ if (yes || wait || follow || stdin) throw new InvalidCommandError();
1303
+ if (action === "status") {
1304
+ const options = parseOptions(values, ["--project"]);
1305
+ return { kind: "project-flag-status", projectId: options["--project"], credentialStore };
1306
+ }
1307
+ if (action === "set") {
1308
+ const options = parseOptions(values, ["--project", "--enabled", "--idempotency-key"]);
1309
+ if (options["--enabled"] !== "true" && options["--enabled"] !== "false")
1310
+ throw new InvalidCommandError();
1311
+ return {
1312
+ kind: "project-flag-set",
1313
+ projectId: options["--project"],
1314
+ enabled: options["--enabled"] === "true",
1315
+ idempotencyKey: options["--idempotency-key"],
1316
+ credentialStore,
1317
+ };
1318
+ }
1319
+ throw new InvalidCommandError();
1320
+ }
1219
1321
  if (subcommand === "dev-access" && values.shift() === "create") {
1220
1322
  if (yes || wait || follow || stdin) throw new InvalidCommandError();
1221
1323
  const options = parseOptions(values, ["--project"]);
@@ -1781,15 +1883,44 @@ export const parseProductCliCommand = (argv: readonly string[]): ProductCliComma
1781
1883
  values.shift();
1782
1884
  const name = values.shift();
1783
1885
  if (!isUserSecretName(name)) throw new InvalidCommandError();
1784
- const options = parseOptions(values, ["--project", "--environment", "--idempotency-key"]);
1886
+ const options = parseOptionalOptions(values, [
1887
+ "--project",
1888
+ "--environment",
1889
+ "--idempotency-key",
1890
+ "--token-user",
1891
+ "--token-organization",
1892
+ "--profile-user",
1893
+ "--profile-organization",
1894
+ ]);
1895
+ const projectId = options["--project"];
1896
+ const environmentId = options["--environment"];
1897
+ const idempotencyKey = options["--idempotency-key"];
1898
+ const environmentToken = handedAccount(
1899
+ options["--token-user"],
1900
+ options["--token-organization"],
1901
+ );
1902
+ const profileAccount = handedAccount(
1903
+ options["--profile-user"],
1904
+ options["--profile-organization"],
1905
+ );
1906
+ // A key form and a saved-login form contradict each other; neither wins.
1907
+ if (
1908
+ projectId === undefined ||
1909
+ environmentId === undefined ||
1910
+ idempotencyKey === undefined ||
1911
+ (environmentToken !== undefined && profileAccount !== undefined)
1912
+ )
1913
+ throw new InvalidCommandError();
1785
1914
  return {
1786
1915
  kind: "secret-set",
1787
- projectId: options["--project"],
1788
- environmentId: options["--environment"],
1916
+ projectId,
1917
+ environmentId,
1789
1918
  name,
1790
- idempotencyKey: options["--idempotency-key"],
1919
+ idempotencyKey,
1791
1920
  stdin,
1792
1921
  wait,
1922
+ ...(environmentToken === undefined ? {} : { environmentToken }),
1923
+ ...(profileAccount === undefined ? {} : { profileAccount }),
1793
1924
  credentialStore,
1794
1925
  };
1795
1926
  }
@@ -2034,6 +2165,32 @@ const accessTtlSeconds = (value: string | undefined): number => {
2034
2165
  /** A platform organization identifier as the API returns it. */
2035
2166
  const ORGANIZATION_ULID = /^[0-7][0-9A-HJKMNP-TV-Z]{25}$/u;
2036
2167
 
2168
+ /** The account a command handed to a terminal must run as; never a credential. */
2169
+ export interface HandedAccount {
2170
+ readonly userId: string;
2171
+ readonly organizationId: string;
2172
+ }
2173
+
2174
+ /**
2175
+ * A user and an organization named together, or neither. A pasted key is refused here, before any
2176
+ * message could repeat it.
2177
+ */
2178
+ const handedAccount = (
2179
+ userId: string | undefined,
2180
+ organizationId: string | undefined,
2181
+ ): HandedAccount | undefined => {
2182
+ if (userId === undefined && organizationId === undefined) return undefined;
2183
+ if (
2184
+ userId === undefined ||
2185
+ organizationId === undefined ||
2186
+ !isProfileUserId(userId) ||
2187
+ WORKOS_USER_API_KEY_PATTERN.test(userId) ||
2188
+ !ORGANIZATION_ULID.test(organizationId)
2189
+ )
2190
+ throw new InvalidCommandError();
2191
+ return { userId, organizationId };
2192
+ };
2193
+
2037
2194
  const isValue = (value: string | undefined): value is string =>
2038
2195
  value !== undefined && value.length > 0 && !value.startsWith("--");
2039
2196