tileflow 0.1.0-alpha.14 → 0.1.0-alpha.16

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.
package/dist/index.js CHANGED
@@ -3,13 +3,13 @@
3
3
  // src/index.ts
4
4
  import { serve } from "@hono/node-server";
5
5
  import { Command } from "commander";
6
- import { createHash as createHash4, randomBytes as randomBytes2 } from "crypto";
6
+ import { createHash as createHash4, randomBytes as randomBytes3 } from "crypto";
7
7
  import { existsSync, readFileSync, realpathSync } from "fs";
8
8
  import { readFile as readFile7, writeFile as writeFile2 } from "fs/promises";
9
9
  import { hostname } from "os";
10
- import { dirname as dirname12, isAbsolute as isAbsolute5, relative as relative5, resolve as resolve11, sep as sep5 } from "path";
11
- import { createInterface } from "readline/promises";
12
- import pc6 from "picocolors";
10
+ import { dirname as dirname13, isAbsolute as isAbsolute5, relative as relative6, resolve as resolve12, sep as sep5 } from "path";
11
+ import { createInterface as createInterface2 } from "readline/promises";
12
+ import pc9 from "picocolors";
13
13
  import { createLocalCoordinates, setupCoordinates } from "@tileflow/coordinates-runtime";
14
14
  import {
15
15
  auditTileflowMapThemeValues as auditTileflowMapThemeValues2,
@@ -33,12 +33,12 @@ import {
33
33
  writeTileflowBuildArtifacts
34
34
  } from "@tileflow/dev/artifacts";
35
35
  import {
36
- assertValidTileflowConfig as assertValidTileflowConfig3,
36
+ assertValidTileflowConfig as assertValidTileflowConfig4,
37
37
  defaultTileflowApiUrl,
38
- defaultTileflowConfigPath,
38
+ defaultTileflowConfigPath as defaultTileflowConfigPath2,
39
39
  defaultTileflowManifestPath,
40
40
  getTileflowMapNames as getTileflowMapNames5,
41
- loadTileflowConfigWithInputs as loadTileflowConfigWithInputs4,
41
+ loadTileflowConfigWithInputs as loadTileflowConfigWithInputs5,
42
42
  TileflowValidationError as TileflowValidationError4
43
43
  } from "@tileflow/dev/config";
44
44
  import {
@@ -3572,7 +3572,7 @@ async function readCoordinatesInput(input, signal) {
3572
3572
  async function readStandardInput(signal) {
3573
3573
  const chunks = [];
3574
3574
  let length = 0;
3575
- return new Promise((resolve12, reject) => {
3575
+ return new Promise((resolve13, reject) => {
3576
3576
  const finish = (result) => {
3577
3577
  process.stdin.removeListener("data", onData);
3578
3578
  process.stdin.removeListener("end", onEnd);
@@ -3594,7 +3594,7 @@ async function readStandardInput(signal) {
3594
3594
  }
3595
3595
  chunks.push(bytes);
3596
3596
  };
3597
- const onEnd = () => finish(() => resolve12(decodeInput(Buffer.concat(chunks))));
3597
+ const onEnd = () => finish(() => resolve13(decodeInput(Buffer.concat(chunks))));
3598
3598
  const onError = () => finish(() => reject(new CoordinatesInputError("INVALID_VALUE")));
3599
3599
  if (signal.aborted) {
3600
3600
  onAbort();
@@ -4128,14 +4128,11 @@ function printFeatureInspectError(error) {
4128
4128
  }
4129
4129
 
4130
4130
  // src/hosted-client.ts
4131
- import {
4132
- serializeTileflowFontBundleManifest,
4133
- serializeTileflowModelBundleManifest
4134
- } from "@tileflow/core";
4131
+ import { serializeTileflowFontBundleManifest } from "@tileflow/core";
4135
4132
 
4136
4133
  // src/hosted-response.ts
4137
4134
  import { z as z2 } from "zod";
4138
- import { tileflowIconPackageLimits, tileflowModelBundleLimits } from "@tileflow/core";
4135
+ import { tileflowIconPackageLimits } from "@tileflow/core";
4139
4136
  var maximumHostedResponseBytes = 1024 * 1024;
4140
4137
  var safeTextSchema = z2.string().min(1).max(300).refine((value) => !/[\p{Cc}]/u.test(value));
4141
4138
  var safeIdentifierSchema = z2.string().min(1).max(200).regex(/^[A-Za-z0-9][A-Za-z0-9._:-]*$/u);
@@ -4155,7 +4152,18 @@ var hostedStyleDeploymentResponseSchema = z2.object({
4155
4152
  styleUrl: publicHttpUrlSchema
4156
4153
  }).strict()
4157
4154
  ).refine((themes) => Object.keys(themes).length > 0),
4158
- version: z2.number().int().positive().max(Number.MAX_SAFE_INTEGER).optional()
4155
+ version: z2.number().int().positive().max(Number.MAX_SAFE_INTEGER).optional(),
4156
+ /**
4157
+ * The Team 3D models the map places, each at its published current version, and whether that
4158
+ * version was prepared from the same glTF binaries as the map's own files.
4159
+ */
4160
+ teamModels: z2.array(
4161
+ z2.object({
4162
+ model: z2.string().regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/u).max(64),
4163
+ version: z2.number().int().positive().max(Number.MAX_SAFE_INTEGER),
4164
+ sources: z2.enum(["match", "differ", "unknown"])
4165
+ }).strict()
4166
+ ).max(256).optional()
4159
4167
  }).strict();
4160
4168
  var hostedIconPackageResponseSchema = z2.object({
4161
4169
  changed: z2.boolean().optional(),
@@ -4173,14 +4181,6 @@ var hostedFontBundleResponseSchema = z2.object({
4173
4181
  id: z2.string().regex(/^fnb_[A-Za-z0-9_-]{16,64}$/u),
4174
4182
  totalBytes: z2.number().int().positive().max(Number.MAX_SAFE_INTEGER)
4175
4183
  });
4176
- var hostedModelBundleResponseSchema = z2.object({
4177
- baseUrl: publicHttpUrlSchema,
4178
- changed: z2.boolean().optional(),
4179
- contentHash: z2.string().regex(/^[a-f0-9]{64}$/u),
4180
- id: z2.string().regex(/^mdb_[A-Za-z0-9_-]{16,64}$/u),
4181
- modelCount: z2.number().int().positive().max(tileflowModelBundleLimits.maxModelCount),
4182
- totalBytes: z2.number().int().positive().max(tileflowModelBundleLimits.maxPackageBytes)
4183
- });
4184
4184
  var hostedStatusStyleSchema = z2.object({
4185
4185
  environment: safeTextSchema,
4186
4186
  key: safeStorageKeySchema,
@@ -4274,7 +4274,7 @@ async function startDeviceAuthorization(appUrl, body, options = {}) {
4274
4274
  async function pollDeviceToken(appUrl, authorization, input, options = {}) {
4275
4275
  const expiresAt = Date.now() + authorization.expiresIn * 1e3;
4276
4276
  const intervalMs = Math.max(authorization.interval, 1) * 1e3;
4277
- const wait = input.sleep ?? ((milliseconds) => new Promise((resolve12) => setTimeout(resolve12, milliseconds)));
4277
+ const wait = input.sleep ?? ((milliseconds) => new Promise((resolve13) => setTimeout(resolve13, milliseconds)));
4278
4278
  while (Date.now() < expiresAt) {
4279
4279
  await wait(intervalMs);
4280
4280
  const response = await requestHostedJson(
@@ -4447,6 +4447,88 @@ async function listAccountTeams(session, options = {}) {
4447
4447
  }
4448
4448
  return { ok: true, teams };
4449
4449
  }
4450
+ async function listMapSetupTeams(session, options = {}) {
4451
+ let response;
4452
+ try {
4453
+ response = await requestHostedJson(
4454
+ session.apiOrigin,
4455
+ "/v1/cli/map-setup/teams",
4456
+ { headers: authorizationHeaders(session.accountSession) },
4457
+ options
4458
+ );
4459
+ } catch (error) {
4460
+ return { error: safeTransportError(error, "Team discovery failed."), ok: false };
4461
+ }
4462
+ if (!response.ok) {
4463
+ return {
4464
+ error: response.status === 401 ? rejectedAccountSessionMessage : `Team discovery failed (${response.status}).`,
4465
+ ok: false
4466
+ };
4467
+ }
4468
+ const body = asRecord(response.body);
4469
+ if (!response.json || body.schemaVersion !== 1 || !Array.isArray(body.teams) || !body.teams.every(isMapSetupTeam)) {
4470
+ return { error: "Team discovery returned an invalid response.", ok: false };
4471
+ }
4472
+ return { ok: true, teams: body.teams };
4473
+ }
4474
+ async function createManagedMapSetup(session, input, options = {}) {
4475
+ let response;
4476
+ try {
4477
+ response = await requestHostedJson(
4478
+ session.apiOrigin,
4479
+ "/v1/cli/maps",
4480
+ {
4481
+ ...jsonRequest("POST", input),
4482
+ headers: {
4483
+ ...authorizationHeaders(session.accountSession),
4484
+ "Content-Type": "application/json"
4485
+ }
4486
+ },
4487
+ options
4488
+ );
4489
+ } catch (error) {
4490
+ return {
4491
+ code: "setup_failed",
4492
+ error: safeTransportError(error, "Map setup request failed."),
4493
+ ok: false
4494
+ };
4495
+ }
4496
+ const body = asRecord(response.body);
4497
+ if (!response.ok) {
4498
+ if (response.status === 401) {
4499
+ return { code: "account_session_rejected", error: rejectedAccountSessionMessage, ok: false };
4500
+ }
4501
+ const code = mapSetupFailureMessages[body.code] ? body.code : null;
4502
+ return code ? { code, error: mapSetupFailureMessages[code], ok: false } : { code: "setup_failed", error: `Map setup failed (${response.status}).`, ok: false };
4503
+ }
4504
+ const map = asRecord(body.map);
4505
+ const team = asRecord(body.team);
4506
+ const delivery = asRecord(body.delivery);
4507
+ if (!response.json || body.schemaVersion !== 1 || typeof map.id !== "string" || !/^map_[A-Za-z0-9_-]{16}$/u.test(map.id) || map.name !== null && typeof map.name !== "string" || map.state !== "setup_pending" && map.state !== "active" || typeof team.name !== "string" || `@${String(team.slug)}` !== input.team || !Array.isArray(delivery.allowedOrigins) || !delivery.allowedOrigins.every((origin) => typeof origin === "string") || delivery.allowedOrigins.length !== input.allowedOrigins.length) {
4508
+ return { code: "setup_failed", error: "Map setup returned an invalid response.", ok: false };
4509
+ }
4510
+ return {
4511
+ ok: true,
4512
+ value: {
4513
+ allowedOrigins: delivery.allowedOrigins,
4514
+ map: { id: map.id, name: map.name, state: map.state },
4515
+ team: { name: team.name, slug: team.slug }
4516
+ }
4517
+ };
4518
+ }
4519
+ var mapSetupFailureMessages = {
4520
+ invalid_request: "Tileflow rejected the Map setup request.",
4521
+ map_limit: "This Team has reached its Map limit. Archive or delete a Map, or change the plan, in the Tileflow dashboard.",
4522
+ permission_required: "Only a Team Admin can create Maps.",
4523
+ setup_conflict: "This setup key was already used with another Team or browser access choice. Omit --setup-key to start a new setup.",
4524
+ team_not_found: "No Team you administer matches this Team."
4525
+ };
4526
+ function isMapSetupTeam(value) {
4527
+ const team = asRecord(value);
4528
+ return typeof team.eligible === "boolean" && typeof team.name === "string" && typeof team.slug === "string" && /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u.test(team.slug) && (team.unavailableReason === void 0 || team.unavailableReason === "map_limit") && Object.keys(team).every(
4529
+ (key) => ["eligible", "name", "slug", "unavailableReason"].includes(key)
4530
+ );
4531
+ }
4450
4532
  async function publishHostedStyle(api, body, responseLabel, options = {}) {
4451
4533
  const response = await requestHostedJson(
4452
4534
  api.apiUrl,
@@ -4533,42 +4615,6 @@ async function uploadHostedFontBundle(api, bundle, options = {}) {
4533
4615
  }
4534
4616
  return { ok: true, value };
4535
4617
  }
4536
- async function uploadHostedModelBundle(api, bundle, options = {}) {
4537
- const formData = new FormData();
4538
- formData.append(
4539
- "manifest",
4540
- new Blob([serializeTileflowModelBundleManifest(bundle.manifest)], {
4541
- type: "application/json"
4542
- }),
4543
- "manifest.json"
4544
- );
4545
- for (const file of bundle.files) {
4546
- const bytes = new Uint8Array(file.source.byteLength);
4547
- bytes.set(file.source);
4548
- formData.append(
4549
- `file:${encodeURIComponent(file.fileName)}`,
4550
- new Blob([bytes.buffer], { type: file.contentType }),
4551
- file.fileName
4552
- );
4553
- }
4554
- const response = await requestHostedJson(
4555
- api.apiUrl,
4556
- `/v1/model-bundles/${encodeURIComponent(bundle.contentHash)}`,
4557
- { body: formData, headers: authorizationHeaders(api.apiKey, api.mapId), method: "PUT" },
4558
- options
4559
- );
4560
- if (!response.ok) return { ok: false, status: response.status };
4561
- const value = parseResponse(
4562
- response,
4563
- hostedModelBundleResponseSchema,
4564
- "Model bundle upload response"
4565
- );
4566
- const publicBaseUrl = new URL(value.baseUrl);
4567
- if (value.contentHash !== bundle.contentHash || value.modelCount !== bundle.manifest.models.length || value.totalBytes !== bundle.manifest.models.reduce((total, model) => total + model.byteLength, 0) || publicBaseUrl.pathname !== `/model-bundles/${value.id}` || publicBaseUrl.search !== "") {
4568
- throw new Error("Model bundle upload response did not confirm the submitted bundle.");
4569
- }
4570
- return { ok: true, value };
4571
- }
4572
4618
  async function fetchHostedMapStatus(api, options = {}) {
4573
4619
  const response = await requestHostedJson(
4574
4620
  api.apiUrl,
@@ -4610,7 +4656,7 @@ async function withHostedResponse(apiUrl, path, init, options, consume) {
4610
4656
  }
4611
4657
  function raceWithAbort(operation, signal) {
4612
4658
  if (signal.aborted) return Promise.reject(signal.reason);
4613
- return new Promise((resolve12, reject) => {
4659
+ return new Promise((resolve13, reject) => {
4614
4660
  const cleanup = () => signal.removeEventListener("abort", onAbort);
4615
4661
  const onAbort = () => {
4616
4662
  cleanup();
@@ -4620,7 +4666,7 @@ function raceWithAbort(operation, signal) {
4620
4666
  operation.then(
4621
4667
  (value) => {
4622
4668
  cleanup();
4623
- resolve12(value);
4669
+ resolve13(value);
4624
4670
  },
4625
4671
  (error) => {
4626
4672
  cleanup();
@@ -4830,12 +4876,12 @@ function cloneStyle(style) {
4830
4876
  }
4831
4877
 
4832
4878
  // src/model-assets.ts
4879
+ import { tileflowTeamModelUrl } from "@tileflow/core";
4833
4880
  import {
4834
- createTileflowModelBundle,
4835
4881
  prepareTileflowMapTreeModels
4836
4882
  } from "@tileflow/dev/icons";
4837
4883
  async function prepareModelAssets(project, options) {
4838
- const prepared = { assets: {}, bundles: {}, identities: {} };
4884
+ const prepared = { assets: {}, buildings: {}, identities: {} };
4839
4885
  for (const [name, map] of Object.entries(project.maps)) {
4840
4886
  if (!map.models?.length) continue;
4841
4887
  const models = await prepareTileflowMapTreeModels(map.models, {
@@ -4844,25 +4890,36 @@ async function prepareModelAssets(project, options) {
4844
4890
  configPath: `maps.${name}.models`
4845
4891
  });
4846
4892
  prepared.assets[name] = { models: models.models };
4847
- const bundle = await createTileflowModelBundle(models.buildings);
4848
- if (bundle) prepared.bundles[name] = bundle;
4893
+ if (Object.keys(models.buildings).length > 0) prepared.buildings[name] = models.buildings;
4849
4894
  if (models.identities.length > 0) prepared.identities[name] = models.identities;
4850
4895
  }
4851
4896
  return prepared;
4852
4897
  }
4853
- function mapAssetsWithBuildingModels(prepared, baseOf) {
4898
+ function mapAssetsWithBuildingModels(prepared, target) {
4854
4899
  const assets = { ...prepared.assets };
4855
- for (const [mapName, bundle] of Object.entries(prepared.bundles)) {
4856
- const base = baseOf(mapName, bundle).replace(/\/+$/u, "");
4900
+ for (const [mapName, buildings] of Object.entries(prepared.buildings)) {
4901
+ const base = target.hosted ? "" : target.localBase(mapName).replace(/\/+$/u, "");
4857
4902
  assets[mapName] = {
4858
4903
  ...assets[mapName],
4859
4904
  buildingModels: Object.fromEntries(
4860
- bundle.manifest.models.map((model) => [model.id, `${base}/${model.file}`])
4905
+ Object.entries(buildings).map(([id, model]) => [
4906
+ id,
4907
+ target.hosted ? tileflowTeamModelUrl(id) : `${base}/${model.fileName}`
4908
+ ])
4861
4909
  )
4862
4910
  };
4863
4911
  }
4864
4912
  return assets;
4865
4913
  }
4914
+ function hostedModelSourceIdentities(prepared, mapName) {
4915
+ const buildings = prepared.buildings[mapName] ?? {};
4916
+ const identities = (prepared.identities[mapName] ?? []).filter(({ id }) => !(id in buildings));
4917
+ return identities.length > 0 ? identities : void 0;
4918
+ }
4919
+ function teamModelSources(prepared, mapName) {
4920
+ const buildings = Object.entries(prepared.buildings[mapName] ?? {});
4921
+ return buildings.length > 0 ? Object.fromEntries(buildings.map(([id, model]) => [id, model.sources])) : void 0;
4922
+ }
4866
4923
 
4867
4924
  // src/hosted-native-deploy.ts
4868
4925
  function failure(message) {
@@ -4902,7 +4959,7 @@ async function prepareNativeHostedDeploy(options) {
4902
4959
  const iconPackage = binding ? compiledIcons.packages.find((entry) => entry.contentHash === binding.packageHash) : void 0;
4903
4960
  const sprite = binding ? `${apiOrigin}/sprites/preflight/${binding.packageHash}/sprite` : void 0;
4904
4961
  const modelAssets = await prepareModelAssets(project, { baseDirectory, target: "hosted" });
4905
- if (modelAssets.bundles[mapName])
4962
+ if (modelAssets.buildings[mapName])
4906
4963
  throw failure(
4907
4964
  "A deployment with --with-native does not take a map's own 3D building models yet. Deploy without --with-native, or remove them from the map's models directories."
4908
4965
  );
@@ -6942,6 +6999,710 @@ async function readCurrentPins(contents) {
6942
6999
  return (await parseTileflowIconsLockfile(contents)).sets;
6943
7000
  }
6944
7001
 
7002
+ // src/map-commands.ts
7003
+ import pc5 from "picocolors";
7004
+
7005
+ // src/map-setup.ts
7006
+ import { randomBytes as randomBytes2 } from "crypto";
7007
+ import { createInterface } from "readline/promises";
7008
+ import pc4 from "picocolors";
7009
+ var maximumOrigins2 = 20;
7010
+ var exactHost = /^(?:[a-z0-9-]+(?:\.[a-z0-9-]+)*|\[[0-9a-f:.]+\])$/u;
7011
+ var teamReference = /^@([A-Za-z0-9][A-Za-z0-9._-]{0,127})$/u;
7012
+ var setupKeyPattern = /^[\x21-\x7e]{8,128}$/u;
7013
+ function resolveMapSetupTeam(teams, requestedSlug) {
7014
+ if (requestedSlug !== void 0) {
7015
+ const team = teams.find(({ slug }) => slug === requestedSlug);
7016
+ if (!team) return { kind: "not_found" };
7017
+ return team.eligible ? { kind: "selected", team } : { kind: "map_limit" };
7018
+ }
7019
+ const eligible = teams.filter((team) => team.eligible);
7020
+ if (eligible.length === 1) return { kind: "selected", team: eligible[0] };
7021
+ if (eligible.length > 1) return { kind: "choose", teams: eligible };
7022
+ return teams.some((team) => team.unavailableReason === "map_limit") ? { kind: "map_limit" } : { kind: "none" };
7023
+ }
7024
+ function normalizeBrowserOrigin(value) {
7025
+ let url;
7026
+ try {
7027
+ url = new URL(value);
7028
+ } catch {
7029
+ return null;
7030
+ }
7031
+ if (!["http:", "https:"].includes(url.protocol) || url.username || url.password || url.pathname !== "/" || url.search || url.hash || !exactHost.test(url.hostname) || value.toLowerCase() !== url.origin.toLowerCase()) {
7032
+ return null;
7033
+ }
7034
+ return url.origin.toLowerCase();
7035
+ }
7036
+ function parseBrowserAccess(options) {
7037
+ const values = options.origin ?? [];
7038
+ if (options.anyWebsite && values.length > 0) {
7039
+ return { error: "Choose either --any-website or --origin, not both.", ok: false };
7040
+ }
7041
+ if (options.anyWebsite) return { ok: true, value: { kind: "any" } };
7042
+ if (values.length === 0) return { ok: true, value: null };
7043
+ return parseOriginList(values);
7044
+ }
7045
+ function parseOriginList(values) {
7046
+ const origins = /* @__PURE__ */ new Set();
7047
+ for (const value of values) {
7048
+ const origin = normalizeBrowserOrigin(value.trim());
7049
+ if (!origin) {
7050
+ return {
7051
+ error: `${JSON.stringify(value)} is not an exact website origin such as https://example.com (no path or wildcard).`,
7052
+ ok: false
7053
+ };
7054
+ }
7055
+ origins.add(origin);
7056
+ }
7057
+ if (origins.size === 0 || origins.size > maximumOrigins2) {
7058
+ return { error: `Enter between 1 and ${maximumOrigins2} website origins.`, ok: false };
7059
+ }
7060
+ return { ok: true, value: { kind: "selected", origins: [...origins].sort() } };
7061
+ }
7062
+ function checkMapSetupOptions(options) {
7063
+ if (options.team !== void 0 && !teamReference.test(options.team)) {
7064
+ return { code: "invalid_team", error: "Team must use @team syntax.", ok: false };
7065
+ }
7066
+ if (options.setupKey !== void 0 && !setupKeyPattern.test(options.setupKey)) {
7067
+ return {
7068
+ code: "invalid_setup_key",
7069
+ error: "Setup key must be 8 to 128 visible ASCII characters without spaces.",
7070
+ ok: false
7071
+ };
7072
+ }
7073
+ if (options.name !== void 0 && (options.name.length < 1 || options.name.length > 200 || options.name !== options.name.trim() || /\p{Cc}/u.test(options.name))) {
7074
+ return { code: "invalid_name", error: "Map name must be 1 to 200 characters.", ok: false };
7075
+ }
7076
+ const access = parseBrowserAccess(options);
7077
+ return access.ok ? { access: access.value, ok: true } : { code: "invalid_browser_access", error: access.error, ok: false };
7078
+ }
7079
+ async function setUpManagedMap(session, options, dependencies) {
7080
+ const checked = checkMapSetupOptions(options);
7081
+ if (!checked.ok) return checked;
7082
+ const discovered = await listMapSetupTeams(session, dependencies.request);
7083
+ if (!discovered.ok) {
7084
+ return { code: "team_discovery_failed", error: discovered.error, ok: false };
7085
+ }
7086
+ const resolution = resolveMapSetupTeam(
7087
+ discovered.teams,
7088
+ options.team ? teamReference.exec(options.team)[1] : void 0
7089
+ );
7090
+ let team = null;
7091
+ if (resolution.kind === "selected") team = resolution.team;
7092
+ else if (resolution.kind === "choose") {
7093
+ team = dependencies.prompts ? await dependencies.prompts.chooseTeam(resolution.teams) : null;
7094
+ if (!team) {
7095
+ const choices = resolution.teams.map(({ slug }) => `@${slug}`);
7096
+ return {
7097
+ code: "team_ambiguous",
7098
+ error: `Several Teams can own this Map. Choose one with --team: ${choices.join(", ")}.`,
7099
+ ok: false,
7100
+ options: choices
7101
+ };
7102
+ }
7103
+ } else if (resolution.kind === "map_limit") {
7104
+ return {
7105
+ code: "map_limit",
7106
+ error: options.team ? `${options.team} has reached its Map limit. Archive or delete a Map, or change the plan, in the Tileflow dashboard.` : "Every Team you administer has reached its Map limit. Archive or delete a Map, or change the plan, in the Tileflow dashboard.",
7107
+ ok: false
7108
+ };
7109
+ } else if (resolution.kind === "not_found") {
7110
+ return { code: "team_not_found", error: `You do not administer ${options.team}.`, ok: false };
7111
+ } else {
7112
+ return {
7113
+ code: "no_eligible_team",
7114
+ error: "This account does not administer a Team. Ask a Team Admin to create the Map.",
7115
+ ok: false
7116
+ };
7117
+ }
7118
+ const access = checked.access ?? (dependencies.prompts ? await dependencies.prompts.chooseAccess() : null);
7119
+ if (!access) {
7120
+ return {
7121
+ code: "browser_access_required",
7122
+ error: "Choose which websites may load this Map: --origin <origin> (repeatable) for exact sites, or --any-website.",
7123
+ ok: false
7124
+ };
7125
+ }
7126
+ const setupKey = options.setupKey ?? `cli-${randomBytes2(16).toString("hex")}`;
7127
+ const created = await createManagedMapSetup(
7128
+ session,
7129
+ {
7130
+ allowedOrigins: access.kind === "any" ? [] : access.origins,
7131
+ idempotencyKey: setupKey,
7132
+ ...options.name ? { name: options.name } : {},
7133
+ team: `@${team.slug}`
7134
+ },
7135
+ dependencies.request
7136
+ );
7137
+ return created.ok ? { ok: true, setupKey, value: created.value } : created;
7138
+ }
7139
+ function describeBrowserAccess(allowedOrigins) {
7140
+ return allowedOrigins.length === 0 ? "Any website" : allowedOrigins.join(", ");
7141
+ }
7142
+ function createTerminalPrompts(json2 = false) {
7143
+ if (json2 || !process.stdin.isTTY || !process.stdout.isTTY) return null;
7144
+ return {
7145
+ async chooseTeam(teams) {
7146
+ console.log(`${pc4.cyan("?")} Which Team should own the new Map?`);
7147
+ teams.forEach(
7148
+ (team, index) => console.log(` ${index + 1}) ${team.name} ${pc4.gray(`@${team.slug}`)}`)
7149
+ );
7150
+ const answer = await ask("Team number", (value) => {
7151
+ const index = Number(value);
7152
+ return Number.isInteger(index) && index >= 1 && index <= teams.length ? teams[index - 1] : null;
7153
+ });
7154
+ return answer;
7155
+ },
7156
+ async chooseAccess() {
7157
+ console.log(`${pc4.cyan("?")} Which websites may load this Map in a browser?`);
7158
+ console.log(" 1) Only selected websites (recommended)");
7159
+ console.log(" 2) Any website");
7160
+ const mode = await ask(
7161
+ "Choice",
7162
+ (value) => value === "1" ? "selected" : value === "2" ? "any" : null
7163
+ );
7164
+ if (mode === null) return null;
7165
+ if (mode === "any") return { kind: "any" };
7166
+ return ask("Website origins, separated by spaces (https://example.com)", (value) => {
7167
+ const parsed = parseOriginList(value.split(/[\s,]+/u).filter(Boolean));
7168
+ if (!parsed.ok) console.log(` ${pc4.yellow(parsed.error)}`);
7169
+ return parsed.ok ? parsed.value : null;
7170
+ });
7171
+ }
7172
+ };
7173
+ }
7174
+ async function ask(question, parse3) {
7175
+ const readline = createInterface({ input: process.stdin, output: process.stdout });
7176
+ try {
7177
+ for (let attempt = 0; attempt < 3; attempt += 1) {
7178
+ const value = parse3((await readline.question(` ${question}: `)).trim());
7179
+ if (value !== null) return value;
7180
+ }
7181
+ return null;
7182
+ } finally {
7183
+ readline.close();
7184
+ }
7185
+ }
7186
+
7187
+ // src/map-commands.ts
7188
+ function registerMapCommands(program2, dependencies) {
7189
+ const maps = program2.command("maps").description("Manage Tileflow managed Maps");
7190
+ addMapSetupOptions(
7191
+ maps.command("create").description("Create a managed Map for this account, ready for tileflow deploy --map-id").option("--api-url <url>", "Tileflow API URL", process.env.TILEFLOW_API_URL).option("--name <name>", "Map name shown in the dashboard").option("--json", "print deterministic schema-version-1 JSON")
7192
+ ).action(async (options) => {
7193
+ const session = await loadSession(options, dependencies);
7194
+ if (!session) return;
7195
+ const result = await setUpManagedMap(session, options, {
7196
+ prompts: createTerminalPrompts(options.json)
7197
+ });
7198
+ if (!result.ok) {
7199
+ emitFailure(
7200
+ options.json,
7201
+ result.code,
7202
+ result.error,
7203
+ result.options ? { options: result.options } : {}
7204
+ );
7205
+ return;
7206
+ }
7207
+ const next = `tileflow deploy --map-id ${result.value.map.id}`;
7208
+ if (options.json) {
7209
+ emitJson({
7210
+ command: "maps create",
7211
+ delivery: { allowedOrigins: result.value.allowedOrigins },
7212
+ map: result.value.map,
7213
+ next,
7214
+ ok: true,
7215
+ schemaVersion: 1,
7216
+ setupKey: result.setupKey,
7217
+ team: result.value.team
7218
+ });
7219
+ return;
7220
+ }
7221
+ printCreatedMap(result.value);
7222
+ console.log(`
7223
+ ${pc5.bold("Next step")}`);
7224
+ console.log(` ${pc5.cyan("\u2022")} Publish the repository map with ${pc5.cyan(next)}.`);
7225
+ });
7226
+ }
7227
+ function addMapSetupOptions(command2) {
7228
+ return command2.option("--team <team>", "Team that owns a new Map, as @team").option(
7229
+ "--origin <origin>",
7230
+ "exact website origin that may load a new Map (repeatable)",
7231
+ (value, previous = []) => [...previous, value]
7232
+ ).option("--any-website", "let any website load a new Map").option("--setup-key <key>", "repeat an interrupted Map creation with the key it printed");
7233
+ }
7234
+ function hasMapSetupOptions(options) {
7235
+ return options.team !== void 0 || (options.origin?.length ?? 0) > 0 || options.anyWebsite === true || options.setupKey !== void 0;
7236
+ }
7237
+ async function createMapForDeploy(options, dependencies) {
7238
+ const session = await loadSession(options, dependencies);
7239
+ if (!session) return null;
7240
+ console.log(`${pc5.cyan("i")} No --map-id given; creating a managed Map for this deploy.`);
7241
+ const result = await setUpManagedMap(session, options, {
7242
+ prompts: dependencies.prompts === void 0 ? createTerminalPrompts() : dependencies.prompts
7243
+ });
7244
+ if (!result.ok) {
7245
+ emitFailure(false, result.code, result.error);
7246
+ return null;
7247
+ }
7248
+ printCreatedMap(result.value);
7249
+ console.log(
7250
+ pc5.gray(
7251
+ `If this deploy is interrupted, rerun it with --setup-key ${result.setupKey} to reuse the same Map.`
7252
+ )
7253
+ );
7254
+ return result.value.map.id;
7255
+ }
7256
+ function printCreatedMap(created) {
7257
+ console.log(
7258
+ `${pc5.green("\u2713")} Created ${pc5.bold(created.map.id)} in ${created.team.name} ${pc5.gray(`@${created.team.slug}`)}.`
7259
+ );
7260
+ console.log(
7261
+ `${pc5.gray("Websites:".padEnd(13))} ${describeBrowserAccess(created.allowedOrigins)}`
7262
+ );
7263
+ }
7264
+ async function loadSession(options, dependencies) {
7265
+ let apiUrl;
7266
+ try {
7267
+ apiUrl = normalizeApiOrigin(options.apiUrl ?? dependencies.defaultApiUrl);
7268
+ } catch (error) {
7269
+ emitFailure(options.json, "invalid_api_url", safeMessage(error));
7270
+ return null;
7271
+ }
7272
+ let config;
7273
+ try {
7274
+ config = await dependencies.loadAuthConfig();
7275
+ } catch (error) {
7276
+ emitFailure(options.json, "auth_state_unavailable", safeMessage(error));
7277
+ return null;
7278
+ }
7279
+ const selected = resolveAccountSession(config, apiUrl);
7280
+ if (selected.kind !== "selected") {
7281
+ emitFailure(
7282
+ options.json,
7283
+ `account_session_${selected.kind}`,
7284
+ selected.kind === "expired" ? "The Tileflow account session has expired. Run tileflow login." : "Creating a Map needs a Tileflow account session. Run tileflow login."
7285
+ );
7286
+ return null;
7287
+ }
7288
+ return selected.session;
7289
+ }
7290
+
7291
+ // src/model-command.ts
7292
+ import { lstat as lstat5 } from "fs/promises";
7293
+ import { basename as basename4, dirname as dirname11, relative as relative4, resolve as resolve8 } from "path";
7294
+ import pc6 from "picocolors";
7295
+ import { z as z5 } from "zod";
7296
+ import { tileflowModelContentType } from "@tileflow/core";
7297
+ import {
7298
+ assertValidTileflowConfig as assertValidTileflowConfig3,
7299
+ defaultTileflowConfigPath,
7300
+ loadTileflowConfigWithInputs as loadTileflowConfigWithInputs4
7301
+ } from "@tileflow/dev/config";
7302
+ import {
7303
+ prepareTileflowMapTreeModels as prepareTileflowMapTreeModels2
7304
+ } from "@tileflow/dev/icons";
7305
+ var modelName = /^[a-z0-9]+(?:-[a-z0-9]+)*$/u;
7306
+ var sha256 = /^[a-f0-9]{64}$/u;
7307
+ var maximumListPages = 20;
7308
+ function registerModelCommands(program2, dependencies) {
7309
+ const models = program2.command("models").description("Publish and manage the Team 3D models that maps place");
7310
+ withTeamOptions2(
7311
+ models.command("publish").description(
7312
+ "Prepare building models and publish each as its Team 3D model's current version"
7313
+ ).argument(
7314
+ "[paths...]",
7315
+ "model files (<name>.glb) or directories; defaults to the maps' models directories"
7316
+ ).option(
7317
+ "-c, --config <path>",
7318
+ "config path, read when no path is given",
7319
+ defaultTileflowConfigPath
7320
+ )
7321
+ ).action(async (paths, options) => {
7322
+ let prepared;
7323
+ try {
7324
+ prepared = paths.length > 0 ? await prepareFromPaths(paths) : await prepareFromConfig(options.config);
7325
+ } catch (error) {
7326
+ return emitFailure(options.json, "model_preparation_failed", safeMessage(error));
7327
+ }
7328
+ if (prepared.size === 0) {
7329
+ return emitFailure(
7330
+ options.json,
7331
+ "no_building_models",
7332
+ "No building models were found. A building model is a .glb whose materials name its parts: surface, secondary, glass, accent and roof."
7333
+ );
7334
+ }
7335
+ const authority = await resolveTeamAuthority(options, ["models:write"], dependencies);
7336
+ if (!authority) return;
7337
+ const results = [];
7338
+ for (const [name, model] of [...prepared].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)) {
7339
+ const body = new FormData();
7340
+ body.append(
7341
+ "metadata",
7342
+ new Blob([JSON.stringify({ sources: model.sources })], { type: "application/json" }),
7343
+ "metadata.json"
7344
+ );
7345
+ const bytes = new Uint8Array(model.bytes.byteLength);
7346
+ bytes.set(model.bytes);
7347
+ body.append(
7348
+ "file",
7349
+ new Blob([bytes.buffer], { type: tileflowModelContentType }),
7350
+ `${name}.glb`
7351
+ );
7352
+ const response = await authorizedTeamRequest(
7353
+ authority,
7354
+ `/v1/models/${encodeURIComponent(name)}/versions/${model.sha256}`,
7355
+ "PUT",
7356
+ { body }
7357
+ );
7358
+ if (!response.ok) return emitHttpFailure2(options.json, response.status, response.body, name);
7359
+ const parsed = publishResponseSchema2.safeParse(response.body);
7360
+ if (!parsed.success || parsed.data.model.current?.contentHash !== model.sha256) {
7361
+ return emitFailure(
7362
+ options.json,
7363
+ "model_publish_unconfirmed",
7364
+ `The publication of ${name} did not confirm the submitted model file.`
7365
+ );
7366
+ }
7367
+ results.push(parsed.data);
7368
+ if (!options.json) {
7369
+ const current = parsed.data.model.current;
7370
+ const action = parsed.data.changed ? "Published" : "Unchanged";
7371
+ console.log(
7372
+ `${pc6.green("\u2713")} ${action} 3D model ${pc6.bold(name)} version ${current.version} (${formatBytes3(current.byteLength)}, ${formatCount(current.summary.levels[0]?.triangles ?? 0)} triangles).`
7373
+ );
7374
+ }
7375
+ }
7376
+ if (options.json) {
7377
+ emitJson({
7378
+ command: "models publish",
7379
+ ok: true,
7380
+ schemaVersion: 1,
7381
+ models: results.map(({ changed, model }) => ({
7382
+ changed,
7383
+ contentHash: model.current.contentHash,
7384
+ name: model.name,
7385
+ version: model.current.version
7386
+ }))
7387
+ });
7388
+ } else if (results.some(({ changed }) => changed)) {
7389
+ console.log("Maps that place them draw the new versions on their next load.");
7390
+ }
7391
+ });
7392
+ withTeamOptions2(models.command("list").description("List the Team's 3D models")).action(
7393
+ async (options) => {
7394
+ const authority = await resolveTeamAuthority(options, ["models:read"], dependencies);
7395
+ if (!authority) return;
7396
+ const listed = [];
7397
+ let cursor;
7398
+ for (let page = 0; page < maximumListPages; page += 1) {
7399
+ const response = await authorizedTeamRequest(
7400
+ authority,
7401
+ `/v1/models${cursor ? `?cursor=${encodeURIComponent(cursor)}` : ""}`,
7402
+ "GET"
7403
+ );
7404
+ if (!response.ok) return emitHttpFailure2(options.json, response.status, response.body);
7405
+ const parsed = listResponseSchema.safeParse(response.body);
7406
+ if (!parsed.success)
7407
+ return emitFailure(options.json, "invalid_response", "The model list was not readable.");
7408
+ listed.push(...parsed.data.models);
7409
+ cursor = parsed.data.nextCursor;
7410
+ if (!cursor) break;
7411
+ }
7412
+ if (options.json)
7413
+ return emitJson({ command: "models list", ok: true, schemaVersion: 1, models: listed });
7414
+ if (listed.length === 0) {
7415
+ console.log("The Team has no 3D models. Publish one with tileflow models publish.");
7416
+ return;
7417
+ }
7418
+ for (const model of listed) {
7419
+ const current = model.current;
7420
+ console.log(
7421
+ `${pc6.bold(model.name)} ${current ? `version ${current.version}, ${formatBytes3(current.byteLength)}` : "no current version"} ${model.usedByMaps === 1 ? "1 map" : `${model.usedByMaps} maps`}${model.state === "purging" ? pc6.yellow(" purging") : ""}`
7422
+ );
7423
+ }
7424
+ }
7425
+ );
7426
+ withTeamOptions2(
7427
+ models.command("status").description("Show a 3D model's versions and the maps that place it").argument("<name>", "model name")
7428
+ ).action(async (name, options) => {
7429
+ if (!modelName.test(name))
7430
+ return emitFailure(options.json, "invalid_model", "Model name is invalid.");
7431
+ const authority = await resolveTeamAuthority(options, ["models:read"], dependencies);
7432
+ if (!authority) return;
7433
+ const detail = await readDetail(authority, name, options.json);
7434
+ if (!detail) return;
7435
+ if (options.json) return emitJson({ ...detail, command: "models status", ok: true });
7436
+ const { model, versions, uses } = detail;
7437
+ console.log(
7438
+ `${pc6.bold(model.name)}${model.state === "purging" ? pc6.yellow(" (purging)") : ""}`
7439
+ );
7440
+ for (const version of versions) {
7441
+ const role = version.state === "current" ? "current" : version.state === "previous" ? "previous" : version.state;
7442
+ console.log(
7443
+ ` version ${version.version} ${role} ${formatBytes3(version.byteLength)} ${formatCount(version.summary.levels[0]?.triangles ?? 0)} triangles ${version.publishedAt}`
7444
+ );
7445
+ }
7446
+ console.log(
7447
+ uses.length === 0 ? " No map places it." : ` Placed by ${uses.map(({ mapName, mapId }) => mapName ?? mapId).join(", ")}.`
7448
+ );
7449
+ });
7450
+ withTeamOptions2(
7451
+ models.command("restore").description("Make a 3D model's previous version current again, in every map that places it").argument("<name>", "model name")
7452
+ ).action(async (name, options) => {
7453
+ if (!modelName.test(name))
7454
+ return emitFailure(options.json, "invalid_model", "Model name is invalid.");
7455
+ const authority = await resolveTeamAuthority(options, ["models:write"], dependencies);
7456
+ if (!authority) return;
7457
+ const detail = await readDetail(authority, name, options.json);
7458
+ if (!detail) return;
7459
+ const { current, previous } = detail.model;
7460
+ if (!current || !previous) {
7461
+ return emitFailure(
7462
+ options.json,
7463
+ "no_previous_version",
7464
+ `${name} has no previous version to restore.`
7465
+ );
7466
+ }
7467
+ const response = await authorizedTeamRequest(
7468
+ authority,
7469
+ `/v1/models/${encodeURIComponent(name)}/restore`,
7470
+ "POST",
7471
+ {
7472
+ body: JSON.stringify({ expectedCurrentVersion: current.version }),
7473
+ headers: { "Content-Type": "application/json" }
7474
+ }
7475
+ );
7476
+ if (!response.ok) return emitHttpFailure2(options.json, response.status, response.body, name);
7477
+ const parsed = restoreResponseSchema.safeParse(response.body);
7478
+ if (!parsed.success || parsed.data.model.current?.version !== previous.version) {
7479
+ return emitFailure(
7480
+ options.json,
7481
+ "model_restore_unconfirmed",
7482
+ `The restore of ${name} was not confirmed.`
7483
+ );
7484
+ }
7485
+ if (options.json)
7486
+ return emitJson({
7487
+ command: "models restore",
7488
+ ok: true,
7489
+ schemaVersion: 1,
7490
+ model: parsed.data.model
7491
+ });
7492
+ console.log(
7493
+ `${pc6.green("\u2713")} ${pc6.bold(name)} is at version ${previous.version} again; version ${current.version} is its previous one. Maps draw it on their next load.`
7494
+ );
7495
+ });
7496
+ withTeamOptions2(
7497
+ models.command("purge").description("Delete a 3D model and its versions; refused while a map places it").argument("<name>", "model name").option("--confirm <name>", "repeat the model name to confirm")
7498
+ ).action(async (name, options) => {
7499
+ if (!modelName.test(name))
7500
+ return emitFailure(options.json, "invalid_model", "Model name is invalid.");
7501
+ if (options.confirm !== name) {
7502
+ return emitFailure(
7503
+ options.json,
7504
+ "confirmation_required",
7505
+ `Repeat the model name to purge it: --confirm ${name}`
7506
+ );
7507
+ }
7508
+ const authority = await resolveTeamAuthority(options, ["models:write"], dependencies);
7509
+ if (!authority) return;
7510
+ const response = await authorizedTeamRequest(
7511
+ authority,
7512
+ `/v1/models/${encodeURIComponent(name)}`,
7513
+ "DELETE",
7514
+ {
7515
+ headers: { "X-Tileflow-Confirm-Model-Purge": name }
7516
+ }
7517
+ );
7518
+ if (!response.ok) return emitHttpFailure2(options.json, response.status, response.body, name);
7519
+ const parsed = purgeResponseSchema2.safeParse(response.body);
7520
+ if (!parsed.success)
7521
+ return emitFailure(options.json, "invalid_response", "The purge was not confirmed.");
7522
+ if (options.json)
7523
+ return emitJson({
7524
+ command: "models purge",
7525
+ ok: true,
7526
+ schemaVersion: 1,
7527
+ purge: parsed.data.purge
7528
+ });
7529
+ console.log(
7530
+ `${pc6.green("\u2713")} ${pc6.bold(name)} is being purged; its name is free once its files are deleted.`
7531
+ );
7532
+ });
7533
+ }
7534
+ function withTeamOptions2(command2) {
7535
+ return command2.option("--team <team>", "Team, as @team, when the account has several").option("--api-key <key>", "Team data API key with the 3D models permission").option("--api-url <url>", "Tileflow API origin").option("--json", "machine-readable output");
7536
+ }
7537
+ async function readDetail(authority, name, json2) {
7538
+ const response = await authorizedTeamRequest(
7539
+ authority,
7540
+ `/v1/models/${encodeURIComponent(name)}`,
7541
+ "GET"
7542
+ );
7543
+ if (!response.ok) {
7544
+ emitHttpFailure2(json2, response.status, response.body, name);
7545
+ return void 0;
7546
+ }
7547
+ const parsed = detailResponseSchema.safeParse(response.body);
7548
+ if (!parsed.success) {
7549
+ emitFailure(json2, "invalid_response", `The details of ${name} were not readable.`);
7550
+ return void 0;
7551
+ }
7552
+ return parsed.data;
7553
+ }
7554
+ async function prepareFromPaths(paths) {
7555
+ const cwd = process.cwd();
7556
+ const directories = /* @__PURE__ */ new Map();
7557
+ for (const path of paths) {
7558
+ const absolute = resolve8(cwd, path);
7559
+ const info = await lstat5(absolute).catch(() => void 0);
7560
+ if (!info) throw new Error(`${path} does not exist.`);
7561
+ const directory = info.isDirectory() ? absolute : dirname11(absolute);
7562
+ if (relative4(cwd, directory).startsWith(".."))
7563
+ throw new Error(`${path} is outside the current directory.`);
7564
+ if (info.isDirectory()) {
7565
+ directories.set(directory, "all");
7566
+ continue;
7567
+ }
7568
+ const file = basename4(absolute);
7569
+ if (!file.toLowerCase().endsWith(".glb"))
7570
+ throw new Error(`${path} is not a glTF binary (.glb).`);
7571
+ const name = file.replace(/(\.mid|\.far)?\.glb$/iu, "");
7572
+ const selected = directories.get(directory);
7573
+ if (selected === "all") continue;
7574
+ directories.set(directory, /* @__PURE__ */ new Set([...selected ?? [], name]));
7575
+ }
7576
+ const models = /* @__PURE__ */ new Map();
7577
+ for (const [directory, selected] of directories) {
7578
+ const parent = dirname11(directory);
7579
+ const local = `./${basename4(directory)}`;
7580
+ const display = relative4(cwd, directory) || ".";
7581
+ const prepared = await prepareTileflowMapTreeModels2([local], {
7582
+ baseDirectory: parent,
7583
+ configPath: display,
7584
+ cwd: parent,
7585
+ target: "hosted"
7586
+ });
7587
+ for (const [name, model] of Object.entries(prepared.buildings)) {
7588
+ if (selected !== "all" && !selected.has(name)) continue;
7589
+ addModel(models, name, model);
7590
+ }
7591
+ if (selected !== "all") {
7592
+ for (const name of selected)
7593
+ if (!prepared.buildings[name])
7594
+ throw new Error(
7595
+ `${display}/${name}.glb is not a building model: its materials must name its parts (surface, secondary, glass, accent, roof).`
7596
+ );
7597
+ }
7598
+ }
7599
+ return models;
7600
+ }
7601
+ async function prepareFromConfig(configPath) {
7602
+ const loaded = await withTileflowConfigSecretsHidden(
7603
+ () => loadTileflowConfigWithInputs4(configPath)
7604
+ );
7605
+ const project = loaded.project;
7606
+ assertValidTileflowConfig3(project);
7607
+ const prepared = await prepareModelAssets(project, {
7608
+ baseDirectory: dirname11(loaded.configFile),
7609
+ target: "hosted"
7610
+ });
7611
+ const models = /* @__PURE__ */ new Map();
7612
+ for (const buildings of Object.values(prepared.buildings))
7613
+ for (const [name, model] of Object.entries(buildings)) addModel(models, name, model);
7614
+ return models;
7615
+ }
7616
+ function addModel(models, name, model) {
7617
+ const known = models.get(name);
7618
+ if (known && known.sha256 !== model.sha256)
7619
+ throw new Error(`Two different models are named ${name}; a Team has one model of each name.`);
7620
+ models.set(name, model);
7621
+ }
7622
+ function emitHttpFailure2(json2, status, body, name) {
7623
+ const error = z5.object({ error: z5.object({ code: z5.string().max(80), message: z5.string().max(400) }).partial() }).partial().safeParse(body);
7624
+ const message = error.success ? error.data.error?.message : void 0;
7625
+ const code = error.success ? error.data.error?.code : void 0;
7626
+ if (status === 404)
7627
+ return emitFailure(
7628
+ json2,
7629
+ "model_not_found",
7630
+ name ? `The Team has no 3D model named ${name}.` : "Not found."
7631
+ );
7632
+ return emitFailure(
7633
+ json2,
7634
+ code ?? `http_${status}`,
7635
+ message ?? `The request failed with ${status}.`
7636
+ );
7637
+ }
7638
+ function formatBytes3(bytes) {
7639
+ if (bytes < 1024) return `${bytes} B`;
7640
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KiB`;
7641
+ return `${(bytes / 1024 / 1024).toFixed(1)} MiB`;
7642
+ }
7643
+ function formatCount(count) {
7644
+ return new Intl.NumberFormat("en-US").format(count);
7645
+ }
7646
+ var summarySchema = z5.object({
7647
+ levels: z5.array(
7648
+ z5.object({
7649
+ level: z5.enum(["near", "mid", "far"]),
7650
+ vertices: z5.number().int().nonnegative(),
7651
+ triangles: z5.number().int().nonnegative()
7652
+ }).strict()
7653
+ ).min(1).max(3),
7654
+ width: z5.number().nonnegative(),
7655
+ depth: z5.number().nonnegative(),
7656
+ height: z5.number().nonnegative(),
7657
+ footprint: z5.object({
7658
+ rings: z5.number().int().nonnegative(),
7659
+ points: z5.number().int().nonnegative(),
7660
+ area: z5.number().nonnegative()
7661
+ }).strict()
7662
+ }).strict();
7663
+ var versionSchema = z5.object({
7664
+ id: z5.string().regex(/^tmv_[A-Za-z0-9_-]{16,64}$/u),
7665
+ version: z5.number().int().positive(),
7666
+ state: z5.enum(["current", "previous", "gc_pending", "deleting", "deleted"]),
7667
+ contentHash: z5.string().regex(sha256),
7668
+ byteLength: z5.number().int().positive(),
7669
+ publishedAt: z5.iso.datetime({ offset: true }),
7670
+ summary: summarySchema
7671
+ }).strict();
7672
+ var modelSchema = z5.object({
7673
+ id: z5.string().regex(/^tmd_[A-Za-z0-9_-]{16,64}$/u),
7674
+ name: z5.string().regex(modelName).max(64),
7675
+ state: z5.enum(["active", "purging"]),
7676
+ current: versionSchema.nullable(),
7677
+ previous: versionSchema.nullable(),
7678
+ url: z5.url(),
7679
+ usedByMaps: z5.number().int().nonnegative(),
7680
+ updatedAt: z5.iso.datetime({ offset: true })
7681
+ }).strict();
7682
+ var listResponseSchema = z5.object({
7683
+ schemaVersion: z5.literal(1),
7684
+ models: z5.array(modelSchema).max(100),
7685
+ nextCursor: z5.string().min(1).max(512).optional()
7686
+ }).strict();
7687
+ var detailResponseSchema = z5.object({
7688
+ schemaVersion: z5.literal(1),
7689
+ model: modelSchema,
7690
+ versions: z5.array(versionSchema).max(100),
7691
+ uses: z5.array(
7692
+ z5.object({
7693
+ mapId: z5.string().min(1).max(160),
7694
+ mapName: z5.string().max(200).nullable(),
7695
+ relationship: z5.enum(["active", "retained"])
7696
+ }).strict()
7697
+ ).max(100)
7698
+ }).strict();
7699
+ var publishResponseSchema2 = z5.object({ schemaVersion: z5.literal(1), changed: z5.boolean(), model: modelSchema }).strict();
7700
+ var restoreResponseSchema = z5.object({ schemaVersion: z5.literal(1), model: modelSchema }).strict();
7701
+ var purgeResponseSchema2 = z5.object({
7702
+ schemaVersion: z5.literal(1),
7703
+ purge: z5.object({ name: z5.string().regex(modelName), state: z5.enum(["purging", "purged"]) }).strict()
7704
+ }).strict();
7705
+
6945
7706
  // src/native-preview-command.ts
6946
7707
  import { tileflowNativeProfile } from "@tileflow/core/native-profile";
6947
7708
  import {
@@ -7030,7 +7791,7 @@ function resolveTileflowExternalOpenInvocation(target, platform) {
7030
7791
  }
7031
7792
 
7032
7793
  // src/project-commands.ts
7033
- import pc4 from "picocolors";
7794
+ import pc7 from "picocolors";
7034
7795
  function registerProjectCommands(program2, dependencies) {
7035
7796
  const projects = program2.command("projects", { hidden: true }).description("Manage internal application destinations");
7036
7797
  projects.command("list").description("List application destinations accessible to the authenticated Tileflow account").option("--api-url <url>", "Tileflow API URL", process.env.TILEFLOW_API_URL).option("--include-archived", "include archived application destinations").option("--json", "print deterministic schema-version-1 JSON").action(async (options) => {
@@ -7046,12 +7807,12 @@ function registerProjectCommands(program2, dependencies) {
7046
7807
  };
7047
7808
  if (options.json) return emitJson2(document);
7048
7809
  if (!result.items.length) {
7049
- console.log(pc4.gray("No managed destinations found."));
7810
+ console.log(pc7.gray("No managed destinations found."));
7050
7811
  return;
7051
7812
  }
7052
7813
  for (const target of result.items) {
7053
7814
  console.log(
7054
- ` ${target.project.archivedAt ? pc4.yellow("\u25CB") : pc4.green("\u25CF")} ${pc4.bold(target.project.name)} ${pc4.gray(target.reference)}${target.project.archivedAt ? pc4.gray(" (archived)") : ""}`
7815
+ ` ${target.project.archivedAt ? pc7.yellow("\u25CB") : pc7.green("\u25CF")} ${pc7.bold(target.project.name)} ${pc7.gray(target.reference)}${target.project.archivedAt ? pc7.gray(" (archived)") : ""}`
7055
7816
  );
7056
7817
  }
7057
7818
  });
@@ -7104,8 +7865,8 @@ function registerProjectCommands(program2, dependencies) {
7104
7865
  ...asRecord2(result.body)
7105
7866
  };
7106
7867
  if (options.json) return emitJson2(document);
7107
- console.log(`${pc4.green("\u2713")} Application ${pc4.bold(options.name.trim())} is ready.`);
7108
- console.log(pc4.gray(`Its technical selector is --project @${organization.slug}/${slug}.`));
7868
+ console.log(`${pc7.green("\u2713")} Application ${pc7.bold(options.name.trim())} is ready.`);
7869
+ console.log(pc7.gray(`Its technical selector is --project @${organization.slug}/${slug}.`));
7109
7870
  }
7110
7871
  );
7111
7872
  projects.command("show <target>").description("Show one application destination").option("--api-url <url>", "Tileflow API URL", process.env.TILEFLOW_API_URL).option("--json", "print deterministic schema-version-1 JSON").action(async (target, options) => {
@@ -7127,8 +7888,8 @@ function registerProjectCommands(program2, dependencies) {
7127
7888
  if (!isProjectItem(project)) {
7128
7889
  return emitLocalFailure(false, "invalid_response", "Destination response was invalid.");
7129
7890
  }
7130
- console.log(`${pc4.bold(project.name)} ${pc4.gray(target)}`);
7131
- console.log(project.archivedAt ? pc4.yellow("Archived") : pc4.green("Active"));
7891
+ console.log(`${pc7.bold(project.name)} ${pc7.gray(target)}`);
7892
+ console.log(project.archivedAt ? pc7.yellow("Archived") : pc7.green("Active"));
7132
7893
  });
7133
7894
  for (const archived of [true, false]) {
7134
7895
  const verb = archived ? "archive" : "unarchive";
@@ -7155,7 +7916,7 @@ function registerProjectCommands(program2, dependencies) {
7155
7916
  ...asRecord2(result.body)
7156
7917
  };
7157
7918
  if (options.json) return emitJson2(document);
7158
- console.log(`${pc4.green("\u2713")} Application ${archived ? "archived" : "unarchived"}.`);
7919
+ console.log(`${pc7.green("\u2713")} Application ${archived ? "archived" : "unarchived"}.`);
7159
7920
  });
7160
7921
  }
7161
7922
  }
@@ -7266,7 +8027,7 @@ function emitLocalFailure(json2, code, message, details = {}) {
7266
8027
  console.error(message);
7267
8028
  emitJson2({ schemaVersion: 1, ok: false, error: { code, ...details } });
7268
8029
  } else {
7269
- console.error(`${pc4.red("\u2715")} ${message}`);
8030
+ console.error(`${pc7.red("\u2715")} ${message}`);
7270
8031
  }
7271
8032
  process.exitCode = 1;
7272
8033
  }
@@ -7301,7 +8062,7 @@ function safeMessage2(error) {
7301
8062
  }
7302
8063
 
7303
8064
  // src/renderer-artifact-command.ts
7304
- import { resolve as resolve8 } from "path";
8065
+ import { resolve as resolve9 } from "path";
7305
8066
  import { auditTileflowMapThemeValues } from "@tileflow/core";
7306
8067
  import {
7307
8068
  createTileflowNativeDiagnostic,
@@ -7389,7 +8150,7 @@ async function runRendererArtifactCommand(command2, options) {
7389
8150
  if (artifacts.nativeBuild)
7390
8151
  console.log("Profile: native-v1 (static artifacts; no device qualification).");
7391
8152
  if (command2 === "build")
7392
- console.log(`Output: ${resolve8(outDir, ...renderer === "native" ? ["native"] : [])}`);
8153
+ console.log(`Output: ${resolve9(outDir, ...renderer === "native" ? ["native"] : [])}`);
7393
8154
  }
7394
8155
  } catch (error) {
7395
8156
  const document = createTileflowCommandFailureDocument6(command2, error, process.cwd(), {
@@ -7410,15 +8171,15 @@ async function runRendererArtifactCommand(command2, options) {
7410
8171
  }
7411
8172
 
7412
8173
  // src/tileset-command.ts
7413
- import pc5 from "picocolors";
7414
- import { z as z6 } from "zod";
8174
+ import pc8 from "picocolors";
8175
+ import { z as z7 } from "zod";
7415
8176
  import { serializeCanonicalJson as serializeCanonicalJson6 } from "@tileflow/core";
7416
8177
  import { inspectTileflowPmtiles } from "@tileflow/dev/tilesets";
7417
8178
 
7418
8179
  // src/tileset-upload.ts
7419
8180
  import { createHash as createHash3 } from "crypto";
7420
8181
  import { open as open5, stat } from "fs/promises";
7421
- import { z as z5 } from "zod";
8182
+ import { z as z6 } from "zod";
7422
8183
  import { serializeCanonicalJson as serializeCanonicalJson5 } from "@tileflow/core";
7423
8184
  var teamTilesetPartSize = 16777216;
7424
8185
  var maximumTeamTilesetBytes = 4e9;
@@ -7979,36 +8740,36 @@ function asRecord3(value) {
7979
8740
  return value && typeof value === "object" && !Array.isArray(value) ? value : {};
7980
8741
  }
7981
8742
  function defaultSleep(milliseconds) {
7982
- return new Promise((resolve12) => setTimeout(resolve12, milliseconds));
7983
- }
7984
- var identifierSchema = z5.string().min(20).max(80).regex(/^[A-Za-z0-9_-]+$/u);
7985
- var uploadPartStatusSchema = z5.object({
7986
- byteCount: z5.number().int().min(1).max(teamTilesetPartSize),
7987
- partNumber: z5.number().int().min(1).max(239),
7988
- uploaded: z5.boolean(),
7989
- validationState: z5.enum(["mismatch", "pending", "processing", "valid"])
8743
+ return new Promise((resolve13) => setTimeout(resolve13, milliseconds));
8744
+ }
8745
+ var identifierSchema = z6.string().min(20).max(80).regex(/^[A-Za-z0-9_-]+$/u);
8746
+ var uploadPartStatusSchema = z6.object({
8747
+ byteCount: z6.number().int().min(1).max(teamTilesetPartSize),
8748
+ partNumber: z6.number().int().min(1).max(239),
8749
+ uploaded: z6.boolean(),
8750
+ validationState: z6.enum(["mismatch", "pending", "processing", "valid"])
7990
8751
  }).strict();
7991
- var publishedVersionSchema = z5.object({
7992
- byteCount: z5.number().int().min(1).max(maximumTeamTilesetBytes),
7993
- contentHash: z5.string().regex(/^[0-9a-f]{64}$/u),
7994
- contentHashAlgorithm: z5.literal("sha256-fixed-parts-v1"),
7995
- publishedVersion: z5.number().int().positive().max(Number.MAX_SAFE_INTEGER),
7996
- schemaVersion: z5.literal(1),
7997
- state: z5.literal("published"),
8752
+ var publishedVersionSchema = z6.object({
8753
+ byteCount: z6.number().int().min(1).max(maximumTeamTilesetBytes),
8754
+ contentHash: z6.string().regex(/^[0-9a-f]{64}$/u),
8755
+ contentHashAlgorithm: z6.literal("sha256-fixed-parts-v1"),
8756
+ publishedVersion: z6.number().int().positive().max(Number.MAX_SAFE_INTEGER),
8757
+ schemaVersion: z6.literal(1),
8758
+ state: z6.literal("published"),
7998
8759
  versionId: identifierSchema.regex(/^tsv_[A-Za-z0-9_-]+$/u)
7999
8760
  }).strict();
8000
- var uploadControlSchema = z5.object({
8001
- byteCount: z5.number().int().min(1).max(maximumTeamTilesetBytes),
8002
- contentHash: z5.string().regex(/^[0-9a-f]{64}$/u),
8003
- contentHashAlgorithm: z5.literal("sha256-fixed-parts-v1"),
8004
- expiresAt: z5.iso.datetime({ offset: true }),
8005
- failure: z5.object({ code: z5.string().regex(/^TF_[A-Z0-9_]{1,96}$/u) }).strict().nullable(),
8006
- partCount: z5.number().int().min(1).max(239),
8007
- parts: z5.array(uploadPartStatusSchema).max(239),
8008
- partSize: z5.literal(teamTilesetPartSize),
8009
- publishedVersion: z5.null(),
8010
- schemaVersion: z5.literal(1),
8011
- state: z5.enum([
8761
+ var uploadControlSchema = z6.object({
8762
+ byteCount: z6.number().int().min(1).max(maximumTeamTilesetBytes),
8763
+ contentHash: z6.string().regex(/^[0-9a-f]{64}$/u),
8764
+ contentHashAlgorithm: z6.literal("sha256-fixed-parts-v1"),
8765
+ expiresAt: z6.iso.datetime({ offset: true }),
8766
+ failure: z6.object({ code: z6.string().regex(/^TF_[A-Z0-9_]{1,96}$/u) }).strict().nullable(),
8767
+ partCount: z6.number().int().min(1).max(239),
8768
+ parts: z6.array(uploadPartStatusSchema).max(239),
8769
+ partSize: z6.literal(teamTilesetPartSize),
8770
+ publishedVersion: z6.null(),
8771
+ schemaVersion: z6.literal(1),
8772
+ state: z6.enum([
8012
8773
  "aborted",
8013
8774
  "aborting",
8014
8775
  "expired",
@@ -8021,32 +8782,32 @@ var uploadControlSchema = z5.object({
8021
8782
  "uploading",
8022
8783
  "validating"
8023
8784
  ]),
8024
- uploadId: z5.string().min(20).max(80).regex(/^tsu_[A-Za-z0-9_-]+$/u),
8025
- versionId: z5.null()
8785
+ uploadId: z6.string().min(20).max(80).regex(/^tsu_[A-Za-z0-9_-]+$/u),
8786
+ versionId: z6.null()
8026
8787
  }).strict();
8027
- var uploadSessionSchema = z5.union([publishedVersionSchema, uploadControlSchema]);
8028
- var authorizationSchema = z5.object({
8029
- expiresAt: z5.iso.datetime({ offset: true }),
8030
- headers: z5.object({
8031
- "Content-Length": z5.string().regex(/^[1-9][0-9]{0,9}$/u),
8032
- "Content-MD5": z5.string().regex(/^[A-Za-z0-9+/]{22}==$/u)
8788
+ var uploadSessionSchema = z6.union([publishedVersionSchema, uploadControlSchema]);
8789
+ var authorizationSchema = z6.object({
8790
+ expiresAt: z6.iso.datetime({ offset: true }),
8791
+ headers: z6.object({
8792
+ "Content-Length": z6.string().regex(/^[1-9][0-9]{0,9}$/u),
8793
+ "Content-MD5": z6.string().regex(/^[A-Za-z0-9+/]{22}==$/u)
8033
8794
  }).strict(),
8034
- method: z5.literal("PUT"),
8035
- partNumber: z5.number().int().min(1).max(239),
8036
- url: z5.url().max(8192).refine((value) => {
8795
+ method: z6.literal("PUT"),
8796
+ partNumber: z6.number().int().min(1).max(239),
8797
+ url: z6.url().max(8192).refine((value) => {
8037
8798
  const url = new URL(value);
8038
8799
  return url.protocol === "https:" && !url.username && !url.password && !url.hash;
8039
8800
  })
8040
8801
  }).strict();
8041
- var authorizationResponseSchema = z5.object({
8042
- authorizations: z5.array(authorizationSchema).min(1).max(16),
8043
- schemaVersion: z5.literal(1)
8802
+ var authorizationResponseSchema = z6.object({
8803
+ authorizations: z6.array(authorizationSchema).min(1).max(16),
8804
+ schemaVersion: z6.literal(1)
8044
8805
  }).strict();
8045
- var partReceiptResponseSchema = z5.object({
8046
- changed: z5.boolean(),
8047
- etag: z5.string().regex(/^[0-9a-f]{32}$/u),
8048
- partNumber: z5.number().int().min(1).max(239),
8049
- schemaVersion: z5.literal(1)
8806
+ var partReceiptResponseSchema = z6.object({
8807
+ changed: z6.boolean(),
8808
+ etag: z6.string().regex(/^[0-9a-f]{32}$/u),
8809
+ partNumber: z6.number().int().min(1).max(239),
8810
+ schemaVersion: z6.literal(1)
8050
8811
  }).strict();
8051
8812
 
8052
8813
  // src/tileset-command.ts
@@ -8069,7 +8830,7 @@ function registerTilesetInspectCommand(parent, name = "inspect") {
8069
8830
  `);
8070
8831
  return;
8071
8832
  }
8072
- console.log(`${pc5.green("\u2713")} PMTiles archive is valid.`);
8833
+ console.log(`${pc8.green("\u2713")} PMTiles archive is valid.`);
8073
8834
  console.log(` Type: ${inspection.contract.tileType}`);
8074
8835
  console.log(` Zooms: ${inspection.contract.minzoom}-${inspection.contract.maxzoom}`);
8075
8836
  console.log(` Source layers: ${inspection.contract.sourceLayers.length}`);
@@ -8079,7 +8840,7 @@ function registerTilesetInspectCommand(parent, name = "inspect") {
8079
8840
  );
8080
8841
  }
8081
8842
  for (const warning of inspection.warnings) {
8082
- console.log(`${pc5.yellow("!")} ${warning.message}`);
8843
+ console.log(`${pc8.yellow("!")} ${warning.message}`);
8083
8844
  }
8084
8845
  } catch (error) {
8085
8846
  const record = asRecord4(error);
@@ -8092,7 +8853,7 @@ function registerTilesetInspectCommand(parent, name = "inspect") {
8092
8853
  `
8093
8854
  );
8094
8855
  } else {
8095
- console.error(`${pc5.red("Error:")} ${message}`);
8856
+ console.error(`${pc8.red("Error:")} ${message}`);
8096
8857
  }
8097
8858
  process.exitCode = 1;
8098
8859
  }
@@ -8180,7 +8941,7 @@ function registerTilesetCommands(program2, dependencies) {
8180
8941
  };
8181
8942
  if (options.json) return emitJson(document);
8182
8943
  console.log(
8183
- `${pc5.green("\u2713")} ${publication.changed ? "Published" : "Unchanged"} ${pc5.bold(options.id)} in ${authority.team ? `@${authority.team.slug}` : "the Team bound to the data key"}.`
8944
+ `${pc8.green("\u2713")} ${publication.changed ? "Published" : "Unchanged"} ${pc8.bold(options.id)} in ${authority.team ? `@${authority.team.slug}` : "the Team bound to the data key"}.`
8184
8945
  );
8185
8946
  } catch (error) {
8186
8947
  if (error instanceof TeamTilesetUploadError) {
@@ -8200,9 +8961,9 @@ function registerTilesetCommands(program2, dependencies) {
8200
8961
  const items = await fetchAllTeamTilesets(authority);
8201
8962
  const document = { command: "tileset list", schemaVersion: 1, tilesets: items };
8202
8963
  if (options.json) return emitJson(document);
8203
- if (items.length === 0) return console.log(pc5.gray("No Team tilesets."));
8964
+ if (items.length === 0) return console.log(pc8.gray("No Team tilesets."));
8204
8965
  for (const item of items) {
8205
- console.log(` ${pc5.bold(item.logicalName)} ${pc5.gray(item.status)}`);
8966
+ console.log(` ${pc8.bold(item.logicalName)} ${pc8.gray(item.status)}`);
8206
8967
  }
8207
8968
  } catch {
8208
8969
  emitFailure(options.json, "tileset_list_failed", "Tileset list failed.");
@@ -8230,7 +8991,7 @@ function registerTilesetCommands(program2, dependencies) {
8230
8991
  const document = { command: "tileset status", schemaVersion: 1, tileset: parsed.tileset };
8231
8992
  if (options.json) return emitJson(document);
8232
8993
  const item = parsed.tileset;
8233
- console.log(`${pc5.bold(item.name)} ${pc5.gray(item.state)}`);
8994
+ console.log(`${pc8.bold(item.name)} ${pc8.gray(item.state)}`);
8234
8995
  console.log(` ID: ${item.id}`);
8235
8996
  console.log(` Current version: ${String(item.currentVersion ?? "unpublished")}`);
8236
8997
  } catch {
@@ -8281,7 +9042,7 @@ function registerTilesetCommands(program2, dependencies) {
8281
9042
  const parsed = tilesetPurgeResponseSchema.parse(response.body);
8282
9043
  const document = { command: "tileset purge", schemaVersion: 1, ...parsed };
8283
9044
  if (options.json) return emitJson(document);
8284
- console.log(`${pc5.green("\u2713")} Purge ${parsed.reclamation}.`);
9045
+ console.log(`${pc8.green("\u2713")} Purge ${parsed.reclamation}.`);
8285
9046
  } catch {
8286
9047
  emitFailure(options.json, "tileset_purge_failed", "Tileset purge failed.");
8287
9048
  }
@@ -8343,69 +9104,69 @@ function inspectionOptionError(message) {
8343
9104
  function asRecord4(value) {
8344
9105
  return value && typeof value === "object" && !Array.isArray(value) ? value : {};
8345
9106
  }
8346
- var safeIdentifierSchema2 = z6.string().min(1).max(160).regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/u);
8347
- var physicalTilesetIdSchema = z6.string().min(20).max(80).regex(/^tls_[A-Za-z0-9_-]+$/u);
8348
- var cursorSchema2 = z6.string().min(1).max(2048).nullable();
8349
- var tilesetInventoryItemSchema = z6.object({
8350
- currentVersion: z6.number().int().positive().max(Number.MAX_SAFE_INTEGER).nullable(),
8351
- hasCurrent: z6.boolean(),
9107
+ var safeIdentifierSchema2 = z7.string().min(1).max(160).regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/u);
9108
+ var physicalTilesetIdSchema = z7.string().min(20).max(80).regex(/^tls_[A-Za-z0-9_-]+$/u);
9109
+ var cursorSchema2 = z7.string().min(1).max(2048).nullable();
9110
+ var tilesetInventoryItemSchema = z7.object({
9111
+ currentVersion: z7.number().int().positive().max(Number.MAX_SAFE_INTEGER).nullable(),
9112
+ hasCurrent: z7.boolean(),
8352
9113
  id: physicalTilesetIdSchema,
8353
- logicalName: z6.string().min(1).max(128).regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/u),
8354
- name: z6.string().min(1).max(200),
8355
- retainedBytes: z6.number().int().min(0).max(Number.MAX_SAFE_INTEGER),
8356
- retainedDeploymentCount: z6.number().int().min(0).max(Number.MAX_SAFE_INTEGER),
8357
- status: z6.enum(["active", "purging", "retired"]),
8358
- updatedAt: z6.iso.datetime({ offset: true }),
8359
- usedByMapCount: z6.number().int().min(0).max(Number.MAX_SAFE_INTEGER)
9114
+ logicalName: z7.string().min(1).max(128).regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/u),
9115
+ name: z7.string().min(1).max(200),
9116
+ retainedBytes: z7.number().int().min(0).max(Number.MAX_SAFE_INTEGER),
9117
+ retainedDeploymentCount: z7.number().int().min(0).max(Number.MAX_SAFE_INTEGER),
9118
+ status: z7.enum(["active", "purging", "retired"]),
9119
+ updatedAt: z7.iso.datetime({ offset: true }),
9120
+ usedByMapCount: z7.number().int().min(0).max(Number.MAX_SAFE_INTEGER)
8360
9121
  }).strict();
8361
- var tilesetListResponseSchema = z6.object({
9122
+ var tilesetListResponseSchema = z7.object({
8362
9123
  nextCursor: cursorSchema2.optional(),
8363
- schemaVersion: z6.literal(1),
8364
- tilesets: z6.array(tilesetInventoryItemSchema).max(100)
9124
+ schemaVersion: z7.literal(1),
9125
+ tilesets: z7.array(tilesetInventoryItemSchema).max(100)
8365
9126
  }).strict();
8366
- var tilesetDependencySchema = z6.object({
8367
- active: z6.boolean(),
9127
+ var tilesetDependencySchema = z7.object({
9128
+ active: z7.boolean(),
8368
9129
  deploymentId: safeIdentifierSchema2,
8369
- deploymentVersion: z6.number().int().positive().max(Number.MAX_SAFE_INTEGER),
8370
- environment: z6.string().min(1).max(64),
9130
+ deploymentVersion: z7.number().int().positive().max(Number.MAX_SAFE_INTEGER),
9131
+ environment: z7.string().min(1).max(64),
8371
9132
  mapId: safeIdentifierSchema2,
8372
9133
  sourceId: safeIdentifierSchema2
8373
9134
  }).strict();
8374
- var tilesetDetailResponseSchema = z6.object({
8375
- schemaVersion: z6.literal(1),
8376
- tileset: z6.object({
8377
- attribution: z6.string().max(4096).nullable(),
8378
- currentVersion: z6.number().int().positive().max(Number.MAX_SAFE_INTEGER).nullable(),
8379
- dependencies: z6.array(tilesetDependencySchema).max(25),
9135
+ var tilesetDetailResponseSchema = z7.object({
9136
+ schemaVersion: z7.literal(1),
9137
+ tileset: z7.object({
9138
+ attribution: z7.string().max(4096).nullable(),
9139
+ currentVersion: z7.number().int().positive().max(Number.MAX_SAFE_INTEGER).nullable(),
9140
+ dependencies: z7.array(tilesetDependencySchema).max(25),
8380
9141
  dependencyNextCursor: cursorSchema2,
8381
9142
  id: physicalTilesetIdSchema,
8382
- name: z6.string().min(1).max(200),
8383
- slug: z6.string().min(1).max(128).regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/u),
8384
- state: z6.enum(["active", "purging", "purged"])
9143
+ name: z7.string().min(1).max(200),
9144
+ slug: z7.string().min(1).max(128).regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/u),
9145
+ state: z7.enum(["active", "purging", "purged"])
8385
9146
  }).strict()
8386
9147
  }).strict();
8387
- var purgeDependencySchema = z6.object({
9148
+ var purgeDependencySchema = z7.object({
8388
9149
  deploymentId: safeIdentifierSchema2,
8389
9150
  mapId: safeIdentifierSchema2,
8390
9151
  sourceId: safeIdentifierSchema2
8391
9152
  }).strict();
8392
- var tilesetInUseResponseSchema = z6.object({
8393
- code: z6.literal("TF_TILESET_IN_USE"),
8394
- dependencies: z6.array(purgeDependencySchema).max(32),
8395
- error: z6.literal("Tileset is used by retained Map deployments"),
9153
+ var tilesetInUseResponseSchema = z7.object({
9154
+ code: z7.literal("TF_TILESET_IN_USE"),
9155
+ dependencies: z7.array(purgeDependencySchema).max(32),
9156
+ error: z7.literal("Tileset is used by retained Map deployments"),
8396
9157
  nextCursor: cursorSchema2
8397
9158
  }).strict();
8398
- var tilesetPurgeResponseSchema = z6.object({
8399
- deliveryFence: z6.enum(["complete", "pending"]),
8400
- pendingVersions: z6.number().int().min(0).max(Number.MAX_SAFE_INTEGER),
8401
- reclamation: z6.enum(["complete", "pending"]),
8402
- state: z6.enum(["purged", "purging"]),
9159
+ var tilesetPurgeResponseSchema = z7.object({
9160
+ deliveryFence: z7.enum(["complete", "pending"]),
9161
+ pendingVersions: z7.number().int().min(0).max(Number.MAX_SAFE_INTEGER),
9162
+ reclamation: z7.enum(["complete", "pending"]),
9163
+ state: z7.enum(["purged", "purging"]),
8403
9164
  tilesetId: physicalTilesetIdSchema
8404
9165
  }).strict();
8405
9166
 
8406
9167
  // src/visual-command.ts
8407
- import { lstat as lstat5, readFile as readFile6 } from "fs/promises";
8408
- import { dirname as dirname11, isAbsolute as isAbsolute4, parse as parse2, relative as relative4, resolve as resolve9, sep as sep4 } from "path";
9168
+ import { lstat as lstat6, readFile as readFile6 } from "fs/promises";
9169
+ import { dirname as dirname12, isAbsolute as isAbsolute4, parse as parse2, relative as relative5, resolve as resolve10, sep as sep4 } from "path";
8409
9170
  import {
8410
9171
  analyzeTileflowCaptureReference,
8411
9172
  compareTileflowCaptureToBaseline,
@@ -8490,14 +9251,14 @@ async function runVisualAnalyze(scene, options) {
8490
9251
  validateVisualSelection([scene]);
8491
9252
  const region = options.region === void 0 ? void 0 : parseTileflowVisualRegion(options.region);
8492
9253
  const cwd = process.cwd();
8493
- const referencePath = resolve9(cwd, options.reference);
9254
+ const referencePath = resolve10(cwd, options.reference);
8494
9255
  const referencePng = await readVisualReference(referencePath);
8495
9256
  const referenceDimensions = validateTileflowVisualReferencePng(referencePng);
8496
9257
  if (region) assertTileflowVisualRegionFits(region, referenceDimensions, "reference PNG");
8497
9258
  const outputDirectory = await resolveVisualDirectory(cwd, options.outputDir, "output");
8498
- const actualPath = resolve9(outputDirectory.path, `${scene}.actual.png`);
8499
- const managedDiffPath = resolve9(outputDirectory.path, `${scene}.diff.png`);
8500
- const reportPath = resolve9(outputDirectory.path, `${scene}.analysis.json`);
9259
+ const actualPath = resolve10(outputDirectory.path, `${scene}.actual.png`);
9260
+ const managedDiffPath = resolve10(outputDirectory.path, `${scene}.diff.png`);
9261
+ const reportPath = resolve10(outputDirectory.path, `${scene}.analysis.json`);
8501
9262
  assertReferenceIsReadOnly(referencePath, [actualPath, managedDiffPath, reportPath]);
8502
9263
  const controller = installSignalAbortController();
8503
9264
  const session = createTileflowCaptureSession3({
@@ -8586,15 +9347,15 @@ async function runVisualDiff(scenes, options) {
8586
9347
  capture,
8587
9348
  files.kind === "complete" ? files.baseline : void 0
8588
9349
  );
8589
- const actualPath = resolve9(outputDirectory.path, `${capture.scene}.actual.png`);
8590
- const diffPath = comparison.diffPng ? resolve9(outputDirectory.path, `${capture.scene}.diff.png`) : void 0;
9350
+ const actualPath = resolve10(outputDirectory.path, `${capture.scene}.actual.png`);
9351
+ const diffPath = comparison.diffPng ? resolve10(outputDirectory.path, `${capture.scene}.diff.png`) : void 0;
8591
9352
  prepared.push({
8592
9353
  capture,
8593
9354
  comparison,
8594
9355
  baselinePath: files.pngPath,
8595
9356
  actualPath,
8596
9357
  diffPath,
8597
- reportPath: resolve9(outputDirectory.path, `${capture.scene}.visual.json`)
9358
+ reportPath: resolve10(outputDirectory.path, `${capture.scene}.visual.json`)
8598
9359
  });
8599
9360
  }
8600
9361
  const document = createTileflowVisualDiffJson(prepared, cwd);
@@ -8614,7 +9375,7 @@ async function runVisualDiff(scenes, options) {
8614
9375
  }
8615
9376
  ]),
8616
9377
  removePaths: prepared.flatMap(
8617
- (item) => item.diffPath ? [] : [resolve9(outputDirectory.path, `${item.capture.scene}.diff.png`)]
9378
+ (item) => item.diffPath ? [] : [resolve10(outputDirectory.path, `${item.capture.scene}.diff.png`)]
8618
9379
  )
8619
9380
  });
8620
9381
  if (options.json) {
@@ -8735,7 +9496,7 @@ async function resolvePreviousBaseline(capture, files) {
8735
9496
  }
8736
9497
  }
8737
9498
  async function readBaselineFiles(directory, scene) {
8738
- const pngPath = resolve9(directory.path, `${scene}.png`);
9499
+ const pngPath = resolve10(directory.path, `${scene}.png`);
8739
9500
  const receiptPath = captureReceiptPath(pngPath);
8740
9501
  const [png, receipt] = await Promise.all([
8741
9502
  readOptionalArtifact(pngPath, directory.boundary, tileflowVisualArtifactLimits2.maximumPngBytes),
@@ -8750,10 +9511,10 @@ async function readBaselineFiles(directory, scene) {
8750
9511
  return { kind: "complete", pngPath, receiptPath, baseline: { png, receipt } };
8751
9512
  }
8752
9513
  async function readOptionalArtifact(path, boundary, maximumBytes) {
8753
- await assertNoSymlinkComponents(dirname11(path), boundary);
9514
+ await assertNoSymlinkComponents(dirname12(path), boundary);
8754
9515
  let info;
8755
9516
  try {
8756
- info = await lstat5(path);
9517
+ info = await lstat6(path);
8757
9518
  } catch (error) {
8758
9519
  if (isMissingFile5(error)) return void 0;
8759
9520
  throw error;
@@ -8773,7 +9534,7 @@ async function readOptionalArtifact(path, boundary, maximumBytes) {
8773
9534
  async function readVisualReference(path) {
8774
9535
  let info;
8775
9536
  try {
8776
- info = await lstat5(path);
9537
+ info = await lstat6(path);
8777
9538
  } catch (error) {
8778
9539
  throw new TileflowCaptureError2("BASELINE_INVALID", "The visual reference PNG cannot be read.", {
8779
9540
  cause: error
@@ -8795,12 +9556,12 @@ async function readVisualReference(path) {
8795
9556
  return new Uint8Array(await readFile6(path));
8796
9557
  }
8797
9558
  async function resolveVisualDirectory(cwd, value, kind) {
8798
- const path = resolve9(cwd, value);
9559
+ const path = resolve10(cwd, value);
8799
9560
  if (path === cwd || path === parse2(path).root) {
8800
9561
  throw new Error(`Visual ${kind} directory must be a dedicated subdirectory.`);
8801
9562
  }
8802
9563
  if (kind === "baseline") {
8803
- for (const managed of [resolve9(cwd, ".tileflow/captures"), resolve9(cwd, ".tileflow/diffs")]) {
9564
+ for (const managed of [resolve10(cwd, ".tileflow/captures"), resolve10(cwd, ".tileflow/diffs")]) {
8804
9565
  if (pathsOverlap2(path, managed)) {
8805
9566
  throw new Error("Visual baselines must not overlap Tileflow-managed capture/diff outputs.");
8806
9567
  }
@@ -8809,7 +9570,7 @@ async function resolveVisualDirectory(cwd, value, kind) {
8809
9570
  const boundary = isInside2(cwd, path) ? cwd : path;
8810
9571
  await assertNoSymlinkComponents(path, boundary);
8811
9572
  try {
8812
- const info = await lstat5(path);
9573
+ const info = await lstat6(path);
8813
9574
  if (info.isSymbolicLink() || !info.isDirectory()) {
8814
9575
  throw new Error(`Visual ${kind} path must be a non-symlink directory.`);
8815
9576
  }
@@ -8836,10 +9597,10 @@ function assertReferenceIsReadOnly(referencePath, outputPaths) {
8836
9597
  }
8837
9598
  }
8838
9599
  function portablePathIdentity2(path) {
8839
- return resolve9(path).normalize("NFC").toLowerCase();
9600
+ return resolve10(path).normalize("NFC").toLowerCase();
8840
9601
  }
8841
9602
  function isInside2(parent, child) {
8842
- const path = relative4(parent, child);
9603
+ const path = relative5(parent, child);
8843
9604
  return path === "" || path !== ".." && !path.startsWith(`..${sep4}`) && !isAbsolute4(path);
8844
9605
  }
8845
9606
  function isMissingFile5(error) {
@@ -8848,8 +9609,8 @@ function isMissingFile5(error) {
8848
9609
 
8849
9610
  // src/workbench-service.ts
8850
9611
  import { opendir, stat as stat2 } from "fs/promises";
8851
- import { resolve as resolve10 } from "path";
8852
- import { z as z7 } from "zod";
9612
+ import { resolve as resolve11 } from "path";
9613
+ import { z as z8 } from "zod";
8853
9614
  import {
8854
9615
  compareTileflowCapturesForReview as compareTileflowCapturesForReview3,
8855
9616
  createTileflowCaptureSession as createTileflowCaptureSession4,
@@ -8862,22 +9623,22 @@ import {
8862
9623
  } from "@tileflow/core";
8863
9624
  import { resolveTileflowPreview } from "@tileflow/dev/preview";
8864
9625
  var prefix = "/__workbench";
8865
- var captureInput = z7.object({
8866
- side: z7.enum(["left", "right"]).default("left"),
8867
- generation: z7.number().int().nonnegative(),
9626
+ var captureInput = z8.object({
9627
+ side: z8.enum(["left", "right"]).default("left"),
9628
+ generation: z8.number().int().nonnegative(),
8868
9629
  name: tileflowCaptureSceneNameSchema2,
8869
9630
  definition: tileflowCaptureSceneSchema3
8870
9631
  }).strict();
8871
- var compareInput = z7.object({ before: z7.string().regex(/^[a-f0-9]{64}$/u), after: z7.string().regex(/^[a-f0-9]{64}$/u) }).strict();
8872
- var deleteInput = z7.object({ id: z7.string().regex(/^[a-f0-9]{64}$/u) }).strict();
8873
- var contextInput = z7.object({
8874
- client: z7.string().regex(/^[a-zA-Z0-9-]{1,80}$/u),
8875
- side: z7.enum(["left", "right"]),
8876
- generation: z7.number().int(),
8877
- context: z7.record(z7.string(), z7.unknown())
9632
+ var compareInput = z8.object({ before: z8.string().regex(/^[a-f0-9]{64}$/u), after: z8.string().regex(/^[a-f0-9]{64}$/u) }).strict();
9633
+ var deleteInput = z8.object({ id: z8.string().regex(/^[a-f0-9]{64}$/u) }).strict();
9634
+ var contextInput = z8.object({
9635
+ client: z8.string().regex(/^[a-zA-Z0-9-]{1,80}$/u),
9636
+ side: z8.enum(["left", "right"]),
9637
+ generation: z8.number().int(),
9638
+ context: z8.record(z8.string(), z8.unknown())
8878
9639
  }).strict();
8879
9640
  function createWorkbenchService(options) {
8880
- const directory = resolve10(options.cwd, captureRecordDirectory);
9641
+ const directory = resolve11(options.cwd, captureRecordDirectory);
8881
9642
  const contexts = /* @__PURE__ */ new Map();
8882
9643
  let captureSession = options.captureSession;
8883
9644
  let pending;
@@ -8949,7 +9710,7 @@ function createWorkbenchService(options) {
8949
9710
  break;
8950
9711
  }
8951
9712
  if (!entry.isFile() || !/^[a-f0-9]{64}\.json$/u.test(entry.name)) continue;
8952
- const info = await stat2(resolve10(directory, entry.name));
9713
+ const info = await stat2(resolve11(directory, entry.name));
8953
9714
  entries.push({ id: entry.name.slice(0, -5), modified: info.mtimeMs });
8954
9715
  }
8955
9716
  } catch (error) {
@@ -9088,7 +9849,7 @@ function createWorkbenchService(options) {
9088
9849
  });
9089
9850
  }
9090
9851
  if (path === "/captures") {
9091
- const offset = z7.coerce.number().int().min(0).max(5e3).parse(url.searchParams.get("offset") ?? 0);
9852
+ const offset = z8.coerce.number().int().min(0).max(5e3).parse(url.searchParams.get("offset") ?? 0);
9092
9853
  return json(await history(offset));
9093
9854
  }
9094
9855
  const image = /^\/captures\/([a-f0-9]{64})\.png$/u.exec(path);
@@ -9219,17 +9980,18 @@ var packageJson = JSON.parse(
9219
9980
  var program = new Command();
9220
9981
  var defaultApiUrl = defaultTileflowApiUrl;
9221
9982
  var defaultAppUrl = "https://tileflow.dev";
9222
- var defaultConfigPath = defaultTileflowConfigPath;
9983
+ var defaultConfigPath = defaultTileflowConfigPath2;
9223
9984
  var defaultManifestPath = defaultTileflowManifestPath;
9224
- program.name("tileflow").description("Beautiful maps from config.").enablePositionalOptions().showHelpAfterError(pc6.gray("\nRun tileflow <command> --help for usage.")).showSuggestionAfterError();
9985
+ program.name("tileflow").description("Beautiful maps from config.").enablePositionalOptions().showHelpAfterError(pc9.gray("\nRun tileflow <command> --help for usage.")).showSuggestionAfterError();
9225
9986
  program.addHelpText("after", "\n -V, --version output the version number\n");
9226
9987
  registerDescribeCommand(program);
9227
9988
  registerLanguageCommand(program);
9228
9989
  registerTilesetCommands(program, { defaultApiUrl, loadAuthConfig });
9229
9990
  registerIconSetCommands(program, { defaultApiUrl, loadAuthConfig });
9991
+ registerModelCommands(program, { defaultApiUrl, loadAuthConfig });
9230
9992
  program.command("init").description("Create a starter tileflow.config.ts").option("-f, --force", "overwrite an existing config").action(async (options) => {
9231
- const configPath = resolve11(process.cwd(), defaultConfigPath);
9232
- const ignorePath = resolve11(process.cwd(), ".gitignore");
9993
+ const configPath = resolve12(process.cwd(), defaultConfigPath);
9994
+ const ignorePath = resolve12(process.cwd(), ".gitignore");
9233
9995
  if (existsSync(configPath) && !options.force) {
9234
9996
  logWarning(`${pathLabel(defaultConfigPath)} already exists.`);
9235
9997
  printNextSteps([`Run ${command("tileflow init --force")} to replace it.`]);
@@ -9245,9 +10007,9 @@ program.command("init").description("Create a starter tileflow.config.ts").optio
9245
10007
  }
9246
10008
  logSuccess(`Created ${pathLabel(defaultConfigPath)}.`);
9247
10009
  printKeyValue("Path", pathLabel(configPath));
9248
- printKeyValue("Map", pc6.bold("madrid"));
10010
+ printKeyValue("Map", pc9.bold("madrid"));
9249
10011
  printNextSteps([
9250
- `Install ${pc6.cyan("@tileflow/core")} and ${pc6.cyan("@tileflow/maps")} in this project if needed.`,
10012
+ `Install ${pc9.cyan("@tileflow/core")} and ${pc9.cyan("@tileflow/maps")} in this project if needed.`,
9251
10013
  `Preview locally with ${command("tileflow preview")}.`,
9252
10014
  `Validate the config with ${command("tileflow validate")}.`
9253
10015
  ]);
@@ -9383,12 +10145,12 @@ program.command("validate").description("Validate a Tileflow config").option("-c
9383
10145
  try {
9384
10146
  if (!options.json) logInfo(`Validating ${pathLabel(options.config)}.`);
9385
10147
  const loaded = await withTileflowConfigSecretsHidden(
9386
- () => loadTileflowConfigWithInputs4(options.config)
10148
+ () => loadTileflowConfigWithInputs5(options.config)
9387
10149
  );
9388
10150
  const project = loaded.project;
9389
- const baseDirectory = dirname12(loaded.configFile);
10151
+ const baseDirectory = dirname13(loaded.configFile);
9390
10152
  failureDefaults = { code: "CONFIG_INVALID", phase: "config-validation" };
9391
- assertValidTileflowConfig3(project);
10153
+ assertValidTileflowConfig4(project);
9392
10154
  const mapNames = getTileflowMapNames5(project).sort();
9393
10155
  failureDefaults = { code: "ICON_COMPILATION_FAILED", phase: "icon-compilation" };
9394
10156
  const compiledIcons = await compileTileflowIconPackages3(project, {
@@ -9406,7 +10168,7 @@ program.command("validate").description("Validate a Tileflow config").option("-c
9406
10168
  (binding) => options.target === "hosted" ? `${normalizeUrl(options.apiBaseUrl)}/sprites/preflight/${binding.packageHash}/sprite` : `/tileflow/icons/${binding.mapName}/sprite`,
9407
10169
  mapAssetsWithBuildingModels(
9408
10170
  modelAssets,
9409
- (mapName) => options.target === "hosted" ? `${normalizeUrl(options.apiBaseUrl)}/model-bundles/preflight` : `/tileflow/models/${mapName}`
10171
+ options.target === "hosted" ? { hosted: true } : { localBase: (mapName) => `/tileflow/models/${mapName}` }
9410
10172
  )
9411
10173
  );
9412
10174
  failureDefaults = { code: "STYLE_INVALID", phase: "style-validation" };
@@ -9526,7 +10288,7 @@ program.command("build").description("Generate static Tileflow styles").option("
9526
10288
  })
9527
10289
  );
9528
10290
  logSuccess("Built Tileflow artifacts.");
9529
- printKeyValue("Output", pathLabel(resolve11(process.cwd(), options.out)));
10291
+ printKeyValue("Output", pathLabel(resolve12(process.cwd(), options.out)));
9530
10292
  }
9531
10293
  );
9532
10294
  program.command("preview").description("Open Map DevTools with inspection and optional comparison").option("-c, --config <path>", "config path", defaultConfigPath).option("--against-config <path>", "second config for live comparison in Map DevTools").option("--against-map <name>", "map on the right; defaults to --map").option("--against-scene <name>", "committed standalone scene on the right").option("--against-theme <name>", "concrete theme on the right; defaults to --theme").option("--host <host>", "bind host: an explicit IP address or localhost", defaultTileflowDevHost).option("--map <name>", "preview one configured map").option("--theme <name>", "preview one concrete theme; defaults to the map default").option("--renderer <renderer>", "artifact renderer: web or native", "web").option("-p, --port <port>", "preview port", "3333").option("--no-open", "start Map DevTools without opening the browser").option("--scene <name>", "preview one committed standalone map scene").option(
@@ -9814,12 +10576,17 @@ program.command("preview").description("Open Map DevTools with inspection and op
9814
10576
  });
9815
10577
  }
9816
10578
  );
9817
- program.command("deploy").description("Deploy maps to Tileflow and write the frontend manifest").option("-c, --config <path>", "config path", defaultConfigPath).option("--manifest <path>", "manifest path written for frontend bundlers", defaultManifestPath).option("--api-url <url>", "Tileflow API URL", process.env.TILEFLOW_API_URL).option("--api-key <key>", "Tileflow API key", process.env.TILEFLOW_API_KEY).option("--map-id <id>", "managed Map destination").option("--with-native", "publish web and native-v1 together for the selected managed Map").option("--map <name>", "configured map to connect when the repository contains multiple maps").option(
10579
+ addMapSetupOptions(
10580
+ program.command("deploy").description(
10581
+ "Deploy maps to Tileflow and write the frontend manifest; without --map-id, create the Map first"
10582
+ )
10583
+ ).option("-c, --config <path>", "config path", defaultConfigPath).option("--manifest <path>", "manifest path written for frontend bundlers", defaultManifestPath).option("--api-url <url>", "Tileflow API URL", process.env.TILEFLOW_API_URL).option("--api-key <key>", "Tileflow API key", process.env.TILEFLOW_API_KEY).option("--map-id <id>", "managed Map destination; omit it to create a new Map").option("--with-native", "publish web and native-v1 together for the selected managed Map").option("--map <name>", "configured map to connect when the repository contains multiple maps").option(
9818
10584
  "--overwrite-self-hosted-manifest",
9819
10585
  "explicitly replace an existing self-hosted manifest at --manifest"
9820
10586
  ).option("--cache-dir <path>", "verified Icon Set artifact cache root").option("--offline", "fail a locked Icon Set cache miss instead of hydrating it").action(
9821
10587
  async (options) => {
9822
10588
  const source = resolveDeploySource(process.env);
10589
+ if (!validateMapSetupTarget(options, source)) return;
9823
10590
  const resolveApi = () => requireApiOptions(options, {
9824
10591
  allowStoredCredential: allowsStoredDeployCredential(source),
9825
10592
  assertExplicitKeyMap: true,
@@ -9839,7 +10606,7 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
9839
10606
  );
9840
10607
  if (result) {
9841
10608
  logSuccess(
9842
- `${result.changed ? "Published" : "Unchanged"} ${pc6.bold(result.mapName)} web + native-v1 (v${result.version}).`
10609
+ `${result.changed ? "Published" : "Unchanged"} ${pc9.bold(result.mapName)} web + native-v1 (v${result.version}).`
9843
10610
  );
9844
10611
  printKeyValue("Manifest", pathLabel(result.manifestPath));
9845
10612
  printKeyValue("Native", link(result.nativeManifestUrl));
@@ -9848,11 +10615,11 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
9848
10615
  }
9849
10616
  logInfo(`Deploying ${pathLabel(options.config)}.`);
9850
10617
  const loaded = await withTileflowConfigSecretsHidden(
9851
- () => loadTileflowConfigWithInputs4(options.config)
10618
+ () => loadTileflowConfigWithInputs5(options.config)
9852
10619
  );
9853
10620
  const project = loaded.project;
9854
- const baseDirectory = dirname12(loaded.configFile);
9855
- assertValidTileflowConfig3(project);
10621
+ const baseDirectory = dirname13(loaded.configFile);
10622
+ assertValidTileflowConfig4(project);
9856
10623
  const configuredMapNames = getTileflowMapNames5(project);
9857
10624
  const mapNames = selectManagedDeployMaps(configuredMapNames, options);
9858
10625
  if (!mapNames) return;
@@ -9883,7 +10650,7 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
9883
10650
  const preflightMapAssets = createCompiledMapAssets(
9884
10651
  compiledIcons,
9885
10652
  (binding) => `${apiUrl}/sprites/preflight/${binding.packageHash}/sprite`,
9886
- mapAssetsWithBuildingModels(modelAssets, () => `${apiUrl}/model-bundles/preflight`)
10653
+ mapAssetsWithBuildingModels(modelAssets, { hosted: true })
9887
10654
  );
9888
10655
  const compiledPreflightStyles = createTileflowStyles2(deploymentProject, {
9889
10656
  apiBaseUrl: apiUrl,
@@ -9934,6 +10701,21 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
9934
10701
  return;
9935
10702
  }
9936
10703
  const existingManifest = outputManifest;
10704
+ let createdMapId = null;
10705
+ if (!options.mapId) {
10706
+ const deployedMapId2 = existingManifest?.maps[mapNames[0]]?.mapId;
10707
+ if (deployedMapId2) {
10708
+ logError(`${pc9.bold(mapNames[0])} is already deployed to ${deployedMapId2}.`);
10709
+ printNextSteps([
10710
+ `Deploy it again with ${command(`tileflow deploy --map-id ${deployedMapId2}`)}.`
10711
+ ]);
10712
+ process.exitCode = 1;
10713
+ return;
10714
+ }
10715
+ createdMapId = await createMapForDeploy(options, { defaultApiUrl, loadAuthConfig });
10716
+ if (!createdMapId) return;
10717
+ options.mapId = createdMapId;
10718
+ }
9937
10719
  const api = await resolveApi();
9938
10720
  if (!api) return;
9939
10721
  const hostedSpriteByPackageHash = /* @__PURE__ */ new Map();
@@ -9953,29 +10735,10 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
9953
10735
  );
9954
10736
  const action = uploaded.value.changed === false ? "Reused" : "Uploaded";
9955
10737
  logSuccess(
9956
- `${action} icon package ${pc6.bold(binding?.label ?? "Icons")} (${plural(iconPackage.manifest.iconNames.length, "icon")}, ${formatBytes3(totalBytes)}, ${iconPackage.contentHash.slice(0, 12)}).`
10738
+ `${action} icon package ${pc9.bold(binding?.label ?? "Icons")} (${plural(iconPackage.manifest.iconNames.length, "icon")}, ${formatBytes4(totalBytes)}, ${iconPackage.contentHash.slice(0, 12)}).`
9957
10739
  );
9958
10740
  hostedSpriteByPackageHash.set(iconPackage.contentHash, uploaded.value.spriteUrl);
9959
10741
  }
9960
- const hostedModelBaseByHash = /* @__PURE__ */ new Map();
9961
- const uniqueModelBundles = [
9962
- ...new Map(
9963
- Object.values(modelAssets.bundles).map((bundle) => [bundle.contentHash, bundle])
9964
- ).values()
9965
- ].sort((left, right) => left.contentHash.localeCompare(right.contentHash));
9966
- for (const bundle of uniqueModelBundles) {
9967
- const uploaded = await uploadHostedModelBundle(api, bundle);
9968
- if (!uploaded.ok) {
9969
- logError(`Model bundle upload failed: ${uploaded.status}.`);
9970
- process.exitCode = 1;
9971
- return;
9972
- }
9973
- const action = uploaded.value.changed === false ? "Reused" : "Uploaded";
9974
- logSuccess(
9975
- `${action} 3D model bundle (${plural(bundle.manifest.models.length, "model")}, ${formatBytes3(uploaded.value.totalBytes)}, ${bundle.contentHash.slice(0, 12)}).`
9976
- );
9977
- hostedModelBaseByHash.set(bundle.contentHash, uploaded.value.baseUrl);
9978
- }
9979
10742
  const hostedMapAssets = createCompiledMapAssets(
9980
10743
  compiledIcons,
9981
10744
  (binding) => {
@@ -9983,11 +10746,7 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
9983
10746
  if (!sprite) throw new Error(`Missing hosted sprite URL for map ${binding.mapName}`);
9984
10747
  return sprite;
9985
10748
  },
9986
- mapAssetsWithBuildingModels(modelAssets, (mapName, bundle) => {
9987
- const base = hostedModelBaseByHash.get(bundle.contentHash);
9988
- if (!base) throw new Error(`Missing hosted model bundle URL for map ${mapName}.`);
9989
- return base;
9990
- })
10749
+ mapAssetsWithBuildingModels(modelAssets, { hosted: true })
9991
10750
  );
9992
10751
  const compiledHostedStyles = createTileflowStyles2(deploymentProject, {
9993
10752
  apiBaseUrl: api.apiUrl,
@@ -10028,7 +10787,7 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
10028
10787
  }
10029
10788
  const action = uploaded.value.changed === false ? "Reused" : "Uploaded";
10030
10789
  logSuccess(
10031
- `${action} font bundle (${plural(bundle.manifest.fontFaces.length, "face")}, ${formatBytes3(uploaded.value.totalBytes)}, ${bundle.contentHash.slice(0, 12)}).`
10790
+ `${action} font bundle (${plural(bundle.manifest.fontFaces.length, "face")}, ${formatBytes4(uploaded.value.totalBytes)}, ${bundle.contentHash.slice(0, 12)}).`
10032
10791
  );
10033
10792
  hostedFontBaseByHash.set(bundle.contentHash, uploaded.value.baseUrl);
10034
10793
  }
@@ -10078,12 +10837,7 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
10078
10837
  fileName: `icons/${mapName}/${file.fileName}`,
10079
10838
  source: file.source
10080
10839
  })),
10081
- ...hostedFonts.bundles[mapName]?.files ?? [],
10082
- ...(modelAssets.bundles[mapName]?.files ?? []).map((file) => ({
10083
- contentType: file.contentType,
10084
- fileName: `models/${mapName}/${file.fileName}`,
10085
- source: file.source
10086
- }))
10840
+ ...hostedFonts.bundles[mapName]?.files ?? []
10087
10841
  ],
10088
10842
  lineage: deploymentProject.mapMetadata?.[mapName]?.lineage ?? [
10089
10843
  { id: map.id, mapVersion: map.version }
@@ -10093,7 +10847,7 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
10093
10847
  fonts: hostedFonts.sourceIdentities[mapName] ?? [],
10094
10848
  icons: compiledIcons.sourceIdentities[mapName] ?? [],
10095
10849
  ...compiledIcons.compositions[mapName] ? { iconComposition: compiledIcons.compositions[mapName] } : {},
10096
- ...modelAssets.identities[mapName] ? { models: modelAssets.identities[mapName] } : {}
10850
+ ...hostedModelSourceIdentities(modelAssets, mapName) ? { models: hostedModelSourceIdentities(modelAssets, mapName) } : {}
10097
10851
  },
10098
10852
  styles: styles[mapName]
10099
10853
  }
@@ -10106,13 +10860,13 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
10106
10860
  const iconBinding = bindingsByMap.get(mapName);
10107
10861
  const iconPackage = iconBinding ? packagesByHash.get(iconBinding.packageHash) : void 0;
10108
10862
  const fontBundle = hostedFonts.bundles[mapName];
10109
- const modelBundle = modelAssets.bundles[mapName];
10863
+ const modelSources = teamModelSources(modelAssets, mapName);
10110
10864
  return {
10111
10865
  iconBinding,
10112
10866
  iconComposition: compiledIcons.compositions[mapName],
10113
10867
  iconPackage,
10114
10868
  fontBundle,
10115
- modelBundle,
10869
+ modelSources,
10116
10870
  mapName,
10117
10871
  teamSources: hostedThemeFamilies[mapName].teamSources,
10118
10872
  buildManifest: {
@@ -10132,7 +10886,7 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
10132
10886
  iconComposition,
10133
10887
  iconPackage,
10134
10888
  mapName,
10135
- modelBundle,
10889
+ modelSources,
10136
10890
  teamSources,
10137
10891
  styles: themeStyles
10138
10892
  } = deployment;
@@ -10143,7 +10897,7 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
10143
10897
  }
10144
10898
  const themeNames = Object.keys(themeStyles).sort();
10145
10899
  logInfo(
10146
- `Deploying compiled map ${pc6.bold(mapName)} (${plural(themeNames.length, "theme")}).`
10900
+ `Deploying compiled map ${pc9.bold(mapName)} (${plural(themeNames.length, "theme")}).`
10147
10901
  );
10148
10902
  const response = await publishHostedStyle(
10149
10903
  api,
@@ -10167,7 +10921,7 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
10167
10921
  } : {},
10168
10922
  ...iconComposition ? { iconComposition } : {},
10169
10923
  ...fontBundle ? { fontBundle: { contentHash: fontBundle.contentHash } } : {},
10170
- ...modelBundle ? { modelBundle: { contentHash: modelBundle.contentHash } } : {},
10924
+ ...modelSources ? { teamModelSources: modelSources } : {},
10171
10925
  source
10172
10926
  },
10173
10927
  `Deploy response for ${mapName}`
@@ -10225,9 +10979,15 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
10225
10979
  };
10226
10980
  const versionLabel = Number.isInteger(body.version) ? ` (v${body.version})` : "";
10227
10981
  if (body.changed === false) {
10228
- logSuccess(`Unchanged ${pc6.bold(mapName)}${versionLabel}.`);
10982
+ logSuccess(`Unchanged ${pc9.bold(mapName)}${versionLabel}.`);
10229
10983
  } else {
10230
- logSuccess(`Published ${pc6.bold(mapName)}${versionLabel}.`);
10984
+ logSuccess(`Published ${pc9.bold(mapName)}${versionLabel}.`);
10985
+ }
10986
+ for (const teamModel of body.teamModels ?? []) {
10987
+ if (teamModel.sources !== "differ") continue;
10988
+ logWarning(
10989
+ `3D model ${pc9.bold(teamModel.model)} draws its published version ${teamModel.version}, which was not prepared from this project's files. Run \`tileflow models publish\` to publish them.`
10990
+ );
10231
10991
  }
10232
10992
  }
10233
10993
  const manifestPath = await writeDeployManifest(options.manifest, {
@@ -10243,6 +11003,7 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
10243
11003
  const deployedMapId = Object.values(deployedMaps)[0]?.mapId;
10244
11004
  if (deployedMapId) {
10245
11005
  printNextSteps([
11006
+ ...createdMapId ? [`Deploy later changes with ${command(`tileflow deploy --map-id ${createdMapId}`)}.`] : [],
10246
11007
  `Check hosted state with ${command(`tileflow status --map-id ${deployedMapId}`)}.`
10247
11008
  ]);
10248
11009
  }
@@ -10343,6 +11104,7 @@ registerProjectCommands(program, {
10343
11104
  defaultApiUrl,
10344
11105
  loadAuthConfig
10345
11106
  });
11107
+ registerMapCommands(program, { defaultApiUrl, loadAuthConfig });
10346
11108
  if (isRootCliVersionRequest(process.argv.slice(2))) {
10347
11109
  process.stdout.write(`${packageJson.version}
10348
11110
  `);
@@ -10589,12 +11351,12 @@ async function runTileflowComparisonPreview(options, address) {
10589
11351
  }
10590
11352
  function comparisonCaptureConfigArgument(configPath) {
10591
11353
  const cwd = realpathSync(process.cwd());
10592
- const requestedConfigPath = resolve11(process.cwd(), configPath);
10593
- const defaultRequestedPath = resolve11(process.cwd(), defaultConfigPath);
11354
+ const requestedConfigPath = resolve12(process.cwd(), configPath);
11355
+ const defaultRequestedPath = resolve12(process.cwd(), defaultConfigPath);
10594
11356
  const absoluteConfigPath = existsSync(requestedConfigPath) ? realpathSync(requestedConfigPath) : requestedConfigPath;
10595
11357
  const absoluteDefaultPath = existsSync(defaultRequestedPath) ? realpathSync(defaultRequestedPath) : defaultRequestedPath;
10596
11358
  if (absoluteConfigPath === absoluteDefaultPath) return void 0;
10597
- const relativeConfigPath = relative5(cwd, absoluteConfigPath);
11359
+ const relativeConfigPath = relative6(cwd, absoluteConfigPath);
10598
11360
  if (relativeConfigPath === "" || relativeConfigPath === ".." || relativeConfigPath.startsWith(`..${sep5}`) || isAbsolute5(relativeConfigPath)) {
10599
11361
  throw new Error(
10600
11362
  "Comparison configs must be inside the current working directory so Copy command can remain reproducible."
@@ -10634,17 +11396,17 @@ async function loginWithDeviceFlow(options) {
10634
11396
  codeChallenge,
10635
11397
  deviceName: hostname()
10636
11398
  });
10637
- printKeyValue("Code", pc6.bold(authorization.userCode));
11399
+ printKeyValue("Code", pc9.bold(authorization.userCode));
10638
11400
  printKeyValue("URL", link(authorization.verificationUriComplete));
10639
11401
  if (options.noBrowser) {
10640
11402
  logMuted("Open the URL above to continue.");
10641
11403
  } else if (process.stdin.isTTY) {
10642
- const readline = createInterface({
11404
+ const readline = createInterface2({
10643
11405
  input: process.stdin,
10644
11406
  output: process.stdout
10645
11407
  });
10646
11408
  try {
10647
- await readline.question(`${pc6.cyan("?")} Press Enter to open Tileflow Dashboard...`);
11409
+ await readline.question(`${pc9.cyan("?")} Press Enter to open Tileflow Dashboard...`);
10648
11410
  openBrowser(authorization.verificationUriComplete);
10649
11411
  } finally {
10650
11412
  readline.close();
@@ -10681,7 +11443,7 @@ function createPkceChallenge(verifier) {
10681
11443
  return createHash4("sha256").update(verifier).digest("base64url");
10682
11444
  }
10683
11445
  function randomBase64Url(byteLength) {
10684
- return randomBytes2(byteLength).toString("base64url");
11446
+ return randomBytes3(byteLength).toString("base64url");
10685
11447
  }
10686
11448
  function openBrowser(url, errorsToStderr = false) {
10687
11449
  openTileflowExternal(url, {
@@ -10695,9 +11457,9 @@ function openBrowser(url, errorsToStderr = false) {
10695
11457
  });
10696
11458
  }
10697
11459
  async function writeDeployManifest(manifestPath, manifest) {
10698
- const outputPath = resolve11(process.cwd(), manifestPath);
11460
+ const outputPath = resolve12(process.cwd(), manifestPath);
10699
11461
  await writeAtomicFile({
10700
- boundaryPath: dirname12(outputPath),
11462
+ boundaryPath: dirname13(outputPath),
10701
11463
  force: true,
10702
11464
  label: "Deploy manifest",
10703
11465
  managed: true,
@@ -10733,11 +11495,6 @@ function createCompiledMapAssets(compiled, resolveSprite, modelAssets) {
10733
11495
  };
10734
11496
  }
10735
11497
  function selectManagedDeployMaps(mapNames, options) {
10736
- if (options.map && !options.mapId) {
10737
- logError("--map requires --map-id.");
10738
- process.exitCode = 1;
10739
- return null;
10740
- }
10741
11498
  if (options.map) {
10742
11499
  if (!mapNames.includes(options.map)) {
10743
11500
  logError(`Unknown Tileflow map: ${options.map}.`);
@@ -10747,24 +11504,40 @@ function selectManagedDeployMaps(mapNames, options) {
10747
11504
  }
10748
11505
  return [options.map];
10749
11506
  }
10750
- if (!options.mapId) return mapNames;
10751
11507
  if (mapNames.length === 1) return [mapNames[0]];
10752
11508
  logError(
10753
11509
  "This managed target requires an explicit map because the config contains multiple maps."
10754
11510
  );
10755
11511
  printKeyValue("Maps", mapNames.join(", "));
10756
11512
  printNextSteps([
10757
- `Retry with ${command(`tileflow deploy --map-id ${options.mapId} --map ${mapNames[0]}`)}.`
11513
+ `Retry with ${command(`tileflow deploy ${options.mapId ? `--map-id ${options.mapId} ` : ""}--map ${mapNames[0]}`)}.`
10758
11514
  ]);
10759
11515
  process.exitCode = 1;
10760
11516
  return null;
10761
11517
  }
10762
- function validateDeployTarget(options) {
10763
- if (!options.mapId) {
10764
- logError("Managed deploy requires --map-id.");
11518
+ function validateMapSetupTarget(options, source) {
11519
+ const fail = (message, steps = []) => {
11520
+ logError(message);
11521
+ printNextSteps(steps);
10765
11522
  process.exitCode = 1;
10766
11523
  return false;
11524
+ };
11525
+ if (options.mapId) {
11526
+ return hasMapSetupOptions(options) ? fail(
11527
+ "--team, --origin, --any-website and --setup-key apply only when deploy creates a Map."
11528
+ ) : true;
11529
+ }
11530
+ const createFirst = `Create the Map with ${command("tileflow maps create")} after ${command("tileflow login")}, then pass --map-id.`;
11531
+ if (options.withNative) return fail("--with-native requires one valid --map-id.", [createFirst]);
11532
+ if (options.apiKey || !allowsStoredDeployCredential(source)) {
11533
+ return fail("Managed deploy with a Tileflow credential or in CI requires --map-id.", [
11534
+ createFirst
11535
+ ]);
10767
11536
  }
11537
+ const checked = checkMapSetupOptions(options);
11538
+ return checked.ok ? true : fail(checked.error);
11539
+ }
11540
+ function validateDeployTarget(options) {
10768
11541
  if (options.mapId && !/^map_[A-Za-z0-9_-]{16}$/u.test(options.mapId)) {
10769
11542
  logError("Managed Map ID is invalid.");
10770
11543
  process.exitCode = 1;
@@ -10785,7 +11558,7 @@ function validateDeployManifestMapNames(mapNames) {
10785
11558
  async function loadExistingDeployManifest(manifestPath, options) {
10786
11559
  let source;
10787
11560
  try {
10788
- source = await readFile7(resolve11(process.cwd(), manifestPath), "utf8");
11561
+ source = await readFile7(resolve12(process.cwd(), manifestPath), "utf8");
10789
11562
  } catch (error) {
10790
11563
  if (error.code === "ENOENT") return null;
10791
11564
  throw new Error("Existing Tileflow manifest could not be read.");
@@ -10845,7 +11618,7 @@ async function requireApiOptions(options, behavior = {}) {
10845
11618
  if (!behavior.silent) {
10846
11619
  logError("CI requires an explicit Tileflow credential with access to the target Map.");
10847
11620
  printNextSteps([
10848
- `Set ${pc6.cyan("TILEFLOW_API_KEY")} from the CI secret store.`,
11621
+ `Set ${pc9.cyan("TILEFLOW_API_KEY")} from the CI secret store.`,
10849
11622
  "The saved personal account session is never used in CI."
10850
11623
  ]);
10851
11624
  }
@@ -10914,17 +11687,17 @@ function printCliError(error) {
10914
11687
  }
10915
11688
  const message = error instanceof Error ? error.message : "Command failed.";
10916
11689
  if (message.includes("Cannot find module '@tileflow/core'")) {
10917
- logError(`Cannot load ${pc6.cyan("@tileflow/core")} from this project.`);
11690
+ logError(`Cannot load ${pc9.cyan("@tileflow/core")} from this project.`);
10918
11691
  printNextSteps([
10919
- `Install ${pc6.cyan("@tileflow/core")} in this project.`,
11692
+ `Install ${pc9.cyan("@tileflow/core")} in this project.`,
10920
11693
  `Run the command again from the project root.`
10921
11694
  ]);
10922
11695
  return;
10923
11696
  }
10924
11697
  if (message.includes("Cannot find module '@tileflow/maps'")) {
10925
- logError(`Cannot load ${pc6.cyan("@tileflow/maps")} from this project.`);
11698
+ logError(`Cannot load ${pc9.cyan("@tileflow/maps")} from this project.`);
10926
11699
  printNextSteps([
10927
- `Install ${pc6.cyan("@tileflow/maps")} in this project.`,
11700
+ `Install ${pc9.cyan("@tileflow/maps")} in this project.`,
10928
11701
  `Run the command again from the project root.`
10929
11702
  ]);
10930
11703
  return;
@@ -10935,51 +11708,51 @@ function printCliError(error) {
10935
11708
  }
10936
11709
  }
10937
11710
  function logSuccess(message) {
10938
- console.log(`${pc6.green("\u2713")} ${message}`);
11711
+ console.log(`${pc9.green("\u2713")} ${message}`);
10939
11712
  }
10940
11713
  function logError(message) {
10941
- console.log(`${pc6.red("\u2715")} ${message}`);
11714
+ console.log(`${pc9.red("\u2715")} ${message}`);
10942
11715
  }
10943
11716
  function logWarning(message) {
10944
- console.log(`${pc6.yellow("!")} ${message}`);
11717
+ console.log(`${pc9.yellow("!")} ${message}`);
10945
11718
  }
10946
11719
  function logInfo(message) {
10947
- console.log(`${pc6.cyan("i")} ${message}`);
11720
+ console.log(`${pc9.cyan("i")} ${message}`);
10948
11721
  }
10949
11722
  function logMuted(message) {
10950
- console.log(pc6.gray(message));
11723
+ console.log(pc9.gray(message));
10951
11724
  }
10952
11725
  function printTitle(title, subtitle) {
10953
- console.log(pc6.bold(title));
11726
+ console.log(pc9.bold(title));
10954
11727
  if (subtitle) {
10955
- console.log(pc6.gray(subtitle));
11728
+ console.log(pc9.gray(subtitle));
10956
11729
  }
10957
11730
  }
10958
11731
  function printKeyValue(label, value) {
10959
- console.log(`${pc6.gray(`${label}:`.padEnd(13))} ${value}`);
11732
+ console.log(`${pc9.gray(`${label}:`.padEnd(13))} ${value}`);
10960
11733
  }
10961
11734
  function printChecks(items) {
10962
11735
  for (const item of items) {
10963
- console.log(` ${pc6.green("\u2713")} ${item}`);
11736
+ console.log(` ${pc9.green("\u2713")} ${item}`);
10964
11737
  }
10965
11738
  }
10966
11739
  function printNextSteps(steps) {
10967
11740
  if (steps.length === 0) return;
10968
11741
  console.log(`
10969
- ${pc6.bold("Next steps")}`);
11742
+ ${pc9.bold("Next steps")}`);
10970
11743
  for (const step of steps) {
10971
- console.log(` ${pc6.cyan("\u2022")} ${step}`);
11744
+ console.log(` ${pc9.cyan("\u2022")} ${step}`);
10972
11745
  }
10973
11746
  }
10974
11747
  function printValidationErrors(messages) {
10975
11748
  logError("Tileflow config has errors.");
10976
11749
  for (const message of messages) {
10977
- const path = message.path ? pc6.cyan(message.path) : pc6.cyan("(root)");
10978
- console.log(` ${pc6.red("\u2715")} ${path} ${pc6.gray("-")} ${message.message}`);
11750
+ const path = message.path ? pc9.cyan(message.path) : pc9.cyan("(root)");
11751
+ console.log(` ${pc9.red("\u2715")} ${path} ${pc9.gray("-")} ${message.message}`);
10979
11752
  }
10980
11753
  }
10981
11754
  function printAccountDetails(session) {
10982
- printKeyValue("Account", pc6.bold(session.account.name));
11755
+ printKeyValue("Account", pc9.bold(session.account.name));
10983
11756
  printKeyValue("Email", session.account.email);
10984
11757
  printKeyValue("API", link(session.apiOrigin));
10985
11758
  printKeyValue("Expires", formatDate(session.expiresAt));
@@ -11008,11 +11781,11 @@ function printDeployedMaps(maps) {
11008
11781
  const entries = Object.entries(maps);
11009
11782
  if (entries.length === 0) return;
11010
11783
  console.log(`
11011
- ${pc6.bold("Maps")}`);
11784
+ ${pc9.bold("Maps")}`);
11012
11785
  for (const [name, map] of entries) {
11013
11786
  for (const [themeName, theme] of Object.entries(map.themes)) {
11014
11787
  const label = `${name}/${themeName}`;
11015
- console.log(` ${pc6.green("\u2713")} ${label.padEnd(24)} ${link(theme.styleUrl)}`);
11788
+ console.log(` ${pc9.green("\u2713")} ${label.padEnd(24)} ${link(theme.styleUrl)}`);
11016
11789
  }
11017
11790
  }
11018
11791
  }
@@ -11030,13 +11803,13 @@ function plural(count, singular, pluralForm = `${singular}s`) {
11030
11803
  return `${count} ${count === 1 ? singular : pluralForm}`;
11031
11804
  }
11032
11805
  function command(value) {
11033
- return pc6.cyan(value);
11806
+ return pc9.cyan(value);
11034
11807
  }
11035
11808
  function link(value) {
11036
- return pc6.cyan(value);
11809
+ return pc9.cyan(value);
11037
11810
  }
11038
11811
  function pathLabel(value) {
11039
- return pc6.cyan(value);
11812
+ return pc9.cyan(value);
11040
11813
  }
11041
11814
  function starterConfig() {
11042
11815
  return `import { defineMap, defineTheme, labels, poi } from "@tileflow/core";
@@ -11082,19 +11855,19 @@ function printHostedCompatibilityIssues(issues) {
11082
11855
  }
11083
11856
  function printMapStatus(status, apiUrl) {
11084
11857
  printTitle("Tileflow status");
11085
- printKeyValue("Map", pc6.bold(status.mapId));
11858
+ printKeyValue("Map", pc9.bold(status.mapId));
11086
11859
  console.log(`
11087
- ${pc6.bold("Styles")}`);
11860
+ ${pc9.bold("Styles")}`);
11088
11861
  if (status.styles.length === 0) {
11089
11862
  logMuted(" No styles deployed.");
11090
11863
  }
11091
11864
  for (const style of status.styles) {
11092
11865
  console.log(
11093
- ` ${pc6.green("\u2713")} ${style.environment.padEnd(16)} ${link(`${apiUrl}/maps/${style.mapId}/style.json`).padEnd(36)} ${formatBytes3(style.size).padStart(10)} ${pc6.gray(formatDate(style.uploaded))}`
11866
+ ` ${pc9.green("\u2713")} ${style.environment.padEnd(16)} ${link(`${apiUrl}/maps/${style.mapId}/style.json`).padEnd(36)} ${formatBytes4(style.size).padStart(10)} ${pc9.gray(formatDate(style.uploaded))}`
11094
11867
  );
11095
11868
  }
11096
11869
  }
11097
- function formatBytes3(bytes) {
11870
+ function formatBytes4(bytes) {
11098
11871
  if (bytes < 1024) return `${bytes} B`;
11099
11872
  const units = ["KB", "MB", "GB", "TB"];
11100
11873
  let value = bytes;