@amerged/ohmyhost-mcp 0.1.33 → 0.1.35

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.
@@ -30,7 +30,7 @@ import {
30
30
  serverIdentityOf,
31
31
  setNegotiatedProtocolVersion,
32
32
  validateEnvelopeMeta
33
- } from "./chunk-YM2ZQAGS.js";
33
+ } from "./chunk-CVTJZFHD.js";
34
34
 
35
35
  // node_modules/.pnpm/ignore@7.0.12/node_modules/ignore/index.js
36
36
  var require_ignore = __commonJS({
@@ -20235,6 +20235,11 @@ function toError(value) {
20235
20235
  // packages/contracts/dist/framework-build-script.mjs
20236
20236
  var PINNED_BUN_VERSION = "1.2.22";
20237
20237
  var FRAMEWORK_VERSION_POLICY = Object.freeze({
20238
+ astro: Object.freeze({ floors: Object.freeze(["7.4.0"]), denied: Object.freeze([]) }),
20239
+ "@astrojs/cloudflare": Object.freeze({
20240
+ floors: Object.freeze(["14.4.0"]),
20241
+ denied: Object.freeze([])
20242
+ }),
20238
20243
  next: Object.freeze({ floors: Object.freeze(["15.5.27", "16.3.8"]), denied: Object.freeze([]) }),
20239
20244
  vite: Object.freeze({ floors: Object.freeze(["5.4.6"]), denied: Object.freeze([]) }),
20240
20245
  "@tanstack/react-start": Object.freeze({
@@ -20289,11 +20294,15 @@ var RESOLVED_VERSION_FIELD = anchoredPattern(RESOLVED_VERSION_SOURCE);
20289
20294
  var DECLARED_VERSION_FIELD = anchoredPattern(`[~^]?${RESOLVED_VERSION_SOURCE}`);
20290
20295
  var STABLE_VERSION_FIELD = anchoredPattern(STABLE_VERSION_SOURCE);
20291
20296
  var FRAMEWORK_VERSION_PACKAGES = Object.freeze([
20297
+ "astro",
20298
+ "@astrojs/cloudflare",
20292
20299
  "next",
20293
20300
  "vite",
20294
20301
  "@tanstack/react-start"
20295
20302
  ]);
20296
20303
  var FRAMEWORK_DEPENDENCY_PACKAGES = Object.freeze([
20304
+ "astro",
20305
+ "@astrojs/cloudflare",
20297
20306
  "next",
20298
20307
  "react",
20299
20308
  "react-dom",
@@ -20304,10 +20313,23 @@ var FRAMEWORK_DEPENDENCY_PACKAGES = Object.freeze([
20304
20313
  "@cloudflare/vite-plugin"
20305
20314
  ]);
20306
20315
  var REACT_PACKAGES = Object.freeze(["react", "react-dom"]);
20307
- var BUILD_FRAMEWORKS = Object.freeze(["nextjs", "vite-static", "tanstack-start"]);
20316
+ var BUILD_FRAMEWORKS = Object.freeze([
20317
+ "astro",
20318
+ "nextjs",
20319
+ "vite-static",
20320
+ "tanstack-start"
20321
+ ]);
20308
20322
  var PUSH_AND_PLAN = "Commit and push the change, then plan the new commit.";
20309
20323
  var VITE_BUILD_SCRIPT = '"vite build", optionally joined with && to one "tsc", "tsc -b", "tsc --build" or "tsc --noEmit" stage';
20310
20324
  var FRAMEWORK_CONVERSION_DETAILS = Object.freeze({
20325
+ astro_template_import_unsupported: {
20326
+ paths: ["module"],
20327
+ template: '"{{path}}" uses a dynamic import, import.meta or require in an Astro template. Move server imports into frontmatter and dynamic browser imports into a normal statically imported browser module, then commit and push the change and plan the new commit.'
20328
+ },
20329
+ astro_config_unsupported: {
20330
+ paths: ["framework_config"],
20331
+ template: '"{{path}}" must use the standard Astro output directories and root base. For edge use output: "server", @astrojs/cloudflare with imageService: "passthrough", and session: false. Static hosting must omit a server adapter. Additional Cloudflare bindings, custom sessions and provider cache are not supported. Commit and push the change, then plan the new commit.'
20332
+ },
20311
20333
  managed_auth_database_required: {
20312
20334
  paths: ["repository_config"],
20313
20335
  template: managedCustomerAuthDatabaseMessage("{{path}}")
@@ -20382,7 +20404,8 @@ var FRAMEWORK_CONVERSION_DETAILS = Object.freeze({
20382
20404
  text: {
20383
20405
  nextjs: 'exactly "next build" or "next build --webpack"; ohmyho.st adds the OpenNext build itself',
20384
20406
  "vite-static": VITE_BUILD_SCRIPT,
20385
- "tanstack-start": VITE_BUILD_SCRIPT
20407
+ "tanstack-start": VITE_BUILD_SCRIPT,
20408
+ astro: 'exactly "astro build"'
20386
20409
  }
20387
20410
  }
20388
20411
  },
@@ -20398,11 +20421,11 @@ var FRAMEWORK_CONVERSION_DETAILS = Object.freeze({
20398
20421
  },
20399
20422
  framework_unsupported: {
20400
20423
  paths: ["manifest"],
20401
- template: `"{{path}}" declares none of next, vite or @tanstack/react-start. Add the app's framework to dependencies, or set runtime.mode: functions in ohmyhost.yaml and add src/ohmyhost/worker.ts. ${PUSH_AND_PLAN}`
20424
+ template: `"{{path}}" declares none of astro, next, vite or @tanstack/react-start. Add the app's framework to dependencies, or set runtime.mode: functions in ohmyhost.yaml and add src/ohmyhost/worker.ts. ${PUSH_AND_PLAN}`
20402
20425
  },
20403
20426
  framework_ambiguous: {
20404
20427
  paths: ["manifest"],
20405
- template: '"{{path}}" declares next together with vite or @tanstack/react-start, so ohmyho.st cannot choose one framework. Remove the packages of the framework this application does not use, commit and push the change, then plan the new commit.'
20428
+ template: '"{{path}}" declares competing application framework packages, so ohmyho.st cannot choose one framework. Remove the packages of the framework this application does not use, commit and push the change, then plan the new commit.'
20406
20429
  },
20407
20430
  lockfile_missing: {
20408
20431
  paths: ["manifest"],
@@ -20441,7 +20464,8 @@ var FRAMEWORK_CONVERSION_DETAILS = Object.freeze({
20441
20464
  text: {
20442
20465
  nextjs: "runtime.mode: edge",
20443
20466
  "vite-static": "runtime.mode: edge with src/ohmyhost/companion.ts when database, auth, mail, storage or functions.crons is set, and static otherwise",
20444
- "tanstack-start": "runtime.mode: edge, or static without functions.crons and storage"
20467
+ "tanstack-start": "runtime.mode: edge, or static without functions.crons and storage",
20468
+ astro: "runtime.mode: static for prerendered sites, or edge with the Cloudflare adapter"
20445
20469
  }
20446
20470
  }
20447
20471
  },
@@ -20456,7 +20480,8 @@ var FRAMEWORK_CONVERSION_DETAILS = Object.freeze({
20456
20480
  text: {
20457
20481
  nextjs: ".open-next/assets",
20458
20482
  "vite-static": "dist",
20459
- "tanstack-start": "dist/client"
20483
+ "tanstack-start": "dist/client",
20484
+ astro: "dist for static, or dist/client for edge"
20460
20485
  }
20461
20486
  }
20462
20487
  },
@@ -20515,8 +20540,8 @@ var DETAIL_DEFINITIONS = FRAMEWORK_CONVERSION_DETAILS;
20515
20540
  var PLACEHOLDER = /\{\{([a-z]+)\}\}/gu;
20516
20541
  var PATH_PATTERN = "[A-Za-z0-9_@()[\\]./${}-]{1,240}";
20517
20542
  var SAFE_PATH = new RegExp(`^${PATH_PATTERN}$`, "u");
20518
- var MODULE_PATH = /^[A-Za-z0-9_@()[\]./${}-]+\.(?:[cm]?[jt]sx?)$/u;
20519
- var FRAMEWORK_CONFIG_NAME = /^(?:next|open-next|vite)\.config\.(?:js|mjs|ts)$/u;
20543
+ var MODULE_PATH = /^[A-Za-z0-9_@()[\]./${}-]+\.(?:astro|[cm]?[jt]sx?)$/u;
20544
+ var FRAMEWORK_CONFIG_NAME = /^(?:astro|next|open-next|vite)\.config\.(?:js|mjs|ts)$/u;
20520
20545
  var MAXIMUM_DETAIL_LENGTH = 2e3;
20521
20546
  var DETAIL_PATTERNS = Object.freeze(Object.entries(DETAIL_DEFINITIONS).map(([code, definition]) => Object.freeze({
20522
20547
  code,
@@ -22322,14 +22347,25 @@ var parseDevAccessTicket = (value) => {
22322
22347
  return input;
22323
22348
  };
22324
22349
  var parseCurrentIdentity = (value) => {
22325
- const input = exactRecord2(value, ["actor_id", "organization_ids"]);
22350
+ const input = exactRecord2(value, ["actor_id", "email", "organization_ids", "organizations"]);
22351
+ const email = input["email"];
22326
22352
  const organizationIds = input["organization_ids"];
22353
+ const organizations = input["organizations"];
22327
22354
  if (typeof input["actor_id"] !== "string" || input["actor_id"].length === 0 || input["actor_id"].length > 256 || [...input["actor_id"]].some((character) => {
22328
22355
  const codePoint = character.codePointAt(0) ?? 0;
22329
22356
  return codePoint <= 31 || codePoint === 127;
22330
- }) || !Array.isArray(organizationIds) || organizationIds.some((organizationId) => !isUlid(organizationId)) || new Set(organizationIds).size !== organizationIds.length) {
22357
+ }) || email !== null && (!boundedString(email, 3, 254) || !/^[^\s@]+@[^\s@.]+(?:\.[^\s@.]+)+$/u.test(email) || /[\p{Cc}\p{Cf}\p{Zl}\p{Zp}]/u.test(email)) || !Array.isArray(organizationIds) || organizationIds.some((organizationId) => !isUlid(organizationId)) || new Set(organizationIds).size !== organizationIds.length || !Array.isArray(organizations) || organizations.length !== organizationIds.length) {
22331
22358
  throw new ResponseContractError();
22332
22359
  }
22360
+ organizations.forEach((organization, index) => {
22361
+ const entry = exactRecord2(organization, ["id", "name"]);
22362
+ if (entry["id"] !== organizationIds[index] || !boundedString(entry["name"], 1, 128) || entry["name"].trim() !== entry["name"] || [...entry["name"]].some((character) => {
22363
+ const codePoint = character.codePointAt(0) ?? 0;
22364
+ return codePoint <= 31 || codePoint === 127;
22365
+ })) {
22366
+ throw new ResponseContractError();
22367
+ }
22368
+ });
22333
22369
  return input;
22334
22370
  };
22335
22371
  var parseAccountProfile = (value) => {
@@ -23221,7 +23257,7 @@ function exact3(v, keys) {
23221
23257
  // apps/product-cli/package.json
23222
23258
  var package_default2 = {
23223
23259
  name: "@ohmyhost/product-cli",
23224
- version: "0.1.33",
23260
+ version: "0.1.35",
23225
23261
  private: true,
23226
23262
  ohmyhost: {
23227
23263
  deployment: "production",
@@ -24327,7 +24363,7 @@ var HELP_USAGE = Object.freeze({
24327
24363
  "project flag set": "ohmyhost project flag set --project ULID --enabled true|false --idempotency-key KEY --json (shows the small Powered by ohmyho.st flag on the production site; while it shows, this project's custom domain uses no domain credits and also works on Free, and each Paid period bought through Stripe adds 250 credits to the workspace, however many projects show the flag)",
24328
24364
  "project handle check": "ohmyhost project handle check --handle HANDLE --json (is this address free? answers with a reason and free alternatives; the address becomes HANDLE.check.omh.st)",
24329
24365
  "project handle set": "ohmyhost project handle set --project ULID --handle HANDLE --if-match ETAG --idempotency-key KEY --json (moves the project to a free address; the old one stops working and anyone may claim it)",
24330
- "database compute set": "ohmyhost database compute set --project ULID --environment dev|prod --profile standard|performance --idempotency-key KEY --yes [--wait] --json\n standard: Free 0.25 CU/1 GB/60s idle, Paid 0.5 CU/2 GB/60s idle. performance: Paid 1 CU/4 GB/300s idle, 2.5x database compute credits per equal active minute; longer idle time also consumes credits.",
24366
+ "database compute set": "ohmyhost database compute set --project ULID --environment dev|prod --profile standard|performance --idempotency-key KEY --yes [--wait] --json\n standard: Free 0.25 CU/1 GB, Paid 0.5 CU/2 GB. performance: Paid 1 CU/4 GB, 2.5x database compute credits per equal active minute. All profiles use the provider-default idle suspension.",
24331
24367
  "database compute get": "ohmyhost database compute get --project ULID [--environment dev|prod] --json",
24332
24368
  "database write": "ohmyhost database write --project ULID --environment dev|prod --statement-file PATH --idempotency-key KEY [--parameters-json JSON] --yes --json",
24333
24369
  "database query": "ohmyhost database query --project ULID --environment dev|prod --statement SQL [--parameters-json JSON] --json",
@@ -29147,7 +29183,7 @@ function createLocalOhmyhostMcpServer(dependencies) {
29147
29183
  });
29148
29184
  registerManagedSourceTools(server, dependencies);
29149
29185
  registerProductTool(server, dependencies, "database_compute_get", "Read current managed database size, memory, region and compute state without running SQL or waking the database. Shared Dev/Prod resolves to one physical database. A null database means no confirmed managed placement. suspend_timeout_seconds=0 uses the provider default; -1 disables scale to zero. A configured value is not proof that an in-progress resize finished. This read never resizes or changes billing.", external_exports.object({ project_id: identifier2, environment: external_exports.enum(["dev", "prod"]).default("dev") }), (client2, input) => client2.getDatabaseCompute({ projectId: input.project_id, environment: input.environment }));
29150
- registerProductTool(server, dependencies, "database_compute_set", "Select standard or performance compute for an existing database: Free 0.25 CU/1 GB/60-second idle suspension, Paid 0.5 CU/2 GB/60-second idle suspension. First read database_compute_get and explain actual size, metered compute cost, possible brief connection interruption and that shared data changes both Dev and Prod. Require the user's decision before confirm=true. SQL data is preserved. Poll the returned operation every 60 seconds; never submit a second change while it runs. Performance requires active Paid access and selects 1 CU/4 GB/300-second idle suspension at 2.5x Paid-standard compute credits per equal active minute; the longer idle window also consumes more active time. This affects only database compute. Read organization_credits_get for the published rate and active meter first. A successful choice is remembered per physical database; automatic Free downgrades preserve it, and an explicit standard choice clears it. Mixed/uncertain transition hours waive the premium; never multiply raw CU usage again.", external_exports.object({
29186
+ registerProductTool(server, dependencies, "database_compute_set", "Select standard or performance compute for an existing database: Free 0.25 CU/1 GB, Paid 0.5 CU/2 GB. All profiles use provider-default idle suspension (suspend_timeout_seconds=0). First read database_compute_get and explain actual size, metered compute cost, possible brief connection interruption and that shared data changes both Dev and Prod. Require the user's decision before confirm=true. SQL data is preserved. Poll the returned operation every 60 seconds; never submit a second change while it runs. Performance requires active Paid access and selects 1 CU/4 GB at 2.5x Paid-standard compute credits per equal active minute. This affects only database compute. Read organization_credits_get for the published rate and active meter first. A successful choice is remembered per physical database; automatic Free downgrades preserve it, and an explicit standard choice clears it. Mixed/uncertain transition hours waive the premium; never multiply raw CU usage again.", external_exports.object({
29151
29187
  project_id: identifier2,
29152
29188
  environment: external_exports.enum(["dev", "prod"]),
29153
29189
  profile: external_exports.enum(["standard", "performance"]),
@@ -29411,7 +29447,7 @@ function createLocalOhmyhostMcpServer(dependencies) {
29411
29447
  });
29412
29448
  return { revoked: true, key_id: input.key_id };
29413
29449
  }, destructiveAnnotations());
29414
- 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));
29450
+ registerProductTool(server, dependencies, "identity_get", "Get the ohmyho.st customer/agent identity this call acts as: user and account email, scoped organization IDs and names 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));
29415
29451
  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 }));
29416
29452
  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({
29417
29453
  project_id: identifier2,
@@ -29896,7 +29932,12 @@ function registerManagedSourceTools(server, dependencies) {
29896
29932
  directory: input.directory,
29897
29933
  metadataDirectory: resolveProductCliEnvironment(dependencies.environment).linkDirectory,
29898
29934
  apiOrigin: client2.productApiOrigin ?? resolveProductCliEnvironment(dependencies.environment).apiOrigin,
29899
- identity: { actor_id: current.actor_id, organization_ids: current.organization_ids },
29935
+ identity: {
29936
+ actor_id: current.actor_id,
29937
+ email: current.email,
29938
+ organization_ids: current.organization_ids,
29939
+ organizations: current.organizations
29940
+ },
29900
29941
  projectId: input.project_id,
29901
29942
  request,
29902
29943
  explicitExpectedCommit: input.expected_commit_sha !== void 0,
@@ -29933,7 +29974,12 @@ function registerManagedSourceTools(server, dependencies) {
29933
29974
  directory: input.directory,
29934
29975
  metadataDirectory: resolveProductCliEnvironment(dependencies.environment).linkDirectory,
29935
29976
  apiOrigin: client2.productApiOrigin ?? resolveProductCliEnvironment(dependencies.environment).apiOrigin,
29936
- identity: { actor_id: current.actor_id, organization_ids: current.organization_ids },
29977
+ identity: {
29978
+ actor_id: current.actor_id,
29979
+ email: current.email,
29980
+ organization_ids: current.organization_ids,
29981
+ organizations: current.organizations
29982
+ },
29937
29983
  projectId: input.project_id,
29938
29984
  operationId: input.operation_id,
29939
29985
  api: port(client2),
@@ -29993,7 +30039,9 @@ async function handoverAccount(client2, signal) {
29993
30039
  const report = await client2.getCurrentIdentity(signal);
29994
30040
  const identity = parseCurrentIdentity({
29995
30041
  actor_id: report.actor_id,
29996
- organization_ids: report.organization_ids
30042
+ email: report.email,
30043
+ organization_ids: report.organization_ids,
30044
+ organizations: report.organizations
29997
30045
  });
29998
30046
  const userId = identity.actor_id.startsWith("user:") ? identity.actor_id.slice(5) : "";
29999
30047
  const [organizationId, ...others] = identity.organization_ids;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amerged/ohmyhost-mcp",
3
- "version": "0.1.33",
3
+ "version": "0.1.35",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "node": ">=22"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ohmyhost/mcp",
3
- "version": "0.1.33",
3
+ "version": "0.1.35",
4
4
  "private": true,
5
5
  "ohmyhost": {
6
6
  "deployment": "production",
@@ -522,7 +522,7 @@ export function createLocalOhmyhostMcpServer(dependencies: LocalOhmyhostMcpDepen
522
522
  server,
523
523
  dependencies,
524
524
  "database_compute_set",
525
- "Select standard or performance compute for an existing database: Free 0.25 CU/1 GB/60-second idle suspension, Paid 0.5 CU/2 GB/60-second idle suspension. First read database_compute_get and explain actual size, metered compute cost, possible brief connection interruption and that shared data changes both Dev and Prod. Require the user's decision before confirm=true. SQL data is preserved. Poll the returned operation every 60 seconds; never submit a second change while it runs. Performance requires active Paid access and selects 1 CU/4 GB/300-second idle suspension at 2.5x Paid-standard compute credits per equal active minute; the longer idle window also consumes more active time. This affects only database compute. Read organization_credits_get for the published rate and active meter first. A successful choice is remembered per physical database; automatic Free downgrades preserve it, and an explicit standard choice clears it. Mixed/uncertain transition hours waive the premium; never multiply raw CU usage again.",
525
+ "Select standard or performance compute for an existing database: Free 0.25 CU/1 GB, Paid 0.5 CU/2 GB. All profiles use provider-default idle suspension (suspend_timeout_seconds=0). First read database_compute_get and explain actual size, metered compute cost, possible brief connection interruption and that shared data changes both Dev and Prod. Require the user's decision before confirm=true. SQL data is preserved. Poll the returned operation every 60 seconds; never submit a second change while it runs. Performance requires active Paid access and selects 1 CU/4 GB at 2.5x Paid-standard compute credits per equal active minute. This affects only database compute. Read organization_credits_get for the published rate and active meter first. A successful choice is remembered per physical database; automatic Free downgrades preserve it, and an explicit standard choice clears it. Mixed/uncertain transition hours waive the premium; never multiply raw CU usage again.",
526
526
  z.object({
527
527
  project_id: identifier,
528
528
  environment: z.enum(["dev", "prod"]),
@@ -1119,7 +1119,7 @@ export function createLocalOhmyhostMcpServer(dependencies: LocalOhmyhostMcpDepen
1119
1119
  server,
1120
1120
  dependencies,
1121
1121
  "identity_get",
1122
- "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.",
1122
+ "Get the ohmyho.st customer/agent identity this call acts as: user and account email, scoped organization IDs and names 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.",
1123
1123
  z.object({}),
1124
1124
  (client, _input, signal) => client.getCurrentIdentity(signal),
1125
1125
  );
@@ -2072,7 +2072,12 @@ function registerManagedSourceTools(
2072
2072
  apiOrigin:
2073
2073
  client.productApiOrigin ??
2074
2074
  resolveProductCliEnvironment(dependencies.environment).apiOrigin,
2075
- identity: { actor_id: current.actor_id, organization_ids: current.organization_ids },
2075
+ identity: {
2076
+ actor_id: current.actor_id,
2077
+ email: current.email,
2078
+ organization_ids: current.organization_ids,
2079
+ organizations: current.organizations,
2080
+ },
2076
2081
  projectId: input.project_id,
2077
2082
  request,
2078
2083
  explicitExpectedCommit: input.expected_commit_sha !== undefined,
@@ -2120,7 +2125,12 @@ function registerManagedSourceTools(
2120
2125
  apiOrigin:
2121
2126
  client.productApiOrigin ??
2122
2127
  resolveProductCliEnvironment(dependencies.environment).apiOrigin,
2123
- identity: { actor_id: current.actor_id, organization_ids: current.organization_ids },
2128
+ identity: {
2129
+ actor_id: current.actor_id,
2130
+ email: current.email,
2131
+ organization_ids: current.organization_ids,
2132
+ organizations: current.organizations,
2133
+ },
2124
2134
  projectId: input.project_id,
2125
2135
  operationId: input.operation_id,
2126
2136
  api: port(client),
@@ -2235,7 +2245,9 @@ async function handoverAccount(
2235
2245
  const report = await client.getCurrentIdentity(signal);
2236
2246
  const identity = parseCurrentIdentity({
2237
2247
  actor_id: report.actor_id,
2248
+ email: report.email,
2238
2249
  organization_ids: report.organization_ids,
2250
+ organizations: report.organizations,
2239
2251
  });
2240
2252
  const userId = identity.actor_id.startsWith("user:") ? identity.actor_id.slice(5) : "";
2241
2253
  const [organizationId, ...others] = identity.organization_ids;
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ohmyhost/product-cli",
3
- "version": "0.1.33",
3
+ "version": "0.1.35",
4
4
  "private": true,
5
5
  "ohmyhost": {
6
6
  "deployment": "production",
@@ -399,7 +399,7 @@ const HELP_USAGE = Object.freeze({
399
399
  "ohmyhost project handle set --project ULID --handle HANDLE --if-match ETAG --idempotency-key KEY --json (moves the project to a free address; the old one stops working and anyone may claim it)",
400
400
  "database compute set":
401
401
  "ohmyhost database compute set --project ULID --environment dev|prod --profile standard|performance --idempotency-key KEY --yes [--wait] --json\n" +
402
- " standard: Free 0.25 CU/1 GB/60s idle, Paid 0.5 CU/2 GB/60s idle. performance: Paid 1 CU/4 GB/300s idle, 2.5x database compute credits per equal active minute; longer idle time also consumes credits.",
402
+ " standard: Free 0.25 CU/1 GB, Paid 0.5 CU/2 GB. performance: Paid 1 CU/4 GB, 2.5x database compute credits per equal active minute. All profiles use the provider-default idle suspension.",
403
403
  "database compute get":
404
404
  "ohmyhost database compute get --project ULID [--environment dev|prod] --json",
405
405
  "database write":
@@ -310,8 +310,12 @@ export async function initializeRepository(
310
310
  return match?.[1] === undefined || match[1].startsWith("_") ? [] : [match[1]];
311
311
  }),
312
312
  );
313
- const apiRouteCount = applicationFiles.filter((path) =>
314
- /^(?:src\/)?app\/api\/.+\/route\.(?:js|jsx|ts|tsx)$/u.test(path),
313
+ const apiRouteCount = applicationFiles.filter(
314
+ (path) =>
315
+ /^(?:src\/)?app\/api\/.+\/route\.(?:js|jsx|ts|tsx)$/u.test(path) ||
316
+ (framework === "astro" &&
317
+ /^src\/pages\/.+\.(?:js|ts)$/u.test(path) &&
318
+ !path.endsWith(".d.ts")),
315
319
  ).length;
316
320
  const supabase = dependencies.has("@supabase/supabase-js") || dependencies.has("@supabase/ssr");
317
321
  const configuredDatabase = existingConfiguration?.database?.enabled === true;
@@ -368,11 +372,15 @@ export async function initializeRepository(
368
372
  const targetStorage = r2 || supabaseStorage;
369
373
  // TanStack Start binds storage only on edge, as a Vite app does through its companion.
370
374
  const tanStackRuntime =
371
- framework !== "tanstack-start"
375
+ framework !== "tanstack-start" && framework !== "astro"
372
376
  ? null
373
377
  : targetStorage
374
378
  ? "edge"
375
- : proposedTanStackRuntime(frameworkConfig, dependencies);
379
+ : framework === "astro"
380
+ ? dependencies.has("@astrojs/cloudflare")
381
+ ? "edge"
382
+ : "static"
383
+ : proposedTanStackRuntime(frameworkConfig, dependencies);
376
384
  const companionRequired =
377
385
  framework === "vite" &&
378
386
  (existingConfiguration === null
@@ -390,7 +398,11 @@ export async function initializeRepository(
390
398
  databaseProvider,
391
399
  hasHealthcheck:
392
400
  applicationFiles.includes("src/app/api/health/route.ts") ||
393
- applicationFiles.includes("app/api/health/route.ts"),
401
+ applicationFiles.includes("app/api/health/route.ts") ||
402
+ (framework === "astro" &&
403
+ applicationFiles.some((path) =>
404
+ /^src\/pages\/api\/health(?:\/index)?\.(?:js|ts)$/u.test(path),
405
+ )),
394
406
  mailEnabled: targetMail,
395
407
  betterAuthEnabled: targetBetterAuth,
396
408
  storageEnabled: targetStorage,
@@ -596,7 +608,10 @@ async function workerModuleBlockers(context: SourceCheckContext): Promise<Reposi
596
608
  const scheduled =
597
609
  (config?.functions?.crons.length ?? 0) > 0 &&
598
610
  ((mode === "edge" &&
599
- (framework === "vite" || framework === "nextjs" || framework === "tanstack-start")) ||
611
+ (framework === "astro" ||
612
+ framework === "vite" ||
613
+ framework === "nextjs" ||
614
+ framework === "tanstack-start")) ||
600
615
  (mode === "functions" && framework === "functions"));
601
616
  const path = framework === "vite" ? VITE_COMPANION_ENTRY_PATH : WORKER_MODULE_PATH;
602
617
  if ((framework !== "functions" && !scheduled) || !files.includes(path)) return [];
@@ -989,7 +1004,10 @@ function hasDirectSafeBuildCommand(
989
1004
  const scripts = manifest["scripts"];
990
1005
  if (!isRecord(scripts) || typeof scripts["build"] !== "string") return false;
991
1006
  return (
992
- (framework === "nextjs" || framework === "vite" || framework === "tanstack-start") &&
1007
+ (framework === "astro" ||
1008
+ framework === "nextjs" ||
1009
+ framework === "vite" ||
1010
+ framework === "tanstack-start") &&
993
1011
  isAdmittedFrameworkBuildScript(framework, scripts["build"])
994
1012
  );
995
1013
  }
@@ -1144,10 +1162,23 @@ async function readFrameworkConfig(
1144
1162
  files: readonly string[],
1145
1163
  framework: RepositoryFramework,
1146
1164
  ): Promise<FrameworkConfigText | null> {
1147
- if (framework !== "nextjs" && framework !== "vite" && framework !== "tanstack-start") return null;
1165
+ if (
1166
+ framework !== "astro" &&
1167
+ framework !== "nextjs" &&
1168
+ framework !== "vite" &&
1169
+ framework !== "tanstack-start"
1170
+ )
1171
+ return null;
1148
1172
  let path: string | null;
1149
1173
  try {
1150
- path = resolveFrameworkConfig(framework === "nextjs" ? "next.config" : "vite.config", files);
1174
+ path = resolveFrameworkConfig(
1175
+ framework === "astro"
1176
+ ? "astro.config"
1177
+ : framework === "nextjs"
1178
+ ? "next.config"
1179
+ : "vite.config",
1180
+ files,
1181
+ );
1151
1182
  } catch (error) {
1152
1183
  // The shared rules name the ambiguous variants; none of them is judged.
1153
1184
  if (error instanceof FrameworkAdmissionError) return null;
@@ -1266,7 +1297,11 @@ function renderConfiguration(input: {
1266
1297
 
1267
1298
  let output = "dist";
1268
1299
  if (input.framework === "nextjs") output = ".open-next/assets";
1269
- else if (input.framework === "tanstack-start") output = "dist/client";
1300
+ else if (
1301
+ input.framework === "tanstack-start" ||
1302
+ (input.framework === "astro" && input.tanStackRuntime === "edge")
1303
+ )
1304
+ output = "dist/client";
1270
1305
 
1271
1306
  let mode = "auto";
1272
1307
  if (input.framework === "functions") mode = "functions";
@@ -63,7 +63,9 @@ export interface SafeProblem {
63
63
 
64
64
  export interface PublicCurrentIdentity {
65
65
  readonly actor_id: string;
66
+ readonly email: string | null;
66
67
  readonly organization_ids: readonly string[];
68
+ readonly organizations: readonly { readonly id: string; readonly name: string }[];
67
69
  }
68
70
 
69
71
  /** One workspace the signed-in user belongs to, with the provider identity a selection needs. */
@@ -635,8 +637,10 @@ export function parseProjectCreditBudget(value: unknown): ProjectCreditBudget {
635
637
  }
636
638
 
637
639
  export const parseCurrentIdentity = (value: unknown): PublicCurrentIdentity => {
638
- const input = exactRecord(value, ["actor_id", "organization_ids"]);
640
+ const input = exactRecord(value, ["actor_id", "email", "organization_ids", "organizations"]);
641
+ const email = input["email"];
639
642
  const organizationIds = input["organization_ids"];
643
+ const organizations = input["organizations"];
640
644
  if (
641
645
  typeof input["actor_id"] !== "string" ||
642
646
  input["actor_id"].length === 0 ||
@@ -645,12 +649,32 @@ export const parseCurrentIdentity = (value: unknown): PublicCurrentIdentity => {
645
649
  const codePoint = character.codePointAt(0) ?? 0;
646
650
  return codePoint <= 31 || codePoint === 127;
647
651
  }) ||
652
+ (email !== null &&
653
+ (!boundedString(email, 3, 254) ||
654
+ !/^[^\s@]+@[^\s@.]+(?:\.[^\s@.]+)+$/u.test(email) ||
655
+ /[\p{Cc}\p{Cf}\p{Zl}\p{Zp}]/u.test(email))) ||
648
656
  !Array.isArray(organizationIds) ||
649
657
  organizationIds.some((organizationId) => !isUlid(organizationId)) ||
650
- new Set(organizationIds).size !== organizationIds.length
658
+ new Set(organizationIds).size !== organizationIds.length ||
659
+ !Array.isArray(organizations) ||
660
+ organizations.length !== organizationIds.length
651
661
  ) {
652
662
  throw new ResponseContractError();
653
663
  }
664
+ organizations.forEach((organization: unknown, index) => {
665
+ const entry = exactRecord(organization, ["id", "name"]);
666
+ if (
667
+ entry["id"] !== organizationIds[index] ||
668
+ !boundedString(entry["name"], 1, 128) ||
669
+ entry["name"].trim() !== entry["name"] ||
670
+ [...entry["name"]].some((character) => {
671
+ const codePoint = character.codePointAt(0) ?? 0;
672
+ return codePoint <= 31 || codePoint === 127;
673
+ })
674
+ ) {
675
+ throw new ResponseContractError();
676
+ }
677
+ });
654
678
  return input as unknown as PublicCurrentIdentity;
655
679
  };
656
680