@amerged/ohmyhost-mcp 0.1.18 → 0.1.20

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.
@@ -26,7 +26,7 @@ import {
26
26
  serverIdentityOf,
27
27
  setNegotiatedProtocolVersion,
28
28
  validateEnvelopeMeta
29
- } from "./chunk-X6IS5LX7.js";
29
+ } from "./chunk-NESQZ3BS.js";
30
30
 
31
31
  // node_modules/.pnpm/@modelcontextprotocol+server@2.0.0/node_modules/@modelcontextprotocol/server/dist/stdio.mjs
32
32
  var StdioServerTransport = class {
@@ -799,7 +799,7 @@ function exact2(v, keys) {
799
799
  // apps/product-cli/package.json
800
800
  var package_default = {
801
801
  name: "@ohmyhost/product-cli",
802
- version: "0.1.18",
802
+ version: "0.1.20",
803
803
  private: true,
804
804
  ohmyhost: {
805
805
  deployment: "production",
@@ -1210,6 +1210,15 @@ function expectPositiveInteger(value, field, options = {}) {
1210
1210
  return value;
1211
1211
  }
1212
1212
 
1213
+ // packages/workos-auth-contracts/dist/user-api-keys.js
1214
+ var WORKOS_USER_API_KEY_PATTERN = /^sk_[A-Za-z0-9_-]{20,128}$/u;
1215
+
1216
+ // apps/product-cli/dist/profile-store.js
1217
+ import { randomUUID } from "node:crypto";
1218
+ import { link, mkdir as mkdir2, open as open2, readFile, readdir, unlink } from "node:fs/promises";
1219
+ import { homedir } from "node:os";
1220
+ import { join } from "node:path";
1221
+
1213
1222
  // apps/product-cli/dist/credential-store.js
1214
1223
  var CredentialStoreUnavailableError = class extends Error {
1215
1224
  constructor() {
@@ -1223,10 +1232,12 @@ var CredentialStoreIntegrityError = class extends Error {
1223
1232
  this.name = "CredentialStoreIntegrityError";
1224
1233
  }
1225
1234
  };
1226
- var CREDENTIAL_ACCOUNT = "credential";
1235
+ var LEGACY_CREDENTIAL_ACCOUNT = "credential";
1227
1236
  var NativeNapiKeyringCredentialStore = class {
1237
+ account;
1228
1238
  createEntry;
1229
- constructor(createEntry = void 0, serviceName = "ohmyhost") {
1239
+ constructor(createEntry = void 0, serviceName = "ohmyhost", account = LEGACY_CREDENTIAL_ACCOUNT) {
1240
+ this.account = account;
1230
1241
  this.createEntry = createEntry ?? createNativeEntryFactory(serviceName);
1231
1242
  }
1232
1243
  async read() {
@@ -1248,7 +1259,7 @@ var NativeNapiKeyringCredentialStore = class {
1248
1259
  throw new CredentialStoreIntegrityError();
1249
1260
  }
1250
1261
  entry() {
1251
- return this.createEntry(CREDENTIAL_ACCOUNT);
1262
+ return this.createEntry(this.account);
1252
1263
  }
1253
1264
  };
1254
1265
  var parseVerifiedCredential = (serialized) => {
@@ -1301,6 +1312,340 @@ var parseCredential = (serialized) => {
1301
1312
  };
1302
1313
  };
1303
1314
 
1315
+ // apps/product-cli/dist/profile-store.js
1316
+ var ProfileNameConflictError = class extends Error {
1317
+ profile;
1318
+ constructor(profile) {
1319
+ super("The profile name is used by another saved login");
1320
+ this.profile = profile;
1321
+ this.name = "ProfileNameConflictError";
1322
+ }
1323
+ };
1324
+ var ProfileLimitError = class extends Error {
1325
+ saved;
1326
+ constructor(saved) {
1327
+ super("The saved logins of this environment reached their limit");
1328
+ this.saved = saved;
1329
+ this.name = "ProfileLimitError";
1330
+ }
1331
+ };
1332
+ var MAX_PROFILES = 64;
1333
+ var MAX_INDEX_ATTEMPTS = 64;
1334
+ var INDEX_FILE = /^index-([1-9][0-9]{0,14})\.json$/u;
1335
+ var SERVICE_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u;
1336
+ var PROFILE_NAME = /^[a-z0-9][a-z0-9_-]{0,62}$/u;
1337
+ var USER_ID = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/u;
1338
+ var ORGANIZATION_ID = /^[0-7][0-9A-HJKMNP-TV-Z]{25}$/u;
1339
+ var isProfileName = (value) => PROFILE_NAME.test(value);
1340
+ var isProfileUserId = (value) => USER_ID.test(value);
1341
+ var profileAccount = (profile) => `profile:${profile.userId}:${profile.organizationId ?? "none"}`;
1342
+ var sameLogin = (left, right) => profileAccount(left) === profileAccount(right);
1343
+ var profileIndexDirectory = (serviceName) => join(homedir(), ".ohmyhost", "profiles", serviceName);
1344
+ var nextProfileIndex = (current, profile, replaces) => [
1345
+ ...current.filter((saved) => !sameLogin(saved, profile) && (replaces === void 0 || !sameLogin(saved, replaces))),
1346
+ profile
1347
+ ];
1348
+ var admitProfile = (next, profile) => {
1349
+ const conflict = next.find((saved) => saved.name === profile.name && saved !== profile);
1350
+ if (conflict !== void 0)
1351
+ throw new ProfileNameConflictError(conflict);
1352
+ if (next.length > MAX_PROFILES)
1353
+ throw new ProfileLimitError(next.length - 1);
1354
+ };
1355
+ var NativeKeyringProfileRegistry = class {
1356
+ serviceName;
1357
+ #createEntry;
1358
+ #index;
1359
+ constructor(createEntry = void 0, serviceName = "ohmyhost", indexDirectory) {
1360
+ this.serviceName = serviceName;
1361
+ if (!SERVICE_NAME.test(serviceName))
1362
+ throw new CredentialStoreUnavailableError();
1363
+ this.#createEntry = createEntry ?? createNativeEntryFactory(serviceName);
1364
+ this.#index = new ProfileIndexFiles(indexDirectory ?? profileIndexDirectory(serviceName));
1365
+ }
1366
+ async list() {
1367
+ return (await this.#index.read()).profiles;
1368
+ }
1369
+ credential(profile) {
1370
+ return new NativeNapiKeyringCredentialStore(this.#createEntry, this.serviceName, profileAccount(profile));
1371
+ }
1372
+ legacy() {
1373
+ return new NativeNapiKeyringCredentialStore(this.#createEntry, this.serviceName, LEGACY_CREDENTIAL_ACCOUNT);
1374
+ }
1375
+ async save(profile, credential, replaces) {
1376
+ assertProfile(profile);
1377
+ const before = await this.list();
1378
+ admitProfile(nextProfileIndex(before, profile, replaces), profile);
1379
+ await this.credential(profile).replace(credential);
1380
+ try {
1381
+ await this.#index.update((current) => {
1382
+ const next = nextProfileIndex(current, profile, replaces);
1383
+ admitProfile(next, profile);
1384
+ return next;
1385
+ });
1386
+ } catch (error) {
1387
+ if (!before.some((saved) => sameLogin(saved, profile)))
1388
+ await this.credential(profile).clear();
1389
+ throw error;
1390
+ }
1391
+ if (replaces !== void 0 && !sameLogin(replaces, profile))
1392
+ await this.credential(replaces).clear();
1393
+ }
1394
+ async remove(profile) {
1395
+ await this.credential(profile).clear();
1396
+ await this.#index.update((current) => current.some((saved) => sameLogin(saved, profile)) ? current.filter((saved) => !sameLogin(saved, profile)) : void 0);
1397
+ }
1398
+ };
1399
+ var ProfileIndexFiles = class {
1400
+ directory;
1401
+ constructor(directory) {
1402
+ this.directory = directory;
1403
+ }
1404
+ async read() {
1405
+ for (let attempt = 0; attempt < MAX_INDEX_ATTEMPTS; attempt += 1) {
1406
+ const version2 = await this.#newest();
1407
+ if (version2 === 0)
1408
+ return { version: version2, profiles: [] };
1409
+ let serialized;
1410
+ try {
1411
+ serialized = await readFile(this.#file(version2), "utf8");
1412
+ } catch (error) {
1413
+ if (fileErrorCode(error) === "ENOENT")
1414
+ continue;
1415
+ throw new CredentialStoreUnavailableError();
1416
+ }
1417
+ return { version: version2, profiles: parseProfileIndex(serialized) };
1418
+ }
1419
+ throw new CredentialStoreUnavailableError();
1420
+ }
1421
+ /** Applies `change` to the newest index; `undefined` means nothing to change. */
1422
+ async update(change) {
1423
+ try {
1424
+ await mkdir2(this.directory, { recursive: true, mode: 448 });
1425
+ } catch {
1426
+ throw new CredentialStoreUnavailableError();
1427
+ }
1428
+ for (let attempt = 0; attempt < MAX_INDEX_ATTEMPTS; attempt += 1) {
1429
+ const { version: version2, profiles } = await this.read();
1430
+ const next = change(profiles);
1431
+ if (next === void 0)
1432
+ return;
1433
+ const temporary = join(this.directory, `.index-${randomUUID()}.tmp`);
1434
+ try {
1435
+ await writeDurably(temporary, serializeProfileIndex(next));
1436
+ await link(temporary, this.#file(version2 + 1));
1437
+ } catch (error) {
1438
+ if (fileErrorCode(error) === "EEXIST")
1439
+ continue;
1440
+ throw new CredentialStoreUnavailableError();
1441
+ } finally {
1442
+ await removeTemporary(temporary);
1443
+ }
1444
+ if (await this.#newest() !== version2 + 1)
1445
+ continue;
1446
+ await this.#prune(version2);
1447
+ return;
1448
+ }
1449
+ throw new CredentialStoreUnavailableError();
1450
+ }
1451
+ async #newest() {
1452
+ let names;
1453
+ try {
1454
+ names = await readdir(this.directory);
1455
+ } catch (error) {
1456
+ if (fileErrorCode(error) === "ENOENT")
1457
+ return 0;
1458
+ throw new CredentialStoreUnavailableError();
1459
+ }
1460
+ return names.reduce((newest, name) => Math.max(newest, indexVersion(name)), 0);
1461
+ }
1462
+ /**
1463
+ * Keeps the superseded version for readers that listed it just now; older ones go. Cleanup only:
1464
+ * a file left behind is ignored, since readers take the newest version, and another process may
1465
+ * be removing the same files at this moment.
1466
+ */
1467
+ async #prune(superseded) {
1468
+ let names;
1469
+ try {
1470
+ names = await readdir(this.directory);
1471
+ } catch {
1472
+ return;
1473
+ }
1474
+ for (const name of names) {
1475
+ const version2 = indexVersion(name);
1476
+ if (version2 === 0 || version2 >= superseded)
1477
+ continue;
1478
+ try {
1479
+ await unlink(join(this.directory, name));
1480
+ } catch {
1481
+ }
1482
+ }
1483
+ }
1484
+ #file(version2) {
1485
+ return join(this.directory, `index-${String(version2)}.json`);
1486
+ }
1487
+ };
1488
+ var indexVersion = (name) => Number(INDEX_FILE.exec(name)?.[1] ?? 0);
1489
+ var removeTemporary = async (path) => {
1490
+ try {
1491
+ await unlink(path);
1492
+ } catch {
1493
+ }
1494
+ };
1495
+ var fileErrorCode = (error) => typeof error === "object" && error !== null && "code" in error ? String(error.code) : void 0;
1496
+ var writeDurably = async (path, text) => {
1497
+ const handle = await open2(path, "wx", 384);
1498
+ try {
1499
+ await handle.writeFile(text, "utf8");
1500
+ await handle.sync();
1501
+ } finally {
1502
+ await handle.close();
1503
+ }
1504
+ };
1505
+ var serializeProfileIndex = (profiles) => JSON.stringify({
1506
+ version: 1,
1507
+ profiles: [...profiles].sort((left, right) => left.name < right.name ? -1 : 1).map((profile) => ({
1508
+ name: profile.name,
1509
+ user_id: profile.userId,
1510
+ organization_id: profile.organizationId,
1511
+ organization_name: profile.organizationName
1512
+ }))
1513
+ });
1514
+ var parseProfileIndex = (serialized) => {
1515
+ try {
1516
+ const value = JSON.parse(serialized);
1517
+ if (!isRecord(value, ["version", "profiles"]) || value["version"] !== 1)
1518
+ throw new Error("Invalid profile index");
1519
+ const entries = value["profiles"];
1520
+ if (!Array.isArray(entries) || entries.length > MAX_PROFILES)
1521
+ throw new Error("Invalid profile index");
1522
+ const profiles = entries.map((entry) => {
1523
+ if (!isRecord(entry, ["name", "user_id", "organization_id", "organization_name"]))
1524
+ throw new Error("Invalid profile");
1525
+ const profile = {
1526
+ name: entry["name"],
1527
+ userId: entry["user_id"],
1528
+ organizationId: entry["organization_id"],
1529
+ organizationName: entry["organization_name"]
1530
+ };
1531
+ assertProfile(profile);
1532
+ return profile;
1533
+ });
1534
+ if (new Set(profiles.map((profile) => profile.name)).size !== profiles.length || new Set(profiles.map(profileAccount)).size !== profiles.length)
1535
+ throw new Error("Duplicate profile");
1536
+ return profiles;
1537
+ } catch {
1538
+ throw new CredentialStoreIntegrityError();
1539
+ }
1540
+ };
1541
+ var assertProfile = (profile) => {
1542
+ 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)
1543
+ throw new CredentialStoreIntegrityError();
1544
+ };
1545
+ var isRecord = (value, keys) => typeof value === "object" && value !== null && !Array.isArray(value) && Object.keys(value).sort().join(",") === [...keys].sort().join(",");
1546
+ var selectProfile = (profiles, request) => {
1547
+ if (request.name !== void 0 && request.bound !== void 0 && request.name !== request.bound)
1548
+ return { outcome: "conflict", field: "binding", profile: null };
1549
+ const named = request.name ?? request.bound;
1550
+ if (named !== void 0) {
1551
+ const profile = profiles.find((saved) => saved.name === named);
1552
+ if (profile === void 0)
1553
+ return { outcome: "not_found" };
1554
+ if (request.organizationId !== void 0 && profile.organizationId !== request.organizationId)
1555
+ return { outcome: "conflict", field: "organization", profile };
1556
+ if (request.userId !== void 0 && profile.userId !== request.userId)
1557
+ return { outcome: "conflict", field: "user", profile };
1558
+ return { outcome: "selected", profile };
1559
+ }
1560
+ const candidates = profiles.filter((profile) => (request.organizationId === void 0 || profile.organizationId === request.organizationId) && (request.userId === void 0 || profile.userId === request.userId));
1561
+ const only = candidates[0];
1562
+ if (candidates.length === 1 && only !== void 0)
1563
+ return { outcome: "selected", profile: only };
1564
+ if (candidates.length > 1)
1565
+ return { outcome: "ambiguous", candidates };
1566
+ return profiles.length === 0 ? { outcome: "none" } : { outcome: "not_found" };
1567
+ };
1568
+ var selectProfileForOrganization = (profiles, request) => {
1569
+ const user = request.userId === void 0 ? {} : { userId: request.userId };
1570
+ if (request.name !== void 0 || request.bound !== void 0)
1571
+ return selectProfile(profiles, {
1572
+ ...request.name === void 0 ? {} : { name: request.name },
1573
+ ...request.bound === void 0 ? {} : { bound: request.bound },
1574
+ ...user
1575
+ });
1576
+ const candidates = profiles.filter((profile) => request.userId === void 0 || profile.userId === request.userId);
1577
+ const inOrganization = candidates.filter((profile) => profile.organizationId === request.organizationId);
1578
+ if (inOrganization.length > 1)
1579
+ return { outcome: "ambiguous", candidates: inOrganization };
1580
+ const unscoped = candidates.filter((profile) => profile.organizationId === null);
1581
+ const [member] = inOrganization;
1582
+ if (member !== void 0)
1583
+ return { outcome: "selected", profile: member };
1584
+ const [open4] = unscoped;
1585
+ if (unscoped.length === 1 && open4 !== void 0)
1586
+ return { outcome: "selected", profile: open4 };
1587
+ return selectProfile(profiles, user);
1588
+ };
1589
+ var defaultProfileName = (profiles, login) => {
1590
+ const same = profiles.find((profile) => profile.userId === login.userId && profile.organizationId === login.organizationId);
1591
+ if (same !== void 0)
1592
+ return same.name;
1593
+ const base = slug(login.organizationName ?? "") || (login.organizationId === null ? "account" : "workspace");
1594
+ const free = (name) => profiles.every((profile) => profile.name !== name);
1595
+ if (free(base))
1596
+ return base;
1597
+ const suffix = (login.organizationId ?? login.userId).replace(/[^A-Za-z0-9]/gu, "").slice(-6).toLowerCase();
1598
+ for (let attempt = 1; attempt <= profiles.length + 1; attempt += 1) {
1599
+ const candidate = `${base.slice(0, 48)}-${suffix}${attempt === 1 ? "" : `-${String(attempt)}`}`;
1600
+ if (free(candidate))
1601
+ return candidate;
1602
+ }
1603
+ throw new CredentialStoreIntegrityError();
1604
+ };
1605
+ var slug = (value) => value.normalize("NFKD").replace(/[\u0300-\u036f]/gu, "").toLowerCase().replace(/[^a-z0-9]+/gu, "-").replace(/^-+/u, "").slice(0, 40).replace(/-+$/u, "");
1606
+ var publicProfile = (profile) => ({
1607
+ name: profile.name,
1608
+ user_id: profile.userId,
1609
+ organization_id: profile.organizationId,
1610
+ organization_name: profile.organizationName
1611
+ });
1612
+ 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.`;
1613
+ var ProfileSelectionError = class extends Error {
1614
+ code;
1615
+ exitCode;
1616
+ suggestedAction;
1617
+ constructor(code, exitCode, suggestedAction) {
1618
+ super(`${code}: ${suggestedAction}`);
1619
+ this.code = code;
1620
+ this.exitCode = exitCode;
1621
+ this.suggestedAction = suggestedAction;
1622
+ this.name = "ProfileSelectionError";
1623
+ }
1624
+ };
1625
+ var profileSelectionError = (selection, request, commandPrefix, selector) => {
1626
+ 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";
1627
+ 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`;
1628
+ if (selection.outcome === "none")
1629
+ return new ProfileSelectionError("authentication_required", 5, `No login is saved on this computer. Run '${login}'.`);
1630
+ if (selection.outcome === "not_found")
1631
+ 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.`);
1632
+ if (selection.outcome === "ambiguous")
1633
+ 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}.`);
1634
+ if (selection.field === "binding")
1635
+ 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.`);
1636
+ const profile = selection.profile;
1637
+ 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.`);
1638
+ };
1639
+ var describeRequest = (request) => {
1640
+ const parts = [
1641
+ ...(request.name ?? request.bound) === void 0 ? [] : [`profile ${String(request.name ?? request.bound)}`],
1642
+ ...request.organizationId === void 0 ? [] : [`organization ${request.organizationId}`],
1643
+ ...request.userId === void 0 ? [] : [`user ${request.userId}`]
1644
+ ];
1645
+ return parts.length === 0 ? "this request" : parts.join(", ");
1646
+ };
1647
+ var describeProfile = (profile) => `profile ${profile.name} (user ${profile.userId}, ${profile.organizationId === null ? "no organization" : `organization ${profile.organizationName === null ? "" : `${profile.organizationName} `}${profile.organizationId}`})`;
1648
+
1304
1649
  // apps/product-cli/dist/device-token-expiry.js
1305
1650
  var DeviceTokenExpiryError = class extends Error {
1306
1651
  constructor() {
@@ -1375,46 +1720,6 @@ var resolveDeviceTokenExpiresAt = (accessToken, expiresInSeconds, now) => {
1375
1720
  return new Date(jwtExpiryMilliseconds).toISOString();
1376
1721
  };
1377
1722
 
1378
- // apps/product-cli/dist/workspace.js
1379
- var InteractiveSessionRequiredError = class extends Error {
1380
- constructor() {
1381
- super("Workspace selection requires an interactive login");
1382
- this.name = "InteractiveSessionRequiredError";
1383
- }
1384
- };
1385
- var chooseWorkspace = (organizations, requested) => {
1386
- if (requested !== void 0) {
1387
- const match = organizations.find((organization) => organization.id === requested);
1388
- return match === void 0 ? { outcome: "not_a_member", organizations } : { outcome: "selected", organization: match };
1389
- }
1390
- if (organizations.length === 0)
1391
- return { outcome: "no_workspace" };
1392
- const only = organizations[0];
1393
- if (organizations.length === 1 && only !== void 0)
1394
- return { outcome: "selected", organization: only };
1395
- return { outcome: "choice_required", organizations };
1396
- };
1397
- var selectWorkspace = async (session, organization, signal) => {
1398
- const stored = await session.store.read();
1399
- if (stored === void 0)
1400
- throw new InteractiveSessionRequiredError();
1401
- const refreshed = await session.refresher.refresh(stored.refreshToken, signal, organization.workos_id);
1402
- await session.store.replace({
1403
- ...refreshed,
1404
- workosOrganizationId: organization.workos_id,
1405
- organizationId: organization.id
1406
- });
1407
- };
1408
- var selectedWorkspaceId = async (session) => (await session.store.read())?.organizationId ?? null;
1409
- var workspaceOf = (credential) => ({
1410
- ...credential.workosOrganizationId === void 0 ? {} : { workosOrganizationId: credential.workosOrganizationId },
1411
- ...credential.organizationId === void 0 ? {} : { organizationId: credential.organizationId }
1412
- });
1413
- var publicWorkspace = (organization) => ({
1414
- id: organization.id,
1415
- name: organization.name
1416
- });
1417
-
1418
1723
  // packages/contracts/dist/feedback.js
1419
1724
  var FEEDBACK_HISTORY_PAGE_SIZE = 25;
1420
1725
  var FEEDBACK_REPLY_MAX_LENGTH = 2e3;
@@ -1648,7 +1953,7 @@ var PROBLEM_POLICY = {
1648
1953
  github_connection_required: {
1649
1954
  status: 409,
1650
1955
  retryable: false,
1651
- action: "Run 'ohmyhost github connect --organization ULID --idempotency-key KEY --json' for this workspace, complete its browser link, then repeat the original source link."
1956
+ 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."
1652
1957
  },
1653
1958
  github_connection_revoked: {
1654
1959
  status: 409,
@@ -1722,7 +2027,11 @@ var PROBLEM_POLICY = {
1722
2027
  retryable: false,
1723
2028
  action: "Run 'ohmyhost organization list --json' and select one with 'ohmyhost organization use --organization ULID --json', or create the first workspace."
1724
2029
  },
1725
- resource_not_found: { status: 404, retryable: false, action: "Check the resource identifier." },
2030
+ resource_not_found: {
2031
+ status: 404,
2032
+ retryable: false,
2033
+ 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."
2034
+ },
1726
2035
  idempotency_key_reused: {
1727
2036
  status: 409,
1728
2037
  retryable: false,
@@ -2034,7 +2343,7 @@ var parseAccountProfile = (value) => {
2034
2343
  });
2035
2344
  if (new Set(workspaces.map((workspace) => workspace.id)).size !== organizationIds.length)
2036
2345
  throw new ResponseContractError();
2037
- return workspaces;
2346
+ return { userId: input["user_id"], organizations: workspaces };
2038
2347
  };
2039
2348
  var parseSafeProblem = (value) => {
2040
2349
  const input = exactRecord2(value, [
@@ -2089,7 +2398,7 @@ var parseOperation = (value) => {
2089
2398
  const blockingOperationId = input["blocking_operation_id"];
2090
2399
  if (blockingOperationId !== void 0 && (state !== "queued" || !isUlid(blockingOperationId) || blockingOperationId === id3))
2091
2400
  throw new ResponseContractError();
2092
- if (!isUlid(id3) || !isOperationState(state) || !isIsoInstant(createdAt) || !isIsoInstant(updatedAt) || result !== void 0 && !isRecord(result)) {
2401
+ if (!isUlid(id3) || !isOperationState(state) || !isIsoInstant(createdAt) || !isIsoInstant(updatedAt) || result !== void 0 && !isRecord2(result)) {
2093
2402
  throw new ResponseContractError();
2094
2403
  }
2095
2404
  let progress;
@@ -2150,12 +2459,12 @@ function parseOperationFailure(value) {
2150
2459
  return safe;
2151
2460
  }
2152
2461
  var exactRecord2 = (value, allowed) => {
2153
- if (!isRecord(value) || Object.keys(value).some((key) => !allowed.includes(key))) {
2462
+ if (!isRecord2(value) || Object.keys(value).some((key) => !allowed.includes(key))) {
2154
2463
  throw new ResponseContractError();
2155
2464
  }
2156
2465
  return value;
2157
2466
  };
2158
- var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2467
+ var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2159
2468
  var boundedString = (value, minimum, maximum) => typeof value === "string" && value.length >= minimum && value.length <= maximum;
2160
2469
  var isUlid = (value) => typeof value === "string" && /^[0-9A-HJKMNP-TV-Z]{26}$/u.test(value);
2161
2470
  var isIsoInstant = (value) => typeof value === "string" && value.length <= 64 && Number.isFinite(Date.parse(value));
@@ -2178,6 +2487,79 @@ var isPaidValidationRecord = (value) => {
2178
2487
  return (record5["type"] === "CNAME" || record5["type"] === "TXT") && boundedString(record5["name"], 1, 253) && boundedString(record5["content"], 1, 2048) && ["traffic", "ownership", "ssl", "dcv_delegation"].includes(String(record5["purpose"]));
2179
2488
  };
2180
2489
 
2490
+ // apps/product-cli/dist/workspace.js
2491
+ var InteractiveSessionRequiredError = class extends Error {
2492
+ constructor() {
2493
+ super("Workspace selection requires an interactive login");
2494
+ this.name = "InteractiveSessionRequiredError";
2495
+ }
2496
+ };
2497
+ var ProfileOrganizationFixedError = class extends Error {
2498
+ profile;
2499
+ organization;
2500
+ constructor(profile, organization) {
2501
+ super("The saved login is bound to another organization");
2502
+ this.profile = profile;
2503
+ this.organization = organization;
2504
+ this.name = "ProfileOrganizationFixedError";
2505
+ }
2506
+ };
2507
+ var AccountVerificationError = class extends Error {
2508
+ constructor() {
2509
+ super("The signed-in account does not match the requested one");
2510
+ this.name = "AccountVerificationError";
2511
+ }
2512
+ };
2513
+ var chooseWorkspace = (organizations, requested) => {
2514
+ if (requested !== void 0) {
2515
+ const match = organizations.find((organization) => organization.id === requested);
2516
+ return match === void 0 ? { outcome: "not_a_member", organizations } : { outcome: "selected", organization: match };
2517
+ }
2518
+ if (organizations.length === 0)
2519
+ return { outcome: "no_workspace" };
2520
+ const only = organizations[0];
2521
+ if (organizations.length === 1 && only !== void 0)
2522
+ return { outcome: "selected", organization: only };
2523
+ return { outcome: "choice_required", organizations };
2524
+ };
2525
+ var verifiedUserId = (identity) => {
2526
+ const actor = parseCurrentIdentity(identity).actor_id;
2527
+ if (!actor.startsWith("user:") || actor.length === 5)
2528
+ throw new AccountVerificationError();
2529
+ return actor.slice(5);
2530
+ };
2531
+ var assertVerifiedLogin = (identity, userId, organizationId) => {
2532
+ const organizations = parseCurrentIdentity(identity).organization_ids;
2533
+ if (verifiedUserId(identity) !== userId || (organizationId === null ? organizations.length !== 0 : organizations.length !== 1 || organizations[0] !== organizationId))
2534
+ throw new AccountVerificationError();
2535
+ };
2536
+ var selectWorkspace = async (session, organization, signal, identity) => {
2537
+ if (session.profile.organizationId === organization.id)
2538
+ return session.profile;
2539
+ if (session.profile.organizationId !== null)
2540
+ throw new ProfileOrganizationFixedError(session.profile, organization);
2541
+ const stored = await session.store.read();
2542
+ if (stored === void 0)
2543
+ throw new InteractiveSessionRequiredError();
2544
+ const refreshed = await session.refresher.refresh(stored.refreshToken, signal, organization.workos_id);
2545
+ assertVerifiedLogin(await identity(refreshed.accessToken), session.profile.userId, organization.id);
2546
+ const bound = {
2547
+ ...session.profile,
2548
+ organizationId: organization.id,
2549
+ organizationName: organization.name
2550
+ };
2551
+ await session.profiles.save(bound, { ...refreshed, workosOrganizationId: organization.workos_id, organizationId: organization.id }, session.profile);
2552
+ return bound;
2553
+ };
2554
+ var workspaceOf = (credential) => ({
2555
+ ...credential.workosOrganizationId === void 0 ? {} : { workosOrganizationId: credential.workosOrganizationId },
2556
+ ...credential.organizationId === void 0 ? {} : { organizationId: credential.organizationId }
2557
+ });
2558
+ var publicWorkspace = (organization) => ({
2559
+ id: organization.id,
2560
+ name: organization.name
2561
+ });
2562
+
2181
2563
  // apps/product-cli/dist/cli.js
2182
2564
  var NetworkTransportError = class extends Error {
2183
2565
  constructor() {
@@ -2206,9 +2588,10 @@ var PublicResponseTooLargeError = class extends Error {
2206
2588
  var CLI_VERSION = package_default.version;
2207
2589
  var HELP_USAGE = Object.freeze({
2208
2590
  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)",
2209
- login: "ohmyhost login [--organization ULID] --json",
2210
- logout: "ohmyhost logout [--revoke] --json",
2211
- whoami: "ohmyhost whoami --json",
2591
+ 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)",
2592
+ logout: "ohmyhost logout [--profile-name NAME] [--revoke] --json (removes only the selected saved login)",
2593
+ whoami: "ohmyhost whoami [--profile-name NAME] --json (the effective user, organization and saved login)",
2594
+ "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)",
2212
2595
  "github connect": "ohmyhost github connect --organization ULID --idempotency-key KEY --json (connect once, then link covered repositories without another browser consent)",
2213
2596
  "github status": "ohmyhost github status --organization ULID --json",
2214
2597
  "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)",
@@ -2223,9 +2606,9 @@ var HELP_USAGE = Object.freeze({
2223
2606
  "credits usage": "ohmyhost credits usage --organization ULID --month YYYY-MM [--cursor ULID] --json",
2224
2607
  "budget get": "ohmyhost budget get --project ULID --json",
2225
2608
  "budget set": "ohmyhost budget set --project ULID --credits NUMBER|none [--mode continue|stop] --idempotency-key KEY --json",
2226
- "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)",
2227
- "organization list": "ohmyhost organization list --json (the workspaces you belong to and the selected one)",
2228
- "organization use": "ohmyhost organization use --organization ULID --json",
2609
+ "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)",
2610
+ "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)",
2611
+ "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)",
2229
2612
  "operation get": "ohmyhost operation get OPERATION_ULID --json",
2230
2613
  "operation reconcile": "ohmyhost operation reconcile OPERATION_ULID --idempotency-key KEY --yes --json",
2231
2614
  "token create": "ohmyhost token create --organization ULID --name NAME --idempotency-key KEY --out .env.local --json",
@@ -2275,7 +2658,7 @@ var HELP_USAGE = Object.freeze({
2275
2658
  delete: "ohmyhost delete --project ULID --if-match ETAG --confirmation-token TOKEN --idempotency-key KEY --yes --json",
2276
2659
  "secret list": "ohmyhost secret list --project ULID --environment ENVIRONMENT_ULID --json",
2277
2660
  "function runs": "ohmyhost function runs --project ULID --environment ENVIRONMENT_ULID [--limit 1-100] --json",
2278
- "secret set": `printf '%s' "$SECRET_VALUE" | ohmyhost secret set NAME --project ULID --environment ENVIRONMENT_ULID --idempotency-key KEY --stdin [--wait] --json`,
2661
+ "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)`,
2279
2662
  "secret delete": "ohmyhost secret delete NAME --project ULID --environment ENVIRONMENT_ULID --idempotency-key KEY [--wait] --json",
2280
2663
  "mail setup": "ohmyhost mail setup --project ULID --environment ULID --domain DOMAIN --sending true --receiving false --idempotency-key KEY --json",
2281
2664
  "mail status": "ohmyhost mail status --project ULID --environment ULID --json",
@@ -2291,6 +2674,14 @@ var HELP_USAGE = Object.freeze({
2291
2674
  });
2292
2675
  var HELP_COMMANDS = Object.freeze(Object.entries(HELP_USAGE).map(([command, usage]) => Object.freeze({ command, usage })));
2293
2676
  var SIGN_IN_WINDOW_MS = 30 * 60 * 1e3;
2677
+ var ACCOUNT_SENSITIVE_PROBLEMS = /* @__PURE__ */ new Set([
2678
+ "resource_not_found",
2679
+ "forbidden",
2680
+ "organization_required",
2681
+ "github_connection_required",
2682
+ "github_connection_revoked",
2683
+ "repository_not_installed"
2684
+ ]);
2294
2685
 
2295
2686
  // packages/sdk-ts/dist/generated/core/bodySerializer.gen.js
2296
2687
  var jsonBodySerializer = {
@@ -3855,6 +4246,47 @@ var getProjectSource = (parameters, options) => {
3855
4246
  ...params
3856
4247
  });
3857
4248
  };
4249
+ var getProjectSourceAutoDeploy = (parameters, options) => {
4250
+ const params = buildClientParams([parameters], [
4251
+ {
4252
+ args: [
4253
+ { in: "headers", key: "X-Request-Id" },
4254
+ { in: "path", key: "project_id" }
4255
+ ]
4256
+ }
4257
+ ]);
4258
+ return (options?.client ?? client).get({
4259
+ responseStyle: "data",
4260
+ security: [{ scheme: "bearer", type: "http" }],
4261
+ url: "/v1/projects/{project_id}/source/auto-deploy",
4262
+ ...options,
4263
+ ...params
4264
+ });
4265
+ };
4266
+ var configureProjectSourceAutoDeploy = (parameters, options) => {
4267
+ const params = buildClientParams([parameters], [
4268
+ {
4269
+ args: [
4270
+ { in: "headers", key: "X-Request-Id" },
4271
+ { in: "headers", key: "Idempotency-Key" },
4272
+ { in: "path", key: "project_id" },
4273
+ { key: "configureSourceAutoDeployRequest", map: "body" }
4274
+ ]
4275
+ }
4276
+ ]);
4277
+ return (options?.client ?? client).put({
4278
+ responseStyle: "data",
4279
+ security: [{ scheme: "bearer", type: "http" }],
4280
+ url: "/v1/projects/{project_id}/source/auto-deploy",
4281
+ ...options,
4282
+ ...params,
4283
+ headers: {
4284
+ "Content-Type": "application/json",
4285
+ ...options?.headers,
4286
+ ...params.headers
4287
+ }
4288
+ });
4289
+ };
3858
4290
  var createCloudflareDnsAuthorization = (parameters, options) => {
3859
4291
  const params = buildClientParams([parameters], [
3860
4292
  {
@@ -3921,6 +4353,24 @@ var changeProjectHandle = (parameters, options) => {
3921
4353
  }
3922
4354
  });
3923
4355
  };
4356
+ var configureProjectDomains = (parameters, options) => {
4357
+ const params = buildClientParams([parameters], [
4358
+ {
4359
+ args: [
4360
+ { in: "headers", key: "X-Request-Id" },
4361
+ { in: "headers", key: "Idempotency-Key" },
4362
+ { in: "path", key: "project_id" }
4363
+ ]
4364
+ }
4365
+ ]);
4366
+ return (options?.client ?? client).put({
4367
+ responseStyle: "data",
4368
+ security: [{ scheme: "bearer", type: "http" }],
4369
+ url: "/v1/projects/{project_id}/domains",
4370
+ ...options,
4371
+ ...params
4372
+ });
4373
+ };
3924
4374
  var planPaidProjectDomain = (parameters, options) => {
3925
4375
  const params = buildClientParams([parameters], [
3926
4376
  {
@@ -4113,6 +4563,24 @@ var getDeploymentLogs = (parameters, options) => {
4113
4563
  ...params
4114
4564
  });
4115
4565
  };
4566
+ var streamDeploymentLogs = (parameters, options) => {
4567
+ const params = buildClientParams([parameters], [
4568
+ {
4569
+ args: [
4570
+ { in: "headers", key: "X-Request-Id" },
4571
+ { in: "path", key: "project_id" },
4572
+ { in: "path", key: "deployment_id" }
4573
+ ]
4574
+ }
4575
+ ]);
4576
+ return (options?.client ?? client).sse.get({
4577
+ responseStyle: "data",
4578
+ security: [{ scheme: "bearer", type: "http" }],
4579
+ url: "/v1/projects/{project_id}/deployments/{deployment_id}/logs/events",
4580
+ ...options,
4581
+ ...params
4582
+ });
4583
+ };
4116
4584
  var getDatabaseCompute = (parameters, options) => {
4117
4585
  const params = buildClientParams([parameters], [
4118
4586
  {
@@ -4398,6 +4866,32 @@ var deleteEnvironmentSecret = (parameters, options) => {
4398
4866
  ...params
4399
4867
  });
4400
4868
  };
4869
+ var putEnvironmentSecret = (parameters, options) => {
4870
+ const params = buildClientParams([parameters], [
4871
+ {
4872
+ args: [
4873
+ { in: "headers", key: "X-Request-Id" },
4874
+ { in: "headers", key: "Idempotency-Key" },
4875
+ { in: "path", key: "project_id" },
4876
+ { in: "path", key: "environment_id" },
4877
+ { in: "path", key: "secret_name" },
4878
+ { key: "putEnvironmentSecretRequest", map: "body" }
4879
+ ]
4880
+ }
4881
+ ]);
4882
+ return (options?.client ?? client).put({
4883
+ responseStyle: "data",
4884
+ security: [{ scheme: "bearer", type: "http" }],
4885
+ url: "/v1/projects/{project_id}/environments/{environment_id}/secrets/{secret_name}",
4886
+ ...options,
4887
+ ...params,
4888
+ headers: {
4889
+ "Content-Type": "application/json",
4890
+ ...options?.headers,
4891
+ ...params.headers
4892
+ }
4893
+ });
4894
+ };
4401
4895
  var getOperation = (parameters, options) => {
4402
4896
  const params = buildClientParams([parameters], [
4403
4897
  {
@@ -4847,9 +5341,6 @@ async function executeManagedMail(client2, input) {
4847
5341
  return result;
4848
5342
  }
4849
5343
 
4850
- // packages/workos-auth-contracts/dist/user-api-keys.js
4851
- var WORKOS_USER_API_KEY_PATTERN = /^sk_[A-Za-z0-9_-]{20,128}$/u;
4852
-
4853
5344
  // packages/workos-auth-contracts/dist/device-flow.js
4854
5345
  var WORKOS_AUTHKIT_DEVICE_TOKEN_ENDPOINT = "https://api.workos.com/user_management/authenticate";
4855
5346
  var allowedDeviceTokenFields = /* @__PURE__ */ new Set([
@@ -4881,8 +5372,404 @@ var parseDeviceTokens = (value) => {
4881
5372
  };
4882
5373
  };
4883
5374
 
4884
- // apps/product-cli/dist/repository-init.js
4885
- var MAXIMUM_PACKAGE_BYTES = 1024 * 1024;
5375
+ // apps/product-cli/dist/product-api.js
5376
+ var GeneratedSdkProductApi = class {
5377
+ #client;
5378
+ constructor(baseUrl, accessToken, fetch2) {
5379
+ this.#client = createClient({ auth: accessToken, baseUrl, fetch: fetch2, throwOnError: true });
5380
+ }
5381
+ async changeDatabaseCompute(input) {
5382
+ return changeDatabaseCompute({
5383
+ project_id: input.projectId,
5384
+ environment: input.environment,
5385
+ profile: input.profile,
5386
+ confirm: true,
5387
+ "Idempotency-Key": input.idempotencyKey
5388
+ }, { client: this.#client });
5389
+ }
5390
+ async getDatabaseCompute(input) {
5391
+ const value = await getDatabaseCompute({ project_id: input.projectId, environment: input.environment }, { client: this.#client });
5392
+ assertDatabaseCompute(value);
5393
+ if (value.project_id !== input.projectId || value.environment !== input.environment)
5394
+ throw new TypeError("Compute response scope mismatch");
5395
+ return value;
5396
+ }
5397
+ async getProjectContext(projectId) {
5398
+ const result = await getProjectContext({ project_id: projectId }, { client: this.#client });
5399
+ assertProjectContext(result);
5400
+ if (result.project_id !== projectId)
5401
+ throw new TypeError("Project context scope mismatch");
5402
+ return result;
5403
+ }
5404
+ async setProjectNotes(input) {
5405
+ const result = await setProjectNotes({
5406
+ project_id: input.projectId,
5407
+ markdown: input.markdown,
5408
+ expected_version: input.expectedVersion,
5409
+ "Idempotency-Key": input.idempotencyKey
5410
+ }, { client: this.#client });
5411
+ assertProjectNotesReceipt(result);
5412
+ return result;
5413
+ }
5414
+ createProjectExport(input) {
5415
+ return createProjectExport({
5416
+ project_id: input.projectId,
5417
+ password: input.password,
5418
+ "Idempotency-Key": input.idempotencyKey
5419
+ }, { client: this.#client });
5420
+ }
5421
+ async getProjectExport(input) {
5422
+ const result = await getProjectExport({ project_id: input.projectId, export_id: input.exportId }, { client: this.#client });
5423
+ assertProjectExport(result);
5424
+ return result;
5425
+ }
5426
+ async getCurrentIdentity() {
5427
+ return getCurrentIdentity({}, { client: this.#client });
5428
+ }
5429
+ async getAccountProfile() {
5430
+ return parseAccountProfile(await getAccountProfile({ client: this.#client }));
5431
+ }
5432
+ getOrganizationAccount(organizationId) {
5433
+ return getOrganizationAccount({ organization_id: organizationId }, { client: this.#client });
5434
+ }
5435
+ getOrganizationCredits(organizationId) {
5436
+ return getOrganizationCredits({ organization_id: organizationId }, { client: this.#client });
5437
+ }
5438
+ async createUserApiKey(input) {
5439
+ return parseUserApiKeyCreation(await createUserApiKey({
5440
+ organization_id: input.organizationId,
5441
+ name: input.name,
5442
+ "Idempotency-Key": input.idempotencyKey
5443
+ }, { client: this.#client }));
5444
+ }
5445
+ async listUserApiKeys(input) {
5446
+ return parseUserApiKeyPage(await listUserApiKeys({ organization_id: input.organizationId, ...input.after ? { after: input.after } : {} }, { client: this.#client }));
5447
+ }
5448
+ async revokeUserApiKey(input) {
5449
+ await revokeUserApiKey({ organization_id: input.organizationId, key_id: input.keyId }, { client: this.#client });
5450
+ }
5451
+ createBillingCheckout(input) {
5452
+ return createBillingCheckout({
5453
+ organization_id: input.organizationId,
5454
+ offer: input.offer,
5455
+ packs: input.packs,
5456
+ "Idempotency-Key": input.idempotencyKey
5457
+ }, { client: this.#client });
5458
+ }
5459
+ getBillingCheckout(input) {
5460
+ return getBillingCheckout({ organization_id: input.organizationId, checkout_id: input.checkoutId }, { client: this.#client });
5461
+ }
5462
+ getBillingRecharge(organizationId) {
5463
+ return getBillingRecharge({ organization_id: organizationId }, { client: this.#client });
5464
+ }
5465
+ configureBillingRecharge(input) {
5466
+ return configureBillingRecharge({
5467
+ organization_id: input.organizationId,
5468
+ "Idempotency-Key": input.idempotencyKey,
5469
+ enabled: input.enabled,
5470
+ monthly_limit_minor: input.monthlyLimitMinor,
5471
+ consent: input.consent,
5472
+ revision: input.revision
5473
+ }, { client: this.#client });
5474
+ }
5475
+ createBillingPortal(organizationId) {
5476
+ return createBillingPortal({ organization_id: organizationId, body: {} }, { client: this.#client });
5477
+ }
5478
+ getOrganizationCreditUsage(input) {
5479
+ return getOrganizationCreditUsage({
5480
+ organization_id: input.organizationId,
5481
+ month: input.month,
5482
+ ...input.cursor ? { cursor: input.cursor } : {}
5483
+ }, { client: this.#client });
5484
+ }
5485
+ getProjectCreditBudget(projectId) {
5486
+ return getProjectCreditBudget({ project_id: projectId }, { client: this.#client });
5487
+ }
5488
+ setProjectCreditBudget(input) {
5489
+ return setProjectCreditBudget({
5490
+ project_id: input.projectId,
5491
+ "Idempotency-Key": input.idempotencyKey,
5492
+ setProjectCreditBudgetRequest: { amount_micros: input.amountMicros, mode: input.mode }
5493
+ }, { client: this.#client });
5494
+ }
5495
+ createOrganization(input) {
5496
+ return createOrganization({
5497
+ "Idempotency-Key": input.idempotencyKey,
5498
+ createOrganizationRequest: {
5499
+ name: input.name,
5500
+ ...input.signupSource === void 0 ? {} : { signup_source: input.signupSource }
5501
+ }
5502
+ }, { client: this.#client });
5503
+ }
5504
+ async listProjects(input) {
5505
+ return listProjects(input, { client: this.#client });
5506
+ }
5507
+ async changeProjectHandle(input) {
5508
+ return changeProjectHandle({
5509
+ project_id: input.projectId,
5510
+ "Idempotency-Key": input.idempotencyKey,
5511
+ "If-Match": input.ifMatch,
5512
+ handle: input.handle
5513
+ }, { client: this.#client });
5514
+ }
5515
+ async checkProjectHandle(input) {
5516
+ return getProjectHandleAvailability({ handle: input.handle }, { client: this.#client });
5517
+ }
5518
+ async getProjectStatus(projectId) {
5519
+ return getProjectStatus({ project_id: projectId }, { client: this.#client });
5520
+ }
5521
+ async writeProjectDatabase(input) {
5522
+ return writeProjectDatabase({
5523
+ project_id: input.projectId,
5524
+ "Idempotency-Key": input.idempotencyKey,
5525
+ projectDatabaseWriteRequest: {
5526
+ environment: input.environment,
5527
+ statement: input.statement,
5528
+ parameters: [...input.parameters]
5529
+ }
5530
+ }, { client: this.#client });
5531
+ }
5532
+ async queryProjectDatabase(input) {
5533
+ return queryProjectDatabase({
5534
+ project_id: input.projectId,
5535
+ projectDatabaseQueryRequest: {
5536
+ environment: input.environment,
5537
+ statement: input.statement,
5538
+ parameters: [...input.parameters]
5539
+ }
5540
+ }, { client: this.#client });
5541
+ }
5542
+ /** The connection URI exists only in this response; nothing in the CLI persists it. */
5543
+ async createProjectDatabaseAccess(input) {
5544
+ const value = await createProjectDatabaseAccess({
5545
+ project_id: input.projectId,
5546
+ projectDatabaseAccessRequest: {
5547
+ environment: input.environment,
5548
+ mode: input.mode,
5549
+ ttl_seconds: input.ttlSeconds,
5550
+ label: input.label
5551
+ }
5552
+ }, { client: this.#client });
5553
+ assertProjectDatabaseAccessCredential(value);
5554
+ return value;
5555
+ }
5556
+ async listProjectDatabaseAccess(input) {
5557
+ const value = await listProjectDatabaseAccess({
5558
+ project_id: input.projectId,
5559
+ ...input.environment === void 0 ? {} : { environment: input.environment }
5560
+ }, { client: this.#client });
5561
+ assertProjectDatabaseAccessPage(value);
5562
+ return value;
5563
+ }
5564
+ async revokeProjectDatabaseAccess(input) {
5565
+ const value = await revokeProjectDatabaseAccess({ project_id: input.projectId, access_id: input.accessId }, { client: this.#client });
5566
+ assertProjectDatabaseAccess(value);
5567
+ return value;
5568
+ }
5569
+ async planPaidDomain(input) {
5570
+ return planPaidProjectDomain({ project_id: input.projectId, paidDomainRequest: { hostname: input.hostname } }, { client: this.#client });
5571
+ }
5572
+ async applyPaidDomain(input) {
5573
+ return applyPaidProjectDomain({
5574
+ "Idempotency-Key": input.idempotencyKey,
5575
+ project_id: input.projectId,
5576
+ paidDomainRequest: { hostname: input.hostname }
5577
+ }, { client: this.#client });
5578
+ }
5579
+ async getPaidDomain(projectId) {
5580
+ return getPaidProjectDomain({ project_id: projectId }, { client: this.#client });
5581
+ }
5582
+ async deletePaidDomain(input) {
5583
+ return deletePaidProjectDomain({
5584
+ "Idempotency-Key": input.idempotencyKey,
5585
+ project_id: input.projectId,
5586
+ paidDomainRequest: { hostname: input.hostname }
5587
+ }, { client: this.#client });
5588
+ }
5589
+ async ensureDevShareLink(projectId) {
5590
+ return ensureProjectDevShareLink({ project_id: projectId }, { client: this.#client });
5591
+ }
5592
+ async rotateDevShareLink(projectId, idempotencyKey2) {
5593
+ return rotateProjectDevShareLink({ project_id: projectId, "Idempotency-Key": idempotencyKey2 }, { client: this.#client });
5594
+ }
5595
+ async revokeDevShareLink(projectId, idempotencyKey2) {
5596
+ return revokeProjectDevShareLink({ project_id: projectId, "Idempotency-Key": idempotencyKey2 }, { client: this.#client });
5597
+ }
5598
+ async setDevAccessMode(projectId, mode, idempotencyKey2) {
5599
+ return setProjectDevAccessMode({
5600
+ project_id: projectId,
5601
+ "Idempotency-Key": idempotencyKey2,
5602
+ setDevAccessModeRequest: { mode }
5603
+ }, { client: this.#client });
5604
+ }
5605
+ async createDevAccessTicket(projectId) {
5606
+ return createProjectDevAccessTicket({ project_id: projectId }, { client: this.#client });
5607
+ }
5608
+ async submitFeedback(input) {
5609
+ return parseFeedbackReceipt(await submitFeedback({ "Idempotency-Key": input.idempotencyKey, feedbackSubmission: input.report }, { client: this.#client }));
5610
+ }
5611
+ async getFeedback(feedbackId, cursor) {
5612
+ return parseFeedbackStatus(await getFeedback({ feedback_id: feedbackId, ...cursor === void 0 ? {} : { cursor } }, { client: this.#client }));
5613
+ }
5614
+ async createProject(input) {
5615
+ return createProject({
5616
+ "Idempotency-Key": input.idempotencyKey,
5617
+ createProjectRequest: {
5618
+ organization_id: input.organizationId,
5619
+ name: input.name,
5620
+ ...input.dataMode === void 0 ? {} : { data_mode: input.dataMode },
5621
+ ...input.devAccessMode === void 0 ? {} : { dev_access_mode: input.devAccessMode },
5622
+ ...input.region === void 0 ? {} : { region: input.region }
5623
+ }
5624
+ }, { client: this.#client });
5625
+ }
5626
+ async getOperation(operationId) {
5627
+ return getOperation({ operation_id: operationId }, { client: this.#client });
5628
+ }
5629
+ async reconcileOperation(input) {
5630
+ return reconcileOperation({
5631
+ "Idempotency-Key": input.idempotencyKey,
5632
+ operation_id: input.operationId
5633
+ }, { client: this.#client });
5634
+ }
5635
+ async connectGithub(input) {
5636
+ return connectGithubOrganization({
5637
+ organization_id: input.organizationId,
5638
+ "Idempotency-Key": input.idempotencyKey,
5639
+ connectGithubOrganizationRequest: {}
5640
+ }, { client: this.#client });
5641
+ }
5642
+ async getGithubConnection(organizationId) {
5643
+ return getGithubOrganizationConnection({ organization_id: organizationId }, { client: this.#client });
5644
+ }
5645
+ async linkSource(input) {
5646
+ return linkProjectSource({
5647
+ "Idempotency-Key": input.idempotencyKey,
5648
+ project_id: input.projectId,
5649
+ linkProjectSourceRequest: {
5650
+ repository_owner: input.repositoryOwner,
5651
+ repository_name: input.repositoryName
5652
+ }
5653
+ }, { client: this.#client });
5654
+ }
5655
+ async configureAutoDeploy(input) {
5656
+ return configureProjectSourceAutoDeploy({
5657
+ "Idempotency-Key": input.idempotencyKey,
5658
+ project_id: input.projectId,
5659
+ configureSourceAutoDeployRequest: { branch: input.branch, enabled: input.enabled }
5660
+ }, { client: this.#client });
5661
+ }
5662
+ async getAutoDeploy(projectId) {
5663
+ return getProjectSourceAutoDeploy({ project_id: projectId }, { client: this.#client });
5664
+ }
5665
+ async authorizeCloudflareDns(input) {
5666
+ return createCloudflareDnsAuthorization({
5667
+ "Idempotency-Key": input.idempotencyKey,
5668
+ project_id: input.projectId,
5669
+ createCloudflareDnsAuthorizationRequest: { zone: input.zone }
5670
+ }, { client: this.#client });
5671
+ }
5672
+ async getCloudflareDnsStatus(projectId) {
5673
+ return getCloudflareDnsAuthorizationStatus({ project_id: projectId }, { client: this.#client });
5674
+ }
5675
+ async applyProjectDomains(input) {
5676
+ return configureProjectDomains({ "Idempotency-Key": input.idempotencyKey, project_id: input.projectId }, { client: this.#client });
5677
+ }
5678
+ async planDeployment(input) {
5679
+ return planDeployment({
5680
+ project_id: input.projectId,
5681
+ planDeploymentRequest: { commit_sha: input.commitSha, environment: input.environment }
5682
+ }, { client: this.#client });
5683
+ }
5684
+ async createDeployment(input) {
5685
+ return createDeployment({
5686
+ "Idempotency-Key": input.idempotencyKey,
5687
+ project_id: input.projectId,
5688
+ createDeploymentRequest: { plan_id: input.planId }
5689
+ }, { client: this.#client });
5690
+ }
5691
+ async getDeployment(input) {
5692
+ return getDeployment({ project_id: input.projectId, deployment_id: input.deploymentId }, { client: this.#client });
5693
+ }
5694
+ async streamOperationEvents(operationId, signal) {
5695
+ return (await streamOperationEvents({ operation_id: operationId }, { client: this.#client, signal, headers: { Accept: "text/event-stream" } })).stream;
5696
+ }
5697
+ async getDeploymentLogs(input) {
5698
+ return getDeploymentLogs({
5699
+ project_id: input.projectId,
5700
+ deployment_id: input.deploymentId,
5701
+ limit: input.limit,
5702
+ ...input.cursor === void 0 ? {} : { cursor: input.cursor }
5703
+ }, { client: this.#client });
5704
+ }
5705
+ async streamDeploymentLogs(input, signal) {
5706
+ return (await streamDeploymentLogs({ project_id: input.projectId, deployment_id: input.deploymentId }, { client: this.#client, signal, headers: { Accept: "text/event-stream" } })).stream;
5707
+ }
5708
+ async planRollback(input) {
5709
+ return planDeploymentRollback({ project_id: input.projectId, target_deployment_id: input.deploymentId }, { client: this.#client });
5710
+ }
5711
+ async rollback(input) {
5712
+ return rollbackDeployment({
5713
+ "Idempotency-Key": input.idempotencyKey,
5714
+ "If-Match": input.ifMatch,
5715
+ "X-Confirmation-Token": input.confirmationToken,
5716
+ project_id: input.projectId,
5717
+ target_deployment_id: input.deploymentId
5718
+ }, { client: this.#client });
5719
+ }
5720
+ async planPromotion(input) {
5721
+ return planDeploymentPromotion({ project_id: input.projectId, source_deployment_id: input.sourceDeploymentId }, { client: this.#client });
5722
+ }
5723
+ async promote(input) {
5724
+ return promoteDeployment({
5725
+ "Idempotency-Key": input.idempotencyKey,
5726
+ "If-Match": input.ifMatch,
5727
+ "X-Confirmation-Token": input.confirmationToken,
5728
+ project_id: input.projectId,
5729
+ source_deployment_id: input.sourceDeploymentId
5730
+ }, { client: this.#client });
5731
+ }
5732
+ async planDelete(projectId) {
5733
+ return planProjectDeletion({ project_id: projectId }, { client: this.#client });
5734
+ }
5735
+ async deleteProject(input) {
5736
+ return deleteProject({
5737
+ "Idempotency-Key": input.idempotencyKey,
5738
+ "If-Match": input.ifMatch,
5739
+ "X-Confirmation-Token": input.confirmationToken,
5740
+ project_id: input.projectId
5741
+ }, { client: this.#client });
5742
+ }
5743
+ async listEnvironmentSecrets(input) {
5744
+ return listEnvironmentSecrets({ project_id: input.projectId, environment_id: input.environmentId }, { client: this.#client });
5745
+ }
5746
+ async listFunctionRuns(input) {
5747
+ return listFunctionRuns({ project_id: input.projectId, environment_id: input.environmentId, limit: input.limit }, { client: this.#client });
5748
+ }
5749
+ async putEnvironmentSecret(input) {
5750
+ return putEnvironmentSecret({
5751
+ "Idempotency-Key": input.idempotencyKey,
5752
+ project_id: input.projectId,
5753
+ environment_id: input.environmentId,
5754
+ secret_name: input.name,
5755
+ putEnvironmentSecretRequest: { value: input.value }
5756
+ }, { client: this.#client });
5757
+ }
5758
+ async deleteEnvironmentSecret(input) {
5759
+ return deleteEnvironmentSecret({
5760
+ "Idempotency-Key": input.idempotencyKey,
5761
+ project_id: input.projectId,
5762
+ environment_id: input.environmentId,
5763
+ secret_name: input.name
5764
+ }, { client: this.#client });
5765
+ }
5766
+ managedMail(input) {
5767
+ return executeManagedMail(this.#client, input);
5768
+ }
5769
+ };
5770
+
5771
+ // apps/product-cli/dist/repository-init.js
5772
+ var MAXIMUM_PACKAGE_BYTES = 1024 * 1024;
4886
5773
  var MAXIMUM_CONFIGURATION_BYTES = 64 * 1024;
4887
5774
  var MAXIMUM_SOURCE_BYTES = 2 * 1024 * 1024;
4888
5775
  var STORAGE_RUNTIME_BINDINGS = Object.freeze([
@@ -4942,33 +5829,95 @@ function environmentToken(environment2 = {}) {
4942
5829
  throw new ProductApiAuthenticationError("invalid_environment_token");
4943
5830
  return value;
4944
5831
  }
5832
+ function environmentProfile(environment2 = {}) {
5833
+ const value = environment2.OHMYHOST_PROFILE;
5834
+ if (value === void 0 || value === "")
5835
+ return void 0;
5836
+ if (!isProfileName(value))
5837
+ throw new ProductApiAuthenticationError("invalid_profile_binding");
5838
+ return value;
5839
+ }
5840
+ var profileRequest = (input, bound) => ({
5841
+ ...input?.name === void 0 ? {} : { name: input.name },
5842
+ ...bound === void 0 ? {} : { bound },
5843
+ ...input?.organizationId === void 0 ? {} : { organizationId: input.organizationId },
5844
+ ...input?.userId === void 0 ? {} : { userId: input.userId }
5845
+ });
4945
5846
  var createAuthenticatedProductTransport = async (signal, overrides = {}) => {
4946
- const profile = resolveProductCliEnvironment(overrides.environment);
5847
+ const platform = resolveProductCliEnvironment(overrides.environment);
4947
5848
  const apiToken = environmentToken(overrides.environment);
5849
+ const bound = environmentProfile(overrides.environment);
5850
+ const baseUrl = overrides.productApiOrigin ?? platform.apiOrigin;
4948
5851
  const boundedFetch = createBoundedFetch(overrides.fetch ?? fetch, signal, REQUEST_TIMEOUT_MS, MAX_PUBLIC_RESPONSE_BYTES);
4949
- if (apiToken !== void 0)
5852
+ const registry = overrides.profiles ?? new NativeKeyringProfileRegistry(void 0, platform.credentialService);
5853
+ const binding = overrides.request?.bindOrganization === true;
5854
+ const request = profileRequest(overrides.request, bound);
5855
+ if (apiToken !== void 0) {
5856
+ await verifyEnvironmentTokenContext({
5857
+ token: apiToken,
5858
+ request: binding ? profileRequest({}, bound) : request,
5859
+ registry,
5860
+ baseUrl,
5861
+ fetch: boundedFetch
5862
+ });
4950
5863
  return {
4951
- baseUrl: overrides.productApiOrigin ?? profile.apiOrigin,
5864
+ baseUrl,
4952
5865
  accessToken: apiToken,
4953
5866
  fetch: boundedFetch,
4954
- // A user API token carries a fixed scope; there is no session that could select a workspace.
4955
- workspace: void 0
5867
+ workspace: void 0,
5868
+ context: { credential: "environment_token", profile: null }
4956
5869
  };
4957
- const store = overrides.credentialStore ?? new NativeNapiKeyringCredentialStore(void 0, profile.credentialService);
4958
- const stored = await store.read();
4959
- if (stored === void 0)
4960
- throw new ProductApiAuthenticationError("authentication_required");
5870
+ }
4961
5871
  const now = overrides.now ?? (() => /* @__PURE__ */ new Date());
4962
5872
  const refresher = overrides.tokenRefresher ?? new PublicWorkosTokenRefresher(overrides.environment, overrides.fetch, now);
4963
- const credential = await currentCredential(stored, store, signal, now, refresher);
5873
+ const profiles = await loadProfiles(registry, {
5874
+ baseUrl,
5875
+ fetch: boundedFetch,
5876
+ signal,
5877
+ now,
5878
+ refresher
5879
+ });
5880
+ const selection = binding && request.organizationId !== void 0 ? selectProfileForOrganization(profiles, {
5881
+ ...request,
5882
+ organizationId: request.organizationId
5883
+ }) : selectProfile(profiles, request);
5884
+ if (selection.outcome !== "selected")
5885
+ throw profileSelectionError(selection, request, platform.commandPrefix, overrides.selector ?? "cli");
5886
+ const profile = selection.profile;
5887
+ const store = registry.credential(profile);
5888
+ const stored = await store.read();
5889
+ if (stored === void 0)
5890
+ throw missingLogin(profile, platform.commandPrefix);
5891
+ const credential = await currentCredential(stored, store, signal, now, refresher, () => registry.remove(profile));
4964
5892
  return {
4965
- baseUrl: overrides.productApiOrigin ?? profile.apiOrigin,
5893
+ baseUrl,
4966
5894
  accessToken: credential.accessToken,
4967
5895
  fetch: boundedFetch,
4968
- workspace: { store, refresher }
5896
+ workspace: { store, refresher, profiles: registry, profile },
5897
+ context: { credential: "profile", profile }
5898
+ };
5899
+ };
5900
+ var listSavedProfiles = async (signal, overrides = {}) => {
5901
+ const platform = resolveProductCliEnvironment(overrides.environment);
5902
+ const apiToken = environmentToken(overrides.environment);
5903
+ const bound = environmentProfile(overrides.environment);
5904
+ const registry = overrides.profiles ?? new NativeKeyringProfileRegistry(void 0, platform.credentialService);
5905
+ const now = overrides.now ?? (() => /* @__PURE__ */ new Date());
5906
+ const profiles = apiToken === void 0 ? await loadProfiles(registry, {
5907
+ baseUrl: overrides.productApiOrigin ?? platform.apiOrigin,
5908
+ fetch: createBoundedFetch(overrides.fetch ?? fetch, signal, REQUEST_TIMEOUT_MS, MAX_PUBLIC_RESPONSE_BYTES),
5909
+ signal,
5910
+ now,
5911
+ refresher: overrides.tokenRefresher ?? new PublicWorkosTokenRefresher(overrides.environment, overrides.fetch, now)
5912
+ }) : await registry.list();
5913
+ return {
5914
+ profiles: profiles.map(publicProfile),
5915
+ bound: bound ?? null,
5916
+ environment_token: apiToken !== void 0
4969
5917
  };
4970
5918
  };
4971
- var currentCredential = async (stored, store, signal, now, tokenRefresher) => {
5919
+ 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'.`);
5920
+ var currentCredential = async (stored, store, signal, now, tokenRefresher, forget) => {
4972
5921
  const expiresAt = stored.expiresAt === void 0 ? NaN : Date.parse(stored.expiresAt);
4973
5922
  if (Number.isFinite(expiresAt) && expiresAt > now().getTime() + REQUEST_TIMEOUT_MS) {
4974
5923
  return stored;
@@ -4984,12 +5933,73 @@ var currentCredential = async (stored, store, signal, now, tokenRefresher) => {
4984
5933
  return carried;
4985
5934
  } catch (error) {
4986
5935
  if (error instanceof TokenRefreshRejectedError) {
4987
- await store.clear();
5936
+ await forget();
4988
5937
  throw new ProductApiAuthenticationError("authentication_required");
4989
5938
  }
4990
5939
  throw new ProductApiAuthenticationError("token_refresh_failed");
4991
5940
  }
4992
5941
  };
5942
+ var loadProfiles = async (registry, context) => {
5943
+ const legacy = registry.legacy();
5944
+ const stored = await legacy.read();
5945
+ if (stored !== void 0)
5946
+ await migrateLegacyLogin(registry, legacy, stored, context);
5947
+ return registry.list();
5948
+ };
5949
+ var migrateLegacyLogin = async (registry, legacy, stored, context) => {
5950
+ let credential;
5951
+ try {
5952
+ credential = await currentCredential(stored, legacy, context.signal, context.now, context.refresher, () => legacy.clear());
5953
+ } catch (error) {
5954
+ if (error instanceof ProductApiAuthenticationError && error.code === "authentication_required")
5955
+ return;
5956
+ throw error;
5957
+ }
5958
+ const api = new GeneratedSdkProductApi(context.baseUrl, credential.accessToken, context.fetch);
5959
+ let identity;
5960
+ try {
5961
+ identity = await api.getCurrentIdentity();
5962
+ } catch (error) {
5963
+ if (!isProblem(error, "unauthenticated"))
5964
+ throw error;
5965
+ return;
5966
+ }
5967
+ const userId = verifiedUserId(identity);
5968
+ const organizationId = parseCurrentIdentity(identity).organization_ids[0] ?? null;
5969
+ const organizationName = organizationId === null ? null : (await api.getAccountProfile()).organizations.find((organization) => organization.id === organizationId)?.name ?? null;
5970
+ const login = { userId, organizationId, organizationName };
5971
+ await registry.save({ name: defaultProfileName(await registry.list(), login), ...login }, credential);
5972
+ await legacy.clear();
5973
+ };
5974
+ var isProblem = (error, code) => typeof error === "object" && error !== null && "code" in error && error.code === code;
5975
+ var verifyEnvironmentTokenContext = async (input) => {
5976
+ const { request } = input;
5977
+ const named = request.name ?? request.bound;
5978
+ if (named === void 0 && request.organizationId === void 0 && request.userId === void 0)
5979
+ return null;
5980
+ 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.`);
5981
+ if (request.name !== void 0 && request.bound !== void 0 && request.name !== request.bound)
5982
+ throw refuse(`This call names profile ${request.name}, but the process is bound to OHMYHOST_PROFILE=${request.bound}.`);
5983
+ const profile = named === void 0 ? void 0 : (await input.registry.list()).find((saved) => saved.name === named);
5984
+ if (named !== void 0 && profile === void 0)
5985
+ throw refuse(`OHMYHOST_TOKEN overrides saved logins in this process, and no saved login is named ${named}.`);
5986
+ const { userId, organizationId } = await readTokenIdentity(input);
5987
+ const owner = `OHMYHOST_TOKEN belongs to user ${userId} in organization ${organizationId ?? "none"},`;
5988
+ if (profile !== void 0 && (profile.userId !== userId || profile.organizationId !== organizationId))
5989
+ throw refuse(`${owner} but this call names ${describeProfile(profile)}.`);
5990
+ if (request.organizationId !== void 0 && request.organizationId !== organizationId)
5991
+ throw refuse(`${owner} but this call targets organization ${request.organizationId}.`);
5992
+ if (request.userId !== void 0 && request.userId !== userId)
5993
+ throw refuse(`${owner} but this call targets user ${request.userId}.`);
5994
+ return { userId, organizationId };
5995
+ };
5996
+ var readTokenIdentity = async (input) => {
5997
+ const identity = parseCurrentIdentity(await new GeneratedSdkProductApi(input.baseUrl, input.token, input.fetch).getCurrentIdentity());
5998
+ return {
5999
+ userId: identity.actor_id.startsWith("user:") ? identity.actor_id.slice(5) : identity.actor_id,
6000
+ organizationId: identity.organization_ids[0] ?? null
6001
+ };
6002
+ };
4993
6003
  var FetchWorkosPublicTransport = class {
4994
6004
  parentSignal;
4995
6005
  timeoutMs;
@@ -5167,7 +6177,7 @@ var boundedSignal = (parent, timeoutMs) => AbortSignal.any([parent, AbortSignal.
5167
6177
  var isAbortOrTimeout = (error) => error instanceof DOMException && (error.name === "AbortError" || error.name === "TimeoutError");
5168
6178
 
5169
6179
  // apps/mcp/dist/local-client.js
5170
- import { lstat as lstat2, open as open2 } from "node:fs/promises";
6180
+ import { lstat as lstat2, open as open3 } from "node:fs/promises";
5171
6181
  import { isAbsolute } from "node:path";
5172
6182
  var OperationEventsUnavailableError = class extends Error {
5173
6183
  constructor() {
@@ -5177,8 +6187,11 @@ var OperationEventsUnavailableError = class extends Error {
5177
6187
  };
5178
6188
  async function createAuthenticatedLocalMcpClient(signal, overrides = {}) {
5179
6189
  try {
5180
- const transport = await createAuthenticatedProductTransport(signal, overrides);
5181
- return new GeneratedSdkLocalMcpClient(transport.baseUrl, transport.accessToken, transport.fetch, transport.workspace);
6190
+ const transport = await createAuthenticatedProductTransport(signal, {
6191
+ ...overrides,
6192
+ selector: "mcp"
6193
+ });
6194
+ return new GeneratedSdkLocalMcpClient(transport.baseUrl, transport.accessToken, transport.fetch, transport.workspace, transport.context, resolveProductCliEnvironment(overrides.environment).commandPrefix);
5182
6195
  } catch (error) {
5183
6196
  if (!(error instanceof ProductApiAuthenticationError))
5184
6197
  throw error;
@@ -5191,11 +6204,44 @@ async function createAuthenticatedLocalMcpClient(signal, overrides = {}) {
5191
6204
  var GeneratedSdkLocalMcpClient = class {
5192
6205
  #client;
5193
6206
  #workspace;
6207
+ #context;
6208
+ #commandPrefix;
5194
6209
  #rescope;
5195
- constructor(baseUrl, accessToken, fetch2, workspace) {
6210
+ constructor(baseUrl, accessToken, fetch2, workspace, context, commandPrefix = "OHMYHOST_ENVIRONMENT=production ohmyhost") {
5196
6211
  this.#rescope = (token) => createClient({ auth: token, baseUrl, fetch: fetch2, throwOnError: true });
5197
6212
  this.#client = this.#rescope(accessToken);
5198
6213
  this.#workspace = workspace;
6214
+ this.#context = context ?? {
6215
+ credential: workspace === void 0 ? "environment_token" : "profile",
6216
+ profile: workspace?.profile ?? null
6217
+ };
6218
+ this.#commandPrefix = commandPrefix;
6219
+ }
6220
+ actingAs() {
6221
+ return this.#contextReport();
6222
+ }
6223
+ /** Which account this call acts as, without any credential. */
6224
+ #contextReport() {
6225
+ const profile = this.#workspace?.profile ?? this.#context.profile;
6226
+ return {
6227
+ credential: this.#context.credential,
6228
+ profile: profile === null ? null : publicProfile(profile)
6229
+ };
6230
+ }
6231
+ /**
6232
+ * Bind this call's login, which has no organization yet, and continue with its new credential.
6233
+ * A login in another organization keeps it; the provider verifies the account first.
6234
+ */
6235
+ async #bind(organization, signal) {
6236
+ const session = this.#interactiveSession();
6237
+ const bound = await selectWorkspace(session, organization, signal, (token) => getCurrentIdentity({}, { client: this.#rescope(token) }));
6238
+ const store = session.profiles.credential(bound);
6239
+ const credential = await store.read();
6240
+ if (credential === void 0)
6241
+ throw new InteractiveSessionRequiredError();
6242
+ this.#workspace = { ...session, store, profile: bound };
6243
+ this.#client = this.#rescope(credential.accessToken);
6244
+ return bound;
5199
6245
  }
5200
6246
  async changeDatabaseCompute(input) {
5201
6247
  return changeDatabaseCompute({
@@ -5261,13 +6307,15 @@ var GeneratedSdkLocalMcpClient = class {
5261
6307
  async getCurrentIdentity(signal) {
5262
6308
  const identity = await getCurrentIdentity({}, { client: this.#client });
5263
6309
  if (this.#workspace === void 0 || parseCurrentIdentity(identity).organization_ids.length > 0)
5264
- return identity;
6310
+ return { ...identity, context: this.#contextReport() };
5265
6311
  const selection = chooseWorkspace(await this.#readWorkspaces(), void 0);
5266
6312
  if (selection.outcome !== "selected")
5267
- return identity;
5268
- await selectWorkspace(this.#workspace, selection.organization, signal);
5269
- const scoped = (await this.#workspace.store.read())?.accessToken;
5270
- return scoped === void 0 ? identity : getCurrentIdentity({}, { client: this.#rescope(scoped) });
6313
+ return { ...identity, context: this.#contextReport() };
6314
+ await this.#bind(selection.organization, signal);
6315
+ return {
6316
+ ...await getCurrentIdentity({}, { client: this.#client }),
6317
+ context: this.#contextReport()
6318
+ };
5271
6319
  }
5272
6320
  async changeProjectHandle(input) {
5273
6321
  return changeProjectHandle({
@@ -5380,29 +6428,39 @@ var GeneratedSdkLocalMcpClient = class {
5380
6428
  ...input.signupSource === void 0 ? {} : { signup_source: input.signupSource }
5381
6429
  }
5382
6430
  }, { client: this.#client });
6431
+ const current = this.#interactiveSession().profile;
6432
+ if (current.organizationId !== null && current.organizationId !== created.id)
6433
+ return {
6434
+ organization: { id: created.id, name: created.name },
6435
+ selected: current.organizationId,
6436
+ profile: publicProfile(current),
6437
+ next_action: `Run '${this.#commandPrefix} login --organization ${created.id} --user ${current.userId} --json' to add a login for the new workspace as the same user, then pass its profile_name.`
6438
+ };
5383
6439
  return this.selectOrganization(created.id, signal);
5384
6440
  }
5385
6441
  async listOrganizations() {
5386
6442
  const session = this.#interactiveSession();
5387
6443
  return {
5388
6444
  organizations: (await this.#readWorkspaces()).map(publicWorkspace),
5389
- selected: await selectedWorkspaceId(session)
6445
+ selected: session.profile.organizationId,
6446
+ profile: publicProfile(session.profile)
5390
6447
  };
5391
6448
  }
5392
6449
  async selectOrganization(organizationId, signal) {
5393
- const session = this.#interactiveSession();
6450
+ this.#interactiveSession();
5394
6451
  const selection = chooseWorkspace(await this.#readWorkspaces(), organizationId);
5395
6452
  if (selection.outcome !== "selected")
5396
6453
  throw new Error(`ORGANIZATION_NOT_FOUND:${organizationId}`);
5397
- await selectWorkspace(session, selection.organization, signal);
6454
+ const profile = await this.#bind(selection.organization, signal);
5398
6455
  return {
5399
6456
  organization: publicWorkspace(selection.organization),
5400
- selected: selection.organization.id
6457
+ selected: selection.organization.id,
6458
+ profile: publicProfile(profile)
5401
6459
  };
5402
6460
  }
5403
6461
  async #readWorkspaces() {
5404
6462
  this.#interactiveSession();
5405
- return parseAccountProfile(await getAccountProfile({ client: this.#client }));
6463
+ return parseAccountProfile(await getAccountProfile({ client: this.#client })).organizations;
5406
6464
  }
5407
6465
  #interactiveSession() {
5408
6466
  if (this.#workspace === void 0)
@@ -5681,7 +6739,7 @@ async function readExportPasswordFile(path) {
5681
6739
  const before = await lstat2(path);
5682
6740
  if (!before.isFile() || before.isSymbolicLink() || before.size < 1 || before.size > 1024 || process.platform !== "win32" && (before.mode & 63) !== 0)
5683
6741
  throw new Error();
5684
- file = await open2(path, "r");
6742
+ file = await open3(path, "r");
5685
6743
  const after = await file.stat();
5686
6744
  if (after.ino !== before.ino || after.dev !== before.dev || after.size !== before.size)
5687
6745
  throw new Error();
@@ -5702,6 +6760,7 @@ async function readExportPasswordFile(path) {
5702
6760
 
5703
6761
  // apps/mcp/dist/local-server.js
5704
6762
  var identifier2 = external_exports.string().min(1).max(256);
6763
+ var profileName = external_exports.string().regex(/^[a-z0-9][a-z0-9_-]{0,62}$/u).optional().describe("Saved login to act as (profile_list shows them). Needed only when several logins could run this call; OHMYHOST_PROFILE of this server must match it.");
5705
6764
  var idempotencyKey = external_exports.string().min(1).max(256);
5706
6765
  var commitSha = external_exports.string().regex(/^[a-f0-9]{40}$/u);
5707
6766
  var domainName = external_exports.string().regex(/^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,63}$/u);
@@ -5848,7 +6907,7 @@ function createLocalOhmyhostMcpServer(dependencies) {
5848
6907
  mode: input.mode,
5849
6908
  idempotencyKey: input.idempotency_key
5850
6909
  }), { readOnlyHint: false, destructiveHint: false, idempotentHint: true });
5851
- registerProductTool(server, dependencies, "organization_create", `Create an organization owned by the signed-in user and select it for this machine. Signup is open. signup_source is optional attribution from a link's r parameter, never a secret; omit it when the customer arrived directly. The server decides any once-per-user bonus; do not promise credits for arbitrary sources. Repeat the same name/source/key after uncertainty. No second login is needed: selected names the workspace every later call uses, so continue with identity_get, github_status and github_connect if needed, then projects. An OHMYHOST_TOKEN process cannot do this; it needs ${commandPrefix} login --json.`, external_exports.object({
6910
+ registerProductTool(server, dependencies, "organization_create", `Create an organization owned by the signed-in user. Signup is open. signup_source is optional attribution from a link's r parameter, never a secret; omit it when the customer arrived directly. The server decides any once-per-user bonus; do not promise credits for arbitrary sources. Repeat the same name/source/key after uncertainty. A login that has no organization yet is bound to the new one, so selected names the workspace later calls use; a login already in another organization keeps it and next_action names the login for the new one. Continue with identity_get, github_status and github_connect if needed, then projects. An OHMYHOST_TOKEN process cannot do this; it needs ${commandPrefix} login --json.`, external_exports.object({
5852
6911
  name: external_exports.string().min(1).max(128),
5853
6912
  signup_source: external_exports.string().regex(/^[a-z0-9][a-z0-9_-]{0,63}$/u).optional(),
5854
6913
  idempotency_key: idempotencyKey
@@ -5857,8 +6916,25 @@ function createLocalOhmyhostMcpServer(dependencies) {
5857
6916
  ...input.signup_source === void 0 ? {} : { signupSource: input.signup_source },
5858
6917
  idempotencyKey: input.idempotency_key
5859
6918
  }, signal), mutationAnnotations());
5860
- registerProductTool(server, dependencies, "organization_list", "List the workspaces the signed-in user belongs to and which one this machine currently uses. selected is null while none is chosen; every project and billing call then answers resource_not_found. Ask the customer which workspace to use instead of guessing, then call organization_use.", external_exports.object({}), (client2) => client2.listOrganizations());
5861
- registerProductTool(server, dependencies, "organization_use", `Select one workspace for this machine's stored login, so later calls act inside it. Read organization_list first, then github_status for the selected workspace and github_connect if needed; the identifier is the platform ULID, not the provider identifier. An OHMYHOST_TOKEN process cannot select anything and needs ${commandPrefix} login --json instead.`, external_exports.object({ organization_id: identifier2 }), (client2, input, signal) => client2.selectOrganization(input.organization_id, signal), mutationAnnotations());
6919
+ registerProductTool(server, dependencies, "organization_list", "List the workspaces the chosen login's user belongs to and which one that login is scoped to. selected is null while none is chosen; every project and billing call then answers resource_not_found. Ask the customer which workspace to use instead of guessing, then call organization_use.", external_exports.object({}), (client2) => client2.listOrganizations());
6920
+ registerProductTool(server, dependencies, "organization_use", `Bind a saved login that has no organization yet to one workspace, so later calls act inside it. A login already in another organization keeps it (profile_organization_fixed): add a login for the other workspace as the same user with ${commandPrefix} login --organization ULID --user USER_ID --json. Read organization_list first, then github_status for the selected workspace and github_connect if needed; the identifier is the platform ULID, not the provider identifier. An OHMYHOST_TOKEN process cannot select anything and needs ${commandPrefix} login --json instead.`, external_exports.object({ organization_id: identifier2 }), (client2, input, signal) => client2.selectOrganization(input.organization_id, signal), mutationAnnotations(), (input) => ({
6921
+ ...input.profile_name === void 0 ? {} : { name: input.profile_name },
6922
+ organizationId: input.organization_id,
6923
+ bindOrganization: true
6924
+ }));
6925
+ server.registerTool("profile_list", {
6926
+ description: `List the saved ohmyho.st logins on this computer: each has a name, a user and an organization, never a token. When the customer named a user or organization, pass the matching name as profile_name on later calls; with several logins and no profile_name a call answers profile_selection_required. With no match, the customer adds one with ${commandPrefix} login --organization ULID --user USER_ID --json, which saves nothing if the browser signs in as another account. bound is this server's OHMYHOST_PROFILE; environment_token means OHMYHOST_TOKEN replaces saved logins here.`,
6927
+ inputSchema: external_exports.object({}),
6928
+ annotations: readAnnotations()
6929
+ }, (async (_input, context) => {
6930
+ try {
6931
+ if (dependencies.listProfiles === void 0)
6932
+ throw new CredentialStoreUnavailableError();
6933
+ return toolResult(await dependencies.listProfiles(context.mcpReq.signal));
6934
+ } catch (error) {
6935
+ return toolError(error, dependencies);
6936
+ }
6937
+ }));
5862
6938
  registerProductTool(server, dependencies, "database_query", "Read one owner-authorized Dev or Prod database query (at most 100 rows, five-second timeout). Explicitly choose the environment. Shared data resolves to the same physical database. Application RLS policies apply; use an authorized SQL export when a full archive is required.", external_exports.object({
5863
6939
  project_id: identifier2,
5864
6940
  environment: external_exports.enum(["dev", "prod"]),
@@ -5956,7 +7032,7 @@ function createLocalOhmyhostMcpServer(dependencies) {
5956
7032
  });
5957
7033
  return { revoked: true, key_id: input.key_id };
5958
7034
  }, destructiveAnnotations());
5959
- registerProductTool(server, dependencies, "identity_get", "Get the current ohmyho.st customer/agent identity. This does not authenticate the hosted application's end users or provision a customer AuthKit tenant; application authentication is the customer's chosen integration.", external_exports.object({}), (client2, _input, signal) => client2.getCurrentIdentity(signal));
7035
+ registerProductTool(server, dependencies, "identity_get", "Get the ohmyho.st customer/agent identity this call acts as: user, organization and, in context, the saved login or OHMYHOST_TOKEN that supplied it. Compare it with the user and organization the customer named before any change. This does not authenticate the hosted application's end users or provision a customer AuthKit tenant; application authentication is the customer's chosen integration.", external_exports.object({}), (client2, _input, signal) => client2.getCurrentIdentity(signal));
5960
7036
  registerProductTool(server, dependencies, "project_handle_check", "Check whether a project address is free before offering it to the customer. A handle is one to five lowercase words of letters and digits joined by single hyphens, three to fifty-nine characters, and never a reserved word such as admin, api, dev or www; it becomes <handle>.check.omh.st for Prod and dev-<handle>.check.omh.st for Dev. The answer names why a handle cannot be used and returns free alternatives, so offer one of those instead of guessing again. Check the name here first, then move an existing project onto it with project_handle_set.", external_exports.object({ handle: external_exports.string().min(1).max(120) }), (client2, input) => client2.checkProjectHandle({ handle: input.handle }));
5961
7037
  registerProductTool(server, dependencies, "project_handle_set", "Move a project to an address the customer chose, after project_handle_check said it is free. The Dev and Prod gateways are re-published at the new address and it is stored only once both serve it; the previous address stops answering and anyone may claim it, so a link already shared stops working. Needs the project's current ETag from project_status and one idempotency key; reuse the same key after an uncertain answer. Refused while another operation is running for the project, and refused for an address that is taken, unusable, or the one the project already has. Returns an operation to follow with operation_get.", external_exports.object({
5962
7038
  project_id: identifier2,
@@ -6032,8 +7108,8 @@ function createLocalOhmyhostMcpServer(dependencies) {
6032
7108
  organizationId: input.organization_id,
6033
7109
  idempotencyKey: input.idempotency_key
6034
7110
  }), mutationAnnotations());
6035
- registerProductTool(server, dependencies, "github_status", "Read this workspace's GitHub connection. If not_connected or revoked, an Owner or Admin must run github_connect. A connected installation covers only repositories selected in its settings.", external_exports.object({ organization_id: identifier2 }), (client2, input) => client2.getGithubConnection(input.organization_id));
6036
- registerProductTool(server, dependencies, "source_link", "Link a repository covered by the workspace GitHub connection. Call github_status first and github_connect once if needed. This returns an operation without another browser consent and never starts a build. Wait for that operation before planning a deploy. If repository access is missing, update the installation using the returned settings action, then retry the same project and key. Never ask for an installation ID or provider token.", external_exports.object({
7111
+ registerProductTool(server, dependencies, "github_status", "Read this workspace's GitHub connection. If not_connected or revoked, an Owner or Admin must run github_connect. A connected installation covers only repositories selected in its settings. Each workspace has its own connection, so the same repository can be connected and linked in the workspaces of separate accounts.", external_exports.object({ organization_id: identifier2 }), (client2, input) => client2.getGithubConnection(input.organization_id));
7112
+ registerProductTool(server, dependencies, "source_link", "Link a repository covered by the workspace GitHub connection. Call github_status first and github_connect once if needed. This returns an operation without another browser consent and never starts a build. Wait for that operation before planning a deploy. If repository access is missing, update the installation using the returned settings action, then retry the same project and key. Never ask for an installation ID or provider token. Errors name the account the call ran as (acting_as): resource_not_found means that account cannot see the project (use the login of the account that owns it), github_connection_required means that workspace has no GitHub connection yet, and repository_not_installed means its GitHub App installation does not cover the repository.", external_exports.object({
6037
7113
  project_id: identifier2,
6038
7114
  repository_owner: identifier2,
6039
7115
  repository_name: identifier2,
@@ -6251,31 +7327,33 @@ function createLocalOhmyhostMcpServer(dependencies) {
6251
7327
  name: input.name,
6252
7328
  idempotencyKey: input.idempotency_key
6253
7329
  }), destructiveAnnotations());
6254
- server.registerTool("secret_set_command", {
6255
- description: "Return the stdin-only CLI command for setting a secret; the value never enters MCP.",
6256
- inputSchema: external_exports.object({
6257
- project_id: identifier2,
6258
- environment_id: identifier2,
6259
- name: secretName,
6260
- idempotency_key: idempotencyKey
6261
- }),
6262
- annotations: readAnnotations()
6263
- }, (input) => toolResult({
6264
- command: [
6265
- `${commandPrefix} secret set`,
6266
- shellQuote(input.name),
6267
- "--project",
6268
- shellQuote(input.project_id),
6269
- "--environment",
6270
- shellQuote(input.environment_id),
6271
- "--idempotency-key",
6272
- shellQuote(input.idempotency_key),
6273
- "--stdin",
6274
- "--wait",
6275
- "--json"
6276
- ].join(" "),
6277
- instruction: "Pass the secret value on stdin in a local terminal. Never place it in MCP arguments, prompts, command-line arguments, source, or logs."
6278
- }));
7330
+ registerProductTool(server, dependencies, "secret_set_command", "Return the stdin-only CLI command for setting a secret; the value never enters MCP. The call runs as the chosen login and first reads the project, so an account that cannot see it answers resource_not_found. The command names that login with --profile-name plus its user and organization (--profile-user, --profile-organization), so a login of the same name that belongs to anyone else is refused, for example on another computer; a server with OHMYHOST_TOKEN instead names its key's user and organization with --token-user and --token-organization, and the CLI then runs only with an OHMYHOST_TOKEN of that account. Either way the later write runs as exactly this account or not at all (acting_as shows which).", external_exports.object({
7331
+ project_id: identifier2,
7332
+ environment_id: identifier2,
7333
+ name: secretName,
7334
+ idempotency_key: idempotencyKey
7335
+ }), async (client2, input, signal) => {
7336
+ await client2.getProject(input.project_id);
7337
+ const account = await handoverAccount(client2, signal);
7338
+ return {
7339
+ command: [
7340
+ `${commandPrefix} secret set`,
7341
+ shellQuote(input.name),
7342
+ "--project",
7343
+ shellQuote(input.project_id),
7344
+ "--environment",
7345
+ shellQuote(input.environment_id),
7346
+ "--idempotency-key",
7347
+ shellQuote(input.idempotency_key),
7348
+ ...account.flags,
7349
+ "--stdin",
7350
+ "--wait",
7351
+ "--json"
7352
+ ].join(" "),
7353
+ acting_as: client2.actingAs?.() ?? null,
7354
+ instruction: `Pass the secret value on stdin in a local terminal; ${account.runs}. Never place the value or a key in MCP arguments, prompts, command-line arguments, source, or logs.`
7355
+ };
7356
+ });
6279
7357
  registerProductTool(server, dependencies, "rollback_plan", "Plan a rollback", external_exports.object({ project_id: identifier2, deployment_id: identifier2 }), (client2, input) => client2.planRollback({ projectId: input.project_id, deploymentId: input.deployment_id }));
6280
7358
  registerProductTool(server, dependencies, "rollback_execute", "Execute a reviewed rollback", external_exports.object({
6281
7359
  project_id: identifier2,
@@ -6304,11 +7382,20 @@ function createLocalOhmyhostMcpServer(dependencies) {
6304
7382
  }), destructiveAnnotations());
6305
7383
  return server;
6306
7384
  }
6307
- function registerProductTool(server, dependencies, name, description, inputSchema, execute, annotations = readAnnotations()) {
7385
+ function registerProductTool(server, dependencies, name, description, schema, execute, annotations = readAnnotations(), selection = (input) => {
7386
+ const organizationId = input["organization_id"];
7387
+ return {
7388
+ ...input.profile_name === void 0 ? {} : { name: input.profile_name },
7389
+ ...typeof organizationId === "string" ? { organizationId } : {}
7390
+ };
7391
+ }) {
7392
+ const inputSchema = schema.extend({ profile_name: profileName });
6308
7393
  const callback = (async (input, context) => {
6309
7394
  const parsed = inputSchema.parse(input);
7395
+ let client2;
6310
7396
  try {
6311
- const result = toolResult(await execute(await dependencies.authenticatedClient(context.mcpReq.signal), parsed, context.mcpReq.signal));
7397
+ client2 = await dependencies.authenticatedClient(context.mcpReq.signal, selection(parsed));
7398
+ const result = toolResult(await execute(client2, parsed, context.mcpReq.signal));
6312
7399
  const scope = parsed;
6313
7400
  if (typeof scope["project_id"] === "string" && identifier2.safeParse(scope["project_id"]).success)
6314
7401
  result.content.push({
@@ -6320,48 +7407,106 @@ function registerProductTool(server, dependencies, name, description, inputSchem
6320
7407
  });
6321
7408
  return result;
6322
7409
  } catch (error) {
6323
- let safe;
6324
- try {
6325
- safe = error instanceof Error && error.name === "InteractiveSessionRequiredError" ? {
6326
- code: "interactive_login_required",
6327
- retryable: false,
6328
- suggestedAction: `A user API token cannot create, list or select a workspace. Run ${resolveProductCliEnvironment(dependencies.environment).commandPrefix} login --json in a process without OHMYHOST_TOKEN, then retry.`
6329
- } : error instanceof Error && error.message.startsWith("ORGANIZATION_NOT_FOUND:") ? {
6330
- code: "organization_not_found",
6331
- retryable: false,
6332
- suggestedAction: "Call organization_list and choose one of the returned workspace identifiers. Do not create another workspace to recover."
6333
- } : error instanceof OperationEventsUnavailableError ? {
6334
- code: "operation_events_unavailable",
6335
- retryable: true,
6336
- suggestedAction: "Read operation_get for current status, then retry operation_logs for this same operation. Do not submit another deployment to recover logs."
6337
- } : error instanceof Error && error.message.startsWith("EXPORT_PASSWORD_FILE_INVALID:") ? {
6338
- code: "export_password_file_invalid",
6339
- retryable: false,
6340
- suggestedAction: "Choose an existing private UTF-8 password file of 1\u20131024 bytes. The user retains the file and password. Do not paste the password into MCP arguments."
6341
- } : parseSafeProblem(error);
6342
- } catch {
6343
- safe = {
6344
- code: "client_request_failed",
6345
- retryable: false,
6346
- suggestedAction: "Check the connection and run 'ohmyhost login' if authentication expired. Inspect the existing operation before resubmitting any mutation."
6347
- };
6348
- }
6349
- return {
6350
- ...toolResult({
6351
- error: {
6352
- code: safe.code,
6353
- retryable: safe.retryable,
6354
- suggested_action: safe.suggestedAction,
6355
- ...safe.requestId === void 0 ? {} : { request_id: safe.requestId },
6356
- ...safe.retryAfterSeconds === void 0 ? {} : { retry_after_seconds: safe.retryAfterSeconds }
6357
- }
6358
- }),
6359
- isError: true
6360
- };
7410
+ return toolError(error, dependencies, client2?.actingAs?.());
6361
7411
  }
6362
7412
  });
6363
7413
  server.registerTool(name, { description, inputSchema, annotations }, callback);
6364
7414
  }
7415
+ async function handoverAccount(client2, signal) {
7416
+ const profile = client2.actingAs?.()?.profile ?? null;
7417
+ if (profile !== null) {
7418
+ if (profile.organization_id === null)
7419
+ throw new ProfileSelectionError("profile_context_mismatch", 2, `The saved login ${profile.name} has no organization, so no command can be tied to its account. Nothing was sent.`);
7420
+ return {
7421
+ flags: [
7422
+ "--profile-name",
7423
+ shellQuote(profile.name),
7424
+ "--profile-user",
7425
+ shellQuote(profile.user_id),
7426
+ "--profile-organization",
7427
+ shellQuote(profile.organization_id)
7428
+ ],
7429
+ runs: `the command runs only as the saved login it names and only while that login belongs to user ${profile.user_id} in organization ${profile.organization_id}, so a login of the same name that belongs to anyone else, for example on another computer, is refused before anything is read or sent`
7430
+ };
7431
+ }
7432
+ const report = await client2.getCurrentIdentity(signal);
7433
+ const identity = parseCurrentIdentity({
7434
+ actor_id: report.actor_id,
7435
+ organization_ids: report.organization_ids
7436
+ });
7437
+ const userId = identity.actor_id.startsWith("user:") ? identity.actor_id.slice(5) : "";
7438
+ const [organizationId, ...others] = identity.organization_ids;
7439
+ if (!isProfileUserId(userId) || organizationId === void 0 || others.length > 0)
7440
+ throw new ProfileSelectionError("environment_token_context_mismatch", 2, "This server's OHMYHOST_TOKEN does not belong to one user in one organization, so no command can be tied to its account. Nothing was sent.");
7441
+ return {
7442
+ flags: ["--token-user", shellQuote(userId), "--token-organization", shellQuote(organizationId)],
7443
+ runs: `run it only where OHMYHOST_TOKEN holds a key of user ${userId} in organization ${organizationId}, loaded from its private env file; it refuses a saved login and any other account's key before anything is sent`
7444
+ };
7445
+ }
7446
+ function fixedOrganizationAction(error, commandPrefix) {
7447
+ return `This saved login stays in its organization. Add a login for the other workspace with ${commandPrefix} login --organization ${error.organization.id} --user ${error.profile.userId} --json, then pass its profile_name.`;
7448
+ }
7449
+ function toolError(error, dependencies, actingAs) {
7450
+ let safe;
7451
+ try {
7452
+ safe = error instanceof Error && error.name === "ProfileSelectionError" ? {
7453
+ code: error.code,
7454
+ retryable: false,
7455
+ suggestedAction: error.suggestedAction
7456
+ } : error instanceof Error && error.name === "ProfileLimitError" ? {
7457
+ code: "profile_limit_reached",
7458
+ retryable: false,
7459
+ suggestedAction: profileLimitAction(resolveProductCliEnvironment(dependencies.environment).commandPrefix)
7460
+ } : error instanceof Error && error.name === "ProfileOrganizationFixedError" ? {
7461
+ code: "profile_organization_fixed",
7462
+ retryable: false,
7463
+ suggestedAction: fixedOrganizationAction(error, resolveProductCliEnvironment(dependencies.environment).commandPrefix)
7464
+ } : error instanceof Error && error.name === "AccountVerificationError" ? {
7465
+ code: "login_verification_failed",
7466
+ retryable: true,
7467
+ suggestedAction: `The signed-in account could not be verified; nothing was saved. Run ${resolveProductCliEnvironment(dependencies.environment).commandPrefix} login --json again.`
7468
+ } : error instanceof Error && error.name === "CredentialStoreUnavailableError" ? {
7469
+ code: "credential_store_unavailable",
7470
+ retryable: true,
7471
+ suggestedAction: "Unlock or repair this computer's credential store and retry. Saved logins were not changed."
7472
+ } : error instanceof Error && error.name === "InteractiveSessionRequiredError" ? {
7473
+ code: "interactive_login_required",
7474
+ retryable: false,
7475
+ suggestedAction: `A user API token cannot create, list or select a workspace. Run ${resolveProductCliEnvironment(dependencies.environment).commandPrefix} login --json in a process without OHMYHOST_TOKEN, then retry.`
7476
+ } : error instanceof Error && error.message.startsWith("ORGANIZATION_NOT_FOUND:") ? {
7477
+ code: "organization_not_found",
7478
+ retryable: false,
7479
+ suggestedAction: "Call organization_list and choose one of the returned workspace identifiers. Do not create another workspace to recover."
7480
+ } : error instanceof OperationEventsUnavailableError ? {
7481
+ code: "operation_events_unavailable",
7482
+ retryable: true,
7483
+ suggestedAction: "Read operation_get for current status, then retry operation_logs for this same operation. Do not submit another deployment to recover logs."
7484
+ } : error instanceof Error && error.message.startsWith("EXPORT_PASSWORD_FILE_INVALID:") ? {
7485
+ code: "export_password_file_invalid",
7486
+ retryable: false,
7487
+ suggestedAction: "Choose an existing private UTF-8 password file of 1\u20131024 bytes. The user retains the file and password. Do not paste the password into MCP arguments."
7488
+ } : parseSafeProblem(error);
7489
+ } catch {
7490
+ safe = {
7491
+ code: "client_request_failed",
7492
+ retryable: false,
7493
+ suggestedAction: "Check the connection and run 'ohmyhost login' if authentication expired. Inspect the existing operation before resubmitting any mutation."
7494
+ };
7495
+ }
7496
+ return {
7497
+ ...toolResult({
7498
+ error: {
7499
+ code: safe.code,
7500
+ retryable: safe.retryable,
7501
+ suggested_action: safe.suggestedAction,
7502
+ ...safe.requestId === void 0 ? {} : { request_id: safe.requestId },
7503
+ ...safe.retryAfterSeconds === void 0 ? {} : { retry_after_seconds: safe.retryAfterSeconds },
7504
+ ...actingAs !== void 0 && ACCOUNT_SENSITIVE_PROBLEMS.has(safe.code) ? { acting_as: actingAs } : {}
7505
+ }
7506
+ }),
7507
+ isError: true
7508
+ };
7509
+ }
6365
7510
  function toolResult(result) {
6366
7511
  const serialized = JSON.stringify(result);
6367
7512
  return {
@@ -6392,11 +7537,17 @@ function destructiveAnnotations() {
6392
7537
  // apps/mcp/dist/stdio-main.js
6393
7538
  var environment = {
6394
7539
  ...process.env["OHMYHOST_ENVIRONMENT"] === void 0 ? {} : { OHMYHOST_ENVIRONMENT: process.env["OHMYHOST_ENVIRONMENT"] },
6395
- ...process.env["OHMYHOST_TOKEN"] === void 0 ? {} : { OHMYHOST_TOKEN: process.env["OHMYHOST_TOKEN"] }
7540
+ ...process.env["OHMYHOST_TOKEN"] === void 0 ? {} : { OHMYHOST_TOKEN: process.env["OHMYHOST_TOKEN"] },
7541
+ ...process.env["OHMYHOST_PROFILE"] === void 0 ? {} : { OHMYHOST_PROFILE: process.env["OHMYHOST_PROFILE"] }
6396
7542
  };
6397
7543
  resolveProductCliEnvironment(environment);
6398
7544
  serveStdio(() => createLocalOhmyhostMcpServer({
6399
7545
  environment,
6400
- authenticatedClient: (signal) => createAuthenticatedLocalMcpClient(signal, { environment })
7546
+ // Every tool call chooses its own saved login; nothing this process remembers can retarget it.
7547
+ authenticatedClient: (signal, request) => createAuthenticatedLocalMcpClient(signal, {
7548
+ environment,
7549
+ ...request === void 0 ? {} : { request }
7550
+ }),
7551
+ listProfiles: (signal) => listSavedProfiles(signal, { environment })
6401
7552
  }), { onerror: (error) => process.stderr.write(`${error.message}
6402
7553
  `) });