@amerged/ohmyhost-cli 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,11 +1,14 @@
1
1
  import { createRequire as __ohmyhostCreateRequire } from "node:module"; const require = __ohmyhostCreateRequire(import.meta.url);
2
2
  import {
3
+ CredentialStoreIntegrityError,
3
4
  CredentialStoreUnavailableError,
5
+ LEGACY_CREDENTIAL_ACCOUNT,
4
6
  NativeNapiKeyringCredentialStore,
5
7
  __commonJS,
6
8
  __require,
7
- __toESM
8
- } from "./chunk-4HFQ54FP.js";
9
+ __toESM,
10
+ createNativeEntryFactory
11
+ } from "./chunk-7D2RFLWM.js";
9
12
 
10
13
  // node_modules/.pnpm/ajv@8.18.0/node_modules/ajv/dist/compile/codegen/code.js
11
14
  var require_code = __commonJS({
@@ -14930,7 +14933,398 @@ async function requireIgnoredTokenFile(path) {
14930
14933
  }
14931
14934
  }
14932
14935
 
14936
+ // apps/product-cli/dist/profile-store.js
14937
+ import { randomUUID } from "node:crypto";
14938
+ import { link, mkdir as mkdir2, open as open2, readFile, readdir, unlink } from "node:fs/promises";
14939
+ import { homedir } from "node:os";
14940
+ import { join } from "node:path";
14941
+ var ProfileNameConflictError = class extends Error {
14942
+ profile;
14943
+ constructor(profile) {
14944
+ super("The profile name is used by another saved login");
14945
+ this.profile = profile;
14946
+ this.name = "ProfileNameConflictError";
14947
+ }
14948
+ };
14949
+ var ProfileLimitError = class extends Error {
14950
+ saved;
14951
+ constructor(saved) {
14952
+ super("The saved logins of this environment reached their limit");
14953
+ this.saved = saved;
14954
+ this.name = "ProfileLimitError";
14955
+ }
14956
+ };
14957
+ var MAX_PROFILES = 64;
14958
+ var MAX_INDEX_ATTEMPTS = 64;
14959
+ var INDEX_FILE = /^index-([1-9][0-9]{0,14})\.json$/u;
14960
+ var SERVICE_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u;
14961
+ var PROFILE_NAME = /^[a-z0-9][a-z0-9_-]{0,62}$/u;
14962
+ var USER_ID = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/u;
14963
+ var ORGANIZATION_ID = /^[0-7][0-9A-HJKMNP-TV-Z]{25}$/u;
14964
+ var isProfileName = (value) => PROFILE_NAME.test(value);
14965
+ var isProfileUserId = (value) => USER_ID.test(value);
14966
+ var profileAccount = (profile) => `profile:${profile.userId}:${profile.organizationId ?? "none"}`;
14967
+ var sameLogin = (left, right) => profileAccount(left) === profileAccount(right);
14968
+ var profileIndexDirectory = (serviceName) => join(homedir(), ".ohmyhost", "profiles", serviceName);
14969
+ var nextProfileIndex = (current, profile, replaces) => [
14970
+ ...current.filter((saved) => !sameLogin(saved, profile) && (replaces === void 0 || !sameLogin(saved, replaces))),
14971
+ profile
14972
+ ];
14973
+ var admitProfile = (next, profile) => {
14974
+ const conflict = next.find((saved) => saved.name === profile.name && saved !== profile);
14975
+ if (conflict !== void 0)
14976
+ throw new ProfileNameConflictError(conflict);
14977
+ if (next.length > MAX_PROFILES)
14978
+ throw new ProfileLimitError(next.length - 1);
14979
+ };
14980
+ var NativeKeyringProfileRegistry = class {
14981
+ serviceName;
14982
+ #createEntry;
14983
+ #index;
14984
+ constructor(createEntry = void 0, serviceName = "ohmyhost", indexDirectory) {
14985
+ this.serviceName = serviceName;
14986
+ if (!SERVICE_NAME.test(serviceName))
14987
+ throw new CredentialStoreUnavailableError();
14988
+ this.#createEntry = createEntry ?? createNativeEntryFactory(serviceName);
14989
+ this.#index = new ProfileIndexFiles(indexDirectory ?? profileIndexDirectory(serviceName));
14990
+ }
14991
+ async list() {
14992
+ return (await this.#index.read()).profiles;
14993
+ }
14994
+ credential(profile) {
14995
+ return new NativeNapiKeyringCredentialStore(this.#createEntry, this.serviceName, profileAccount(profile));
14996
+ }
14997
+ legacy() {
14998
+ return new NativeNapiKeyringCredentialStore(this.#createEntry, this.serviceName, LEGACY_CREDENTIAL_ACCOUNT);
14999
+ }
15000
+ async save(profile, credential, replaces) {
15001
+ assertProfile(profile);
15002
+ const before = await this.list();
15003
+ admitProfile(nextProfileIndex(before, profile, replaces), profile);
15004
+ await this.credential(profile).replace(credential);
15005
+ try {
15006
+ await this.#index.update((current) => {
15007
+ const next = nextProfileIndex(current, profile, replaces);
15008
+ admitProfile(next, profile);
15009
+ return next;
15010
+ });
15011
+ } catch (error) {
15012
+ if (!before.some((saved) => sameLogin(saved, profile)))
15013
+ await this.credential(profile).clear();
15014
+ throw error;
15015
+ }
15016
+ if (replaces !== void 0 && !sameLogin(replaces, profile))
15017
+ await this.credential(replaces).clear();
15018
+ }
15019
+ async remove(profile) {
15020
+ await this.credential(profile).clear();
15021
+ await this.#index.update((current) => current.some((saved) => sameLogin(saved, profile)) ? current.filter((saved) => !sameLogin(saved, profile)) : void 0);
15022
+ }
15023
+ };
15024
+ var ProfileIndexFiles = class {
15025
+ directory;
15026
+ constructor(directory) {
15027
+ this.directory = directory;
15028
+ }
15029
+ async read() {
15030
+ for (let attempt = 0; attempt < MAX_INDEX_ATTEMPTS; attempt += 1) {
15031
+ const version2 = await this.#newest();
15032
+ if (version2 === 0)
15033
+ return { version: version2, profiles: [] };
15034
+ let serialized;
15035
+ try {
15036
+ serialized = await readFile(this.#file(version2), "utf8");
15037
+ } catch (error) {
15038
+ if (fileErrorCode(error) === "ENOENT")
15039
+ continue;
15040
+ throw new CredentialStoreUnavailableError();
15041
+ }
15042
+ return { version: version2, profiles: parseProfileIndex(serialized) };
15043
+ }
15044
+ throw new CredentialStoreUnavailableError();
15045
+ }
15046
+ /** Applies `change` to the newest index; `undefined` means nothing to change. */
15047
+ async update(change) {
15048
+ try {
15049
+ await mkdir2(this.directory, { recursive: true, mode: 448 });
15050
+ } catch {
15051
+ throw new CredentialStoreUnavailableError();
15052
+ }
15053
+ for (let attempt = 0; attempt < MAX_INDEX_ATTEMPTS; attempt += 1) {
15054
+ const { version: version2, profiles } = await this.read();
15055
+ const next = change(profiles);
15056
+ if (next === void 0)
15057
+ return;
15058
+ const temporary = join(this.directory, `.index-${randomUUID()}.tmp`);
15059
+ try {
15060
+ await writeDurably(temporary, serializeProfileIndex(next));
15061
+ await link(temporary, this.#file(version2 + 1));
15062
+ } catch (error) {
15063
+ if (fileErrorCode(error) === "EEXIST")
15064
+ continue;
15065
+ throw new CredentialStoreUnavailableError();
15066
+ } finally {
15067
+ await removeTemporary(temporary);
15068
+ }
15069
+ if (await this.#newest() !== version2 + 1)
15070
+ continue;
15071
+ await this.#prune(version2);
15072
+ return;
15073
+ }
15074
+ throw new CredentialStoreUnavailableError();
15075
+ }
15076
+ async #newest() {
15077
+ let names;
15078
+ try {
15079
+ names = await readdir(this.directory);
15080
+ } catch (error) {
15081
+ if (fileErrorCode(error) === "ENOENT")
15082
+ return 0;
15083
+ throw new CredentialStoreUnavailableError();
15084
+ }
15085
+ return names.reduce((newest, name) => Math.max(newest, indexVersion(name)), 0);
15086
+ }
15087
+ /**
15088
+ * Keeps the superseded version for readers that listed it just now; older ones go. Cleanup only:
15089
+ * a file left behind is ignored, since readers take the newest version, and another process may
15090
+ * be removing the same files at this moment.
15091
+ */
15092
+ async #prune(superseded) {
15093
+ let names;
15094
+ try {
15095
+ names = await readdir(this.directory);
15096
+ } catch {
15097
+ return;
15098
+ }
15099
+ for (const name of names) {
15100
+ const version2 = indexVersion(name);
15101
+ if (version2 === 0 || version2 >= superseded)
15102
+ continue;
15103
+ try {
15104
+ await unlink(join(this.directory, name));
15105
+ } catch {
15106
+ }
15107
+ }
15108
+ }
15109
+ #file(version2) {
15110
+ return join(this.directory, `index-${String(version2)}.json`);
15111
+ }
15112
+ };
15113
+ var indexVersion = (name) => Number(INDEX_FILE.exec(name)?.[1] ?? 0);
15114
+ var removeTemporary = async (path) => {
15115
+ try {
15116
+ await unlink(path);
15117
+ } catch {
15118
+ }
15119
+ };
15120
+ var fileErrorCode = (error) => typeof error === "object" && error !== null && "code" in error ? String(error.code) : void 0;
15121
+ var writeDurably = async (path, text) => {
15122
+ const handle = await open2(path, "wx", 384);
15123
+ try {
15124
+ await handle.writeFile(text, "utf8");
15125
+ await handle.sync();
15126
+ } finally {
15127
+ await handle.close();
15128
+ }
15129
+ };
15130
+ var serializeProfileIndex = (profiles) => JSON.stringify({
15131
+ version: 1,
15132
+ profiles: [...profiles].sort((left, right) => left.name < right.name ? -1 : 1).map((profile) => ({
15133
+ name: profile.name,
15134
+ user_id: profile.userId,
15135
+ organization_id: profile.organizationId,
15136
+ organization_name: profile.organizationName
15137
+ }))
15138
+ });
15139
+ var parseProfileIndex = (serialized) => {
15140
+ try {
15141
+ const value = JSON.parse(serialized);
15142
+ if (!isRecord(value, ["version", "profiles"]) || value["version"] !== 1)
15143
+ throw new Error("Invalid profile index");
15144
+ const entries = value["profiles"];
15145
+ if (!Array.isArray(entries) || entries.length > MAX_PROFILES)
15146
+ throw new Error("Invalid profile index");
15147
+ const profiles = entries.map((entry) => {
15148
+ if (!isRecord(entry, ["name", "user_id", "organization_id", "organization_name"]))
15149
+ throw new Error("Invalid profile");
15150
+ const profile = {
15151
+ name: entry["name"],
15152
+ userId: entry["user_id"],
15153
+ organizationId: entry["organization_id"],
15154
+ organizationName: entry["organization_name"]
15155
+ };
15156
+ assertProfile(profile);
15157
+ return profile;
15158
+ });
15159
+ if (new Set(profiles.map((profile) => profile.name)).size !== profiles.length || new Set(profiles.map(profileAccount)).size !== profiles.length)
15160
+ throw new Error("Duplicate profile");
15161
+ return profiles;
15162
+ } catch {
15163
+ throw new CredentialStoreIntegrityError();
15164
+ }
15165
+ };
15166
+ var assertProfile = (profile) => {
15167
+ if (typeof profile.name !== "string" || !PROFILE_NAME.test(profile.name) || typeof profile.userId !== "string" || !USER_ID.test(profile.userId) || !(profile.organizationId === null || typeof profile.organizationId === "string" && ORGANIZATION_ID.test(profile.organizationId)) || !(profile.organizationName === null || typeof profile.organizationName === "string" && profile.organizationName.length > 0 && profile.organizationName.length <= 128 && !new RegExp("\\p{Cc}", "u").test(profile.organizationName)) || profile.organizationId === null && profile.organizationName !== null)
15168
+ throw new CredentialStoreIntegrityError();
15169
+ };
15170
+ var isRecord = (value, keys) => typeof value === "object" && value !== null && !Array.isArray(value) && Object.keys(value).sort().join(",") === [...keys].sort().join(",");
15171
+ var selectProfile = (profiles, request) => {
15172
+ if (request.name !== void 0 && request.bound !== void 0 && request.name !== request.bound)
15173
+ return { outcome: "conflict", field: "binding", profile: null };
15174
+ const named = request.name ?? request.bound;
15175
+ if (named !== void 0) {
15176
+ const profile = profiles.find((saved) => saved.name === named);
15177
+ if (profile === void 0)
15178
+ return { outcome: "not_found" };
15179
+ if (request.organizationId !== void 0 && profile.organizationId !== request.organizationId)
15180
+ return { outcome: "conflict", field: "organization", profile };
15181
+ if (request.userId !== void 0 && profile.userId !== request.userId)
15182
+ return { outcome: "conflict", field: "user", profile };
15183
+ return { outcome: "selected", profile };
15184
+ }
15185
+ const candidates = profiles.filter((profile) => (request.organizationId === void 0 || profile.organizationId === request.organizationId) && (request.userId === void 0 || profile.userId === request.userId));
15186
+ const only = candidates[0];
15187
+ if (candidates.length === 1 && only !== void 0)
15188
+ return { outcome: "selected", profile: only };
15189
+ if (candidates.length > 1)
15190
+ return { outcome: "ambiguous", candidates };
15191
+ return profiles.length === 0 ? { outcome: "none" } : { outcome: "not_found" };
15192
+ };
15193
+ var selectProfileForOrganization = (profiles, request) => {
15194
+ const user = request.userId === void 0 ? {} : { userId: request.userId };
15195
+ if (request.name !== void 0 || request.bound !== void 0)
15196
+ return selectProfile(profiles, {
15197
+ ...request.name === void 0 ? {} : { name: request.name },
15198
+ ...request.bound === void 0 ? {} : { bound: request.bound },
15199
+ ...user
15200
+ });
15201
+ const candidates = profiles.filter((profile) => request.userId === void 0 || profile.userId === request.userId);
15202
+ const inOrganization = candidates.filter((profile) => profile.organizationId === request.organizationId);
15203
+ if (inOrganization.length > 1)
15204
+ return { outcome: "ambiguous", candidates: inOrganization };
15205
+ const unscoped = candidates.filter((profile) => profile.organizationId === null);
15206
+ const [member] = inOrganization;
15207
+ if (member !== void 0)
15208
+ return { outcome: "selected", profile: member };
15209
+ const [open5] = unscoped;
15210
+ if (unscoped.length === 1 && open5 !== void 0)
15211
+ return { outcome: "selected", profile: open5 };
15212
+ return selectProfile(profiles, user);
15213
+ };
15214
+ var defaultProfileName = (profiles, login) => {
15215
+ const same = profiles.find((profile) => profile.userId === login.userId && profile.organizationId === login.organizationId);
15216
+ if (same !== void 0)
15217
+ return same.name;
15218
+ const base = slug(login.organizationName ?? "") || (login.organizationId === null ? "account" : "workspace");
15219
+ const free = (name) => profiles.every((profile) => profile.name !== name);
15220
+ if (free(base))
15221
+ return base;
15222
+ const suffix = (login.organizationId ?? login.userId).replace(/[^A-Za-z0-9]/gu, "").slice(-6).toLowerCase();
15223
+ for (let attempt = 1; attempt <= profiles.length + 1; attempt += 1) {
15224
+ const candidate = `${base.slice(0, 48)}-${suffix}${attempt === 1 ? "" : `-${String(attempt)}`}`;
15225
+ if (free(candidate))
15226
+ return candidate;
15227
+ }
15228
+ throw new CredentialStoreIntegrityError();
15229
+ };
15230
+ var slug = (value) => value.normalize("NFKD").replace(/[\u0300-\u036f]/gu, "").toLowerCase().replace(/[^a-z0-9]+/gu, "-").replace(/^-+/u, "").slice(0, 40).replace(/-+$/u, "");
15231
+ var publicProfile = (profile) => ({
15232
+ name: profile.name,
15233
+ user_id: profile.userId,
15234
+ organization_id: profile.organizationId,
15235
+ organization_name: profile.organizationName
15236
+ });
15237
+ var profileLimitAction = (commandPrefix) => `Nothing was saved: this environment already keeps ${String(MAX_PROFILES)} saved logins, the most one computer holds. Remove one you no longer need with '${commandPrefix} logout --profile-name NAME --json' ('${commandPrefix} profile list --json' shows them), then retry.`;
15238
+ var ProfileSelectionError = class extends Error {
15239
+ code;
15240
+ exitCode;
15241
+ suggestedAction;
15242
+ constructor(code, exitCode, suggestedAction) {
15243
+ super(`${code}: ${suggestedAction}`);
15244
+ this.code = code;
15245
+ this.exitCode = exitCode;
15246
+ this.suggestedAction = suggestedAction;
15247
+ this.name = "ProfileSelectionError";
15248
+ }
15249
+ };
15250
+ var profileSelectionError = (selection, request, commandPrefix, selector) => {
15251
+ const choose = selector === "cli" ? `pass --profile-name NAME (see '${commandPrefix} profile list --json') or set OHMYHOST_PROFILE for this process` : "pass profile_name (see profile_list) or set OHMYHOST_PROFILE in this MCP server's configuration";
15252
+ const login = `${commandPrefix} login${request.organizationId === void 0 ? "" : ` --organization ${request.organizationId}`}${request.userId === void 0 ? "" : ` --user ${request.userId}`}${(request.name ?? request.bound) === void 0 ? "" : ` --profile-name ${String(request.name ?? request.bound)}`} --json`;
15253
+ if (selection.outcome === "none")
15254
+ return new ProfileSelectionError("authentication_required", 5, `No login is saved on this computer. Run '${login}'.`);
15255
+ if (selection.outcome === "not_found")
15256
+ return new ProfileSelectionError("profile_not_found", 5, `No saved login matches ${describeRequest(request)}. Run '${login}' to add it, signing in as that user; '${commandPrefix} profile list --json' shows the saved logins.`);
15257
+ if (selection.outcome === "ambiguous")
15258
+ return new ProfileSelectionError("profile_selection_required", 2, `Several saved logins could run this: ${selection.candidates.map((profile2) => profile2.name).join(", ")}. Ask the customer which account to use, then ${choose}.`);
15259
+ if (selection.field === "binding")
15260
+ return new ProfileSelectionError("profile_context_mismatch", 2, `This call names profile ${String(request.name)}, but the process is bound to OHMYHOST_PROFILE=${String(request.bound)}. Use the bound profile or start a process for the other one.`);
15261
+ const profile = selection.profile;
15262
+ return new ProfileSelectionError("profile_context_mismatch", 2, selection.field === "organization" ? `Profile ${String(profile?.name)} is signed in to organization ${String(profile?.organizationId ?? "none")}, but this targets organization ${String(request.organizationId)}. Use the profile of that organization or change the target; nothing was sent.` : `Profile ${String(profile?.name)} belongs to user ${String(profile?.userId)}, not ${String(request.userId)}. Use that user's profile; nothing was sent.`);
15263
+ };
15264
+ var describeRequest = (request) => {
15265
+ const parts = [
15266
+ ...(request.name ?? request.bound) === void 0 ? [] : [`profile ${String(request.name ?? request.bound)}`],
15267
+ ...request.organizationId === void 0 ? [] : [`organization ${request.organizationId}`],
15268
+ ...request.userId === void 0 ? [] : [`user ${request.userId}`]
15269
+ ];
15270
+ return parts.length === 0 ? "this request" : parts.join(", ");
15271
+ };
15272
+ var describeProfile = (profile) => `profile ${profile.name} (user ${profile.userId}, ${profile.organizationId === null ? "no organization" : `organization ${profile.organizationName === null ? "" : `${profile.organizationName} `}${profile.organizationId}`})`;
15273
+
15274
+ // packages/workos-auth-contracts/dist/parsing.js
15275
+ var WorkosContractError = class extends Error {
15276
+ code;
15277
+ constructor(code, message) {
15278
+ super(message);
15279
+ this.code = code;
15280
+ this.name = "WorkosContractError";
15281
+ }
15282
+ };
15283
+ var expectRecord = (value, code = "invalid_object") => {
15284
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
15285
+ throw new WorkosContractError(code, "WorkOS response must be an object");
15286
+ }
15287
+ return value;
15288
+ };
15289
+ function expectString(value, field, options = {}) {
15290
+ if (value === void 0 && options.optional === true)
15291
+ return void 0;
15292
+ if (typeof value !== "string" || value.length === 0) {
15293
+ throw new WorkosContractError("invalid_field", `WorkOS field ${field} must be a non-empty string`);
15294
+ }
15295
+ return value;
15296
+ }
15297
+ function expectPositiveInteger(value, field, options = {}) {
15298
+ if (value === void 0 && options.optional === true)
15299
+ return void 0;
15300
+ if (!Number.isSafeInteger(value) || value <= 0) {
15301
+ throw new WorkosContractError("invalid_field", `WorkOS field ${field} must be positive`);
15302
+ }
15303
+ return value;
15304
+ }
15305
+ var parseJsonObject = (body) => {
15306
+ try {
15307
+ return expectRecord(JSON.parse(body));
15308
+ } catch (error) {
15309
+ if (error instanceof WorkosContractError)
15310
+ throw error;
15311
+ throw new WorkosContractError("invalid_json", "WorkOS returned invalid JSON");
15312
+ }
15313
+ };
15314
+
15315
+ // packages/workos-auth-contracts/dist/user-api-keys.js
15316
+ var WORKOS_USER_API_KEY_PATTERN = /^sk_[A-Za-z0-9_-]{20,128}$/u;
15317
+
14933
15318
  // apps/product-cli/dist/command.js
15319
+ var extractProfileName = (argv) => {
15320
+ const index = argv.indexOf("--profile-name");
15321
+ if (index < 0)
15322
+ return { argv };
15323
+ const value = argv[index + 1];
15324
+ if (value === void 0 || !isProfileName(value) || argv.indexOf("--profile-name", index + 1) >= 0)
15325
+ throw new InvalidCommandError();
15326
+ return { argv: [...argv.slice(0, index), ...argv.slice(index + 2)], profileName: value };
15327
+ };
14934
15328
  var InvalidCommandError = class extends Error {
14935
15329
  constructor() {
14936
15330
  super("The command arguments are invalid.");
@@ -14945,11 +15339,17 @@ var recognizedCommandLabel = (argv) => {
14945
15339
  return "version";
14946
15340
  while (values.length > 0) {
14947
15341
  const value = values.shift();
15342
+ if (value === "--profile-name") {
15343
+ values.shift();
15344
+ continue;
15345
+ }
14948
15346
  if (value === "--json" || value === "--dry-run" || value === "--revoke" || value === "--yes" || value === "--wait" || value === "--follow" || value === "--stdin") {
14949
15347
  continue;
14950
15348
  }
14951
15349
  if (value === "help" || value === "version" || value === "whoami" || value === "init" || value === "login" || value === "logout")
14952
15350
  return value;
15351
+ if (value === "profile" && values[0] === "list")
15352
+ return "profile list";
14953
15353
  if (value === "github" && values[0] === "connect")
14954
15354
  return "github connect";
14955
15355
  if (value === "github" && values[0] === "status")
@@ -14958,6 +15358,8 @@ var recognizedCommandLabel = (argv) => {
14958
15358
  return "operation get";
14959
15359
  if (value === "credits" && values[0] === "account")
14960
15360
  return "credits account";
15361
+ if (value === "referral" && values[0] === "link")
15362
+ return "referral link";
14961
15363
  if (value === "credits" && values[0] === "balance")
14962
15364
  return "credits balance";
14963
15365
  if (value === "billing" && values[0] === "recharge" && values[1] === "get")
@@ -15012,6 +15414,10 @@ var recognizedCommandLabel = (argv) => {
15012
15414
  return "project dev-access mode";
15013
15415
  if (value === "project" && values[0] === "dev-access" && values[1] === "create")
15014
15416
  return "project dev-access create";
15417
+ if (value === "project" && values[0] === "flag" && values[1] === "status")
15418
+ return "project flag status";
15419
+ if (value === "project" && values[0] === "flag" && values[1] === "set")
15420
+ return "project flag set";
15015
15421
  if (value === "database" && values[0] === "compute" && values[1] === "set")
15016
15422
  return "database compute set";
15017
15423
  if (value === "database" && values[0] === "compute" && values[1] === "get")
@@ -15159,16 +15565,20 @@ var parseProductCliCommand = (argv) => {
15159
15565
  if (dryRun)
15160
15566
  throw new InvalidCommandError();
15161
15567
  if (first === "login" && !revoke && !yes && !wait && !follow && !stdin) {
15162
- const options = parseOptionalOptions(values, ["--organization"]);
15568
+ const options = parseOptionalOptions(values, ["--organization", "--user"]);
15163
15569
  const organizationId = options["--organization"];
15164
- if (organizationId !== void 0 && !ORGANIZATION_ULID.test(organizationId))
15570
+ const userId = options["--user"];
15571
+ if (organizationId !== void 0 && !ORGANIZATION_ULID.test(organizationId) || userId !== void 0 && !isProfileUserId(userId))
15165
15572
  throw new InvalidCommandError();
15166
15573
  return {
15167
15574
  kind: "login",
15168
15575
  ...organizationId === void 0 ? {} : { organizationId },
15576
+ ...userId === void 0 ? {} : { userId },
15169
15577
  credentialStore
15170
15578
  };
15171
15579
  }
15580
+ if (first === "profile" && values.length === 1 && values[0] === "list" && !revoke && !yes && !wait && !follow && !stdin)
15581
+ return { kind: "profile-list" };
15172
15582
  if (first === "logout") {
15173
15583
  if (!yes && !wait && !follow && !stdin && values.length === 0)
15174
15584
  return { kind: "logout", revoke, credentialStore };
@@ -15352,6 +15762,13 @@ var parseProductCliCommand = (argv) => {
15352
15762
  const options = parseOptions(values, ["--organization"]);
15353
15763
  return { kind: "credits-account", organizationId: options["--organization"], credentialStore };
15354
15764
  }
15765
+ if (first === "referral" && values[0] === "link") {
15766
+ values.shift();
15767
+ if (yes || wait || follow || stdin)
15768
+ throw new InvalidCommandError();
15769
+ const options = parseOptions(values, ["--organization"]);
15770
+ return { kind: "referral-link", organizationId: options["--organization"], credentialStore };
15771
+ }
15355
15772
  if (first === "credits" && values.shift() === "balance") {
15356
15773
  if (yes || wait || follow || stdin)
15357
15774
  throw new InvalidCommandError();
@@ -15548,6 +15965,28 @@ var parseProductCliCommand = (argv) => {
15548
15965
  credentialStore
15549
15966
  };
15550
15967
  }
15968
+ if (subcommand === "flag") {
15969
+ const action = values.shift();
15970
+ if (yes || wait || follow || stdin)
15971
+ throw new InvalidCommandError();
15972
+ if (action === "status") {
15973
+ const options2 = parseOptions(values, ["--project"]);
15974
+ return { kind: "project-flag-status", projectId: options2["--project"], credentialStore };
15975
+ }
15976
+ if (action === "set") {
15977
+ const options2 = parseOptions(values, ["--project", "--enabled", "--idempotency-key"]);
15978
+ if (options2["--enabled"] !== "true" && options2["--enabled"] !== "false")
15979
+ throw new InvalidCommandError();
15980
+ return {
15981
+ kind: "project-flag-set",
15982
+ projectId: options2["--project"],
15983
+ enabled: options2["--enabled"] === "true",
15984
+ idempotencyKey: options2["--idempotency-key"],
15985
+ credentialStore
15986
+ };
15987
+ }
15988
+ throw new InvalidCommandError();
15989
+ }
15551
15990
  if (subcommand === "dev-access" && values.shift() === "create") {
15552
15991
  if (yes || wait || follow || stdin)
15553
15992
  throw new InvalidCommandError();
@@ -16092,15 +16531,32 @@ var parseProductCliCommand = (argv) => {
16092
16531
  const name = values.shift();
16093
16532
  if (!isUserSecretName(name))
16094
16533
  throw new InvalidCommandError();
16095
- const options = parseOptions(values, ["--project", "--environment", "--idempotency-key"]);
16534
+ const options = parseOptionalOptions(values, [
16535
+ "--project",
16536
+ "--environment",
16537
+ "--idempotency-key",
16538
+ "--token-user",
16539
+ "--token-organization",
16540
+ "--profile-user",
16541
+ "--profile-organization"
16542
+ ]);
16543
+ const projectId = options["--project"];
16544
+ const environmentId = options["--environment"];
16545
+ const idempotencyKey = options["--idempotency-key"];
16546
+ const environmentToken2 = handedAccount(options["--token-user"], options["--token-organization"]);
16547
+ const profileAccount2 = handedAccount(options["--profile-user"], options["--profile-organization"]);
16548
+ if (projectId === void 0 || environmentId === void 0 || idempotencyKey === void 0 || environmentToken2 !== void 0 && profileAccount2 !== void 0)
16549
+ throw new InvalidCommandError();
16096
16550
  return {
16097
16551
  kind: "secret-set",
16098
- projectId: options["--project"],
16099
- environmentId: options["--environment"],
16552
+ projectId,
16553
+ environmentId,
16100
16554
  name,
16101
- idempotencyKey: options["--idempotency-key"],
16555
+ idempotencyKey,
16102
16556
  stdin,
16103
16557
  wait,
16558
+ ...environmentToken2 === void 0 ? {} : { environmentToken: environmentToken2 },
16559
+ ...profileAccount2 === void 0 ? {} : { profileAccount: profileAccount2 },
16104
16560
  credentialStore
16105
16561
  };
16106
16562
  }
@@ -16349,6 +16805,13 @@ var accessTtlSeconds = (value) => {
16349
16805
  return seconds;
16350
16806
  };
16351
16807
  var ORGANIZATION_ULID = /^[0-7][0-9A-HJKMNP-TV-Z]{25}$/u;
16808
+ var handedAccount = (userId, organizationId) => {
16809
+ if (userId === void 0 && organizationId === void 0)
16810
+ return void 0;
16811
+ if (userId === void 0 || organizationId === void 0 || !isProfileUserId(userId) || WORKOS_USER_API_KEY_PATTERN.test(userId) || !ORGANIZATION_ULID.test(organizationId))
16812
+ throw new InvalidCommandError();
16813
+ return { userId, organizationId };
16814
+ };
16352
16815
  var isValue = (value) => value !== void 0 && value.length > 0 && !value.startsWith("--");
16353
16816
  var validGithubBranchName = (value) => !(value.length < 1 || value.length > 1024 || !/^[A-Za-z0-9]/u.test(value) || value.startsWith("-") || value.startsWith("/") || value.endsWith("/") || value.endsWith(".") || value.includes("//") || value.includes("..") || value.includes("@{") || [...value].some((character) => {
16354
16817
  const code = character.codePointAt(0) ?? 0;
@@ -16357,46 +16820,6 @@ var validGithubBranchName = (value) => !(value.length < 1 || value.length > 1024
16357
16820
  var validPaidHostname = (value) => /^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,63}$/u.test(value) && !value.endsWith(".poc.aixyte.com") && value !== "omh.st" && !value.endsWith(".omh.st") && value !== "ohmyho.st" && !value.endsWith(".ohmyho.st");
16358
16821
  var isUserSecretName = (value) => isValue(value) && /^[A-Z][A-Z0-9_]{0,127}$/u.test(value) && value !== "DATABASE_URL" && value !== "HYPERDRIVE" && value !== "BETTER_AUTH_SECRET" && value !== "BETTER_AUTH_URL" && value !== "OHMYHOST_MAIL_KEY" && value !== "OHMYHOST_MAIL_GATEWAY_URL";
16359
16822
 
16360
- // apps/product-cli/dist/workspace.js
16361
- var InteractiveSessionRequiredError = class extends Error {
16362
- constructor() {
16363
- super("Workspace selection requires an interactive login");
16364
- this.name = "InteractiveSessionRequiredError";
16365
- }
16366
- };
16367
- var chooseWorkspace = (organizations, requested) => {
16368
- if (requested !== void 0) {
16369
- const match = organizations.find((organization) => organization.id === requested);
16370
- return match === void 0 ? { outcome: "not_a_member", organizations } : { outcome: "selected", organization: match };
16371
- }
16372
- if (organizations.length === 0)
16373
- return { outcome: "no_workspace" };
16374
- const only = organizations[0];
16375
- if (organizations.length === 1 && only !== void 0)
16376
- return { outcome: "selected", organization: only };
16377
- return { outcome: "choice_required", organizations };
16378
- };
16379
- var selectWorkspace = async (session, organization, signal) => {
16380
- const stored = await session.store.read();
16381
- if (stored === void 0)
16382
- throw new InteractiveSessionRequiredError();
16383
- const refreshed = await session.refresher.refresh(stored.refreshToken, signal, organization.workos_id);
16384
- await session.store.replace({
16385
- ...refreshed,
16386
- workosOrganizationId: organization.workos_id,
16387
- organizationId: organization.id
16388
- });
16389
- };
16390
- var selectedWorkspaceId = async (session) => (await session.store.read())?.organizationId ?? null;
16391
- var workspaceOf = (credential) => ({
16392
- ...credential.workosOrganizationId === void 0 ? {} : { workosOrganizationId: credential.workosOrganizationId },
16393
- ...credential.organizationId === void 0 ? {} : { organizationId: credential.organizationId }
16394
- });
16395
- var publicWorkspace = (organization) => ({
16396
- id: organization.id,
16397
- name: organization.name
16398
- });
16399
-
16400
16823
  // packages/contracts/dist/feedback.js
16401
16824
  var FEEDBACK_HISTORY_PAGE_SIZE = 25;
16402
16825
  var FEEDBACK_REPLY_MAX_LENGTH = 2e3;
@@ -16801,7 +17224,7 @@ function assertOrganizationAccount(value) {
16801
17224
  var failures = {
16802
17225
  operation_abandoned: [
16803
17226
  "The operation stopped renewing its execution lease and was ended.",
16804
- "Inspect project status and deployment diagnostics before creating a fresh plan. Keep this operation ID; the expired execution cannot resume."
17227
+ "Inspect project status and, when this operation names a deployment_id, its diagnostics with 'ohmyhost deployment logs --project PROJECT_ULID --deployment DEPLOYMENT_ULID --follow --json' or MCP deployment_logs before creating a fresh plan. Keep this operation ID; the expired execution cannot resume."
16805
17228
  ],
16806
17229
  database_write_rejected: [
16807
17230
  "The database rejected this write; it was not committed.",
@@ -16865,7 +17288,7 @@ var failures = {
16865
17288
  ],
16866
17289
  build_failed: [
16867
17290
  "The build did not produce a verified deployable artifact.",
16868
- "Inspect the deployment build diagnostics and verify the install and build commands for the admitted commit. Correct the reported failure before planning a new deployment."
17291
+ "Read the BUILD_FAILED diagnostic of this operation's deployment_id with 'ohmyhost deployment logs --project PROJECT_ULID --deployment DEPLOYMENT_ULID --follow --json' or MCP deployment_logs; its excerpt is the tail of the install and build output. Correct that failure and verify the commands for the admitted commit before planning a new deployment."
16869
17292
  ],
16870
17293
  insufficient_organization_credits: [
16871
17294
  "The organization does not have sufficient available credits for this operation.",
@@ -16881,7 +17304,7 @@ var failures = {
16881
17304
  ],
16882
17305
  runtime_candidate_failed: [
16883
17306
  "The deployed candidate failed its runtime checks.",
16884
- "Inspect deployment runtime diagnostics, required secrets and the health-check route before creating a new deployment plan."
17307
+ "Read the HEALTH_CHECK_FAILED diagnostic of this operation's deployment_id with 'ohmyhost deployment logs --project PROJECT_ULID --deployment DEPLOYMENT_ULID --follow --json' or MCP deployment_logs. Its route must answer 2xx to a cookieless GET without a redirect; status_code and, for owners, excerpt show what it answered. Fix that, often a missing secret or a redirect, then plan a new deployment."
16885
17308
  ],
16886
17309
  runtime_candidate_rejected: [
16887
17310
  "The runtime rejected the built Worker script.",
@@ -16923,7 +17346,7 @@ var PROBLEM_POLICY = {
16923
17346
  github_connection_required: {
16924
17347
  status: 409,
16925
17348
  retryable: false,
16926
- action: "Run 'ohmyhost github connect --organization ULID --idempotency-key KEY --json' for this workspace, complete its browser link, then repeat the original source link."
17349
+ action: "This workspace has no GitHub connection yet. Run 'ohmyhost github connect --organization ULID --idempotency-key KEY --json' for it, complete its browser link, then repeat the original source link. If the project belongs to another account, use that account's login instead; error.acting_as names the login this ran as."
16927
17350
  },
16928
17351
  github_connection_revoked: {
16929
17352
  status: 409,
@@ -16997,7 +17420,11 @@ var PROBLEM_POLICY = {
16997
17420
  retryable: false,
16998
17421
  action: "Run 'ohmyhost organization list --json' and select one with 'ohmyhost organization use --organization ULID --json', or create the first workspace."
16999
17422
  },
17000
- resource_not_found: { status: 404, retryable: false, action: "Check the resource identifier." },
17423
+ resource_not_found: {
17424
+ status: 404,
17425
+ retryable: false,
17426
+ action: "Check the resource identifier and the account: a project is visible only to its own organization, and error.acting_as names the login this ran as. Use the login of the account that owns it."
17427
+ },
17001
17428
  idempotency_key_reused: {
17002
17429
  status: 409,
17003
17430
  retryable: false,
@@ -17134,6 +17561,11 @@ var PROBLEM_POLICY = {
17134
17561
  retryable: false,
17135
17562
  action: "Finish database provisioning, then request the export again. No backup was accepted."
17136
17563
  },
17564
+ powered_by_flag_required: {
17565
+ status: 409,
17566
+ retryable: false,
17567
+ action: "Keep the flag on, remove the custom domain with 'ohmyhost domain paid delete', or buy Paid, then switch the flag off."
17568
+ },
17137
17569
  rate_limited: { status: 429, retryable: true, action: "Retry after the server delay." },
17138
17570
  insufficient_organization_credits: {
17139
17571
  status: 402,
@@ -17148,7 +17580,7 @@ var PROBLEM_POLICY = {
17148
17580
  paid_plan_required: {
17149
17581
  status: 402,
17150
17582
  retryable: false,
17151
- action: "Ask the organization Owner to activate Paid through billing checkout, then retry. Top-ups and signup credits do not activate Paid."
17583
+ action: "Ask the organization Owner to activate Paid through billing checkout, then retry. Top-ups and signup credits do not activate Paid. For a website domain alone, a Free workspace may instead show the powered-by flag on the project."
17152
17584
  },
17153
17585
  interactive_login_required: {
17154
17586
  status: 403,
@@ -17366,6 +17798,18 @@ var parseDevAccessState = (value) => {
17366
17798
  throw new ResponseContractError();
17367
17799
  return input;
17368
17800
  };
17801
+ var parsePoweredByFlag = (value) => {
17802
+ const input = exactRecord2(value, ["project_id", "enabled", "enabled_at"]);
17803
+ if (!isUlid(input["project_id"]) || typeof input["enabled"] !== "boolean" || (input["enabled"] ? !isIsoInstant(input["enabled_at"]) : input["enabled_at"] !== null))
17804
+ throw new ResponseContractError();
17805
+ return input;
17806
+ };
17807
+ var parseOrganizationReferral = (value) => {
17808
+ const input = exactRecord2(value, ["organization_id", "referral_url"]);
17809
+ if (!isUlid(input["organization_id"]) || input["referral_url"] !== `https://ohmyho.st/?r=ref-${String(input["organization_id"]).toLowerCase()}`)
17810
+ throw new ResponseContractError();
17811
+ return input;
17812
+ };
17369
17813
  var parseDevAccessTicket = (value) => {
17370
17814
  const input = exactRecord2(value, [
17371
17815
  "ticket_id",
@@ -17488,7 +17932,7 @@ var parseAccountProfile = (value) => {
17488
17932
  });
17489
17933
  if (new Set(workspaces.map((workspace) => workspace.id)).size !== organizationIds.length)
17490
17934
  throw new ResponseContractError();
17491
- return workspaces;
17935
+ return { userId: input["user_id"], organizations: workspaces };
17492
17936
  };
17493
17937
  var parseProject = (value) => {
17494
17938
  const input = exactRecord2(value, [
@@ -17825,6 +18269,7 @@ var parseSafeProblem = (value) => {
17825
18269
  var parseOperation = (value) => {
17826
18270
  const input = exactRecord2(value, [
17827
18271
  "blocking_operation_id",
18272
+ "deployment_id",
17828
18273
  "id",
17829
18274
  "state",
17830
18275
  "created_at",
@@ -17840,9 +18285,10 @@ var parseOperation = (value) => {
17840
18285
  const updatedAt = input["updated_at"];
17841
18286
  const result = input["result"];
17842
18287
  const blockingOperationId = input["blocking_operation_id"];
17843
- if (blockingOperationId !== void 0 && (state !== "queued" || !isUlid(blockingOperationId) || blockingOperationId === id3))
18288
+ const deploymentId = input["deployment_id"];
18289
+ if (blockingOperationId !== void 0 && (state !== "queued" || !isUlid(blockingOperationId) || blockingOperationId === id3) || deploymentId !== void 0 && !isUlid(deploymentId))
17844
18290
  throw new ResponseContractError();
17845
- if (!isUlid(id3) || !isOperationState(state) || !isIsoInstant(createdAt) || !isIsoInstant(updatedAt) || result !== void 0 && !isRecord(result)) {
18291
+ if (!isUlid(id3) || !isOperationState(state) || !isIsoInstant(createdAt) || !isIsoInstant(updatedAt) || result !== void 0 && !isRecord2(result)) {
17846
18292
  throw new ResponseContractError();
17847
18293
  }
17848
18294
  let progress;
@@ -17887,6 +18333,7 @@ var parseOperation = (value) => {
17887
18333
  created_at: createdAt,
17888
18334
  updated_at: updatedAt,
17889
18335
  ...blockingOperationId === void 0 ? {} : { blocking_operation_id: blockingOperationId },
18336
+ ...deploymentId === void 0 ? {} : { deployment_id: deploymentId },
17890
18337
  ...progress === void 0 ? {} : { progress },
17891
18338
  ...result === void 0 ? {} : { result },
17892
18339
  ...error === void 0 ? {} : { error },
@@ -17903,12 +18350,12 @@ function parseOperationFailure(value) {
17903
18350
  return safe;
17904
18351
  }
17905
18352
  var exactRecord2 = (value, allowed) => {
17906
- if (!isRecord(value) || Object.keys(value).some((key) => !allowed.includes(key))) {
18353
+ if (!isRecord2(value) || Object.keys(value).some((key) => !allowed.includes(key))) {
17907
18354
  throw new ResponseContractError();
17908
18355
  }
17909
18356
  return value;
17910
18357
  };
17911
- var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
18358
+ var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
17912
18359
  var boundedString = (value, minimum, maximum) => typeof value === "string" && value.length >= minimum && value.length <= maximum;
17913
18360
  var isUlid = (value) => typeof value === "string" && /^[0-9A-HJKMNP-TV-Z]{26}$/u.test(value);
17914
18361
  var isIsoInstant = (value) => typeof value === "string" && value.length <= 64 && Number.isFinite(Date.parse(value));
@@ -17936,8 +18383,81 @@ var isPaidValidationRecord = (value) => {
17936
18383
  };
17937
18384
  var isHttpsUrlValue = (value) => typeof value === "string" && isHttpsUrl(value);
17938
18385
 
18386
+ // apps/product-cli/dist/workspace.js
18387
+ var InteractiveSessionRequiredError = class extends Error {
18388
+ constructor() {
18389
+ super("Workspace selection requires an interactive login");
18390
+ this.name = "InteractiveSessionRequiredError";
18391
+ }
18392
+ };
18393
+ var ProfileOrganizationFixedError = class extends Error {
18394
+ profile;
18395
+ organization;
18396
+ constructor(profile, organization) {
18397
+ super("The saved login is bound to another organization");
18398
+ this.profile = profile;
18399
+ this.organization = organization;
18400
+ this.name = "ProfileOrganizationFixedError";
18401
+ }
18402
+ };
18403
+ var AccountVerificationError = class extends Error {
18404
+ constructor() {
18405
+ super("The signed-in account does not match the requested one");
18406
+ this.name = "AccountVerificationError";
18407
+ }
18408
+ };
18409
+ var chooseWorkspace = (organizations, requested) => {
18410
+ if (requested !== void 0) {
18411
+ const match = organizations.find((organization) => organization.id === requested);
18412
+ return match === void 0 ? { outcome: "not_a_member", organizations } : { outcome: "selected", organization: match };
18413
+ }
18414
+ if (organizations.length === 0)
18415
+ return { outcome: "no_workspace" };
18416
+ const only = organizations[0];
18417
+ if (organizations.length === 1 && only !== void 0)
18418
+ return { outcome: "selected", organization: only };
18419
+ return { outcome: "choice_required", organizations };
18420
+ };
18421
+ var verifiedUserId = (identity) => {
18422
+ const actor = parseCurrentIdentity(identity).actor_id;
18423
+ if (!actor.startsWith("user:") || actor.length === 5)
18424
+ throw new AccountVerificationError();
18425
+ return actor.slice(5);
18426
+ };
18427
+ var assertVerifiedLogin = (identity, userId, organizationId) => {
18428
+ const organizations = parseCurrentIdentity(identity).organization_ids;
18429
+ if (verifiedUserId(identity) !== userId || (organizationId === null ? organizations.length !== 0 : organizations.length !== 1 || organizations[0] !== organizationId))
18430
+ throw new AccountVerificationError();
18431
+ };
18432
+ var selectWorkspace = async (session, organization, signal, identity) => {
18433
+ if (session.profile.organizationId === organization.id)
18434
+ return session.profile;
18435
+ if (session.profile.organizationId !== null)
18436
+ throw new ProfileOrganizationFixedError(session.profile, organization);
18437
+ const stored = await session.store.read();
18438
+ if (stored === void 0)
18439
+ throw new InteractiveSessionRequiredError();
18440
+ const refreshed = await session.refresher.refresh(stored.refreshToken, signal, organization.workos_id);
18441
+ assertVerifiedLogin(await identity(refreshed.accessToken), session.profile.userId, organization.id);
18442
+ const bound = {
18443
+ ...session.profile,
18444
+ organizationId: organization.id,
18445
+ organizationName: organization.name
18446
+ };
18447
+ await session.profiles.save(bound, { ...refreshed, workosOrganizationId: organization.workos_id, organizationId: organization.id }, session.profile);
18448
+ return bound;
18449
+ };
18450
+ var workspaceOf = (credential) => ({
18451
+ ...credential.workosOrganizationId === void 0 ? {} : { workosOrganizationId: credential.workosOrganizationId },
18452
+ ...credential.organizationId === void 0 ? {} : { organizationId: credential.organizationId }
18453
+ });
18454
+ var publicWorkspace = (organization) => ({
18455
+ id: organization.id,
18456
+ name: organization.name
18457
+ });
18458
+
17939
18459
  // apps/product-cli/dist/cli.js
17940
- import { open as open2 } from "node:fs/promises";
18460
+ import { open as open3 } from "node:fs/promises";
17941
18461
 
17942
18462
  // packages/contracts/dist/database-access.js
17943
18463
  function parseProjectDatabaseWrite(value) {
@@ -18089,7 +18609,7 @@ function exact2(v, keys) {
18089
18609
  // apps/product-cli/package.json
18090
18610
  var package_default = {
18091
18611
  name: "@ohmyhost/product-cli",
18092
- version: "0.1.19",
18612
+ version: "0.1.21",
18093
18613
  private: true,
18094
18614
  ohmyhost: {
18095
18615
  deployment: "production",
@@ -18253,52 +18773,11 @@ function assertRecharge(value) {
18253
18773
  "payment_required",
18254
18774
  "monthly_limit",
18255
18775
  "needs_reconciliation",
18256
- "tax_required"
18257
- ].includes(v["status"]) || v["setup_url"] !== null && !stripeUrl(v["setup_url"], "checkout.stripe.com") || v["invoice_url"] !== null && !stripeUrl(v["invoice_url"], "invoice.stripe.com"))
18258
- throw new Error("billing_response_invalid");
18259
- assertBillingTaxIssue(v["billing_issue"]);
18260
- }
18261
-
18262
- // packages/workos-auth-contracts/dist/parsing.js
18263
- var WorkosContractError = class extends Error {
18264
- code;
18265
- constructor(code, message) {
18266
- super(message);
18267
- this.code = code;
18268
- this.name = "WorkosContractError";
18269
- }
18270
- };
18271
- var expectRecord = (value, code = "invalid_object") => {
18272
- if (typeof value !== "object" || value === null || Array.isArray(value)) {
18273
- throw new WorkosContractError(code, "WorkOS response must be an object");
18274
- }
18275
- return value;
18276
- };
18277
- function expectString(value, field, options = {}) {
18278
- if (value === void 0 && options.optional === true)
18279
- return void 0;
18280
- if (typeof value !== "string" || value.length === 0) {
18281
- throw new WorkosContractError("invalid_field", `WorkOS field ${field} must be a non-empty string`);
18282
- }
18283
- return value;
18284
- }
18285
- function expectPositiveInteger(value, field, options = {}) {
18286
- if (value === void 0 && options.optional === true)
18287
- return void 0;
18288
- if (!Number.isSafeInteger(value) || value <= 0) {
18289
- throw new WorkosContractError("invalid_field", `WorkOS field ${field} must be positive`);
18290
- }
18291
- return value;
18292
- }
18293
- var parseJsonObject = (body) => {
18294
- try {
18295
- return expectRecord(JSON.parse(body));
18296
- } catch (error) {
18297
- if (error instanceof WorkosContractError)
18298
- throw error;
18299
- throw new WorkosContractError("invalid_json", "WorkOS returned invalid JSON");
18300
- }
18301
- };
18776
+ "tax_required"
18777
+ ].includes(v["status"]) || v["setup_url"] !== null && !stripeUrl(v["setup_url"], "checkout.stripe.com") || v["invoice_url"] !== null && !stripeUrl(v["invoice_url"], "invoice.stripe.com"))
18778
+ throw new Error("billing_response_invalid");
18779
+ assertBillingTaxIssue(v["billing_issue"]);
18780
+ }
18302
18781
 
18303
18782
  // apps/product-cli/dist/device-token-expiry.js
18304
18783
  var DeviceTokenExpiryError = class extends Error {
@@ -18420,14 +18899,16 @@ var PsqlUnavailableError = class extends Error {
18420
18899
  var CLI_VERSION = package_default.version;
18421
18900
  var HELP_USAGE = Object.freeze({
18422
18901
  init: "ohmyhost init [--directory PATH] [--root PATH] [--project SLUG] [--region us|eu] [--dry-run] --json (pass the project's hosting region so storage.jurisdiction matches it; us when omitted)",
18423
- login: "ohmyhost login [--organization ULID] --json",
18424
- logout: "ohmyhost logout [--revoke] --json",
18425
- whoami: "ohmyhost whoami --json",
18902
+ login: "ohmyhost login [--organization ULID] [--user USER_ID] [--profile-name NAME] --json (adds one saved login; nothing is saved unless the browser signed in as that user and organization)",
18903
+ logout: "ohmyhost logout [--profile-name NAME] [--revoke] --json (removes only the selected saved login)",
18904
+ whoami: "ohmyhost whoami [--profile-name NAME] --json (the effective user, organization and saved login)",
18905
+ "profile list": "ohmyhost profile list --json (saved logins on this computer: name, user and organization, never a token; pass --profile-name NAME or set OHMYHOST_PROFILE to choose one)",
18426
18906
  "github connect": "ohmyhost github connect --organization ULID --idempotency-key KEY --json (connect once, then link covered repositories without another browser consent)",
18427
18907
  "github status": "ohmyhost github status --organization ULID --json",
18428
18908
  "export create": "ohmyhost export create --project ULID --idempotency-key KEY --stdin --json (password on stdin only; one accepted SQL ZIP per project per 24 hours)",
18429
18909
  "export get": "ohmyhost export get EXPORT_ULID --project ULID --json (poll the original job; signed ZIP download lasts 24 hours)",
18430
18910
  "credits account": "ohmyhost credits account --organization ULID --json",
18911
+ "referral link": "ohmyhost referral link --organization ULID --json (the workspace's link to share; a new user who signs up through it starts with a free Paid month and 1,000 credits, and their first payment gives this workspace the same)",
18431
18912
  "credits balance": "ohmyhost credits balance --organization ULID --json",
18432
18913
  "billing recharge get": "ohmyhost billing recharge get --organization ULID --json",
18433
18914
  "billing recharge set": "ohmyhost billing recharge set --organization ULID --enabled true|false --monthly-limit-minor CENTS --revision N --idempotency-key KEY [--consent off_session_v1] --json (explicit Owner consent required before enabling)",
@@ -18437,9 +18918,9 @@ var HELP_USAGE = Object.freeze({
18437
18918
  "credits usage": "ohmyhost credits usage --organization ULID --month YYYY-MM [--cursor ULID] --json",
18438
18919
  "budget get": "ohmyhost budget get --project ULID --json",
18439
18920
  "budget set": "ohmyhost budget set --project ULID --credits NUMBER|none [--mode continue|stop] --idempotency-key KEY --json",
18440
- "organization create": "ohmyhost organization create --name NAME --idempotency-key KEY [--source SOURCE] --json (SOURCE is optional attribution from a link's r value; the new workspace is selected immediately)",
18441
- "organization list": "ohmyhost organization list --json (the workspaces you belong to and the selected one)",
18442
- "organization use": "ohmyhost organization use --organization ULID --json",
18921
+ "organization create": "ohmyhost organization create --name NAME --idempotency-key KEY [--source SOURCE] --json (SOURCE is optional attribution from a link's r value; a login without organization is bound to the new workspace, another login keeps its own)",
18922
+ "organization list": "ohmyhost organization list [--profile-name NAME] --json (the workspaces of the chosen login's user and the one that login is scoped to)",
18923
+ "organization use": "ohmyhost organization use --organization ULID [--profile-name NAME] --json (binds a login that has no organization yet; another organization needs its own login)",
18443
18924
  "operation get": "ohmyhost operation get OPERATION_ULID --json",
18444
18925
  "operation reconcile": "ohmyhost operation reconcile OPERATION_ULID --idempotency-key KEY --yes --json",
18445
18926
  "token create": "ohmyhost token create --organization ULID --name NAME --idempotency-key KEY --out .env.local --json",
@@ -18457,6 +18938,8 @@ var HELP_USAGE = Object.freeze({
18457
18938
  "project dev-share rotate": "ohmyhost project dev-share rotate --project ULID --idempotency-key KEY --yes --json",
18458
18939
  "project dev-share revoke": "ohmyhost project dev-share revoke --project ULID --idempotency-key KEY --yes --json",
18459
18940
  "project dev-access mode": "ohmyhost project dev-access mode --project ULID --mode protected|public --idempotency-key KEY --yes --json",
18941
+ "project flag status": "ohmyhost project flag status --project ULID --json",
18942
+ "project flag set": "ohmyhost project flag set --project ULID --enabled true|false --idempotency-key KEY --json (shows the small Powered by ohmyho.st flag on the production site; while it shows, a Free workspace may connect its own domain without the domain fee and each Paid period adds 250 credits)",
18460
18943
  "project handle check": "ohmyhost project handle check --handle HANDLE --json (is this address free? answers with a reason and free alternatives; the address becomes HANDLE.check.omh.st)",
18461
18944
  "project handle set": "ohmyhost project handle set --project ULID --handle HANDLE --if-match ETAG --idempotency-key KEY --json (moves the project to a free address; the old one stops working and anyone may claim it)",
18462
18945
  "database compute set": "ohmyhost database compute set --project ULID --environment dev|prod --profile standard|performance --idempotency-key KEY --yes [--wait] --json\n standard: Free 0.25 CU/1 GB/60s idle, Paid 0.5 CU/2 GB/60s idle. performance: Paid 1 CU/4 GB/300s idle, 2.5x database compute credits per equal active minute; longer idle time also consumes credits.",
@@ -18489,7 +18972,7 @@ var HELP_USAGE = Object.freeze({
18489
18972
  delete: "ohmyhost delete --project ULID --if-match ETAG --confirmation-token TOKEN --idempotency-key KEY --yes --json",
18490
18973
  "secret list": "ohmyhost secret list --project ULID --environment ENVIRONMENT_ULID --json",
18491
18974
  "function runs": "ohmyhost function runs --project ULID --environment ENVIRONMENT_ULID [--limit 1-100] --json",
18492
- "secret set": `printf '%s' "$SECRET_VALUE" | ohmyhost secret set NAME --project ULID --environment ENVIRONMENT_ULID --idempotency-key KEY --stdin [--wait] --json`,
18975
+ "secret set": `printf '%s' "$SECRET_VALUE" | ohmyhost secret set NAME --project ULID --environment ENVIRONMENT_ULID --idempotency-key KEY [--profile-user USER_ID --profile-organization ULID | --token-user USER_ID --token-organization ULID] --stdin [--wait] --json (with --profile-user and --profile-organization the saved login that runs it must belong to that user and organization; with --token-user and --token-organization it runs only with an OHMYHOST_TOKEN of that user and organization, never with a saved login)`,
18493
18976
  "secret delete": "ohmyhost secret delete NAME --project ULID --environment ENVIRONMENT_ULID --idempotency-key KEY [--wait] --json",
18494
18977
  "mail setup": "ohmyhost mail setup --project ULID --environment ULID --domain DOMAIN --sending true --receiving false --idempotency-key KEY --json",
18495
18978
  "mail status": "ohmyhost mail status --project ULID --environment ULID --json",
@@ -18507,8 +18990,8 @@ var HELP_COMMANDS = Object.freeze(Object.entries(HELP_USAGE).map(([command, usag
18507
18990
  var cliMetadataResult = (kind, topic) => kind === "help" ? success("help", {
18508
18991
  status: "succeeded",
18509
18992
  cli_version: CLI_VERSION,
18510
- usage: "ohmyhost <command> [options] --json",
18511
- environment: "OHMYHOST_ENVIRONMENT=development|production (default: production)",
18993
+ usage: "ohmyhost <command> [options] [--profile-name NAME] --json",
18994
+ environment: "OHMYHOST_ENVIRONMENT=development|production (default: production); OHMYHOST_PROFILE=NAME binds this process to one saved login",
18512
18995
  commands: HELP_COMMANDS.filter((entry) => !topic || entry.command === topic)
18513
18996
  }) : success("version", { status: "succeeded", cli_version: CLI_VERSION });
18514
18997
  var ProductCliFailure = class extends Error {
@@ -18529,8 +19012,11 @@ var ProductCliFailure = class extends Error {
18529
19012
  };
18530
19013
  var ProductCli = class {
18531
19014
  dependencies;
19015
+ /** The saved login of this run; binding a login without organization replaces it. */
19016
+ #profile;
18532
19017
  constructor(dependencies) {
18533
19018
  this.dependencies = dependencies;
19019
+ this.#profile = dependencies.profile;
18534
19020
  }
18535
19021
  async run(argv, signal = new AbortController().signal) {
18536
19022
  let command;
@@ -18560,8 +19046,10 @@ var ProductCli = class {
18560
19046
  if (this.dependencies.apiToken !== void 0 && (command.kind === "organization-create" || command.kind === "organization-list" || command.kind === "organization-use")) {
18561
19047
  throw new InteractiveSessionRequiredError();
18562
19048
  }
19049
+ if (command.kind === "profile-list")
19050
+ return await this.listProfiles();
18563
19051
  if (command.kind === "login")
18564
- return await this.login(command.credentialStore, command.organizationId, signal);
19052
+ return await this.login(command.organizationId, command.userId, signal);
18565
19053
  if (command.kind === "logout") {
18566
19054
  return await this.logout(command.credentialStore, command.revoke, signal);
18567
19055
  }
@@ -18634,6 +19122,11 @@ var ProductCli = class {
18634
19122
  }
18635
19123
  return success(name, { status: "succeeded", billing: result });
18636
19124
  }
19125
+ if (command.kind === "referral-link") {
19126
+ const api = await this.authenticatedApi(command.credentialStore, signal);
19127
+ const referral = parseOrganizationReferral(await api.getOrganizationReferral(command.organizationId));
19128
+ return success("referral link", { status: "succeeded", referral });
19129
+ }
18637
19130
  if (command.kind === "credits-account") {
18638
19131
  const api = await this.authenticatedApi(command.credentialStore, signal);
18639
19132
  const account = await api.getOrganizationAccount(command.organizationId);
@@ -18672,35 +19165,48 @@ var ProductCli = class {
18672
19165
  ...command.signupSource === void 0 ? {} : { signupSource: command.signupSource },
18673
19166
  idempotencyKey: command.idempotencyKey
18674
19167
  }));
18675
- const selection = await this.chooseAndStoreWorkspace(await api.getAccountProfile(), organization.id, command.credentialStore, signal);
19168
+ const selection = chooseWorkspace((await api.getAccountProfile()).organizations, organization.id);
18676
19169
  if (selection.outcome !== "selected")
18677
19170
  throw new ProductCliFailure(2, "organization_not_found", true, `The new workspace is not yet readable for this account. Run '${this.dependencies.commandPrefix} organization use --organization ${organization.id} --json' again.`);
19171
+ const current = this.#profile;
19172
+ if (current !== void 0 && current.organizationId !== null && current.organizationId !== organization.id)
19173
+ return success("organization create", {
19174
+ status: "succeeded",
19175
+ organization,
19176
+ selected: current.organizationId,
19177
+ profile: publicProfile(current),
19178
+ next_action: `Run '${this.dependencies.commandPrefix} login --organization ${organization.id} --user ${current.userId} --json' to add a login for the new workspace as the same user; profile ${current.name} stays in organization ${current.organizationId}.`
19179
+ });
19180
+ const profile = await this.bindOrganization(selection.organization, signal);
18678
19181
  return success("organization create", {
18679
19182
  status: "succeeded",
18680
19183
  organization,
18681
19184
  selected: organization.id,
19185
+ profile: publicProfile(profile),
18682
19186
  next_action: `Run '${this.dependencies.commandPrefix} whoami --json' to confirm the selected workspace, then create a project.`,
18683
19187
  ...await this.githubNextAction(await this.authenticatedApi(command.credentialStore, signal), organization.id)
18684
19188
  });
18685
19189
  }
18686
19190
  if (command.kind === "organization-list") {
18687
19191
  const api = await this.authenticatedApi(command.credentialStore, signal);
18688
- const organizations = await api.getAccountProfile();
19192
+ const account = await api.getAccountProfile();
18689
19193
  return success("organization list", {
18690
19194
  status: "listed",
18691
- organizations: organizations.map(publicWorkspace),
18692
- selected: await selectedWorkspaceId(this.workspaceSession(command.credentialStore))
19195
+ organizations: account.organizations.map(publicWorkspace),
19196
+ selected: this.#profile?.organizationId ?? null
18693
19197
  });
18694
19198
  }
18695
19199
  if (command.kind === "organization-use") {
18696
19200
  const api = await this.authenticatedApi(command.credentialStore, signal);
18697
- const selection = await this.chooseAndStoreWorkspace(await api.getAccountProfile(), command.organizationId, command.credentialStore, signal);
19201
+ const selection = chooseWorkspace((await api.getAccountProfile()).organizations, command.organizationId);
18698
19202
  if (selection.outcome !== "selected")
18699
19203
  throw this.unknownWorkspace(selection);
19204
+ const profile = await this.bindOrganization(selection.organization, signal);
18700
19205
  return success("organization use", {
18701
19206
  status: "selected",
18702
19207
  organization: publicWorkspace(selection.organization),
18703
19208
  selected: selection.organization.id,
19209
+ profile: publicProfile(profile),
18704
19210
  ...await this.githubNextAction(await this.authenticatedApi(command.credentialStore, signal), selection.organization.id)
18705
19211
  });
18706
19212
  }
@@ -18782,13 +19288,18 @@ var ProductCli = class {
18782
19288
  return await this.getProjectStatus(command, signal);
18783
19289
  if (command.kind === "project-dev-access-create")
18784
19290
  return await this.createDevAccessTicket(command, signal);
19291
+ if (command.kind === "project-flag-status" || command.kind === "project-flag-set") {
19292
+ const api = await this.authenticatedApi(command.credentialStore, signal);
19293
+ const flag = parsePoweredByFlag(command.kind === "project-flag-status" ? await api.getPoweredByFlag(command.projectId) : await api.setPoweredByFlag(command.projectId, command.enabled, command.idempotencyKey));
19294
+ return success(command.kind === "project-flag-status" ? "project flag status" : "project flag set", { status: "succeeded", flag });
19295
+ }
18785
19296
  if (command.kind === "project-dev-share-link" || command.kind === "project-dev-share-rotate" || command.kind === "project-dev-share-revoke" || command.kind === "project-dev-access-mode-set") {
18786
19297
  const api = await this.authenticatedApi(command.credentialStore, signal);
18787
19298
  const state = parseDevAccessState(command.kind === "project-dev-share-link" ? await api.ensureDevShareLink(command.projectId) : command.kind === "project-dev-share-rotate" ? await api.rotateDevShareLink(command.projectId, command.idempotencyKey) : command.kind === "project-dev-share-revoke" ? await api.revokeDevShareLink(command.projectId, command.idempotencyKey) : await api.setDevAccessMode(command.projectId, command.mode, command.idempotencyKey));
18788
19299
  return success(command.kind === "project-dev-share-link" ? "project dev-share link" : command.kind === "project-dev-share-rotate" ? "project dev-share rotate" : command.kind === "project-dev-share-revoke" ? "project dev-share revoke" : "project dev-access mode", { status: "succeeded", state });
18789
19300
  }
18790
19301
  if (command.kind === "database-write") {
18791
- const file = await open2(command.statementFile, "r"), buffer = Buffer.alloc(65537);
19302
+ const file = await open3(command.statementFile, "r"), buffer = Buffer.alloc(65537);
18792
19303
  let statement;
18793
19304
  try {
18794
19305
  const stat3 = await file.stat();
@@ -18901,8 +19412,10 @@ var ProductCli = class {
18901
19412
  return failure(name, 3, error.code, false, error.message);
18902
19413
  if (error instanceof InteractiveSessionRequiredError)
18903
19414
  return failure(name, 2, "interactive_login_required", false, `A user API token cannot create, list or select a workspace. Run '${this.dependencies.commandPrefix} login --json' in a process without OHMYHOST_TOKEN.`);
19415
+ if (error instanceof ProfileLimitError)
19416
+ return failure(name, 2, "profile_limit_reached", false, profileLimitAction(this.dependencies.commandPrefix));
18904
19417
  this.recordDiagnostic(diagnosticDescriptor(error, signal));
18905
- return classifyFailure(name, error, signal);
19418
+ return classifyFailure(name, error, signal, this.context());
18906
19419
  }
18907
19420
  }
18908
19421
  githubConnectAction(organizationId) {
@@ -18925,22 +19438,53 @@ var ProductCli = class {
18925
19438
  return success("whoami", {
18926
19439
  status: "authenticated",
18927
19440
  identity,
19441
+ context: this.context(),
18928
19442
  ...identity.organization_ids.length === 1 && identity.organization_ids[0] !== void 0 ? await this.githubNextAction(api, identity.organization_ids[0]) : {}
18929
19443
  });
18930
- const selection = await this.chooseAndStoreWorkspace(await api.getAccountProfile(), void 0, command.credentialStore, signal);
18931
- if (selection.outcome === "selected")
19444
+ const selection = chooseWorkspace((await api.getAccountProfile()).organizations, void 0);
19445
+ if (selection.outcome === "selected") {
19446
+ await this.bindOrganization(selection.organization, signal);
18932
19447
  return success("whoami", {
18933
19448
  status: "authenticated",
18934
19449
  identity: parseCurrentIdentity(await api.getCurrentIdentity()),
18935
19450
  organization: publicWorkspace(selection.organization),
18936
19451
  selected: selection.organization.id,
19452
+ context: this.context(),
18937
19453
  ...await this.githubNextAction(api, selection.organization.id)
18938
19454
  });
19455
+ }
18939
19456
  return success("whoami", {
18940
19457
  status: "authenticated",
18941
19458
  identity,
19459
+ context: this.context(),
18942
19460
  organizations: selection.outcome === "choice_required" ? selection.organizations.map(publicWorkspace) : [],
18943
- next_action: selection.outcome === "choice_required" ? `Ask the customer which workspace to use, then run '${this.dependencies.commandPrefix} organization use --organization ULID --json'.` : `Run '${this.dependencies.commandPrefix} organization create --name NAME --idempotency-key KEY --json' to create the first workspace.`
19461
+ next_action: this.workspaceAction(selection)
19462
+ });
19463
+ }
19464
+ /** Which account this run acts as, without any credential. */
19465
+ context() {
19466
+ if (this.dependencies.apiToken !== void 0)
19467
+ return { credential: "environment_token" };
19468
+ return {
19469
+ credential: "profile",
19470
+ profile: this.#profile === void 0 ? null : publicProfile(this.#profile)
19471
+ };
19472
+ }
19473
+ /** The next step for a login that has no organization yet. */
19474
+ workspaceAction(selection) {
19475
+ const prefix = this.dependencies.commandPrefix;
19476
+ const named = this.#profile === void 0 ? "" : ` --profile-name ${this.#profile.name}`;
19477
+ return selection.outcome === "choice_required" ? `Ask the customer which workspace to use, then run '${prefix} organization use --organization ULID${named} --json'.` : `Run '${prefix} organization create --name NAME --idempotency-key KEY${named} --json' to create the first workspace.`;
19478
+ }
19479
+ async listProfiles() {
19480
+ const profiles = this.dependencies.profiles;
19481
+ if (profiles === void 0)
19482
+ throw new ProviderCapabilityBlockedError();
19483
+ return success("profile list", {
19484
+ status: "listed",
19485
+ profiles: (await profiles.list()).map(publicProfile),
19486
+ bound: this.dependencies.profileBinding ?? null,
19487
+ environment_token: this.dependencies.apiToken !== void 0
18944
19488
  });
18945
19489
  }
18946
19490
  recordDiagnostic(event) {
@@ -18953,7 +19497,11 @@ var ProductCli = class {
18953
19497
  }
18954
19498
  }
18955
19499
  // The reviewed boundary proof parses this signature, so keep its parameters brace-free.
18956
- async login(credentialStore, organizationId, signal) {
19500
+ async login(organizationId, expectedUserId, signal) {
19501
+ const profiles = this.dependencies.profiles;
19502
+ const readIdentity = this.dependencies.currentIdentity;
19503
+ if (profiles === void 0 || readIdentity === void 0)
19504
+ throw new ProviderCapabilityBlockedError();
18957
19505
  const signInDeadline = this.dependencies.now().getTime() + SIGN_IN_WINDOW_MS;
18958
19506
  let session = await this.dependencies.deviceFlow.authorize();
18959
19507
  await presentAuthorization(this.dependencies.deviceAuthorizationPresenter, session.authorization);
@@ -18990,53 +19538,112 @@ var ProductCli = class {
18990
19538
  throw error;
18991
19539
  throw new ProductCliFailure(4, "device_token_expiry_missing", false, "Run login again; the authorization response was incomplete.");
18992
19540
  }
18993
- await this.dependencies.credentialStore(credentialStore).replace({
19541
+ const signedIn = {
18994
19542
  accessToken: result.tokens.accessToken,
18995
19543
  refreshToken: result.tokens.refreshToken,
18996
19544
  expiresAt
18997
- });
18998
- const profile = await this.dependencies.accountProfile(() => this.authenticatedToken(credentialStore, signal));
18999
- const selection = await this.chooseAndStoreWorkspace(profile, organizationId, credentialStore, signal);
19545
+ };
19546
+ const account = await this.dependencies.accountProfile(async () => signedIn.accessToken);
19547
+ if (!isProfileUserId(account.userId))
19548
+ throw this.unverifiedLogin();
19549
+ if (expectedUserId !== void 0 && account.userId !== expectedUserId)
19550
+ throw new ProductCliFailure(4, "login_account_mismatch", false, `The browser signed in as user ${account.userId}, not ${expectedUserId}. Nothing was saved and every saved login is unchanged. Sign out of ohmyho.st in that browser or use the browser profile of ${expectedUserId}, then run '${this.dependencies.commandPrefix} login${organizationId === void 0 ? "" : ` --organization ${organizationId}`} --user ${expectedUserId} --json' again.`);
19551
+ const selection = chooseWorkspace(account.organizations, organizationId);
19552
+ if (selection.outcome === "not_a_member")
19553
+ throw this.unknownWorkspace(selection);
19554
+ const credential = selection.outcome === "selected" ? await runBounded((bounded2) => this.dependencies.tokenRefresher.refresh(signedIn.refreshToken, bounded2, selection.organization.workos_id), signal, this.dependencies.lifecycleTimeoutMs) : signedIn;
19555
+ const identity = await readIdentity(async () => credential.accessToken);
19556
+ const scopedId = selection.outcome === "selected" ? selection.organization.id : parseCurrentIdentity(identity).organization_ids[0] ?? null;
19557
+ const organization = scopedId === null ? null : account.organizations.find((candidate) => candidate.id === scopedId);
19558
+ if (organization === void 0)
19559
+ throw this.unverifiedLogin();
19560
+ try {
19561
+ assertVerifiedLogin(identity, account.userId, scopedId);
19562
+ } catch (error) {
19563
+ if (error instanceof AccountVerificationError)
19564
+ throw this.unverifiedLogin();
19565
+ throw error;
19566
+ }
19567
+ const login = {
19568
+ userId: account.userId,
19569
+ organizationId: scopedId,
19570
+ organizationName: organization?.name ?? null
19571
+ };
19572
+ const profile = {
19573
+ name: this.dependencies.profileName ?? defaultProfileName(await profiles.list(), login),
19574
+ ...login
19575
+ };
19576
+ try {
19577
+ await profiles.save(profile, organization === null ? credential : {
19578
+ ...credential,
19579
+ workosOrganizationId: organization.workos_id,
19580
+ organizationId: organization.id
19581
+ });
19582
+ } catch (error) {
19583
+ if (!(error instanceof ProfileNameConflictError))
19584
+ throw error;
19585
+ throw new ProductCliFailure(2, "profile_name_conflict", false, `The name ${profile.name} already belongs to the saved login of user ${error.profile.userId} in organization ${String(error.profile.organizationId ?? "none")}. Nothing was saved. Choose another --profile-name, or remove that login with '${this.dependencies.commandPrefix} logout --profile-name ${profile.name} --json' first.`);
19586
+ }
19587
+ this.#profile = profile;
19000
19588
  const readGithubConnection = this.dependencies.githubConnectionStatus;
19001
- if (selection.outcome === "selected")
19589
+ if (organization !== null)
19002
19590
  return success("login", {
19003
19591
  status: "authenticated",
19004
- organization: publicWorkspace(selection.organization),
19592
+ organization: publicWorkspace(organization),
19593
+ profile: publicProfile(profile),
19005
19594
  ...readGithubConnection === void 0 ? {} : await this.githubNextAction({
19006
- getGithubConnection: (selectedOrganizationId) => readGithubConnection(() => this.authenticatedToken(credentialStore, signal), selectedOrganizationId)
19007
- }, selection.organization.id)
19595
+ getGithubConnection: (selectedOrganizationId) => readGithubConnection(async () => credential.accessToken, selectedOrganizationId)
19596
+ }, organization.id)
19008
19597
  });
19009
- if (selection.outcome === "not_a_member")
19010
- throw this.unknownWorkspace(selection);
19011
19598
  return success("login", {
19012
19599
  status: "authenticated",
19013
19600
  organization: null,
19601
+ profile: publicProfile(profile),
19014
19602
  organizations: selection.outcome === "choice_required" ? selection.organizations.map(publicWorkspace) : [],
19015
- next_action: selection.outcome === "choice_required" ? `Ask the customer which workspace to use, then run '${this.dependencies.commandPrefix} organization use --organization ULID --json'.` : `Run '${this.dependencies.commandPrefix} organization create --name NAME --idempotency-key KEY --json' to create the first workspace.`
19603
+ next_action: this.workspaceAction(selection)
19016
19604
  });
19017
19605
  }
19018
19606
  }
19019
- workspaceSession(credentialStore) {
19020
- return {
19021
- store: this.dependencies.credentialStore(credentialStore),
19022
- refresher: this.dependencies.tokenRefresher
19023
- };
19024
- }
19025
- /** Decide the workspace and, when the answer cannot be wrong, put it into the credential. */
19026
- async chooseAndStoreWorkspace(organizations, requested, credentialStore, signal) {
19027
- const selection = chooseWorkspace(organizations, requested);
19028
- if (selection.outcome === "selected")
19029
- await runBounded((bounded2) => selectWorkspace(this.workspaceSession(credentialStore), selection.organization, bounded2), signal, this.dependencies.lifecycleTimeoutMs);
19030
- return selection;
19607
+ /** The provider answered with another account than the one this login requested. */
19608
+ unverifiedLogin() {
19609
+ return new ProductCliFailure(4, "login_verification_failed", true, `The signed-in account could not be verified; nothing was saved. Run '${this.dependencies.commandPrefix} login --json' again.`);
19610
+ }
19611
+ /**
19612
+ * Bind the login of this run, which has no organization yet, to one of its user's organizations.
19613
+ * A login that already has one keeps it; another organization is its own login.
19614
+ */
19615
+ async bindOrganization(organization, signal) {
19616
+ const profiles = this.dependencies.profiles;
19617
+ const profile = this.#profile;
19618
+ if (profiles === void 0 || profile === void 0)
19619
+ throw new InteractiveSessionRequiredError();
19620
+ let bound;
19621
+ try {
19622
+ bound = await runBounded((bounded2) => selectWorkspace({
19623
+ store: profiles.credential(profile),
19624
+ refresher: this.dependencies.tokenRefresher,
19625
+ profiles,
19626
+ profile
19627
+ }, organization, bounded2, (accessToken) => this.dependencies.productApi(async () => accessToken).getCurrentIdentity()), signal, this.dependencies.lifecycleTimeoutMs);
19628
+ } catch (error) {
19629
+ if (error instanceof AccountVerificationError)
19630
+ throw this.unverifiedLogin();
19631
+ if (!(error instanceof ProfileOrganizationFixedError))
19632
+ throw error;
19633
+ throw new ProductCliFailure(2, "profile_organization_fixed", false, `Profile ${profile.name} stays signed in to organization ${String(profile.organizationId)}. Run '${this.dependencies.commandPrefix} login --organization ${organization.id} --user ${profile.userId} --json' to add a separate login for ${organization.id} as the same user; a saved login never switches organizations.`);
19634
+ }
19635
+ this.#profile = bound;
19636
+ return bound;
19031
19637
  }
19032
19638
  unknownWorkspace(selection) {
19033
19639
  return new ProductCliFailure(2, "organization_not_found", false, `You are not a member of that workspace. Run '${this.dependencies.commandPrefix} organization list --json' and choose one of ${String(selection.outcome === "not_a_member" ? selection.organizations.length : 0)}.`);
19034
19640
  }
19035
19641
  async logout(credentialStore, revoke, signal) {
19036
- const store = this.dependencies.credentialStore(credentialStore);
19642
+ const store = this.store(credentialStore);
19643
+ const removed = this.#profile === void 0 ? {} : { profile: publicProfile(this.#profile) };
19037
19644
  if (!revoke) {
19038
- await store.clear();
19039
- return success("logout", { status: "logged_out" });
19645
+ await this.forgetLogin(store);
19646
+ return success("logout", { status: "logged_out", ...removed });
19040
19647
  }
19041
19648
  const credential = await store.read();
19042
19649
  let revocationError;
@@ -19047,7 +19654,7 @@ var ProductCli = class {
19047
19654
  } catch (error) {
19048
19655
  revocationError = error;
19049
19656
  } finally {
19050
- await store.clear();
19657
+ await this.forgetLogin(store);
19051
19658
  }
19052
19659
  if (revocationError !== void 0) {
19053
19660
  if (isCancellation(revocationError) && signal.aborted)
@@ -19063,7 +19670,29 @@ var ProductCli = class {
19063
19670
  }
19064
19671
  throw revocationError;
19065
19672
  }
19066
- return success("logout", { status: "logged_out" });
19673
+ return success("logout", { status: "logged_out", ...removed });
19674
+ }
19675
+ /** The credential store of this run's saved login; it follows a binding made during the run. */
19676
+ store(kind) {
19677
+ const profiles = this.dependencies.profiles;
19678
+ if (profiles !== void 0 && this.#profile !== void 0)
19679
+ return profiles.credential(this.#profile);
19680
+ if (this.dependencies.credentialStore === void 0)
19681
+ throw new ProductCliFailure(5, "authentication_required", false, this.loginAction());
19682
+ return this.dependencies.credentialStore(kind);
19683
+ }
19684
+ /** Removes only this run's saved login; every other saved login stays untouched. */
19685
+ async forgetLogin(store) {
19686
+ const profiles = this.dependencies.profiles;
19687
+ if (profiles !== void 0 && this.#profile !== void 0)
19688
+ await profiles.remove(this.#profile);
19689
+ else
19690
+ await store.clear();
19691
+ }
19692
+ /** How to sign this run's login in again, naming its organization and alias when known. */
19693
+ loginAction() {
19694
+ const profile = this.#profile;
19695
+ return `Run '${this.dependencies.commandPrefix} login${profile?.organizationId === void 0 || profile.organizationId === null ? "" : ` --organization ${profile.organizationId}`}${profile === void 0 ? "" : ` --profile-name ${profile.name}`} --json'.`;
19067
19696
  }
19068
19697
  async getOperation(command, signal) {
19069
19698
  const api = await this.authenticatedApi(command.credentialStore, signal);
@@ -19116,7 +19745,7 @@ var ProductCli = class {
19116
19745
  async getProjectStatus(command, signal) {
19117
19746
  const api = await this.authenticatedApi(command.credentialStore, signal);
19118
19747
  const project = parseProjectStatus(await api.getProjectStatus(command.projectId));
19119
- return success("project status", { status: "succeeded", project });
19748
+ return success("project status", { status: "succeeded", project, context: this.context() });
19120
19749
  }
19121
19750
  async createDevAccessTicket(command, signal) {
19122
19751
  const api = await this.authenticatedApi(command.credentialStore, signal);
@@ -19259,14 +19888,18 @@ var ProductCli = class {
19259
19888
  const source = project.source;
19260
19889
  if (project.project_id !== command.projectId || source === null || source.status !== "ready" || source.repository_full_name.toLowerCase() !== `${command.repositoryOwner}/${command.repositoryName}`.toLowerCase())
19261
19890
  throw new ResponseContractError();
19891
+ const [organizationId, ...others] = parseCurrentIdentity(await api.getCurrentIdentity()).organization_ids;
19892
+ if (organizationId === void 0 || others.length > 0)
19893
+ throw new ResponseContractError();
19262
19894
  await this.dependencies.projectLinkStore.save({
19263
- version: 1,
19895
+ version: 2,
19264
19896
  api_origin: this.dependencies.productApiOrigin,
19897
+ organization_id: organizationId,
19265
19898
  project_id: command.projectId,
19266
19899
  installation_id: source.installation_id,
19267
19900
  repository_full_name: source.repository_full_name
19268
19901
  });
19269
- return success("link", { status: "completed", operation });
19902
+ return success("link", { status: "completed", operation, organization_id: organizationId });
19270
19903
  }
19271
19904
  async configureAutoDeploy(command, signal) {
19272
19905
  const api = await this.authenticatedApi(command.credentialStore, signal);
@@ -19509,10 +20142,10 @@ var ProductCli = class {
19509
20142
  async authenticatedToken(credentialStore, signal) {
19510
20143
  if (this.dependencies.apiToken !== void 0)
19511
20144
  return this.dependencies.apiToken;
19512
- const store = this.dependencies.credentialStore(credentialStore);
20145
+ const store = this.store(credentialStore);
19513
20146
  const stored = await store.read();
19514
20147
  if (stored === void 0) {
19515
- throw new ProductCliFailure(5, "authentication_required", false, "Run 'ohmyhost login'.");
20148
+ throw new ProductCliFailure(5, "authentication_required", false, this.loginAction());
19516
20149
  }
19517
20150
  let refresh;
19518
20151
  try {
@@ -19520,8 +20153,8 @@ var ProductCli = class {
19520
20153
  } catch (error) {
19521
20154
  if (!(error instanceof TokenRefreshRejectedError))
19522
20155
  throw error;
19523
- await store.clear();
19524
- throw new ProductCliFailure(5, "authentication_required", false, "Run 'ohmyhost login'.");
20156
+ await this.forgetLogin(store);
20157
+ throw new ProductCliFailure(5, "authentication_required", false, this.loginAction());
19525
20158
  }
19526
20159
  if (refresh.refreshed)
19527
20160
  await store.replace(refresh.credential);
@@ -19587,7 +20220,16 @@ var redactVerificationUri = (value) => {
19587
20220
  url.hash = "";
19588
20221
  return url.toString();
19589
20222
  };
19590
- var classifyFailure = (command, error, signal) => {
20223
+ var ACCOUNT_SENSITIVE_PROBLEMS = /* @__PURE__ */ new Set([
20224
+ "resource_not_found",
20225
+ "forbidden",
20226
+ "organization_required",
20227
+ "github_connection_required",
20228
+ "github_connection_revoked",
20229
+ "repository_not_installed"
20230
+ ]);
20231
+ var classifyCliFailure = (command, error, signal) => classifyFailure(command, error, signal);
20232
+ var classifyFailure = (command, error, signal, actingAs) => {
19591
20233
  if (signal.aborted || isCancellation(error)) {
19592
20234
  return failure(command, 130, "command_cancelled", true, "Run the command again.");
19593
20235
  }
@@ -19621,7 +20263,7 @@ var classifyFailure = (command, error, signal) => {
19621
20263
  if (looksLikeProblem(error)) {
19622
20264
  try {
19623
20265
  const problem = parseSafeProblem(error);
19624
- return failure(command, 7, problem.code, problem.retryable, problem.suggestedAction, void 0, problem.retryAfterSeconds, problem.requestId, problem.docsUrl);
20266
+ return failure(command, 7, problem.code, problem.retryable, problem.suggestedAction, void 0, problem.retryAfterSeconds, problem.requestId, problem.docsUrl, ACCOUNT_SENSITIVE_PROBLEMS.has(problem.code) ? actingAs : void 0);
19625
20267
  } catch {
19626
20268
  return failure(command, 7, "response_contract_invalid", false, "Retry only after the client or server contract is repaired.");
19627
20269
  }
@@ -19641,7 +20283,7 @@ var success = (command, body) => ({
19641
20283
  `,
19642
20284
  stderr: ""
19643
20285
  });
19644
- var failure = (command, exitCode, code, retryable, suggestedAction, operation, retryAfterSeconds, requestId, docsUrl) => {
20286
+ var failure = (command, exitCode, code, retryable, suggestedAction, operation, retryAfterSeconds, requestId, docsUrl, actingAs) => {
19645
20287
  const document = {
19646
20288
  version: 1,
19647
20289
  command,
@@ -19654,7 +20296,8 @@ var failure = (command, exitCode, code, retryable, suggestedAction, operation, r
19654
20296
  ...retryAfterSeconds === void 0 ? {} : { retry_after_seconds: retryAfterSeconds },
19655
20297
  ...requestId === void 0 ? {} : { request_id: requestId },
19656
20298
  ...docsUrl === void 0 ? {} : { docs_url: docsUrl },
19657
- ...operation?.error === void 0 ? {} : { message: operation.error.message }
20299
+ ...operation?.error === void 0 ? {} : { message: operation.error.message },
20300
+ ...actingAs === void 0 ? {} : { acting_as: actingAs }
19658
20301
  }
19659
20302
  };
19660
20303
  return { exitCode, stdout: `${JSON.stringify(document)}
@@ -19683,6 +20326,8 @@ var parsedCommandName = (command) => {
19683
20326
  return "billing portal";
19684
20327
  if (command.kind === "credits-account")
19685
20328
  return "credits account";
20329
+ if (command.kind === "referral-link")
20330
+ return "referral link";
19686
20331
  if (command.kind === "credits-balance")
19687
20332
  return "credits balance";
19688
20333
  if (command.kind === "credits-usage")
@@ -19733,6 +20378,10 @@ var parsedCommandName = (command) => {
19733
20378
  return "project dev-share revoke";
19734
20379
  if (command.kind === "project-dev-access-mode-set")
19735
20380
  return "project dev-access mode";
20381
+ if (command.kind === "project-flag-status")
20382
+ return "project flag status";
20383
+ if (command.kind === "project-flag-set")
20384
+ return "project flag set";
19736
20385
  if (command.kind === "project-handle-check")
19737
20386
  return "project handle check";
19738
20387
  if (command.kind === "project-handle-set")
@@ -19787,6 +20436,8 @@ var parsedCommandName = (command) => {
19787
20436
  return "secret delete";
19788
20437
  if (command.kind === "managed-mail")
19789
20438
  return command.label;
20439
+ if (command.kind === "profile-list")
20440
+ return "profile list";
19790
20441
  return command.kind;
19791
20442
  };
19792
20443
  var boundedSignal = (parent, timeoutMs) => AbortSignal.any([parent, AbortSignal.timeout(timeoutMs)]);
@@ -20773,6 +21424,23 @@ var getGithubOrganizationConnection = (parameters, options) => {
20773
21424
  ...params
20774
21425
  });
20775
21426
  };
21427
+ var getOrganizationReferral = (parameters, options) => {
21428
+ const params = buildClientParams([parameters], [
21429
+ {
21430
+ args: [
21431
+ { in: "headers", key: "X-Request-Id" },
21432
+ { in: "path", key: "organization_id" }
21433
+ ]
21434
+ }
21435
+ ]);
21436
+ return (options?.client ?? client).get({
21437
+ responseStyle: "data",
21438
+ security: [{ scheme: "bearer", type: "http" }],
21439
+ url: "/v1/organizations/{organization_id}/referral",
21440
+ ...options,
21441
+ ...params
21442
+ });
21443
+ };
20776
21444
  var getOrganizationAccount = (parameters, options) => {
20777
21445
  const params = buildClientParams([parameters], [{ args: [{ in: "path", key: "organization_id" }] }]);
20778
21446
  return (options?.client ?? client).get({
@@ -21224,6 +21892,23 @@ var deleteProject = (parameters, options) => {
21224
21892
  ...params
21225
21893
  });
21226
21894
  };
21895
+ var getProject = (parameters, options) => {
21896
+ const params = buildClientParams([parameters], [
21897
+ {
21898
+ args: [
21899
+ { in: "headers", key: "X-Request-Id" },
21900
+ { in: "path", key: "project_id" }
21901
+ ]
21902
+ }
21903
+ ]);
21904
+ return (options?.client ?? client).get({
21905
+ responseStyle: "data",
21906
+ security: [{ scheme: "bearer", type: "http" }],
21907
+ url: "/v1/projects/{project_id}",
21908
+ ...options,
21909
+ ...params
21910
+ });
21911
+ };
21227
21912
  var getProjectContext = (parameters, options) => {
21228
21913
  const params = buildClientParams([parameters], [
21229
21914
  {
@@ -21377,6 +22062,47 @@ var setProjectDevAccessMode = (parameters, options) => {
21377
22062
  }
21378
22063
  });
21379
22064
  };
22065
+ var getProjectPoweredByFlag = (parameters, options) => {
22066
+ const params = buildClientParams([parameters], [
22067
+ {
22068
+ args: [
22069
+ { in: "headers", key: "X-Request-Id" },
22070
+ { in: "path", key: "project_id" }
22071
+ ]
22072
+ }
22073
+ ]);
22074
+ return (options?.client ?? client).get({
22075
+ responseStyle: "data",
22076
+ security: [{ scheme: "bearer", type: "http" }],
22077
+ url: "/v1/projects/{project_id}/powered-by-flag",
22078
+ ...options,
22079
+ ...params
22080
+ });
22081
+ };
22082
+ var setProjectPoweredByFlag = (parameters, options) => {
22083
+ const params = buildClientParams([parameters], [
22084
+ {
22085
+ args: [
22086
+ { in: "headers", key: "X-Request-Id" },
22087
+ { in: "headers", key: "Idempotency-Key" },
22088
+ { in: "path", key: "project_id" },
22089
+ { key: "setPoweredByFlagRequest", map: "body" }
22090
+ ]
22091
+ }
22092
+ ]);
22093
+ return (options?.client ?? client).put({
22094
+ responseStyle: "data",
22095
+ security: [{ scheme: "bearer", type: "http" }],
22096
+ url: "/v1/projects/{project_id}/powered-by-flag",
22097
+ ...options,
22098
+ ...params,
22099
+ headers: {
22100
+ "Content-Type": "application/json",
22101
+ ...options?.headers,
22102
+ ...params.headers
22103
+ }
22104
+ });
22105
+ };
21380
22106
  var createProjectDevAccessTicket = (parameters, options) => {
21381
22107
  const params = buildClientParams([parameters], [
21382
22108
  {
@@ -22568,6 +23294,9 @@ var GeneratedSdkProductApi = class {
22568
23294
  async getAccountProfile() {
22569
23295
  return parseAccountProfile(await getAccountProfile({ client: this.#client }));
22570
23296
  }
23297
+ getOrganizationReferral(organizationId) {
23298
+ return getOrganizationReferral({ organization_id: organizationId }, { client: this.#client });
23299
+ }
22571
23300
  getOrganizationAccount(organizationId) {
22572
23301
  return getOrganizationAccount({ organization_id: organizationId }, { client: this.#client });
22573
23302
  }
@@ -22741,6 +23470,16 @@ var GeneratedSdkProductApi = class {
22741
23470
  setDevAccessModeRequest: { mode }
22742
23471
  }, { client: this.#client });
22743
23472
  }
23473
+ async getPoweredByFlag(projectId) {
23474
+ return getProjectPoweredByFlag({ project_id: projectId }, { client: this.#client });
23475
+ }
23476
+ async setPoweredByFlag(projectId, enabled, idempotencyKey) {
23477
+ return setProjectPoweredByFlag({
23478
+ project_id: projectId,
23479
+ "Idempotency-Key": idempotencyKey,
23480
+ setPoweredByFlagRequest: { enabled }
23481
+ }, { client: this.#client });
23482
+ }
22744
23483
  async createDevAccessTicket(projectId) {
22745
23484
  return createProjectDevAccessTicket({ project_id: projectId }, { client: this.#client });
22746
23485
  }
@@ -22908,18 +23647,75 @@ var GeneratedSdkProductApi = class {
22908
23647
  };
22909
23648
 
22910
23649
  // apps/product-cli/dist/project-link-store.js
22911
- import { appendFile, chmod, mkdir as mkdir2, readFile, rename, stat, writeFile } from "node:fs/promises";
22912
- import { join } from "node:path";
22913
- async function readLinkedProjectId(directory, apiOrigin) {
23650
+ import { appendFile, chmod, mkdir as mkdir3, readFile as readFile2, readdir as readdir2, rename, stat, unlink as unlink2, writeFile } from "node:fs/promises";
23651
+ import { join as join2 } from "node:path";
23652
+ var ULID2 = /^[0-7][0-9A-HJKMNP-TV-Z]{25}$/u;
23653
+ var LINKS = "links";
23654
+ var LEGACY_LINK = "link.json";
23655
+ async function readProjectLinks(directory, apiOrigin) {
23656
+ let names = [];
22914
23657
  try {
22915
- const link = JSON.parse(await readFile(join(directory, "link.json"), "utf8"));
22916
- if (typeof link !== "object" || link === null)
22917
- return null;
22918
- const { version: version2, api_origin, project_id } = link;
22919
- return version2 === 1 && api_origin === apiOrigin && typeof project_id === "string" && /^[0-9A-HJKMNP-TV-Z]{26}$/u.test(project_id) ? project_id : null;
23658
+ names = await readdir2(join2(directory, LINKS));
22920
23659
  } catch {
22921
- return null;
23660
+ names = [];
22922
23661
  }
23662
+ const links = [];
23663
+ for (const name of names.sort()) {
23664
+ const organizationId = /^([0-7][0-9A-HJKMNP-TV-Z]{25})\.json$/u.exec(name)?.[1];
23665
+ if (organizationId === void 0)
23666
+ continue;
23667
+ const link2 = await readJson(join2(directory, LINKS, name));
23668
+ if (isRecord3(link2, [
23669
+ "version",
23670
+ "api_origin",
23671
+ "organization_id",
23672
+ "project_id",
23673
+ "installation_id",
23674
+ "repository_full_name"
23675
+ ]) && link2["version"] === 2 && link2["api_origin"] === apiOrigin && link2["organization_id"] === organizationId && typeof link2["project_id"] === "string" && ULID2.test(link2["project_id"]) && typeof link2["installation_id"] === "string" && typeof link2["repository_full_name"] === "string")
23676
+ links.push(link2);
23677
+ }
23678
+ const legacy = await readJson(join2(directory, LEGACY_LINK));
23679
+ return {
23680
+ links,
23681
+ legacy: typeof legacy === "object" && legacy !== null && legacy["version"] === 1 && legacy["api_origin"] === apiOrigin && typeof legacy["project_id"] === "string" && ULID2.test(legacy["project_id"]) && typeof legacy["installation_id"] === "string" && typeof legacy["repository_full_name"] === "string" ? {
23682
+ project_id: legacy["project_id"],
23683
+ installation_id: legacy["installation_id"],
23684
+ repository_full_name: legacy["repository_full_name"]
23685
+ } : null
23686
+ };
23687
+ }
23688
+ function chooseLinkedProject(directory, organizationId) {
23689
+ if (organizationId !== void 0) {
23690
+ const link2 = directory.links.find((candidate) => candidate.organization_id === organizationId);
23691
+ if (link2 !== void 0)
23692
+ return { outcome: "linked", projectId: link2.project_id, organizationId };
23693
+ return directory.legacy === null ? { outcome: "none" } : { outcome: "linked", projectId: directory.legacy.project_id, organizationId: null };
23694
+ }
23695
+ const candidates = [
23696
+ ...directory.links.map((link2) => ({
23697
+ projectId: link2.project_id,
23698
+ organizationId: link2.organization_id
23699
+ })),
23700
+ ...directory.legacy === null ? [] : [{ projectId: directory.legacy.project_id, organizationId: null }]
23701
+ ];
23702
+ const [only] = candidates;
23703
+ if (candidates.length === 1 && only !== void 0)
23704
+ return { outcome: "linked", ...only };
23705
+ if (candidates.length === 0)
23706
+ return { outcome: "none" };
23707
+ return {
23708
+ outcome: "ambiguous",
23709
+ organizationIds: directory.links.map((link2) => link2.organization_id)
23710
+ };
23711
+ }
23712
+ function linkedOrganization(directory, projectId) {
23713
+ const organizations = new Set(directory.links.filter((link2) => link2.project_id === projectId).map((link2) => link2.organization_id));
23714
+ const [organizationId] = organizations;
23715
+ return organizations.size === 1 ? organizationId : void 0;
23716
+ }
23717
+ async function removeLegacyProjectLink(directory) {
23718
+ await unlink2(join2(directory, LEGACY_LINK));
22923
23719
  }
22924
23720
  var OwnerOnlyProjectLinkStore = class {
22925
23721
  directory;
@@ -22930,10 +23726,15 @@ var OwnerOnlyProjectLinkStore = class {
22930
23726
  this.repositoryRoot = repositoryRoot;
22931
23727
  }
22932
23728
  async save(metadata) {
22933
- await mkdir2(this.directory, { mode: 448, recursive: true });
22934
- await chmod(this.directory, 448);
22935
- const target = join(this.directory, "link.json");
22936
- const temporary = join(this.directory, `.link-${process.pid}.tmp`);
23729
+ if (!ULID2.test(metadata.organization_id))
23730
+ throw new TypeError("Invalid linked organization");
23731
+ const links = join2(this.directory, LINKS);
23732
+ for (const directory of [this.directory, links]) {
23733
+ await mkdir3(directory, { mode: 448, recursive: true });
23734
+ await chmod(directory, 448);
23735
+ }
23736
+ const target = join2(links, `${metadata.organization_id}.json`);
23737
+ const temporary = join2(links, `.link-${process.pid}.tmp`);
22937
23738
  await writeFile(temporary, `${JSON.stringify(metadata)}
22938
23739
  `, { encoding: "utf8", mode: 384 });
22939
23740
  await chmod(temporary, 384);
@@ -22943,16 +23744,25 @@ var OwnerOnlyProjectLinkStore = class {
22943
23744
  await ignoreLinkDirectory(this.repositoryRoot);
22944
23745
  }
22945
23746
  };
23747
+ async function readJson(path) {
23748
+ try {
23749
+ const value = JSON.parse(await readFile2(path, "utf8"));
23750
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
23751
+ } catch {
23752
+ return null;
23753
+ }
23754
+ }
23755
+ var isRecord3 = (value, keys) => typeof value === "object" && value !== null && Object.keys(value).sort().join(",") === [...keys].sort().join(",");
22946
23756
  async function ignoreLinkDirectory(root) {
22947
23757
  try {
22948
- await stat(join(root, ".git"));
23758
+ await stat(join2(root, ".git"));
22949
23759
  } catch {
22950
23760
  return;
22951
23761
  }
22952
- const file = join(root, ".gitignore");
23762
+ const file = join2(root, ".gitignore");
22953
23763
  let current = "";
22954
23764
  try {
22955
- current = await readFile(file, "utf8");
23765
+ current = await readFile2(file, "utf8");
22956
23766
  } catch {
22957
23767
  current = "";
22958
23768
  }
@@ -22965,7 +23775,7 @@ async function ignoreLinkDirectory(root) {
22965
23775
 
22966
23776
  // apps/product-cli/dist/repository-init.js
22967
23777
  import { createHash } from "node:crypto";
22968
- import { lstat as lstat2, open as open3, readFile as readFile2, readdir, stat as stat2 } from "node:fs/promises";
23778
+ import { lstat as lstat2, open as open4, readFile as readFile3, readdir as readdir3, stat as stat2 } from "node:fs/promises";
22969
23779
  import { dirname as dirname2, posix, resolve as resolve2 } from "node:path";
22970
23780
 
22971
23781
  // packages/contracts/dist/application-root.js
@@ -23933,7 +24743,7 @@ async function cronBlockers(framework, config, applicationDirectory, files) {
23933
24743
  ];
23934
24744
  }
23935
24745
  const modulePath = framework === "vite" ? "src/ohmyhost/companion.ts" : WORKER_MODULE_PATH;
23936
- const source = files.includes(modulePath) ? await readFile2(resolve2(applicationDirectory, modulePath), "utf8") : null;
24746
+ const source = files.includes(modulePath) ? await readFile3(resolve2(applicationDirectory, modulePath), "utf8") : null;
23937
24747
  if (framework !== "functions" && source !== null && !DEFAULT_EXPORT_PATTERN.test(source)) {
23938
24748
  return [defaultExportBlocker(modulePath)];
23939
24749
  }
@@ -23957,7 +24767,7 @@ async function storageClientBlockers(config, applicationDirectory, files) {
23957
24767
  const information = await stat2(absolute);
23958
24768
  if (!information.isFile() || information.size > MAXIMUM_SOURCE_BYTES)
23959
24769
  continue;
23960
- if (callsStorageClient(await readFile2(absolute, "utf8")))
24770
+ if (callsStorageClient(await readFile3(absolute, "utf8")))
23961
24771
  return [];
23962
24772
  }
23963
24773
  return [
@@ -23978,7 +24788,7 @@ async function databaseContractBlockers(config, applicationDirectory, files) {
23978
24788
  const information = await stat2(absolute);
23979
24789
  if (!information.isFile() || information.size > MAXIMUM_SOURCE_BYTES)
23980
24790
  continue;
23981
- const source = (await readFile2(absolute, "utf8")).replaceAll(INERT_SOURCE_PATTERN, " ");
24791
+ const source = (await readFile3(absolute, "utf8")).replaceAll(INERT_SOURCE_PATTERN, " ");
23982
24792
  if (DATABASE_PRIVATE_BINDING_PATTERN.test(source)) {
23983
24793
  blockers.push({
23984
24794
  code: "database_binding_private",
@@ -23997,7 +24807,7 @@ async function databaseContractBlockers(config, applicationDirectory, files) {
23997
24807
  async function workerModuleBlockers(framework, applicationDirectory, files) {
23998
24808
  if (framework !== "functions" || !files.includes(WORKER_MODULE_PATH))
23999
24809
  return [];
24000
- const source = await readFile2(resolve2(applicationDirectory, WORKER_MODULE_PATH), "utf8");
24810
+ const source = await readFile3(resolve2(applicationDirectory, WORKER_MODULE_PATH), "utf8");
24001
24811
  return DEFAULT_EXPORT_PATTERN.test(source) ? [] : [defaultExportBlocker(WORKER_MODULE_PATH)];
24002
24812
  }
24003
24813
  function migrationFilenameBlockers(files, configuredPath, provider) {
@@ -24024,7 +24834,7 @@ async function migrationSqlBlockers(applicationDirectory, files, configuredPath,
24024
24834
  }
24025
24835
  const migrations = await Promise.all(migrationPaths.map(async (path) => Object.freeze({
24026
24836
  path,
24027
- body: new Uint8Array(await readFile2(resolve2(applicationDirectory, root, path)))
24837
+ body: new Uint8Array(await readFile3(resolve2(applicationDirectory, root, path)))
24028
24838
  })));
24029
24839
  const blockers = [];
24030
24840
  for (const migration of migrations) {
@@ -24063,7 +24873,7 @@ async function readExistingConfiguration(repositoryRoot, repositoryFiles, applic
24063
24873
  if (!information.isFile() || information.size > MAXIMUM_CONFIGURATION_BYTES) {
24064
24874
  throw new RepositoryInitError("repository_configuration_invalid", "Existing ohmyhost.yaml must be one bounded regular file.");
24065
24875
  }
24066
- const configuration = await readFile2(configurationPath, "utf8");
24876
+ const configuration = await readFile3(configurationPath, "utf8");
24067
24877
  let parsed;
24068
24878
  try {
24069
24879
  parsed = parseOhmyhostConfigYaml(configuration);
@@ -24191,7 +25001,7 @@ async function tryReadPackageJson(root) {
24191
25001
  }
24192
25002
  function hasDirectSafeBuildCommand(packageJson, framework) {
24193
25003
  const scripts = packageJson["scripts"];
24194
- if (!isRecord2(scripts) || typeof scripts["build"] !== "string")
25004
+ if (!isRecord4(scripts) || typeof scripts["build"] !== "string")
24195
25005
  return false;
24196
25006
  const command = scripts["build"];
24197
25007
  if (command.includes("..") || /(?:^|\s)cd(?:\s|$)/u.test(command))
@@ -24239,7 +25049,7 @@ async function collectRepositoryFiles(root) {
24239
25049
  const directory = directories.pop();
24240
25050
  if (directory === void 0)
24241
25051
  break;
24242
- for (const entry of await readdir(resolve2(root, directory), { withFileTypes: true })) {
25052
+ for (const entry of await readdir3(resolve2(root, directory), { withFileTypes: true })) {
24243
25053
  const relative = directory === "" ? entry.name : posix.join(directory, entry.name);
24244
25054
  if (entry.isSymbolicLink()) {
24245
25055
  throw new RepositoryInitError("repository_symlink_forbidden", "Repository symlinks are forbidden");
@@ -24272,8 +25082,8 @@ async function readPackageJson(root) {
24272
25082
  throw new RepositoryInitError("package_manifest_invalid", "package.json is invalid");
24273
25083
  }
24274
25084
  try {
24275
- const value = JSON.parse(await readFile2(path, "utf8"));
24276
- if (!isRecord2(value))
25085
+ const value = JSON.parse(await readFile3(path, "utf8"));
25086
+ if (!isRecord4(value))
24277
25087
  throw new TypeError();
24278
25088
  return value;
24279
25089
  } catch {
@@ -24284,7 +25094,7 @@ function dependencyNames(packageJson) {
24284
25094
  const names = /* @__PURE__ */ new Set();
24285
25095
  for (const field of ["dependencies", "devDependencies", "optionalDependencies"]) {
24286
25096
  const value = packageJson[field];
24287
- if (!isRecord2(value))
25097
+ if (!isRecord4(value))
24288
25098
  continue;
24289
25099
  for (const [name, version2] of Object.entries(value)) {
24290
25100
  if (typeof version2 === "string" && version2.length > 0)
@@ -24423,9 +25233,9 @@ function repositoryBlockers(packageJson, framework, nextStandaloneOutput, tanSta
24423
25233
  });
24424
25234
  }
24425
25235
  const scripts = packageJson["scripts"];
24426
- const scriptRecord = isRecord2(scripts) ? scripts : {};
25236
+ const scriptRecord = isRecord4(scripts) ? scripts : {};
24427
25237
  if (framework === "functions") {
24428
- } else if (!isRecord2(scripts) || typeof scripts["build"] !== "string" || scripts["build"].length === 0) {
25238
+ } else if (!isRecord4(scripts) || typeof scripts["build"] !== "string" || scripts["build"].length === 0) {
24429
25239
  blockers.push({ code: "build_script_missing", message: "Declare a non-empty build script." });
24430
25240
  } else if (!hasDirectSafeBuildCommand(packageJson, framework)) {
24431
25241
  blockers.push({
@@ -24441,7 +25251,7 @@ function repositoryBlockers(packageJson, framework, nextStandaloneOutput, tanSta
24441
25251
  });
24442
25252
  }
24443
25253
  if (framework === "nextjs") {
24444
- const dependencies = isRecord2(packageJson["dependencies"]) ? packageJson["dependencies"] : {};
25254
+ const dependencies = isRecord4(packageJson["dependencies"]) ? packageJson["dependencies"] : {};
24445
25255
  const nextBuild = scriptRecord["build"] === "next build" || scriptRecord["build"] === "next build --webpack";
24446
25256
  const nextPackageConfigured = nextBuild && compatibility.frameworks[0]?.classification !== "unsupported" && exactVersion(dependencies["react"]) && exactVersion(dependencies["react-dom"]) && frameworkConfigs.next !== null;
24447
25257
  const nextConfigured = nextPackageConfigured && frameworkConfigs.ambiguous.every((config) => config !== "open-next");
@@ -24466,7 +25276,7 @@ function exactVersion(value) {
24466
25276
  function dependencyVersion(packageJson, name) {
24467
25277
  for (const field of ["dependencies", "devDependencies", "optionalDependencies"]) {
24468
25278
  const dependencies = packageJson[field];
24469
- if (!isRecord2(dependencies))
25279
+ if (!isRecord4(dependencies))
24470
25280
  continue;
24471
25281
  const version2 = dependencies[name];
24472
25282
  if (typeof version2 === "string" && version2.length > 0)
@@ -24537,7 +25347,7 @@ async function detectNextStandaloneOutput(root, framework, nextConfigPath) {
24537
25347
  const information = await stat2(path);
24538
25348
  if (!information.isFile() || information.size > MAXIMUM_CONFIGURATION_BYTES)
24539
25349
  return false;
24540
- return declaresStandaloneOutput(await readFile2(path, "utf8"));
25350
+ return declaresStandaloneOutput(await readFile3(path, "utf8"));
24541
25351
  }
24542
25352
  function declaresStandaloneOutput(source) {
24543
25353
  const tokens = sourceTokens(source);
@@ -24606,7 +25416,7 @@ async function detectTanStackRuntime(root, files, framework, packageJson, viteCo
24606
25416
  const information = await stat2(path);
24607
25417
  if (!information.isFile() || information.size > MAXIMUM_PACKAGE_BYTES)
24608
25418
  return "ambiguous";
24609
- const source = await readFile2(path, "utf8");
25419
+ const source = await readFile3(path, "utf8");
24610
25420
  const tokens = sourceTokens(source);
24611
25421
  if (isCompleteTanStackStatic(tokens))
24612
25422
  return "static";
@@ -24678,10 +25488,10 @@ function directSourceObjectProperty(tokens, start, end, name) {
24678
25488
  }
24679
25489
  return null;
24680
25490
  }
24681
- function matchingSourceDelimiter(tokens, start, open4, close) {
25491
+ function matchingSourceDelimiter(tokens, start, open5, close) {
24682
25492
  let depth = 0;
24683
25493
  for (let index = start; index < tokens.length; index += 1) {
24684
- if (tokens[index] === open4)
25494
+ if (tokens[index] === open5)
24685
25495
  depth += 1;
24686
25496
  if (tokens[index] === close)
24687
25497
  depth -= 1;
@@ -24715,7 +25525,7 @@ async function detectR2Storage(root, files) {
24715
25525
  const information = await stat2(path);
24716
25526
  if (!information.isFile() || information.size > MAXIMUM_CONFIGURATION_BYTES)
24717
25527
  continue;
24718
- const source = await readFile2(path, "utf8");
25528
+ const source = await readFile3(path, "utf8");
24719
25529
  if (/\br2_buckets\b/u.test(source))
24720
25530
  return true;
24721
25531
  }
@@ -24757,7 +25567,7 @@ function viteCompanionScaffold(files, capabilities) {
24757
25567
  async function createConfiguration(root, configuration) {
24758
25568
  let handle;
24759
25569
  try {
24760
- handle = await open3(resolve2(root, "ohmyhost.yaml"), "wx", 384);
25570
+ handle = await open4(resolve2(root, "ohmyhost.yaml"), "wx", 384);
24761
25571
  await handle.writeFile(configuration, "utf8");
24762
25572
  await handle.sync();
24763
25573
  } catch (error) {
@@ -24770,7 +25580,7 @@ async function createConfiguration(root, configuration) {
24770
25580
  await handle?.close();
24771
25581
  }
24772
25582
  }
24773
- function isRecord2(value) {
25583
+ function isRecord4(value) {
24774
25584
  return typeof value === "object" && value !== null && !Array.isArray(value);
24775
25585
  }
24776
25586
  function isNodeError(value) {
@@ -24778,11 +25588,8 @@ function isNodeError(value) {
24778
25588
  }
24779
25589
 
24780
25590
  // apps/product-cli/dist/composition.js
24781
- import { randomUUID } from "node:crypto";
24782
- import { join as join2 } from "node:path";
24783
-
24784
- // packages/workos-auth-contracts/dist/user-api-keys.js
24785
- var WORKOS_USER_API_KEY_PATTERN = /^sk_[A-Za-z0-9_-]{20,128}$/u;
25591
+ import { randomUUID as randomUUID2 } from "node:crypto";
25592
+ import { join as join3 } from "node:path";
24786
25593
 
24787
25594
  // packages/workos-auth-contracts/dist/device-flow.js
24788
25595
  var WORKOS_AUTHKIT_DEVICE_AUTHORIZATION_ENDPOINT = "https://api.workos.com/user_management/authorize/device";
@@ -25012,37 +25819,99 @@ function environmentToken(environment = {}) {
25012
25819
  throw new ProductApiAuthenticationError("invalid_environment_token");
25013
25820
  return value;
25014
25821
  }
25822
+ function environmentProfile(environment = {}) {
25823
+ const value = environment.OHMYHOST_PROFILE;
25824
+ if (value === void 0 || value === "")
25825
+ return void 0;
25826
+ if (!isProfileName(value))
25827
+ throw new ProductApiAuthenticationError("invalid_profile_binding");
25828
+ return value;
25829
+ }
25830
+ var profileRequest = (input, bound) => ({
25831
+ ...input?.name === void 0 ? {} : { name: input.name },
25832
+ ...bound === void 0 ? {} : { bound },
25833
+ ...input?.organizationId === void 0 ? {} : { organizationId: input.organizationId },
25834
+ ...input?.userId === void 0 ? {} : { userId: input.userId }
25835
+ });
25015
25836
  var createProductionAuthenticatedProductApi = async (signal, overrides = {}) => {
25016
25837
  const transport = await createAuthenticatedProductTransport(signal, overrides);
25017
25838
  return new GeneratedSdkProductApi(transport.baseUrl, transport.accessToken, transport.fetch);
25018
25839
  };
25019
25840
  var createAuthenticatedProductTransport = async (signal, overrides = {}) => {
25020
- const profile = resolveProductCliEnvironment(overrides.environment);
25841
+ const platform = resolveProductCliEnvironment(overrides.environment);
25021
25842
  const apiToken = environmentToken(overrides.environment);
25843
+ const bound = environmentProfile(overrides.environment);
25844
+ const baseUrl = overrides.productApiOrigin ?? platform.apiOrigin;
25022
25845
  const boundedFetch = createBoundedFetch(overrides.fetch ?? fetch, signal, REQUEST_TIMEOUT_MS, MAX_PUBLIC_RESPONSE_BYTES);
25023
- if (apiToken !== void 0)
25846
+ const registry = overrides.profiles ?? new NativeKeyringProfileRegistry(void 0, platform.credentialService);
25847
+ const binding = overrides.request?.bindOrganization === true;
25848
+ const request = profileRequest(overrides.request, bound);
25849
+ if (apiToken !== void 0) {
25850
+ await verifyEnvironmentTokenContext({
25851
+ token: apiToken,
25852
+ request: binding ? profileRequest({}, bound) : request,
25853
+ registry,
25854
+ baseUrl,
25855
+ fetch: boundedFetch
25856
+ });
25024
25857
  return {
25025
- baseUrl: overrides.productApiOrigin ?? profile.apiOrigin,
25858
+ baseUrl,
25026
25859
  accessToken: apiToken,
25027
25860
  fetch: boundedFetch,
25028
- // A user API token carries a fixed scope; there is no session that could select a workspace.
25029
- workspace: void 0
25861
+ workspace: void 0,
25862
+ context: { credential: "environment_token", profile: null }
25030
25863
  };
25031
- const store = overrides.credentialStore ?? new NativeNapiKeyringCredentialStore(void 0, profile.credentialService);
25032
- const stored = await store.read();
25033
- if (stored === void 0)
25034
- throw new ProductApiAuthenticationError("authentication_required");
25864
+ }
25035
25865
  const now = overrides.now ?? (() => /* @__PURE__ */ new Date());
25036
25866
  const refresher = overrides.tokenRefresher ?? new PublicWorkosTokenRefresher(overrides.environment, overrides.fetch, now);
25037
- const credential = await currentCredential(stored, store, signal, now, refresher);
25867
+ const profiles = await loadProfiles(registry, {
25868
+ baseUrl,
25869
+ fetch: boundedFetch,
25870
+ signal,
25871
+ now,
25872
+ refresher
25873
+ });
25874
+ const selection = binding && request.organizationId !== void 0 ? selectProfileForOrganization(profiles, {
25875
+ ...request,
25876
+ organizationId: request.organizationId
25877
+ }) : selectProfile(profiles, request);
25878
+ if (selection.outcome !== "selected")
25879
+ throw profileSelectionError(selection, request, platform.commandPrefix, overrides.selector ?? "cli");
25880
+ const profile = selection.profile;
25881
+ const store = registry.credential(profile);
25882
+ const stored = await store.read();
25883
+ if (stored === void 0)
25884
+ throw missingLogin(profile, platform.commandPrefix);
25885
+ const credential = await currentCredential(stored, store, signal, now, refresher, () => registry.remove(profile));
25038
25886
  return {
25039
- baseUrl: overrides.productApiOrigin ?? profile.apiOrigin,
25887
+ baseUrl,
25040
25888
  accessToken: credential.accessToken,
25041
25889
  fetch: boundedFetch,
25042
- workspace: { store, refresher }
25890
+ workspace: { store, refresher, profiles: registry, profile },
25891
+ context: { credential: "profile", profile }
25892
+ };
25893
+ };
25894
+ var listSavedProfiles = async (signal, overrides = {}) => {
25895
+ const platform = resolveProductCliEnvironment(overrides.environment);
25896
+ const apiToken = environmentToken(overrides.environment);
25897
+ const bound = environmentProfile(overrides.environment);
25898
+ const registry = overrides.profiles ?? new NativeKeyringProfileRegistry(void 0, platform.credentialService);
25899
+ const now = overrides.now ?? (() => /* @__PURE__ */ new Date());
25900
+ const profiles = apiToken === void 0 ? await loadProfiles(registry, {
25901
+ baseUrl: overrides.productApiOrigin ?? platform.apiOrigin,
25902
+ fetch: createBoundedFetch(overrides.fetch ?? fetch, signal, REQUEST_TIMEOUT_MS, MAX_PUBLIC_RESPONSE_BYTES),
25903
+ signal,
25904
+ now,
25905
+ refresher: overrides.tokenRefresher ?? new PublicWorkosTokenRefresher(overrides.environment, overrides.fetch, now)
25906
+ }) : await registry.list();
25907
+ return {
25908
+ profiles: profiles.map(publicProfile),
25909
+ bound: bound ?? null,
25910
+ environment_token: apiToken !== void 0
25043
25911
  };
25044
25912
  };
25045
- var currentCredential = async (stored, store, signal, now, tokenRefresher) => {
25913
+ var missingLogin = (profile, commandPrefix) => new ProfileSelectionError("authentication_required", 5, `The saved login ${profile.name} has no credential on this computer. Run '${commandPrefix} login${profile.organizationId === null ? "" : ` --organization ${profile.organizationId}`} --profile-name ${profile.name} --json'.`);
25914
+ var currentCredential = async (stored, store, signal, now, tokenRefresher, forget) => {
25046
25915
  const expiresAt = stored.expiresAt === void 0 ? NaN : Date.parse(stored.expiresAt);
25047
25916
  if (Number.isFinite(expiresAt) && expiresAt > now().getTime() + REQUEST_TIMEOUT_MS) {
25048
25917
  return stored;
@@ -25058,119 +25927,350 @@ var currentCredential = async (stored, store, signal, now, tokenRefresher) => {
25058
25927
  return carried;
25059
25928
  } catch (error) {
25060
25929
  if (error instanceof TokenRefreshRejectedError) {
25061
- await store.clear();
25930
+ await forget();
25062
25931
  throw new ProductApiAuthenticationError("authentication_required");
25063
25932
  }
25064
25933
  throw new ProductApiAuthenticationError("token_refresh_failed");
25065
25934
  }
25066
25935
  };
25936
+ var loadProfiles = async (registry, context) => {
25937
+ const legacy = registry.legacy();
25938
+ const stored = await legacy.read();
25939
+ if (stored !== void 0)
25940
+ await migrateLegacyLogin(registry, legacy, stored, context);
25941
+ return registry.list();
25942
+ };
25943
+ var migrateLegacyLogin = async (registry, legacy, stored, context) => {
25944
+ let credential;
25945
+ try {
25946
+ credential = await currentCredential(stored, legacy, context.signal, context.now, context.refresher, () => legacy.clear());
25947
+ } catch (error) {
25948
+ if (error instanceof ProductApiAuthenticationError && error.code === "authentication_required")
25949
+ return;
25950
+ throw error;
25951
+ }
25952
+ const api = new GeneratedSdkProductApi(context.baseUrl, credential.accessToken, context.fetch);
25953
+ let identity;
25954
+ try {
25955
+ identity = await api.getCurrentIdentity();
25956
+ } catch (error) {
25957
+ if (!isProblem(error, "unauthenticated"))
25958
+ throw error;
25959
+ return;
25960
+ }
25961
+ const userId = verifiedUserId(identity);
25962
+ const organizationId = parseCurrentIdentity(identity).organization_ids[0] ?? null;
25963
+ const organizationName = organizationId === null ? null : (await api.getAccountProfile()).organizations.find((organization) => organization.id === organizationId)?.name ?? null;
25964
+ const login = { userId, organizationId, organizationName };
25965
+ await registry.save({ name: defaultProfileName(await registry.list(), login), ...login }, credential);
25966
+ await legacy.clear();
25967
+ };
25968
+ var isProblem = (error, code) => typeof error === "object" && error !== null && "code" in error && error.code === code;
25969
+ var verifyEnvironmentTokenContext = async (input) => {
25970
+ const { request } = input;
25971
+ const named = request.name ?? request.bound;
25972
+ if (named === void 0 && request.organizationId === void 0 && request.userId === void 0)
25973
+ return null;
25974
+ const refuse = (target) => new ProfileSelectionError("environment_token_context_mismatch", 2, `${target} Unset OHMYHOST_TOKEN to use the saved login, or load the key of that account from its private env file. Nothing was sent.`);
25975
+ if (request.name !== void 0 && request.bound !== void 0 && request.name !== request.bound)
25976
+ throw refuse(`This call names profile ${request.name}, but the process is bound to OHMYHOST_PROFILE=${request.bound}.`);
25977
+ const profile = named === void 0 ? void 0 : (await input.registry.list()).find((saved) => saved.name === named);
25978
+ if (named !== void 0 && profile === void 0)
25979
+ throw refuse(`OHMYHOST_TOKEN overrides saved logins in this process, and no saved login is named ${named}.`);
25980
+ const { userId, organizationId } = await readTokenIdentity(input);
25981
+ const owner = `OHMYHOST_TOKEN belongs to user ${userId} in organization ${organizationId ?? "none"},`;
25982
+ if (profile !== void 0 && (profile.userId !== userId || profile.organizationId !== organizationId))
25983
+ throw refuse(`${owner} but this call names ${describeProfile(profile)}.`);
25984
+ if (request.organizationId !== void 0 && request.organizationId !== organizationId)
25985
+ throw refuse(`${owner} but this call targets organization ${request.organizationId}.`);
25986
+ if (request.userId !== void 0 && request.userId !== userId)
25987
+ throw refuse(`${owner} but this call targets user ${request.userId}.`);
25988
+ return { userId, organizationId };
25989
+ };
25990
+ var readTokenIdentity = async (input) => {
25991
+ const identity = parseCurrentIdentity(await new GeneratedSdkProductApi(input.baseUrl, input.token, input.fetch).getCurrentIdentity());
25992
+ return {
25993
+ userId: identity.actor_id.startsWith("user:") ? identity.actor_id.slice(5) : identity.actor_id,
25994
+ organizationId: identity.organization_ids[0] ?? null
25995
+ };
25996
+ };
25997
+ var commandOrganizationId = (command) => {
25998
+ if (command.kind === "organization-use" || command.kind === "login")
25999
+ return void 0;
26000
+ if (command.kind === "feedback-submit")
26001
+ return command.report.organization_id;
26002
+ return "organizationId" in command && typeof command.organizationId === "string" ? command.organizationId : void 0;
26003
+ };
26004
+ var commandProjectId = (command) => {
26005
+ if (command.kind === "managed-mail")
26006
+ return command.request.projectId;
26007
+ return "projectId" in command && typeof command.projectId === "string" ? command.projectId : void 0;
26008
+ };
25067
26009
  var runProductionCli = async (argv, environment, io, signal, overrides = {}) => {
25068
26010
  const diagnostics = new StderrDiagnosticSink(io);
26011
+ const written = (result) => {
26012
+ io.writeStdout(result.stdout);
26013
+ if (result.stderr.length > 0)
26014
+ io.writeStderr(result.stderr);
26015
+ return result.exitCode;
26016
+ };
26017
+ let args;
26018
+ let profileName;
26019
+ try {
26020
+ const extracted = extractProfileName(argv);
26021
+ args = extracted.argv;
26022
+ profileName = extracted.profileName;
26023
+ } catch {
26024
+ return written(invalidCommandFailure(recognizedCommandLabel(argv)));
26025
+ }
26026
+ const workingDirectory = overrides.workingDirectory ?? process.cwd();
25069
26027
  let command;
26028
+ let linked;
26029
+ let links = { links: [], legacy: null };
25070
26030
  try {
25071
- const parsed = await parseWithLinkedProject(argv, async () => {
25072
- const profile2 = resolveProductCliEnvironment(environment);
25073
- return readLinkedProjectId(join2(overrides.workingDirectory ?? process.cwd(), profile2.linkDirectory), profile2.apiOrigin);
26031
+ const parsed = await parseWithLinkedProject(args, async () => {
26032
+ let platform2;
26033
+ try {
26034
+ platform2 = resolveProductCliEnvironment(environment);
26035
+ } catch {
26036
+ return { outcome: "none" };
26037
+ }
26038
+ links = await readProjectLinks(join3(workingDirectory, platform2.linkDirectory), platform2.apiOrigin);
26039
+ let named = profileName;
26040
+ try {
26041
+ named ??= environmentProfile(environment);
26042
+ } catch {
26043
+ }
26044
+ const profiles = named === void 0 ? [] : await (overrides.profiles ?? new NativeKeyringProfileRegistry(void 0, platform2.credentialService)).list();
26045
+ return chooseLinkedProject(links, profiles.find((profile2) => profile2.name === named)?.organizationId ?? void 0);
25074
26046
  });
25075
26047
  command = parsed.command;
25076
- if (parsed.linkedProjectId !== null)
25077
- io.writeStderr(`ohmyhost: using linked project ${parsed.linkedProjectId}
26048
+ linked = parsed.linked;
26049
+ if (linked !== null)
26050
+ io.writeStderr(`ohmyhost: using linked project ${linked.projectId}
25078
26051
  `);
25079
26052
  } catch (error) {
25080
- const result2 = error instanceof InvalidCommandError ? invalidCommandFailure(recognizedCommandLabel(argv)) : errorDocument("unknown", 1, "internal_error", false, "Retry the command.");
26053
+ if (error instanceof LinkedProjectAmbiguousError)
26054
+ return written(errorDocument(recognizedCommandLabel(args), 2, "linked_project_selection_required", false, `This directory is linked to projects of several organizations (${error.organizationIds.join(", ")}). Pass --project ULID, or choose the login with --profile-name NAME; '${resolveProductCliEnvironment(environment).commandPrefix} profile list --json' shows the saved logins.`));
26055
+ if (error instanceof CredentialStoreUnavailableError)
26056
+ return written(errorDocument(recognizedCommandLabel(args), 3, "credential_store_unavailable", true, "Unlock or repair the selected credential store and retry."));
26057
+ const result = error instanceof InvalidCommandError ? invalidCommandFailure(recognizedCommandLabel(args)) : errorDocument("unknown", 1, "internal_error", false, "Retry the command.");
25081
26058
  if (!(error instanceof InvalidCommandError)) {
25082
26059
  diagnostics.record({
25083
- correlationId: randomUUID(),
26060
+ correlationId: randomUUID2(),
25084
26061
  category: "internal",
25085
26062
  subsystem: "parser",
25086
26063
  errorKind: "unexpected"
25087
26064
  });
25088
26065
  }
25089
- io.writeStdout(result2.stdout);
25090
- return result2.exitCode;
26066
+ return written(result);
25091
26067
  }
25092
26068
  if (command.kind === "init") {
26069
+ if (profileName !== void 0)
26070
+ return written(invalidCommandFailure("init"));
25093
26071
  return runOfflineInit(command, io, diagnostics);
25094
26072
  }
25095
26073
  if (command.kind === "help" || command.kind === "version") {
25096
- const result2 = cliMetadataResult(command.kind, command.kind === "help" ? command.topic : void 0);
25097
- io.writeStdout(result2.stdout);
25098
- return result2.exitCode;
26074
+ return written(cliMetadataResult(command.kind, command.kind === "help" ? command.topic : void 0));
25099
26075
  }
25100
- let profile;
26076
+ let platform;
25101
26077
  try {
25102
- profile = resolveProductCliEnvironment(environment);
26078
+ platform = resolveProductCliEnvironment(environment);
25103
26079
  } catch {
25104
- const result2 = errorDocument(recognizedCommandLabel(argv), 2, "invalid_environment", false, "Set OHMYHOST_ENVIRONMENT to development or production.");
25105
- io.writeStdout(result2.stdout);
25106
- return result2.exitCode;
26080
+ return written(errorDocument(recognizedCommandLabel(args), 2, "invalid_environment", false, "Set OHMYHOST_ENVIRONMENT to development or production."));
25107
26081
  }
25108
26082
  let apiToken;
25109
26083
  try {
25110
26084
  apiToken = environmentToken(environment);
25111
26085
  } catch {
25112
- const result2 = errorDocument(recognizedCommandLabel(argv), 2, "invalid_environment_token", false, "Set OHMYHOST_TOKEN to a valid user API key, or unset it to use interactive login.");
25113
- io.writeStdout(result2.stdout);
25114
- return result2.exitCode;
25115
- }
25116
- if (apiToken !== void 0 && (command.kind === "login" || command.kind === "logout")) {
25117
- const result2 = errorDocument(recognizedCommandLabel(argv), 2, "environment_token_active", false, "Unset OHMYHOST_TOKEN before changing interactive login credentials. Logout does not revoke a user API key.");
25118
- io.writeStdout(result2.stdout);
25119
- return result2.exitCode;
26086
+ return written(errorDocument(recognizedCommandLabel(args), 2, "invalid_environment_token", false, "Set OHMYHOST_TOKEN to a valid user API key, or unset it to use interactive login."));
25120
26087
  }
25121
- const selectedOverrides = {
25122
- ...overrides,
25123
- credentialStore: overrides.credentialStore ?? new NativeNapiKeyringCredentialStore(void 0, profile.credentialService)
25124
- };
25125
- if (command.kind === "logout" && !command.revoke) {
25126
- return runOfflineLogout(io, diagnostics, selectedOverrides);
26088
+ let bound;
26089
+ try {
26090
+ bound = environmentProfile(environment);
26091
+ } catch {
26092
+ return written(errorDocument(recognizedCommandLabel(args), 2, "invalid_profile_binding", false, `Set OHMYHOST_PROFILE to the name of a saved login ('${platform.commandPrefix} profile list --json'), or unset it.`));
25127
26093
  }
25128
- const productApiOrigin = profile.apiOrigin;
26094
+ if (apiToken !== void 0 && (command.kind === "login" || command.kind === "logout")) {
26095
+ return written(errorDocument(recognizedCommandLabel(args), 2, "environment_token_active", false, "Unset OHMYHOST_TOKEN before changing interactive login credentials. Logout does not revoke a user API key."));
26096
+ }
26097
+ const requiredToken = command.kind === "secret-set" ? command.environmentToken : void 0;
26098
+ const handedAccount2 = command.kind === "secret-set" ? requiredToken ?? command.profileAccount : void 0;
26099
+ if (requiredToken !== void 0 && apiToken === void 0)
26100
+ return written(errorDocument(recognizedCommandLabel(args), 2, "environment_token_required", false, `This command runs only with OHMYHOST_TOKEN holding a key of user ${requiredToken.userId} in organization ${requiredToken.organizationId}; a saved login cannot run it. Load that key from its private env file into this process and retry. Nothing was sent.`));
26101
+ const registry = overrides.profiles ?? new NativeKeyringProfileRegistry(void 0, platform.credentialService);
26102
+ const linkDirectory = join3(workingDirectory, platform.linkDirectory);
26103
+ const projectId = commandProjectId(command);
26104
+ const organizationId = handedAccount2?.organizationId ?? commandOrganizationId(command) ?? linked?.organizationId ?? (projectId === void 0 ? void 0 : linkedOrganization(links, projectId));
26105
+ const request = profileRequest({
26106
+ ...profileName === void 0 ? {} : { name: profileName },
26107
+ ...organizationId === void 0 || organizationId === null ? {} : { organizationId },
26108
+ ...handedAccount2 === void 0 ? {} : { userId: handedAccount2.userId }
26109
+ }, bound);
25129
26110
  const transport = new FetchWorkosPublicTransport(signal, REQUEST_TIMEOUT_MS);
26111
+ const now = () => /* @__PURE__ */ new Date();
26112
+ const refresher = new PublicWorkosTokenRefresher(environment);
26113
+ const boundedFetch = () => createBoundedFetch(fetch, signal, REQUEST_TIMEOUT_MS, MAX_PUBLIC_RESPONSE_BYTES);
26114
+ let profile;
26115
+ try {
26116
+ if (apiToken !== void 0) {
26117
+ if (command.kind !== "profile-list")
26118
+ await verifyEnvironmentTokenContext({
26119
+ token: apiToken,
26120
+ request,
26121
+ registry,
26122
+ baseUrl: platform.apiOrigin,
26123
+ fetch: boundedFetch()
26124
+ });
26125
+ if (linked !== null && linked.organizationId === null)
26126
+ await adoptLegacyLink({
26127
+ links,
26128
+ directory: linkDirectory,
26129
+ repositoryRoot: workingDirectory,
26130
+ apiOrigin: platform.apiOrigin,
26131
+ organizationId: (await readTokenIdentity({
26132
+ token: apiToken,
26133
+ baseUrl: platform.apiOrigin,
26134
+ fetch: boundedFetch()
26135
+ })).organizationId,
26136
+ accessToken: apiToken,
26137
+ fetch: boundedFetch(),
26138
+ actor: "OHMYHOST_TOKEN",
26139
+ commandPrefix: platform.commandPrefix
26140
+ });
26141
+ } else if (command.kind === "logout" && !command.revoke) {
26142
+ return await runOfflineLogout(registry, request, platform.commandPrefix, io, diagnostics);
26143
+ } else if (command.kind !== "login") {
26144
+ const profiles = await loadProfiles(registry, {
26145
+ baseUrl: platform.apiOrigin,
26146
+ fetch: boundedFetch(),
26147
+ signal,
26148
+ now,
26149
+ refresher
26150
+ });
26151
+ if (command.kind !== "profile-list") {
26152
+ const selection = command.kind === "organization-use" ? selectProfileForOrganization(profiles, {
26153
+ ...request,
26154
+ organizationId: command.organizationId
26155
+ }) : selectProfile(profiles, request);
26156
+ if (selection.outcome !== "selected")
26157
+ throw profileSelectionError(selection, request, platform.commandPrefix, "cli");
26158
+ profile = selection.profile;
26159
+ io.writeStderr(`ohmyhost: acting as ${describeProfile(profile)}
26160
+ `);
26161
+ if (linked !== null && linked.organizationId === null) {
26162
+ const selected2 = await createAuthenticatedProductTransport(signal, {
26163
+ environment,
26164
+ profiles: registry,
26165
+ request: { name: profile.name }
26166
+ });
26167
+ await adoptLegacyLink({
26168
+ links,
26169
+ directory: linkDirectory,
26170
+ repositoryRoot: workingDirectory,
26171
+ apiOrigin: platform.apiOrigin,
26172
+ organizationId: profile.organizationId,
26173
+ accessToken: selected2.accessToken,
26174
+ fetch: selected2.fetch,
26175
+ actor: `profile ${profile.name}`,
26176
+ commandPrefix: platform.commandPrefix
26177
+ });
26178
+ }
26179
+ }
26180
+ }
26181
+ } catch (error) {
26182
+ return written(compositionFailure(recognizedCommandLabel(args), error, signal, platform.commandPrefix));
26183
+ }
26184
+ const selected = profile;
26185
+ const productApiOrigin = platform.apiOrigin;
25130
26186
  const lifecycle = new GeneratedSdkSessionRevoker({
25131
26187
  environment,
25132
- credentialStore: selectedOverrides.credentialStore
26188
+ profiles: registry,
26189
+ ...selected === void 0 ? {} : { request: { name: selected.name } }
25133
26190
  });
25134
26191
  const cli = new ProductCli({
25135
26192
  ...apiToken === void 0 ? {} : { apiToken },
25136
- commandPrefix: profile.commandPrefix,
25137
- credentialStore: () => selectedOverrides.credentialStore,
26193
+ commandPrefix: platform.commandPrefix,
26194
+ profiles: registry,
26195
+ ...selected === void 0 ? {} : { profile: selected },
26196
+ // A login in a process bound to one saved login renews exactly that one.
26197
+ ...(profileName ?? bound) === void 0 ? {} : { profileName: profileName ?? bound },
26198
+ ...bound === void 0 ? {} : { profileBinding: bound },
25138
26199
  deviceFlow: new WorkosDeviceFlowClient({
25139
- clientId: profile.clientId,
26200
+ clientId: platform.clientId,
25140
26201
  scope: "openid offline_access",
25141
- verificationUri: profile.verificationUri
25142
- }, transport, { now: () => /* @__PURE__ */ new Date() }),
26202
+ verificationUri: platform.verificationUri
26203
+ }, transport, { now }),
25143
26204
  deviceAuthorizationPresenter: new StderrDeviceAuthorizationPresenter(io),
25144
- productApi: (accessToken) => new GeneratedSdkProductApi(productApiOrigin, accessToken, createBoundedFetch(fetch, signal, REQUEST_TIMEOUT_MS, MAX_PUBLIC_RESPONSE_BYTES)),
25145
- // Login receives read-only workspace and GitHub queries instead of the product surface.
25146
- accountProfile: async (accessToken) => await new GeneratedSdkProductApi(productApiOrigin, accessToken, createBoundedFetch(fetch, signal, REQUEST_TIMEOUT_MS, MAX_PUBLIC_RESPONSE_BYTES)).getAccountProfile(),
25147
- githubConnectionStatus: (accessToken, organizationId) => getGithubOrganizationConnection({ organization_id: organizationId }, {
26205
+ productApi: (accessToken) => new GeneratedSdkProductApi(productApiOrigin, accessToken, boundedFetch()),
26206
+ // Login receives read-only identity, workspace and GitHub queries instead of the product surface.
26207
+ accountProfile: async (accessToken) => await new GeneratedSdkProductApi(productApiOrigin, accessToken, boundedFetch()).getAccountProfile(),
26208
+ currentIdentity: async (accessToken) => await new GeneratedSdkProductApi(productApiOrigin, accessToken, boundedFetch()).getCurrentIdentity(),
26209
+ githubConnectionStatus: (accessToken, organizationId2) => getGithubOrganizationConnection({ organization_id: organizationId2 }, {
25148
26210
  client: createClient({
25149
26211
  baseUrl: productApiOrigin,
25150
26212
  auth: accessToken,
25151
26213
  throwOnError: true,
25152
- fetch: createBoundedFetch(fetch, signal, REQUEST_TIMEOUT_MS, MAX_PUBLIC_RESPONSE_BYTES)
26214
+ fetch: boundedFetch()
25153
26215
  }),
25154
26216
  signal
25155
26217
  }),
25156
- projectLinkStore: new OwnerOnlyProjectLinkStore(join2(process.cwd(), profile.linkDirectory), process.cwd()),
25157
- productApiOrigin: profile.apiOrigin,
25158
- tokenRefresher: new PublicWorkosTokenRefresher(environment),
26218
+ projectLinkStore: new OwnerOnlyProjectLinkStore(linkDirectory, workingDirectory),
26219
+ productApiOrigin,
26220
+ tokenRefresher: refresher,
25159
26221
  tokenRevoker: lifecycle,
25160
- now: () => /* @__PURE__ */ new Date(),
26222
+ now,
25161
26223
  waitUntil,
25162
26224
  lifecycleTimeoutMs: REQUEST_TIMEOUT_MS,
25163
- createCorrelationId: randomUUID,
26225
+ createCorrelationId: randomUUID2,
25164
26226
  diagnosticSink: diagnostics,
25165
26227
  operationEventSink: { write: (value) => io.writeStdout(value) },
25166
26228
  readSecretValue: overrides.readSecretValue ?? ((readSignal) => readSecretValueFromStandardInput(process.stdin, readSignal)),
25167
26229
  spawnPsql: runLocalPsql
25168
26230
  });
25169
- const result = await cli.runCommand(command, signal);
25170
- io.writeStdout(result.stdout);
25171
- if (result.stderr.length > 0)
25172
- io.writeStderr(result.stderr);
25173
- return result.exitCode;
26231
+ return written(await cli.runCommand(command, signal));
26232
+ };
26233
+ var adoptLegacyLink = async (input) => {
26234
+ const legacy = input.links.legacy;
26235
+ if (legacy === null)
26236
+ return;
26237
+ const refuse = () => new ProfileSelectionError("linked_project_organization_mismatch", 2, `This directory's link names project ${legacy.project_id}, which ${input.actor} cannot see${input.organizationId === null ? "" : ` in organization ${input.organizationId}`}. Nothing was sent to it. Pass --project ULID or --profile-name NAME for the account that owns it, or run '${input.commandPrefix} link' again for this account.`);
26238
+ let organizationId;
26239
+ try {
26240
+ const project = await getProject({ project_id: legacy.project_id }, {
26241
+ client: createClient({
26242
+ baseUrl: input.apiOrigin,
26243
+ auth: input.accessToken,
26244
+ fetch: input.fetch,
26245
+ throwOnError: true
26246
+ })
26247
+ });
26248
+ organizationId = project.organization_id;
26249
+ } catch (error) {
26250
+ if (isProblem(error, "resource_not_found") || isProblem(error, "forbidden"))
26251
+ throw refuse();
26252
+ throw error;
26253
+ }
26254
+ if (organizationId !== input.organizationId)
26255
+ throw refuse();
26256
+ await new OwnerOnlyProjectLinkStore(input.directory, input.repositoryRoot).save({
26257
+ version: 2,
26258
+ api_origin: input.apiOrigin,
26259
+ organization_id: organizationId,
26260
+ project_id: legacy.project_id,
26261
+ installation_id: legacy.installation_id,
26262
+ repository_full_name: legacy.repository_full_name
26263
+ });
26264
+ await removeLegacyProjectLink(input.directory);
26265
+ };
26266
+ var compositionFailure = (command, error, signal, commandPrefix) => {
26267
+ if (error instanceof ProfileSelectionError)
26268
+ return errorDocument(command, error.exitCode, error.code, false, error.suggestedAction);
26269
+ if (error instanceof ProfileLimitError)
26270
+ return errorDocument(command, 2, "profile_limit_reached", false, profileLimitAction(commandPrefix));
26271
+ if (error instanceof ProductApiAuthenticationError)
26272
+ return error.code === "authentication_required" ? errorDocument(command, 5, "authentication_required", false, `Run '${commandPrefix} login --json'.`) : errorDocument(command, 5, "token_refresh_failed", true, `Run '${commandPrefix} login --json' if retrying does not succeed.`);
26273
+ return classifyCliFailure(command, error, signal);
25174
26274
  };
25175
26275
  var runOfflineInit = async (command, io, diagnostics) => {
25176
26276
  try {
@@ -25208,7 +26308,7 @@ var runOfflineInit = async (command, io, diagnostics) => {
25208
26308
  } catch (error) {
25209
26309
  const known = error instanceof RepositoryInitError;
25210
26310
  diagnostics.record({
25211
- correlationId: randomUUID(),
26311
+ correlationId: randomUUID2(),
25212
26312
  category: known ? "configuration" : "internal",
25213
26313
  subsystem: "runtime",
25214
26314
  errorKind: known ? "invalid_response" : "unexpected"
@@ -25329,7 +26429,7 @@ var GeneratedSdkSessionRevoker = class {
25329
26429
  fetch: transport.fetch,
25330
26430
  throwOnError: true
25331
26431
  });
25332
- const result = await revokeCurrentSession({ "Idempotency-Key": randomUUID() }, { client: client2, signal });
26432
+ const result = await revokeCurrentSession({ "Idempotency-Key": randomUUID2() }, { client: client2, signal });
25333
26433
  if (result.revoked !== true)
25334
26434
  throw new LifecycleTransportError();
25335
26435
  } catch (error) {
@@ -25339,25 +26439,27 @@ var GeneratedSdkSessionRevoker = class {
25339
26439
  }
25340
26440
  }
25341
26441
  };
25342
- async function parseWithLinkedProject(argv, linkedProjectId) {
26442
+ var LinkedProjectAmbiguousError = class extends Error {
26443
+ organizationIds;
26444
+ constructor(organizationIds) {
26445
+ super("Several organizations' links apply");
26446
+ this.organizationIds = organizationIds;
26447
+ this.name = "LinkedProjectAmbiguousError";
26448
+ }
26449
+ };
26450
+ async function parseWithLinkedProject(argv, linkedProject) {
25343
26451
  try {
25344
- return { command: parseProductCliCommand(argv), linkedProjectId: null };
26452
+ return { command: parseProductCliCommand(argv), linked: null };
25345
26453
  } catch (error) {
25346
26454
  if (!(error instanceof InvalidCommandError) || argv.includes("--project"))
25347
26455
  throw error;
25348
- let projectId;
25349
- try {
25350
- projectId = await linkedProjectId();
25351
- } catch {
25352
- projectId = null;
25353
- }
25354
- if (projectId === null)
26456
+ const linked = await linkedProject();
26457
+ if (linked.outcome === "ambiguous")
26458
+ throw new LinkedProjectAmbiguousError(linked.organizationIds);
26459
+ if (linked.outcome === "none")
25355
26460
  throw error;
25356
26461
  try {
25357
- return {
25358
- command: parseProductCliCommand([...argv, "--project", projectId]),
25359
- linkedProjectId: projectId
25360
- };
26462
+ return { command: parseProductCliCommand([...argv, "--project", linked.projectId]), linked };
25361
26463
  } catch {
25362
26464
  throw error;
25363
26465
  }
@@ -25485,14 +26587,25 @@ var waitUntil = async (instant, signal) => {
25485
26587
  };
25486
26588
  var boundedSignal2 = (parent, timeoutMs) => AbortSignal.any([parent, AbortSignal.timeout(timeoutMs)]);
25487
26589
  var isAbortOrTimeout = (error) => error instanceof DOMException && (error.name === "AbortError" || error.name === "TimeoutError");
25488
- var runOfflineLogout = async (io, diagnostics, overrides) => {
26590
+ var runOfflineLogout = async (registry, request, commandPrefix, io, diagnostics) => {
25489
26591
  let result;
25490
26592
  try {
25491
- await (overrides.credentialStore ?? new NativeNapiKeyringCredentialStore()).clear();
25492
- result = resultDocument("logout", 0, "logged_out");
26593
+ const selection = selectProfile(await registry.list(), request);
26594
+ if (selection.outcome === "selected") {
26595
+ await registry.remove(selection.profile);
26596
+ result = resultDocument("logout", 0, "logged_out", {
26597
+ profile: publicProfile(selection.profile)
26598
+ });
26599
+ } else if (selection.outcome === "none" && request.name === void 0 && request.bound === void 0) {
26600
+ await registry.legacy().clear();
26601
+ result = resultDocument("logout", 0, "logged_out");
26602
+ } else {
26603
+ const failure2 = profileSelectionError(selection, request, commandPrefix, "cli");
26604
+ result = errorDocument("logout", failure2.exitCode, failure2.code, false, failure2.suggestedAction);
26605
+ }
25493
26606
  } catch {
25494
26607
  diagnostics.record({
25495
- correlationId: randomUUID(),
26608
+ correlationId: randomUUID2(),
25496
26609
  category: "credential_store",
25497
26610
  subsystem: "credential_store",
25498
26611
  errorKind: "unavailable"
@@ -25533,9 +26646,9 @@ var readSecretValueFromStandardInput = async (input, signal) => {
25533
26646
  }
25534
26647
  };
25535
26648
  var invalidCommandFailure = (command) => errorDocument(command, 2, "invalid_command", false, "Check 'ohmyhost --help'.");
25536
- var resultDocument = (command, exitCode, status) => ({
26649
+ var resultDocument = (command, exitCode, status, body = {}) => ({
25537
26650
  exitCode,
25538
- stdout: `${JSON.stringify({ version: 1, command, status })}
26651
+ stdout: `${JSON.stringify({ version: 1, command, status, ...body })}
25539
26652
  `,
25540
26653
  stderr: ""
25541
26654
  });
@@ -25558,26 +26671,48 @@ var errorDocument = (command, exitCode, code, retryable, suggestedAction) => ({
25558
26671
  export {
25559
26672
  UserTokenFileError,
25560
26673
  prepareUserTokenFile,
26674
+ ProfileNameConflictError,
26675
+ ProfileLimitError,
26676
+ isProfileName,
26677
+ isProfileUserId,
26678
+ sameLogin,
26679
+ profileIndexDirectory,
26680
+ NativeKeyringProfileRegistry,
26681
+ ProfileIndexFiles,
26682
+ selectProfile,
26683
+ selectProfileForOrganization,
26684
+ defaultProfileName,
26685
+ publicProfile,
26686
+ profileLimitAction,
26687
+ ProfileSelectionError,
26688
+ profileSelectionError,
26689
+ describeProfile,
26690
+ extractProfileName,
25561
26691
  InvalidCommandError,
25562
26692
  recognizedCommandLabel,
25563
26693
  parseProductCliCommand,
25564
- InteractiveSessionRequiredError,
25565
- chooseWorkspace,
25566
- selectWorkspace,
25567
- selectedWorkspaceId,
25568
- workspaceOf,
25569
- publicWorkspace,
25570
26694
  parsePaidDomainPlan,
25571
26695
  parsePaidDomain,
25572
26696
  parseCloudflareDnsAuthorization,
25573
26697
  parseCloudflareDnsAuthorizationStatus,
25574
26698
  parseDevAccessState,
26699
+ parsePoweredByFlag,
26700
+ parseOrganizationReferral,
25575
26701
  parseDevAccessTicket,
25576
26702
  parseCurrentIdentity,
25577
26703
  parseAccountProfile,
25578
26704
  parseOperationEvent,
25579
26705
  parseSafeProblem,
25580
26706
  parseOperation,
26707
+ InteractiveSessionRequiredError,
26708
+ ProfileOrganizationFixedError,
26709
+ AccountVerificationError,
26710
+ chooseWorkspace,
26711
+ verifiedUserId,
26712
+ assertVerifiedLogin,
26713
+ selectWorkspace,
26714
+ workspaceOf,
26715
+ publicWorkspace,
25581
26716
  NetworkTransportError,
25582
26717
  LifecycleTransportError,
25583
26718
  TokenRefreshRejectedError,
@@ -25587,8 +26722,13 @@ export {
25587
26722
  PsqlUnavailableError,
25588
26723
  cliMetadataResult,
25589
26724
  ProductCli,
26725
+ ACCOUNT_SENSITIVE_PROBLEMS,
26726
+ classifyCliFailure,
25590
26727
  GeneratedSdkProductApi,
25591
- readLinkedProjectId,
26728
+ readProjectLinks,
26729
+ chooseLinkedProject,
26730
+ linkedOrganization,
26731
+ removeLegacyProjectLink,
25592
26732
  OwnerOnlyProjectLinkStore,
25593
26733
  RepositoryInitError,
25594
26734
  RepositoryInitBlockedError,
@@ -25598,10 +26738,13 @@ export {
25598
26738
  resolveProductCliEnvironment,
25599
26739
  createProductionAuthenticatedProductApi,
25600
26740
  createAuthenticatedProductTransport,
26741
+ listSavedProfiles,
26742
+ loadProfiles,
25601
26743
  runProductionCli,
25602
26744
  StderrDeviceAuthorizationPresenter,
25603
26745
  PublicWorkosTokenRefresher,
25604
26746
  GeneratedSdkSessionRevoker,
26747
+ LinkedProjectAmbiguousError,
25605
26748
  parseWithLinkedProject,
25606
26749
  readBoundedResponseBody,
25607
26750
  createBoundedFetch,