tileflow 0.1.0-alpha.14 → 0.1.0-alpha.15

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
@@ -7,9 +7,9 @@ import { createHash as createHash4, randomBytes as randomBytes2 } 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";
10
+ import { dirname as dirname13, isAbsolute as isAbsolute5, relative as relative6, resolve as resolve12, sep as sep5 } from "path";
11
11
  import { createInterface } from "readline/promises";
12
- import pc6 from "picocolors";
12
+ import pc7 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(
@@ -4533,42 +4533,6 @@ async function uploadHostedFontBundle(api, bundle, options = {}) {
4533
4533
  }
4534
4534
  return { ok: true, value };
4535
4535
  }
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
4536
  async function fetchHostedMapStatus(api, options = {}) {
4573
4537
  const response = await requestHostedJson(
4574
4538
  api.apiUrl,
@@ -4610,7 +4574,7 @@ async function withHostedResponse(apiUrl, path, init, options, consume) {
4610
4574
  }
4611
4575
  function raceWithAbort(operation, signal) {
4612
4576
  if (signal.aborted) return Promise.reject(signal.reason);
4613
- return new Promise((resolve12, reject) => {
4577
+ return new Promise((resolve13, reject) => {
4614
4578
  const cleanup = () => signal.removeEventListener("abort", onAbort);
4615
4579
  const onAbort = () => {
4616
4580
  cleanup();
@@ -4620,7 +4584,7 @@ function raceWithAbort(operation, signal) {
4620
4584
  operation.then(
4621
4585
  (value) => {
4622
4586
  cleanup();
4623
- resolve12(value);
4587
+ resolve13(value);
4624
4588
  },
4625
4589
  (error) => {
4626
4590
  cleanup();
@@ -4830,12 +4794,12 @@ function cloneStyle(style) {
4830
4794
  }
4831
4795
 
4832
4796
  // src/model-assets.ts
4797
+ import { tileflowTeamModelUrl } from "@tileflow/core";
4833
4798
  import {
4834
- createTileflowModelBundle,
4835
4799
  prepareTileflowMapTreeModels
4836
4800
  } from "@tileflow/dev/icons";
4837
4801
  async function prepareModelAssets(project, options) {
4838
- const prepared = { assets: {}, bundles: {}, identities: {} };
4802
+ const prepared = { assets: {}, buildings: {}, identities: {} };
4839
4803
  for (const [name, map] of Object.entries(project.maps)) {
4840
4804
  if (!map.models?.length) continue;
4841
4805
  const models = await prepareTileflowMapTreeModels(map.models, {
@@ -4844,25 +4808,36 @@ async function prepareModelAssets(project, options) {
4844
4808
  configPath: `maps.${name}.models`
4845
4809
  });
4846
4810
  prepared.assets[name] = { models: models.models };
4847
- const bundle = await createTileflowModelBundle(models.buildings);
4848
- if (bundle) prepared.bundles[name] = bundle;
4811
+ if (Object.keys(models.buildings).length > 0) prepared.buildings[name] = models.buildings;
4849
4812
  if (models.identities.length > 0) prepared.identities[name] = models.identities;
4850
4813
  }
4851
4814
  return prepared;
4852
4815
  }
4853
- function mapAssetsWithBuildingModels(prepared, baseOf) {
4816
+ function mapAssetsWithBuildingModels(prepared, target) {
4854
4817
  const assets = { ...prepared.assets };
4855
- for (const [mapName, bundle] of Object.entries(prepared.bundles)) {
4856
- const base = baseOf(mapName, bundle).replace(/\/+$/u, "");
4818
+ for (const [mapName, buildings] of Object.entries(prepared.buildings)) {
4819
+ const base = target.hosted ? "" : target.localBase(mapName).replace(/\/+$/u, "");
4857
4820
  assets[mapName] = {
4858
4821
  ...assets[mapName],
4859
4822
  buildingModels: Object.fromEntries(
4860
- bundle.manifest.models.map((model) => [model.id, `${base}/${model.file}`])
4823
+ Object.entries(buildings).map(([id, model]) => [
4824
+ id,
4825
+ target.hosted ? tileflowTeamModelUrl(id) : `${base}/${model.fileName}`
4826
+ ])
4861
4827
  )
4862
4828
  };
4863
4829
  }
4864
4830
  return assets;
4865
4831
  }
4832
+ function hostedModelSourceIdentities(prepared, mapName) {
4833
+ const buildings = prepared.buildings[mapName] ?? {};
4834
+ const identities = (prepared.identities[mapName] ?? []).filter(({ id }) => !(id in buildings));
4835
+ return identities.length > 0 ? identities : void 0;
4836
+ }
4837
+ function teamModelSources(prepared, mapName) {
4838
+ const buildings = Object.entries(prepared.buildings[mapName] ?? {});
4839
+ return buildings.length > 0 ? Object.fromEntries(buildings.map(([id, model]) => [id, model.sources])) : void 0;
4840
+ }
4866
4841
 
4867
4842
  // src/hosted-native-deploy.ts
4868
4843
  function failure(message) {
@@ -4902,7 +4877,7 @@ async function prepareNativeHostedDeploy(options) {
4902
4877
  const iconPackage = binding ? compiledIcons.packages.find((entry) => entry.contentHash === binding.packageHash) : void 0;
4903
4878
  const sprite = binding ? `${apiOrigin}/sprites/preflight/${binding.packageHash}/sprite` : void 0;
4904
4879
  const modelAssets = await prepareModelAssets(project, { baseDirectory, target: "hosted" });
4905
- if (modelAssets.bundles[mapName])
4880
+ if (modelAssets.buildings[mapName])
4906
4881
  throw failure(
4907
4882
  "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
4883
  );
@@ -6942,6 +6917,421 @@ async function readCurrentPins(contents) {
6942
6917
  return (await parseTileflowIconsLockfile(contents)).sets;
6943
6918
  }
6944
6919
 
6920
+ // src/model-command.ts
6921
+ import { lstat as lstat5 } from "fs/promises";
6922
+ import { basename as basename4, dirname as dirname11, relative as relative4, resolve as resolve8 } from "path";
6923
+ import pc4 from "picocolors";
6924
+ import { z as z5 } from "zod";
6925
+ import { tileflowModelContentType } from "@tileflow/core";
6926
+ import {
6927
+ assertValidTileflowConfig as assertValidTileflowConfig3,
6928
+ defaultTileflowConfigPath,
6929
+ loadTileflowConfigWithInputs as loadTileflowConfigWithInputs4
6930
+ } from "@tileflow/dev/config";
6931
+ import {
6932
+ prepareTileflowMapTreeModels as prepareTileflowMapTreeModels2
6933
+ } from "@tileflow/dev/icons";
6934
+ var modelName = /^[a-z0-9]+(?:-[a-z0-9]+)*$/u;
6935
+ var sha256 = /^[a-f0-9]{64}$/u;
6936
+ var maximumListPages = 20;
6937
+ function registerModelCommands(program2, dependencies) {
6938
+ const models = program2.command("models").description("Publish and manage the Team 3D models that maps place");
6939
+ withTeamOptions2(
6940
+ models.command("publish").description(
6941
+ "Prepare building models and publish each as its Team 3D model's current version"
6942
+ ).argument(
6943
+ "[paths...]",
6944
+ "model files (<name>.glb) or directories; defaults to the maps' models directories"
6945
+ ).option(
6946
+ "-c, --config <path>",
6947
+ "config path, read when no path is given",
6948
+ defaultTileflowConfigPath
6949
+ )
6950
+ ).action(async (paths, options) => {
6951
+ let prepared;
6952
+ try {
6953
+ prepared = paths.length > 0 ? await prepareFromPaths(paths) : await prepareFromConfig(options.config);
6954
+ } catch (error) {
6955
+ return emitFailure(options.json, "model_preparation_failed", safeMessage(error));
6956
+ }
6957
+ if (prepared.size === 0) {
6958
+ return emitFailure(
6959
+ options.json,
6960
+ "no_building_models",
6961
+ "No building models were found. A building model is a .glb whose materials name its parts: surface, secondary, glass, accent and roof."
6962
+ );
6963
+ }
6964
+ const authority = await resolveTeamAuthority(options, ["models:write"], dependencies);
6965
+ if (!authority) return;
6966
+ const results = [];
6967
+ for (const [name, model] of [...prepared].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)) {
6968
+ const body = new FormData();
6969
+ body.append(
6970
+ "metadata",
6971
+ new Blob([JSON.stringify({ sources: model.sources })], { type: "application/json" }),
6972
+ "metadata.json"
6973
+ );
6974
+ const bytes = new Uint8Array(model.bytes.byteLength);
6975
+ bytes.set(model.bytes);
6976
+ body.append(
6977
+ "file",
6978
+ new Blob([bytes.buffer], { type: tileflowModelContentType }),
6979
+ `${name}.glb`
6980
+ );
6981
+ const response = await authorizedTeamRequest(
6982
+ authority,
6983
+ `/v1/models/${encodeURIComponent(name)}/versions/${model.sha256}`,
6984
+ "PUT",
6985
+ { body }
6986
+ );
6987
+ if (!response.ok) return emitHttpFailure2(options.json, response.status, response.body, name);
6988
+ const parsed = publishResponseSchema2.safeParse(response.body);
6989
+ if (!parsed.success || parsed.data.model.current?.contentHash !== model.sha256) {
6990
+ return emitFailure(
6991
+ options.json,
6992
+ "model_publish_unconfirmed",
6993
+ `The publication of ${name} did not confirm the submitted model file.`
6994
+ );
6995
+ }
6996
+ results.push(parsed.data);
6997
+ if (!options.json) {
6998
+ const current = parsed.data.model.current;
6999
+ const action = parsed.data.changed ? "Published" : "Unchanged";
7000
+ console.log(
7001
+ `${pc4.green("\u2713")} ${action} 3D model ${pc4.bold(name)} version ${current.version} (${formatBytes3(current.byteLength)}, ${formatCount(current.summary.levels[0]?.triangles ?? 0)} triangles).`
7002
+ );
7003
+ }
7004
+ }
7005
+ if (options.json) {
7006
+ emitJson({
7007
+ command: "models publish",
7008
+ ok: true,
7009
+ schemaVersion: 1,
7010
+ models: results.map(({ changed, model }) => ({
7011
+ changed,
7012
+ contentHash: model.current.contentHash,
7013
+ name: model.name,
7014
+ version: model.current.version
7015
+ }))
7016
+ });
7017
+ } else if (results.some(({ changed }) => changed)) {
7018
+ console.log("Maps that place them draw the new versions on their next load.");
7019
+ }
7020
+ });
7021
+ withTeamOptions2(models.command("list").description("List the Team's 3D models")).action(
7022
+ async (options) => {
7023
+ const authority = await resolveTeamAuthority(options, ["models:read"], dependencies);
7024
+ if (!authority) return;
7025
+ const listed = [];
7026
+ let cursor;
7027
+ for (let page = 0; page < maximumListPages; page += 1) {
7028
+ const response = await authorizedTeamRequest(
7029
+ authority,
7030
+ `/v1/models${cursor ? `?cursor=${encodeURIComponent(cursor)}` : ""}`,
7031
+ "GET"
7032
+ );
7033
+ if (!response.ok) return emitHttpFailure2(options.json, response.status, response.body);
7034
+ const parsed = listResponseSchema.safeParse(response.body);
7035
+ if (!parsed.success)
7036
+ return emitFailure(options.json, "invalid_response", "The model list was not readable.");
7037
+ listed.push(...parsed.data.models);
7038
+ cursor = parsed.data.nextCursor;
7039
+ if (!cursor) break;
7040
+ }
7041
+ if (options.json)
7042
+ return emitJson({ command: "models list", ok: true, schemaVersion: 1, models: listed });
7043
+ if (listed.length === 0) {
7044
+ console.log("The Team has no 3D models. Publish one with tileflow models publish.");
7045
+ return;
7046
+ }
7047
+ for (const model of listed) {
7048
+ const current = model.current;
7049
+ console.log(
7050
+ `${pc4.bold(model.name)} ${current ? `version ${current.version}, ${formatBytes3(current.byteLength)}` : "no current version"} ${model.usedByMaps === 1 ? "1 map" : `${model.usedByMaps} maps`}${model.state === "purging" ? pc4.yellow(" purging") : ""}`
7051
+ );
7052
+ }
7053
+ }
7054
+ );
7055
+ withTeamOptions2(
7056
+ models.command("status").description("Show a 3D model's versions and the maps that place it").argument("<name>", "model name")
7057
+ ).action(async (name, options) => {
7058
+ if (!modelName.test(name))
7059
+ return emitFailure(options.json, "invalid_model", "Model name is invalid.");
7060
+ const authority = await resolveTeamAuthority(options, ["models:read"], dependencies);
7061
+ if (!authority) return;
7062
+ const detail = await readDetail(authority, name, options.json);
7063
+ if (!detail) return;
7064
+ if (options.json) return emitJson({ ...detail, command: "models status", ok: true });
7065
+ const { model, versions, uses } = detail;
7066
+ console.log(
7067
+ `${pc4.bold(model.name)}${model.state === "purging" ? pc4.yellow(" (purging)") : ""}`
7068
+ );
7069
+ for (const version of versions) {
7070
+ const role = version.state === "current" ? "current" : version.state === "previous" ? "previous" : version.state;
7071
+ console.log(
7072
+ ` version ${version.version} ${role} ${formatBytes3(version.byteLength)} ${formatCount(version.summary.levels[0]?.triangles ?? 0)} triangles ${version.publishedAt}`
7073
+ );
7074
+ }
7075
+ console.log(
7076
+ uses.length === 0 ? " No map places it." : ` Placed by ${uses.map(({ mapName, mapId }) => mapName ?? mapId).join(", ")}.`
7077
+ );
7078
+ });
7079
+ withTeamOptions2(
7080
+ models.command("restore").description("Make a 3D model's previous version current again, in every map that places it").argument("<name>", "model name")
7081
+ ).action(async (name, options) => {
7082
+ if (!modelName.test(name))
7083
+ return emitFailure(options.json, "invalid_model", "Model name is invalid.");
7084
+ const authority = await resolveTeamAuthority(options, ["models:write"], dependencies);
7085
+ if (!authority) return;
7086
+ const detail = await readDetail(authority, name, options.json);
7087
+ if (!detail) return;
7088
+ const { current, previous } = detail.model;
7089
+ if (!current || !previous) {
7090
+ return emitFailure(
7091
+ options.json,
7092
+ "no_previous_version",
7093
+ `${name} has no previous version to restore.`
7094
+ );
7095
+ }
7096
+ const response = await authorizedTeamRequest(
7097
+ authority,
7098
+ `/v1/models/${encodeURIComponent(name)}/restore`,
7099
+ "POST",
7100
+ {
7101
+ body: JSON.stringify({ expectedCurrentVersion: current.version }),
7102
+ headers: { "Content-Type": "application/json" }
7103
+ }
7104
+ );
7105
+ if (!response.ok) return emitHttpFailure2(options.json, response.status, response.body, name);
7106
+ const parsed = restoreResponseSchema.safeParse(response.body);
7107
+ if (!parsed.success || parsed.data.model.current?.version !== previous.version) {
7108
+ return emitFailure(
7109
+ options.json,
7110
+ "model_restore_unconfirmed",
7111
+ `The restore of ${name} was not confirmed.`
7112
+ );
7113
+ }
7114
+ if (options.json)
7115
+ return emitJson({
7116
+ command: "models restore",
7117
+ ok: true,
7118
+ schemaVersion: 1,
7119
+ model: parsed.data.model
7120
+ });
7121
+ console.log(
7122
+ `${pc4.green("\u2713")} ${pc4.bold(name)} is at version ${previous.version} again; version ${current.version} is its previous one. Maps draw it on their next load.`
7123
+ );
7124
+ });
7125
+ withTeamOptions2(
7126
+ 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")
7127
+ ).action(async (name, options) => {
7128
+ if (!modelName.test(name))
7129
+ return emitFailure(options.json, "invalid_model", "Model name is invalid.");
7130
+ if (options.confirm !== name) {
7131
+ return emitFailure(
7132
+ options.json,
7133
+ "confirmation_required",
7134
+ `Repeat the model name to purge it: --confirm ${name}`
7135
+ );
7136
+ }
7137
+ const authority = await resolveTeamAuthority(options, ["models:write"], dependencies);
7138
+ if (!authority) return;
7139
+ const response = await authorizedTeamRequest(
7140
+ authority,
7141
+ `/v1/models/${encodeURIComponent(name)}`,
7142
+ "DELETE",
7143
+ {
7144
+ headers: { "X-Tileflow-Confirm-Model-Purge": name }
7145
+ }
7146
+ );
7147
+ if (!response.ok) return emitHttpFailure2(options.json, response.status, response.body, name);
7148
+ const parsed = purgeResponseSchema2.safeParse(response.body);
7149
+ if (!parsed.success)
7150
+ return emitFailure(options.json, "invalid_response", "The purge was not confirmed.");
7151
+ if (options.json)
7152
+ return emitJson({
7153
+ command: "models purge",
7154
+ ok: true,
7155
+ schemaVersion: 1,
7156
+ purge: parsed.data.purge
7157
+ });
7158
+ console.log(
7159
+ `${pc4.green("\u2713")} ${pc4.bold(name)} is being purged; its name is free once its files are deleted.`
7160
+ );
7161
+ });
7162
+ }
7163
+ function withTeamOptions2(command2) {
7164
+ 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");
7165
+ }
7166
+ async function readDetail(authority, name, json2) {
7167
+ const response = await authorizedTeamRequest(
7168
+ authority,
7169
+ `/v1/models/${encodeURIComponent(name)}`,
7170
+ "GET"
7171
+ );
7172
+ if (!response.ok) {
7173
+ emitHttpFailure2(json2, response.status, response.body, name);
7174
+ return void 0;
7175
+ }
7176
+ const parsed = detailResponseSchema.safeParse(response.body);
7177
+ if (!parsed.success) {
7178
+ emitFailure(json2, "invalid_response", `The details of ${name} were not readable.`);
7179
+ return void 0;
7180
+ }
7181
+ return parsed.data;
7182
+ }
7183
+ async function prepareFromPaths(paths) {
7184
+ const cwd = process.cwd();
7185
+ const directories = /* @__PURE__ */ new Map();
7186
+ for (const path of paths) {
7187
+ const absolute = resolve8(cwd, path);
7188
+ const info = await lstat5(absolute).catch(() => void 0);
7189
+ if (!info) throw new Error(`${path} does not exist.`);
7190
+ const directory = info.isDirectory() ? absolute : dirname11(absolute);
7191
+ if (relative4(cwd, directory).startsWith(".."))
7192
+ throw new Error(`${path} is outside the current directory.`);
7193
+ if (info.isDirectory()) {
7194
+ directories.set(directory, "all");
7195
+ continue;
7196
+ }
7197
+ const file = basename4(absolute);
7198
+ if (!file.toLowerCase().endsWith(".glb"))
7199
+ throw new Error(`${path} is not a glTF binary (.glb).`);
7200
+ const name = file.replace(/(\.mid|\.far)?\.glb$/iu, "");
7201
+ const selected = directories.get(directory);
7202
+ if (selected === "all") continue;
7203
+ directories.set(directory, /* @__PURE__ */ new Set([...selected ?? [], name]));
7204
+ }
7205
+ const models = /* @__PURE__ */ new Map();
7206
+ for (const [directory, selected] of directories) {
7207
+ const parent = dirname11(directory);
7208
+ const local = `./${basename4(directory)}`;
7209
+ const display = relative4(cwd, directory) || ".";
7210
+ const prepared = await prepareTileflowMapTreeModels2([local], {
7211
+ baseDirectory: parent,
7212
+ configPath: display,
7213
+ cwd: parent,
7214
+ target: "hosted"
7215
+ });
7216
+ for (const [name, model] of Object.entries(prepared.buildings)) {
7217
+ if (selected !== "all" && !selected.has(name)) continue;
7218
+ addModel(models, name, model);
7219
+ }
7220
+ if (selected !== "all") {
7221
+ for (const name of selected)
7222
+ if (!prepared.buildings[name])
7223
+ throw new Error(
7224
+ `${display}/${name}.glb is not a building model: its materials must name its parts (surface, secondary, glass, accent, roof).`
7225
+ );
7226
+ }
7227
+ }
7228
+ return models;
7229
+ }
7230
+ async function prepareFromConfig(configPath) {
7231
+ const loaded = await withTileflowConfigSecretsHidden(
7232
+ () => loadTileflowConfigWithInputs4(configPath)
7233
+ );
7234
+ const project = loaded.project;
7235
+ assertValidTileflowConfig3(project);
7236
+ const prepared = await prepareModelAssets(project, {
7237
+ baseDirectory: dirname11(loaded.configFile),
7238
+ target: "hosted"
7239
+ });
7240
+ const models = /* @__PURE__ */ new Map();
7241
+ for (const buildings of Object.values(prepared.buildings))
7242
+ for (const [name, model] of Object.entries(buildings)) addModel(models, name, model);
7243
+ return models;
7244
+ }
7245
+ function addModel(models, name, model) {
7246
+ const known = models.get(name);
7247
+ if (known && known.sha256 !== model.sha256)
7248
+ throw new Error(`Two different models are named ${name}; a Team has one model of each name.`);
7249
+ models.set(name, model);
7250
+ }
7251
+ function emitHttpFailure2(json2, status, body, name) {
7252
+ const error = z5.object({ error: z5.object({ code: z5.string().max(80), message: z5.string().max(400) }).partial() }).partial().safeParse(body);
7253
+ const message = error.success ? error.data.error?.message : void 0;
7254
+ const code = error.success ? error.data.error?.code : void 0;
7255
+ if (status === 404)
7256
+ return emitFailure(
7257
+ json2,
7258
+ "model_not_found",
7259
+ name ? `The Team has no 3D model named ${name}.` : "Not found."
7260
+ );
7261
+ return emitFailure(
7262
+ json2,
7263
+ code ?? `http_${status}`,
7264
+ message ?? `The request failed with ${status}.`
7265
+ );
7266
+ }
7267
+ function formatBytes3(bytes) {
7268
+ if (bytes < 1024) return `${bytes} B`;
7269
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KiB`;
7270
+ return `${(bytes / 1024 / 1024).toFixed(1)} MiB`;
7271
+ }
7272
+ function formatCount(count) {
7273
+ return new Intl.NumberFormat("en-US").format(count);
7274
+ }
7275
+ var summarySchema = z5.object({
7276
+ levels: z5.array(
7277
+ z5.object({
7278
+ level: z5.enum(["near", "mid", "far"]),
7279
+ vertices: z5.number().int().nonnegative(),
7280
+ triangles: z5.number().int().nonnegative()
7281
+ }).strict()
7282
+ ).min(1).max(3),
7283
+ width: z5.number().nonnegative(),
7284
+ depth: z5.number().nonnegative(),
7285
+ height: z5.number().nonnegative(),
7286
+ footprint: z5.object({
7287
+ rings: z5.number().int().nonnegative(),
7288
+ points: z5.number().int().nonnegative(),
7289
+ area: z5.number().nonnegative()
7290
+ }).strict()
7291
+ }).strict();
7292
+ var versionSchema = z5.object({
7293
+ id: z5.string().regex(/^tmv_[A-Za-z0-9_-]{16,64}$/u),
7294
+ version: z5.number().int().positive(),
7295
+ state: z5.enum(["current", "previous", "gc_pending", "deleting", "deleted"]),
7296
+ contentHash: z5.string().regex(sha256),
7297
+ byteLength: z5.number().int().positive(),
7298
+ publishedAt: z5.iso.datetime({ offset: true }),
7299
+ summary: summarySchema
7300
+ }).strict();
7301
+ var modelSchema = z5.object({
7302
+ id: z5.string().regex(/^tmd_[A-Za-z0-9_-]{16,64}$/u),
7303
+ name: z5.string().regex(modelName).max(64),
7304
+ state: z5.enum(["active", "purging"]),
7305
+ current: versionSchema.nullable(),
7306
+ previous: versionSchema.nullable(),
7307
+ url: z5.url(),
7308
+ usedByMaps: z5.number().int().nonnegative(),
7309
+ updatedAt: z5.iso.datetime({ offset: true })
7310
+ }).strict();
7311
+ var listResponseSchema = z5.object({
7312
+ schemaVersion: z5.literal(1),
7313
+ models: z5.array(modelSchema).max(100),
7314
+ nextCursor: z5.string().min(1).max(512).optional()
7315
+ }).strict();
7316
+ var detailResponseSchema = z5.object({
7317
+ schemaVersion: z5.literal(1),
7318
+ model: modelSchema,
7319
+ versions: z5.array(versionSchema).max(100),
7320
+ uses: z5.array(
7321
+ z5.object({
7322
+ mapId: z5.string().min(1).max(160),
7323
+ mapName: z5.string().max(200).nullable(),
7324
+ relationship: z5.enum(["active", "retained"])
7325
+ }).strict()
7326
+ ).max(100)
7327
+ }).strict();
7328
+ var publishResponseSchema2 = z5.object({ schemaVersion: z5.literal(1), changed: z5.boolean(), model: modelSchema }).strict();
7329
+ var restoreResponseSchema = z5.object({ schemaVersion: z5.literal(1), model: modelSchema }).strict();
7330
+ var purgeResponseSchema2 = z5.object({
7331
+ schemaVersion: z5.literal(1),
7332
+ purge: z5.object({ name: z5.string().regex(modelName), state: z5.enum(["purging", "purged"]) }).strict()
7333
+ }).strict();
7334
+
6945
7335
  // src/native-preview-command.ts
6946
7336
  import { tileflowNativeProfile } from "@tileflow/core/native-profile";
6947
7337
  import {
@@ -7030,7 +7420,7 @@ function resolveTileflowExternalOpenInvocation(target, platform) {
7030
7420
  }
7031
7421
 
7032
7422
  // src/project-commands.ts
7033
- import pc4 from "picocolors";
7423
+ import pc5 from "picocolors";
7034
7424
  function registerProjectCommands(program2, dependencies) {
7035
7425
  const projects = program2.command("projects", { hidden: true }).description("Manage internal application destinations");
7036
7426
  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 +7436,12 @@ function registerProjectCommands(program2, dependencies) {
7046
7436
  };
7047
7437
  if (options.json) return emitJson2(document);
7048
7438
  if (!result.items.length) {
7049
- console.log(pc4.gray("No managed destinations found."));
7439
+ console.log(pc5.gray("No managed destinations found."));
7050
7440
  return;
7051
7441
  }
7052
7442
  for (const target of result.items) {
7053
7443
  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)") : ""}`
7444
+ ` ${target.project.archivedAt ? pc5.yellow("\u25CB") : pc5.green("\u25CF")} ${pc5.bold(target.project.name)} ${pc5.gray(target.reference)}${target.project.archivedAt ? pc5.gray(" (archived)") : ""}`
7055
7445
  );
7056
7446
  }
7057
7447
  });
@@ -7104,8 +7494,8 @@ function registerProjectCommands(program2, dependencies) {
7104
7494
  ...asRecord2(result.body)
7105
7495
  };
7106
7496
  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}.`));
7497
+ console.log(`${pc5.green("\u2713")} Application ${pc5.bold(options.name.trim())} is ready.`);
7498
+ console.log(pc5.gray(`Its technical selector is --project @${organization.slug}/${slug}.`));
7109
7499
  }
7110
7500
  );
7111
7501
  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 +7517,8 @@ function registerProjectCommands(program2, dependencies) {
7127
7517
  if (!isProjectItem(project)) {
7128
7518
  return emitLocalFailure(false, "invalid_response", "Destination response was invalid.");
7129
7519
  }
7130
- console.log(`${pc4.bold(project.name)} ${pc4.gray(target)}`);
7131
- console.log(project.archivedAt ? pc4.yellow("Archived") : pc4.green("Active"));
7520
+ console.log(`${pc5.bold(project.name)} ${pc5.gray(target)}`);
7521
+ console.log(project.archivedAt ? pc5.yellow("Archived") : pc5.green("Active"));
7132
7522
  });
7133
7523
  for (const archived of [true, false]) {
7134
7524
  const verb = archived ? "archive" : "unarchive";
@@ -7155,7 +7545,7 @@ function registerProjectCommands(program2, dependencies) {
7155
7545
  ...asRecord2(result.body)
7156
7546
  };
7157
7547
  if (options.json) return emitJson2(document);
7158
- console.log(`${pc4.green("\u2713")} Application ${archived ? "archived" : "unarchived"}.`);
7548
+ console.log(`${pc5.green("\u2713")} Application ${archived ? "archived" : "unarchived"}.`);
7159
7549
  });
7160
7550
  }
7161
7551
  }
@@ -7266,7 +7656,7 @@ function emitLocalFailure(json2, code, message, details = {}) {
7266
7656
  console.error(message);
7267
7657
  emitJson2({ schemaVersion: 1, ok: false, error: { code, ...details } });
7268
7658
  } else {
7269
- console.error(`${pc4.red("\u2715")} ${message}`);
7659
+ console.error(`${pc5.red("\u2715")} ${message}`);
7270
7660
  }
7271
7661
  process.exitCode = 1;
7272
7662
  }
@@ -7301,7 +7691,7 @@ function safeMessage2(error) {
7301
7691
  }
7302
7692
 
7303
7693
  // src/renderer-artifact-command.ts
7304
- import { resolve as resolve8 } from "path";
7694
+ import { resolve as resolve9 } from "path";
7305
7695
  import { auditTileflowMapThemeValues } from "@tileflow/core";
7306
7696
  import {
7307
7697
  createTileflowNativeDiagnostic,
@@ -7389,7 +7779,7 @@ async function runRendererArtifactCommand(command2, options) {
7389
7779
  if (artifacts.nativeBuild)
7390
7780
  console.log("Profile: native-v1 (static artifacts; no device qualification).");
7391
7781
  if (command2 === "build")
7392
- console.log(`Output: ${resolve8(outDir, ...renderer === "native" ? ["native"] : [])}`);
7782
+ console.log(`Output: ${resolve9(outDir, ...renderer === "native" ? ["native"] : [])}`);
7393
7783
  }
7394
7784
  } catch (error) {
7395
7785
  const document = createTileflowCommandFailureDocument6(command2, error, process.cwd(), {
@@ -7410,15 +7800,15 @@ async function runRendererArtifactCommand(command2, options) {
7410
7800
  }
7411
7801
 
7412
7802
  // src/tileset-command.ts
7413
- import pc5 from "picocolors";
7414
- import { z as z6 } from "zod";
7803
+ import pc6 from "picocolors";
7804
+ import { z as z7 } from "zod";
7415
7805
  import { serializeCanonicalJson as serializeCanonicalJson6 } from "@tileflow/core";
7416
7806
  import { inspectTileflowPmtiles } from "@tileflow/dev/tilesets";
7417
7807
 
7418
7808
  // src/tileset-upload.ts
7419
7809
  import { createHash as createHash3 } from "crypto";
7420
7810
  import { open as open5, stat } from "fs/promises";
7421
- import { z as z5 } from "zod";
7811
+ import { z as z6 } from "zod";
7422
7812
  import { serializeCanonicalJson as serializeCanonicalJson5 } from "@tileflow/core";
7423
7813
  var teamTilesetPartSize = 16777216;
7424
7814
  var maximumTeamTilesetBytes = 4e9;
@@ -7979,36 +8369,36 @@ function asRecord3(value) {
7979
8369
  return value && typeof value === "object" && !Array.isArray(value) ? value : {};
7980
8370
  }
7981
8371
  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"])
8372
+ return new Promise((resolve13) => setTimeout(resolve13, milliseconds));
8373
+ }
8374
+ var identifierSchema = z6.string().min(20).max(80).regex(/^[A-Za-z0-9_-]+$/u);
8375
+ var uploadPartStatusSchema = z6.object({
8376
+ byteCount: z6.number().int().min(1).max(teamTilesetPartSize),
8377
+ partNumber: z6.number().int().min(1).max(239),
8378
+ uploaded: z6.boolean(),
8379
+ validationState: z6.enum(["mismatch", "pending", "processing", "valid"])
7990
8380
  }).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"),
8381
+ var publishedVersionSchema = z6.object({
8382
+ byteCount: z6.number().int().min(1).max(maximumTeamTilesetBytes),
8383
+ contentHash: z6.string().regex(/^[0-9a-f]{64}$/u),
8384
+ contentHashAlgorithm: z6.literal("sha256-fixed-parts-v1"),
8385
+ publishedVersion: z6.number().int().positive().max(Number.MAX_SAFE_INTEGER),
8386
+ schemaVersion: z6.literal(1),
8387
+ state: z6.literal("published"),
7998
8388
  versionId: identifierSchema.regex(/^tsv_[A-Za-z0-9_-]+$/u)
7999
8389
  }).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([
8390
+ var uploadControlSchema = z6.object({
8391
+ byteCount: z6.number().int().min(1).max(maximumTeamTilesetBytes),
8392
+ contentHash: z6.string().regex(/^[0-9a-f]{64}$/u),
8393
+ contentHashAlgorithm: z6.literal("sha256-fixed-parts-v1"),
8394
+ expiresAt: z6.iso.datetime({ offset: true }),
8395
+ failure: z6.object({ code: z6.string().regex(/^TF_[A-Z0-9_]{1,96}$/u) }).strict().nullable(),
8396
+ partCount: z6.number().int().min(1).max(239),
8397
+ parts: z6.array(uploadPartStatusSchema).max(239),
8398
+ partSize: z6.literal(teamTilesetPartSize),
8399
+ publishedVersion: z6.null(),
8400
+ schemaVersion: z6.literal(1),
8401
+ state: z6.enum([
8012
8402
  "aborted",
8013
8403
  "aborting",
8014
8404
  "expired",
@@ -8021,32 +8411,32 @@ var uploadControlSchema = z5.object({
8021
8411
  "uploading",
8022
8412
  "validating"
8023
8413
  ]),
8024
- uploadId: z5.string().min(20).max(80).regex(/^tsu_[A-Za-z0-9_-]+$/u),
8025
- versionId: z5.null()
8414
+ uploadId: z6.string().min(20).max(80).regex(/^tsu_[A-Za-z0-9_-]+$/u),
8415
+ versionId: z6.null()
8026
8416
  }).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)
8417
+ var uploadSessionSchema = z6.union([publishedVersionSchema, uploadControlSchema]);
8418
+ var authorizationSchema = z6.object({
8419
+ expiresAt: z6.iso.datetime({ offset: true }),
8420
+ headers: z6.object({
8421
+ "Content-Length": z6.string().regex(/^[1-9][0-9]{0,9}$/u),
8422
+ "Content-MD5": z6.string().regex(/^[A-Za-z0-9+/]{22}==$/u)
8033
8423
  }).strict(),
8034
- method: z5.literal("PUT"),
8035
- partNumber: z5.number().int().min(1).max(239),
8036
- url: z5.url().max(8192).refine((value) => {
8424
+ method: z6.literal("PUT"),
8425
+ partNumber: z6.number().int().min(1).max(239),
8426
+ url: z6.url().max(8192).refine((value) => {
8037
8427
  const url = new URL(value);
8038
8428
  return url.protocol === "https:" && !url.username && !url.password && !url.hash;
8039
8429
  })
8040
8430
  }).strict();
8041
- var authorizationResponseSchema = z5.object({
8042
- authorizations: z5.array(authorizationSchema).min(1).max(16),
8043
- schemaVersion: z5.literal(1)
8431
+ var authorizationResponseSchema = z6.object({
8432
+ authorizations: z6.array(authorizationSchema).min(1).max(16),
8433
+ schemaVersion: z6.literal(1)
8044
8434
  }).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)
8435
+ var partReceiptResponseSchema = z6.object({
8436
+ changed: z6.boolean(),
8437
+ etag: z6.string().regex(/^[0-9a-f]{32}$/u),
8438
+ partNumber: z6.number().int().min(1).max(239),
8439
+ schemaVersion: z6.literal(1)
8050
8440
  }).strict();
8051
8441
 
8052
8442
  // src/tileset-command.ts
@@ -8069,7 +8459,7 @@ function registerTilesetInspectCommand(parent, name = "inspect") {
8069
8459
  `);
8070
8460
  return;
8071
8461
  }
8072
- console.log(`${pc5.green("\u2713")} PMTiles archive is valid.`);
8462
+ console.log(`${pc6.green("\u2713")} PMTiles archive is valid.`);
8073
8463
  console.log(` Type: ${inspection.contract.tileType}`);
8074
8464
  console.log(` Zooms: ${inspection.contract.minzoom}-${inspection.contract.maxzoom}`);
8075
8465
  console.log(` Source layers: ${inspection.contract.sourceLayers.length}`);
@@ -8079,7 +8469,7 @@ function registerTilesetInspectCommand(parent, name = "inspect") {
8079
8469
  );
8080
8470
  }
8081
8471
  for (const warning of inspection.warnings) {
8082
- console.log(`${pc5.yellow("!")} ${warning.message}`);
8472
+ console.log(`${pc6.yellow("!")} ${warning.message}`);
8083
8473
  }
8084
8474
  } catch (error) {
8085
8475
  const record = asRecord4(error);
@@ -8092,7 +8482,7 @@ function registerTilesetInspectCommand(parent, name = "inspect") {
8092
8482
  `
8093
8483
  );
8094
8484
  } else {
8095
- console.error(`${pc5.red("Error:")} ${message}`);
8485
+ console.error(`${pc6.red("Error:")} ${message}`);
8096
8486
  }
8097
8487
  process.exitCode = 1;
8098
8488
  }
@@ -8180,7 +8570,7 @@ function registerTilesetCommands(program2, dependencies) {
8180
8570
  };
8181
8571
  if (options.json) return emitJson(document);
8182
8572
  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"}.`
8573
+ `${pc6.green("\u2713")} ${publication.changed ? "Published" : "Unchanged"} ${pc6.bold(options.id)} in ${authority.team ? `@${authority.team.slug}` : "the Team bound to the data key"}.`
8184
8574
  );
8185
8575
  } catch (error) {
8186
8576
  if (error instanceof TeamTilesetUploadError) {
@@ -8200,9 +8590,9 @@ function registerTilesetCommands(program2, dependencies) {
8200
8590
  const items = await fetchAllTeamTilesets(authority);
8201
8591
  const document = { command: "tileset list", schemaVersion: 1, tilesets: items };
8202
8592
  if (options.json) return emitJson(document);
8203
- if (items.length === 0) return console.log(pc5.gray("No Team tilesets."));
8593
+ if (items.length === 0) return console.log(pc6.gray("No Team tilesets."));
8204
8594
  for (const item of items) {
8205
- console.log(` ${pc5.bold(item.logicalName)} ${pc5.gray(item.status)}`);
8595
+ console.log(` ${pc6.bold(item.logicalName)} ${pc6.gray(item.status)}`);
8206
8596
  }
8207
8597
  } catch {
8208
8598
  emitFailure(options.json, "tileset_list_failed", "Tileset list failed.");
@@ -8230,7 +8620,7 @@ function registerTilesetCommands(program2, dependencies) {
8230
8620
  const document = { command: "tileset status", schemaVersion: 1, tileset: parsed.tileset };
8231
8621
  if (options.json) return emitJson(document);
8232
8622
  const item = parsed.tileset;
8233
- console.log(`${pc5.bold(item.name)} ${pc5.gray(item.state)}`);
8623
+ console.log(`${pc6.bold(item.name)} ${pc6.gray(item.state)}`);
8234
8624
  console.log(` ID: ${item.id}`);
8235
8625
  console.log(` Current version: ${String(item.currentVersion ?? "unpublished")}`);
8236
8626
  } catch {
@@ -8281,7 +8671,7 @@ function registerTilesetCommands(program2, dependencies) {
8281
8671
  const parsed = tilesetPurgeResponseSchema.parse(response.body);
8282
8672
  const document = { command: "tileset purge", schemaVersion: 1, ...parsed };
8283
8673
  if (options.json) return emitJson(document);
8284
- console.log(`${pc5.green("\u2713")} Purge ${parsed.reclamation}.`);
8674
+ console.log(`${pc6.green("\u2713")} Purge ${parsed.reclamation}.`);
8285
8675
  } catch {
8286
8676
  emitFailure(options.json, "tileset_purge_failed", "Tileset purge failed.");
8287
8677
  }
@@ -8343,69 +8733,69 @@ function inspectionOptionError(message) {
8343
8733
  function asRecord4(value) {
8344
8734
  return value && typeof value === "object" && !Array.isArray(value) ? value : {};
8345
8735
  }
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(),
8736
+ var safeIdentifierSchema2 = z7.string().min(1).max(160).regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/u);
8737
+ var physicalTilesetIdSchema = z7.string().min(20).max(80).regex(/^tls_[A-Za-z0-9_-]+$/u);
8738
+ var cursorSchema2 = z7.string().min(1).max(2048).nullable();
8739
+ var tilesetInventoryItemSchema = z7.object({
8740
+ currentVersion: z7.number().int().positive().max(Number.MAX_SAFE_INTEGER).nullable(),
8741
+ hasCurrent: z7.boolean(),
8352
8742
  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)
8743
+ logicalName: z7.string().min(1).max(128).regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/u),
8744
+ name: z7.string().min(1).max(200),
8745
+ retainedBytes: z7.number().int().min(0).max(Number.MAX_SAFE_INTEGER),
8746
+ retainedDeploymentCount: z7.number().int().min(0).max(Number.MAX_SAFE_INTEGER),
8747
+ status: z7.enum(["active", "purging", "retired"]),
8748
+ updatedAt: z7.iso.datetime({ offset: true }),
8749
+ usedByMapCount: z7.number().int().min(0).max(Number.MAX_SAFE_INTEGER)
8360
8750
  }).strict();
8361
- var tilesetListResponseSchema = z6.object({
8751
+ var tilesetListResponseSchema = z7.object({
8362
8752
  nextCursor: cursorSchema2.optional(),
8363
- schemaVersion: z6.literal(1),
8364
- tilesets: z6.array(tilesetInventoryItemSchema).max(100)
8753
+ schemaVersion: z7.literal(1),
8754
+ tilesets: z7.array(tilesetInventoryItemSchema).max(100)
8365
8755
  }).strict();
8366
- var tilesetDependencySchema = z6.object({
8367
- active: z6.boolean(),
8756
+ var tilesetDependencySchema = z7.object({
8757
+ active: z7.boolean(),
8368
8758
  deploymentId: safeIdentifierSchema2,
8369
- deploymentVersion: z6.number().int().positive().max(Number.MAX_SAFE_INTEGER),
8370
- environment: z6.string().min(1).max(64),
8759
+ deploymentVersion: z7.number().int().positive().max(Number.MAX_SAFE_INTEGER),
8760
+ environment: z7.string().min(1).max(64),
8371
8761
  mapId: safeIdentifierSchema2,
8372
8762
  sourceId: safeIdentifierSchema2
8373
8763
  }).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),
8764
+ var tilesetDetailResponseSchema = z7.object({
8765
+ schemaVersion: z7.literal(1),
8766
+ tileset: z7.object({
8767
+ attribution: z7.string().max(4096).nullable(),
8768
+ currentVersion: z7.number().int().positive().max(Number.MAX_SAFE_INTEGER).nullable(),
8769
+ dependencies: z7.array(tilesetDependencySchema).max(25),
8380
8770
  dependencyNextCursor: cursorSchema2,
8381
8771
  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"])
8772
+ name: z7.string().min(1).max(200),
8773
+ slug: z7.string().min(1).max(128).regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/u),
8774
+ state: z7.enum(["active", "purging", "purged"])
8385
8775
  }).strict()
8386
8776
  }).strict();
8387
- var purgeDependencySchema = z6.object({
8777
+ var purgeDependencySchema = z7.object({
8388
8778
  deploymentId: safeIdentifierSchema2,
8389
8779
  mapId: safeIdentifierSchema2,
8390
8780
  sourceId: safeIdentifierSchema2
8391
8781
  }).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"),
8782
+ var tilesetInUseResponseSchema = z7.object({
8783
+ code: z7.literal("TF_TILESET_IN_USE"),
8784
+ dependencies: z7.array(purgeDependencySchema).max(32),
8785
+ error: z7.literal("Tileset is used by retained Map deployments"),
8396
8786
  nextCursor: cursorSchema2
8397
8787
  }).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"]),
8788
+ var tilesetPurgeResponseSchema = z7.object({
8789
+ deliveryFence: z7.enum(["complete", "pending"]),
8790
+ pendingVersions: z7.number().int().min(0).max(Number.MAX_SAFE_INTEGER),
8791
+ reclamation: z7.enum(["complete", "pending"]),
8792
+ state: z7.enum(["purged", "purging"]),
8403
8793
  tilesetId: physicalTilesetIdSchema
8404
8794
  }).strict();
8405
8795
 
8406
8796
  // 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";
8797
+ import { lstat as lstat6, readFile as readFile6 } from "fs/promises";
8798
+ import { dirname as dirname12, isAbsolute as isAbsolute4, parse as parse2, relative as relative5, resolve as resolve10, sep as sep4 } from "path";
8409
8799
  import {
8410
8800
  analyzeTileflowCaptureReference,
8411
8801
  compareTileflowCaptureToBaseline,
@@ -8490,14 +8880,14 @@ async function runVisualAnalyze(scene, options) {
8490
8880
  validateVisualSelection([scene]);
8491
8881
  const region = options.region === void 0 ? void 0 : parseTileflowVisualRegion(options.region);
8492
8882
  const cwd = process.cwd();
8493
- const referencePath = resolve9(cwd, options.reference);
8883
+ const referencePath = resolve10(cwd, options.reference);
8494
8884
  const referencePng = await readVisualReference(referencePath);
8495
8885
  const referenceDimensions = validateTileflowVisualReferencePng(referencePng);
8496
8886
  if (region) assertTileflowVisualRegionFits(region, referenceDimensions, "reference PNG");
8497
8887
  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`);
8888
+ const actualPath = resolve10(outputDirectory.path, `${scene}.actual.png`);
8889
+ const managedDiffPath = resolve10(outputDirectory.path, `${scene}.diff.png`);
8890
+ const reportPath = resolve10(outputDirectory.path, `${scene}.analysis.json`);
8501
8891
  assertReferenceIsReadOnly(referencePath, [actualPath, managedDiffPath, reportPath]);
8502
8892
  const controller = installSignalAbortController();
8503
8893
  const session = createTileflowCaptureSession3({
@@ -8586,15 +8976,15 @@ async function runVisualDiff(scenes, options) {
8586
8976
  capture,
8587
8977
  files.kind === "complete" ? files.baseline : void 0
8588
8978
  );
8589
- const actualPath = resolve9(outputDirectory.path, `${capture.scene}.actual.png`);
8590
- const diffPath = comparison.diffPng ? resolve9(outputDirectory.path, `${capture.scene}.diff.png`) : void 0;
8979
+ const actualPath = resolve10(outputDirectory.path, `${capture.scene}.actual.png`);
8980
+ const diffPath = comparison.diffPng ? resolve10(outputDirectory.path, `${capture.scene}.diff.png`) : void 0;
8591
8981
  prepared.push({
8592
8982
  capture,
8593
8983
  comparison,
8594
8984
  baselinePath: files.pngPath,
8595
8985
  actualPath,
8596
8986
  diffPath,
8597
- reportPath: resolve9(outputDirectory.path, `${capture.scene}.visual.json`)
8987
+ reportPath: resolve10(outputDirectory.path, `${capture.scene}.visual.json`)
8598
8988
  });
8599
8989
  }
8600
8990
  const document = createTileflowVisualDiffJson(prepared, cwd);
@@ -8614,7 +9004,7 @@ async function runVisualDiff(scenes, options) {
8614
9004
  }
8615
9005
  ]),
8616
9006
  removePaths: prepared.flatMap(
8617
- (item) => item.diffPath ? [] : [resolve9(outputDirectory.path, `${item.capture.scene}.diff.png`)]
9007
+ (item) => item.diffPath ? [] : [resolve10(outputDirectory.path, `${item.capture.scene}.diff.png`)]
8618
9008
  )
8619
9009
  });
8620
9010
  if (options.json) {
@@ -8735,7 +9125,7 @@ async function resolvePreviousBaseline(capture, files) {
8735
9125
  }
8736
9126
  }
8737
9127
  async function readBaselineFiles(directory, scene) {
8738
- const pngPath = resolve9(directory.path, `${scene}.png`);
9128
+ const pngPath = resolve10(directory.path, `${scene}.png`);
8739
9129
  const receiptPath = captureReceiptPath(pngPath);
8740
9130
  const [png, receipt] = await Promise.all([
8741
9131
  readOptionalArtifact(pngPath, directory.boundary, tileflowVisualArtifactLimits2.maximumPngBytes),
@@ -8750,10 +9140,10 @@ async function readBaselineFiles(directory, scene) {
8750
9140
  return { kind: "complete", pngPath, receiptPath, baseline: { png, receipt } };
8751
9141
  }
8752
9142
  async function readOptionalArtifact(path, boundary, maximumBytes) {
8753
- await assertNoSymlinkComponents(dirname11(path), boundary);
9143
+ await assertNoSymlinkComponents(dirname12(path), boundary);
8754
9144
  let info;
8755
9145
  try {
8756
- info = await lstat5(path);
9146
+ info = await lstat6(path);
8757
9147
  } catch (error) {
8758
9148
  if (isMissingFile5(error)) return void 0;
8759
9149
  throw error;
@@ -8773,7 +9163,7 @@ async function readOptionalArtifact(path, boundary, maximumBytes) {
8773
9163
  async function readVisualReference(path) {
8774
9164
  let info;
8775
9165
  try {
8776
- info = await lstat5(path);
9166
+ info = await lstat6(path);
8777
9167
  } catch (error) {
8778
9168
  throw new TileflowCaptureError2("BASELINE_INVALID", "The visual reference PNG cannot be read.", {
8779
9169
  cause: error
@@ -8795,12 +9185,12 @@ async function readVisualReference(path) {
8795
9185
  return new Uint8Array(await readFile6(path));
8796
9186
  }
8797
9187
  async function resolveVisualDirectory(cwd, value, kind) {
8798
- const path = resolve9(cwd, value);
9188
+ const path = resolve10(cwd, value);
8799
9189
  if (path === cwd || path === parse2(path).root) {
8800
9190
  throw new Error(`Visual ${kind} directory must be a dedicated subdirectory.`);
8801
9191
  }
8802
9192
  if (kind === "baseline") {
8803
- for (const managed of [resolve9(cwd, ".tileflow/captures"), resolve9(cwd, ".tileflow/diffs")]) {
9193
+ for (const managed of [resolve10(cwd, ".tileflow/captures"), resolve10(cwd, ".tileflow/diffs")]) {
8804
9194
  if (pathsOverlap2(path, managed)) {
8805
9195
  throw new Error("Visual baselines must not overlap Tileflow-managed capture/diff outputs.");
8806
9196
  }
@@ -8809,7 +9199,7 @@ async function resolveVisualDirectory(cwd, value, kind) {
8809
9199
  const boundary = isInside2(cwd, path) ? cwd : path;
8810
9200
  await assertNoSymlinkComponents(path, boundary);
8811
9201
  try {
8812
- const info = await lstat5(path);
9202
+ const info = await lstat6(path);
8813
9203
  if (info.isSymbolicLink() || !info.isDirectory()) {
8814
9204
  throw new Error(`Visual ${kind} path must be a non-symlink directory.`);
8815
9205
  }
@@ -8836,10 +9226,10 @@ function assertReferenceIsReadOnly(referencePath, outputPaths) {
8836
9226
  }
8837
9227
  }
8838
9228
  function portablePathIdentity2(path) {
8839
- return resolve9(path).normalize("NFC").toLowerCase();
9229
+ return resolve10(path).normalize("NFC").toLowerCase();
8840
9230
  }
8841
9231
  function isInside2(parent, child) {
8842
- const path = relative4(parent, child);
9232
+ const path = relative5(parent, child);
8843
9233
  return path === "" || path !== ".." && !path.startsWith(`..${sep4}`) && !isAbsolute4(path);
8844
9234
  }
8845
9235
  function isMissingFile5(error) {
@@ -8848,8 +9238,8 @@ function isMissingFile5(error) {
8848
9238
 
8849
9239
  // src/workbench-service.ts
8850
9240
  import { opendir, stat as stat2 } from "fs/promises";
8851
- import { resolve as resolve10 } from "path";
8852
- import { z as z7 } from "zod";
9241
+ import { resolve as resolve11 } from "path";
9242
+ import { z as z8 } from "zod";
8853
9243
  import {
8854
9244
  compareTileflowCapturesForReview as compareTileflowCapturesForReview3,
8855
9245
  createTileflowCaptureSession as createTileflowCaptureSession4,
@@ -8862,22 +9252,22 @@ import {
8862
9252
  } from "@tileflow/core";
8863
9253
  import { resolveTileflowPreview } from "@tileflow/dev/preview";
8864
9254
  var prefix = "/__workbench";
8865
- var captureInput = z7.object({
8866
- side: z7.enum(["left", "right"]).default("left"),
8867
- generation: z7.number().int().nonnegative(),
9255
+ var captureInput = z8.object({
9256
+ side: z8.enum(["left", "right"]).default("left"),
9257
+ generation: z8.number().int().nonnegative(),
8868
9258
  name: tileflowCaptureSceneNameSchema2,
8869
9259
  definition: tileflowCaptureSceneSchema3
8870
9260
  }).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())
9261
+ var compareInput = z8.object({ before: z8.string().regex(/^[a-f0-9]{64}$/u), after: z8.string().regex(/^[a-f0-9]{64}$/u) }).strict();
9262
+ var deleteInput = z8.object({ id: z8.string().regex(/^[a-f0-9]{64}$/u) }).strict();
9263
+ var contextInput = z8.object({
9264
+ client: z8.string().regex(/^[a-zA-Z0-9-]{1,80}$/u),
9265
+ side: z8.enum(["left", "right"]),
9266
+ generation: z8.number().int(),
9267
+ context: z8.record(z8.string(), z8.unknown())
8878
9268
  }).strict();
8879
9269
  function createWorkbenchService(options) {
8880
- const directory = resolve10(options.cwd, captureRecordDirectory);
9270
+ const directory = resolve11(options.cwd, captureRecordDirectory);
8881
9271
  const contexts = /* @__PURE__ */ new Map();
8882
9272
  let captureSession = options.captureSession;
8883
9273
  let pending;
@@ -8949,7 +9339,7 @@ function createWorkbenchService(options) {
8949
9339
  break;
8950
9340
  }
8951
9341
  if (!entry.isFile() || !/^[a-f0-9]{64}\.json$/u.test(entry.name)) continue;
8952
- const info = await stat2(resolve10(directory, entry.name));
9342
+ const info = await stat2(resolve11(directory, entry.name));
8953
9343
  entries.push({ id: entry.name.slice(0, -5), modified: info.mtimeMs });
8954
9344
  }
8955
9345
  } catch (error) {
@@ -9088,7 +9478,7 @@ function createWorkbenchService(options) {
9088
9478
  });
9089
9479
  }
9090
9480
  if (path === "/captures") {
9091
- const offset = z7.coerce.number().int().min(0).max(5e3).parse(url.searchParams.get("offset") ?? 0);
9481
+ const offset = z8.coerce.number().int().min(0).max(5e3).parse(url.searchParams.get("offset") ?? 0);
9092
9482
  return json(await history(offset));
9093
9483
  }
9094
9484
  const image = /^\/captures\/([a-f0-9]{64})\.png$/u.exec(path);
@@ -9219,17 +9609,18 @@ var packageJson = JSON.parse(
9219
9609
  var program = new Command();
9220
9610
  var defaultApiUrl = defaultTileflowApiUrl;
9221
9611
  var defaultAppUrl = "https://tileflow.dev";
9222
- var defaultConfigPath = defaultTileflowConfigPath;
9612
+ var defaultConfigPath = defaultTileflowConfigPath2;
9223
9613
  var defaultManifestPath = defaultTileflowManifestPath;
9224
- program.name("tileflow").description("Beautiful maps from config.").enablePositionalOptions().showHelpAfterError(pc6.gray("\nRun tileflow <command> --help for usage.")).showSuggestionAfterError();
9614
+ program.name("tileflow").description("Beautiful maps from config.").enablePositionalOptions().showHelpAfterError(pc7.gray("\nRun tileflow <command> --help for usage.")).showSuggestionAfterError();
9225
9615
  program.addHelpText("after", "\n -V, --version output the version number\n");
9226
9616
  registerDescribeCommand(program);
9227
9617
  registerLanguageCommand(program);
9228
9618
  registerTilesetCommands(program, { defaultApiUrl, loadAuthConfig });
9229
9619
  registerIconSetCommands(program, { defaultApiUrl, loadAuthConfig });
9620
+ registerModelCommands(program, { defaultApiUrl, loadAuthConfig });
9230
9621
  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");
9622
+ const configPath = resolve12(process.cwd(), defaultConfigPath);
9623
+ const ignorePath = resolve12(process.cwd(), ".gitignore");
9233
9624
  if (existsSync(configPath) && !options.force) {
9234
9625
  logWarning(`${pathLabel(defaultConfigPath)} already exists.`);
9235
9626
  printNextSteps([`Run ${command("tileflow init --force")} to replace it.`]);
@@ -9245,9 +9636,9 @@ program.command("init").description("Create a starter tileflow.config.ts").optio
9245
9636
  }
9246
9637
  logSuccess(`Created ${pathLabel(defaultConfigPath)}.`);
9247
9638
  printKeyValue("Path", pathLabel(configPath));
9248
- printKeyValue("Map", pc6.bold("madrid"));
9639
+ printKeyValue("Map", pc7.bold("madrid"));
9249
9640
  printNextSteps([
9250
- `Install ${pc6.cyan("@tileflow/core")} and ${pc6.cyan("@tileflow/maps")} in this project if needed.`,
9641
+ `Install ${pc7.cyan("@tileflow/core")} and ${pc7.cyan("@tileflow/maps")} in this project if needed.`,
9251
9642
  `Preview locally with ${command("tileflow preview")}.`,
9252
9643
  `Validate the config with ${command("tileflow validate")}.`
9253
9644
  ]);
@@ -9383,12 +9774,12 @@ program.command("validate").description("Validate a Tileflow config").option("-c
9383
9774
  try {
9384
9775
  if (!options.json) logInfo(`Validating ${pathLabel(options.config)}.`);
9385
9776
  const loaded = await withTileflowConfigSecretsHidden(
9386
- () => loadTileflowConfigWithInputs4(options.config)
9777
+ () => loadTileflowConfigWithInputs5(options.config)
9387
9778
  );
9388
9779
  const project = loaded.project;
9389
- const baseDirectory = dirname12(loaded.configFile);
9780
+ const baseDirectory = dirname13(loaded.configFile);
9390
9781
  failureDefaults = { code: "CONFIG_INVALID", phase: "config-validation" };
9391
- assertValidTileflowConfig3(project);
9782
+ assertValidTileflowConfig4(project);
9392
9783
  const mapNames = getTileflowMapNames5(project).sort();
9393
9784
  failureDefaults = { code: "ICON_COMPILATION_FAILED", phase: "icon-compilation" };
9394
9785
  const compiledIcons = await compileTileflowIconPackages3(project, {
@@ -9406,7 +9797,7 @@ program.command("validate").description("Validate a Tileflow config").option("-c
9406
9797
  (binding) => options.target === "hosted" ? `${normalizeUrl(options.apiBaseUrl)}/sprites/preflight/${binding.packageHash}/sprite` : `/tileflow/icons/${binding.mapName}/sprite`,
9407
9798
  mapAssetsWithBuildingModels(
9408
9799
  modelAssets,
9409
- (mapName) => options.target === "hosted" ? `${normalizeUrl(options.apiBaseUrl)}/model-bundles/preflight` : `/tileflow/models/${mapName}`
9800
+ options.target === "hosted" ? { hosted: true } : { localBase: (mapName) => `/tileflow/models/${mapName}` }
9410
9801
  )
9411
9802
  );
9412
9803
  failureDefaults = { code: "STYLE_INVALID", phase: "style-validation" };
@@ -9526,7 +9917,7 @@ program.command("build").description("Generate static Tileflow styles").option("
9526
9917
  })
9527
9918
  );
9528
9919
  logSuccess("Built Tileflow artifacts.");
9529
- printKeyValue("Output", pathLabel(resolve11(process.cwd(), options.out)));
9920
+ printKeyValue("Output", pathLabel(resolve12(process.cwd(), options.out)));
9530
9921
  }
9531
9922
  );
9532
9923
  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(
@@ -9839,7 +10230,7 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
9839
10230
  );
9840
10231
  if (result) {
9841
10232
  logSuccess(
9842
- `${result.changed ? "Published" : "Unchanged"} ${pc6.bold(result.mapName)} web + native-v1 (v${result.version}).`
10233
+ `${result.changed ? "Published" : "Unchanged"} ${pc7.bold(result.mapName)} web + native-v1 (v${result.version}).`
9843
10234
  );
9844
10235
  printKeyValue("Manifest", pathLabel(result.manifestPath));
9845
10236
  printKeyValue("Native", link(result.nativeManifestUrl));
@@ -9848,11 +10239,11 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
9848
10239
  }
9849
10240
  logInfo(`Deploying ${pathLabel(options.config)}.`);
9850
10241
  const loaded = await withTileflowConfigSecretsHidden(
9851
- () => loadTileflowConfigWithInputs4(options.config)
10242
+ () => loadTileflowConfigWithInputs5(options.config)
9852
10243
  );
9853
10244
  const project = loaded.project;
9854
- const baseDirectory = dirname12(loaded.configFile);
9855
- assertValidTileflowConfig3(project);
10245
+ const baseDirectory = dirname13(loaded.configFile);
10246
+ assertValidTileflowConfig4(project);
9856
10247
  const configuredMapNames = getTileflowMapNames5(project);
9857
10248
  const mapNames = selectManagedDeployMaps(configuredMapNames, options);
9858
10249
  if (!mapNames) return;
@@ -9883,7 +10274,7 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
9883
10274
  const preflightMapAssets = createCompiledMapAssets(
9884
10275
  compiledIcons,
9885
10276
  (binding) => `${apiUrl}/sprites/preflight/${binding.packageHash}/sprite`,
9886
- mapAssetsWithBuildingModels(modelAssets, () => `${apiUrl}/model-bundles/preflight`)
10277
+ mapAssetsWithBuildingModels(modelAssets, { hosted: true })
9887
10278
  );
9888
10279
  const compiledPreflightStyles = createTileflowStyles2(deploymentProject, {
9889
10280
  apiBaseUrl: apiUrl,
@@ -9953,29 +10344,10 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
9953
10344
  );
9954
10345
  const action = uploaded.value.changed === false ? "Reused" : "Uploaded";
9955
10346
  logSuccess(
9956
- `${action} icon package ${pc6.bold(binding?.label ?? "Icons")} (${plural(iconPackage.manifest.iconNames.length, "icon")}, ${formatBytes3(totalBytes)}, ${iconPackage.contentHash.slice(0, 12)}).`
10347
+ `${action} icon package ${pc7.bold(binding?.label ?? "Icons")} (${plural(iconPackage.manifest.iconNames.length, "icon")}, ${formatBytes4(totalBytes)}, ${iconPackage.contentHash.slice(0, 12)}).`
9957
10348
  );
9958
10349
  hostedSpriteByPackageHash.set(iconPackage.contentHash, uploaded.value.spriteUrl);
9959
10350
  }
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
10351
  const hostedMapAssets = createCompiledMapAssets(
9980
10352
  compiledIcons,
9981
10353
  (binding) => {
@@ -9983,11 +10355,7 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
9983
10355
  if (!sprite) throw new Error(`Missing hosted sprite URL for map ${binding.mapName}`);
9984
10356
  return sprite;
9985
10357
  },
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
- })
10358
+ mapAssetsWithBuildingModels(modelAssets, { hosted: true })
9991
10359
  );
9992
10360
  const compiledHostedStyles = createTileflowStyles2(deploymentProject, {
9993
10361
  apiBaseUrl: api.apiUrl,
@@ -10028,7 +10396,7 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
10028
10396
  }
10029
10397
  const action = uploaded.value.changed === false ? "Reused" : "Uploaded";
10030
10398
  logSuccess(
10031
- `${action} font bundle (${plural(bundle.manifest.fontFaces.length, "face")}, ${formatBytes3(uploaded.value.totalBytes)}, ${bundle.contentHash.slice(0, 12)}).`
10399
+ `${action} font bundle (${plural(bundle.manifest.fontFaces.length, "face")}, ${formatBytes4(uploaded.value.totalBytes)}, ${bundle.contentHash.slice(0, 12)}).`
10032
10400
  );
10033
10401
  hostedFontBaseByHash.set(bundle.contentHash, uploaded.value.baseUrl);
10034
10402
  }
@@ -10078,12 +10446,7 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
10078
10446
  fileName: `icons/${mapName}/${file.fileName}`,
10079
10447
  source: file.source
10080
10448
  })),
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
- }))
10449
+ ...hostedFonts.bundles[mapName]?.files ?? []
10087
10450
  ],
10088
10451
  lineage: deploymentProject.mapMetadata?.[mapName]?.lineage ?? [
10089
10452
  { id: map.id, mapVersion: map.version }
@@ -10093,7 +10456,7 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
10093
10456
  fonts: hostedFonts.sourceIdentities[mapName] ?? [],
10094
10457
  icons: compiledIcons.sourceIdentities[mapName] ?? [],
10095
10458
  ...compiledIcons.compositions[mapName] ? { iconComposition: compiledIcons.compositions[mapName] } : {},
10096
- ...modelAssets.identities[mapName] ? { models: modelAssets.identities[mapName] } : {}
10459
+ ...hostedModelSourceIdentities(modelAssets, mapName) ? { models: hostedModelSourceIdentities(modelAssets, mapName) } : {}
10097
10460
  },
10098
10461
  styles: styles[mapName]
10099
10462
  }
@@ -10106,13 +10469,13 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
10106
10469
  const iconBinding = bindingsByMap.get(mapName);
10107
10470
  const iconPackage = iconBinding ? packagesByHash.get(iconBinding.packageHash) : void 0;
10108
10471
  const fontBundle = hostedFonts.bundles[mapName];
10109
- const modelBundle = modelAssets.bundles[mapName];
10472
+ const modelSources = teamModelSources(modelAssets, mapName);
10110
10473
  return {
10111
10474
  iconBinding,
10112
10475
  iconComposition: compiledIcons.compositions[mapName],
10113
10476
  iconPackage,
10114
10477
  fontBundle,
10115
- modelBundle,
10478
+ modelSources,
10116
10479
  mapName,
10117
10480
  teamSources: hostedThemeFamilies[mapName].teamSources,
10118
10481
  buildManifest: {
@@ -10132,7 +10495,7 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
10132
10495
  iconComposition,
10133
10496
  iconPackage,
10134
10497
  mapName,
10135
- modelBundle,
10498
+ modelSources,
10136
10499
  teamSources,
10137
10500
  styles: themeStyles
10138
10501
  } = deployment;
@@ -10143,7 +10506,7 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
10143
10506
  }
10144
10507
  const themeNames = Object.keys(themeStyles).sort();
10145
10508
  logInfo(
10146
- `Deploying compiled map ${pc6.bold(mapName)} (${plural(themeNames.length, "theme")}).`
10509
+ `Deploying compiled map ${pc7.bold(mapName)} (${plural(themeNames.length, "theme")}).`
10147
10510
  );
10148
10511
  const response = await publishHostedStyle(
10149
10512
  api,
@@ -10167,7 +10530,7 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
10167
10530
  } : {},
10168
10531
  ...iconComposition ? { iconComposition } : {},
10169
10532
  ...fontBundle ? { fontBundle: { contentHash: fontBundle.contentHash } } : {},
10170
- ...modelBundle ? { modelBundle: { contentHash: modelBundle.contentHash } } : {},
10533
+ ...modelSources ? { teamModelSources: modelSources } : {},
10171
10534
  source
10172
10535
  },
10173
10536
  `Deploy response for ${mapName}`
@@ -10225,9 +10588,15 @@ program.command("deploy").description("Deploy maps to Tileflow and write the fro
10225
10588
  };
10226
10589
  const versionLabel = Number.isInteger(body.version) ? ` (v${body.version})` : "";
10227
10590
  if (body.changed === false) {
10228
- logSuccess(`Unchanged ${pc6.bold(mapName)}${versionLabel}.`);
10591
+ logSuccess(`Unchanged ${pc7.bold(mapName)}${versionLabel}.`);
10229
10592
  } else {
10230
- logSuccess(`Published ${pc6.bold(mapName)}${versionLabel}.`);
10593
+ logSuccess(`Published ${pc7.bold(mapName)}${versionLabel}.`);
10594
+ }
10595
+ for (const teamModel of body.teamModels ?? []) {
10596
+ if (teamModel.sources !== "differ") continue;
10597
+ logWarning(
10598
+ `3D model ${pc7.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.`
10599
+ );
10231
10600
  }
10232
10601
  }
10233
10602
  const manifestPath = await writeDeployManifest(options.manifest, {
@@ -10589,12 +10958,12 @@ async function runTileflowComparisonPreview(options, address) {
10589
10958
  }
10590
10959
  function comparisonCaptureConfigArgument(configPath) {
10591
10960
  const cwd = realpathSync(process.cwd());
10592
- const requestedConfigPath = resolve11(process.cwd(), configPath);
10593
- const defaultRequestedPath = resolve11(process.cwd(), defaultConfigPath);
10961
+ const requestedConfigPath = resolve12(process.cwd(), configPath);
10962
+ const defaultRequestedPath = resolve12(process.cwd(), defaultConfigPath);
10594
10963
  const absoluteConfigPath = existsSync(requestedConfigPath) ? realpathSync(requestedConfigPath) : requestedConfigPath;
10595
10964
  const absoluteDefaultPath = existsSync(defaultRequestedPath) ? realpathSync(defaultRequestedPath) : defaultRequestedPath;
10596
10965
  if (absoluteConfigPath === absoluteDefaultPath) return void 0;
10597
- const relativeConfigPath = relative5(cwd, absoluteConfigPath);
10966
+ const relativeConfigPath = relative6(cwd, absoluteConfigPath);
10598
10967
  if (relativeConfigPath === "" || relativeConfigPath === ".." || relativeConfigPath.startsWith(`..${sep5}`) || isAbsolute5(relativeConfigPath)) {
10599
10968
  throw new Error(
10600
10969
  "Comparison configs must be inside the current working directory so Copy command can remain reproducible."
@@ -10634,7 +11003,7 @@ async function loginWithDeviceFlow(options) {
10634
11003
  codeChallenge,
10635
11004
  deviceName: hostname()
10636
11005
  });
10637
- printKeyValue("Code", pc6.bold(authorization.userCode));
11006
+ printKeyValue("Code", pc7.bold(authorization.userCode));
10638
11007
  printKeyValue("URL", link(authorization.verificationUriComplete));
10639
11008
  if (options.noBrowser) {
10640
11009
  logMuted("Open the URL above to continue.");
@@ -10644,7 +11013,7 @@ async function loginWithDeviceFlow(options) {
10644
11013
  output: process.stdout
10645
11014
  });
10646
11015
  try {
10647
- await readline.question(`${pc6.cyan("?")} Press Enter to open Tileflow Dashboard...`);
11016
+ await readline.question(`${pc7.cyan("?")} Press Enter to open Tileflow Dashboard...`);
10648
11017
  openBrowser(authorization.verificationUriComplete);
10649
11018
  } finally {
10650
11019
  readline.close();
@@ -10695,9 +11064,9 @@ function openBrowser(url, errorsToStderr = false) {
10695
11064
  });
10696
11065
  }
10697
11066
  async function writeDeployManifest(manifestPath, manifest) {
10698
- const outputPath = resolve11(process.cwd(), manifestPath);
11067
+ const outputPath = resolve12(process.cwd(), manifestPath);
10699
11068
  await writeAtomicFile({
10700
- boundaryPath: dirname12(outputPath),
11069
+ boundaryPath: dirname13(outputPath),
10701
11070
  force: true,
10702
11071
  label: "Deploy manifest",
10703
11072
  managed: true,
@@ -10785,7 +11154,7 @@ function validateDeployManifestMapNames(mapNames) {
10785
11154
  async function loadExistingDeployManifest(manifestPath, options) {
10786
11155
  let source;
10787
11156
  try {
10788
- source = await readFile7(resolve11(process.cwd(), manifestPath), "utf8");
11157
+ source = await readFile7(resolve12(process.cwd(), manifestPath), "utf8");
10789
11158
  } catch (error) {
10790
11159
  if (error.code === "ENOENT") return null;
10791
11160
  throw new Error("Existing Tileflow manifest could not be read.");
@@ -10845,7 +11214,7 @@ async function requireApiOptions(options, behavior = {}) {
10845
11214
  if (!behavior.silent) {
10846
11215
  logError("CI requires an explicit Tileflow credential with access to the target Map.");
10847
11216
  printNextSteps([
10848
- `Set ${pc6.cyan("TILEFLOW_API_KEY")} from the CI secret store.`,
11217
+ `Set ${pc7.cyan("TILEFLOW_API_KEY")} from the CI secret store.`,
10849
11218
  "The saved personal account session is never used in CI."
10850
11219
  ]);
10851
11220
  }
@@ -10914,17 +11283,17 @@ function printCliError(error) {
10914
11283
  }
10915
11284
  const message = error instanceof Error ? error.message : "Command failed.";
10916
11285
  if (message.includes("Cannot find module '@tileflow/core'")) {
10917
- logError(`Cannot load ${pc6.cyan("@tileflow/core")} from this project.`);
11286
+ logError(`Cannot load ${pc7.cyan("@tileflow/core")} from this project.`);
10918
11287
  printNextSteps([
10919
- `Install ${pc6.cyan("@tileflow/core")} in this project.`,
11288
+ `Install ${pc7.cyan("@tileflow/core")} in this project.`,
10920
11289
  `Run the command again from the project root.`
10921
11290
  ]);
10922
11291
  return;
10923
11292
  }
10924
11293
  if (message.includes("Cannot find module '@tileflow/maps'")) {
10925
- logError(`Cannot load ${pc6.cyan("@tileflow/maps")} from this project.`);
11294
+ logError(`Cannot load ${pc7.cyan("@tileflow/maps")} from this project.`);
10926
11295
  printNextSteps([
10927
- `Install ${pc6.cyan("@tileflow/maps")} in this project.`,
11296
+ `Install ${pc7.cyan("@tileflow/maps")} in this project.`,
10928
11297
  `Run the command again from the project root.`
10929
11298
  ]);
10930
11299
  return;
@@ -10935,51 +11304,51 @@ function printCliError(error) {
10935
11304
  }
10936
11305
  }
10937
11306
  function logSuccess(message) {
10938
- console.log(`${pc6.green("\u2713")} ${message}`);
11307
+ console.log(`${pc7.green("\u2713")} ${message}`);
10939
11308
  }
10940
11309
  function logError(message) {
10941
- console.log(`${pc6.red("\u2715")} ${message}`);
11310
+ console.log(`${pc7.red("\u2715")} ${message}`);
10942
11311
  }
10943
11312
  function logWarning(message) {
10944
- console.log(`${pc6.yellow("!")} ${message}`);
11313
+ console.log(`${pc7.yellow("!")} ${message}`);
10945
11314
  }
10946
11315
  function logInfo(message) {
10947
- console.log(`${pc6.cyan("i")} ${message}`);
11316
+ console.log(`${pc7.cyan("i")} ${message}`);
10948
11317
  }
10949
11318
  function logMuted(message) {
10950
- console.log(pc6.gray(message));
11319
+ console.log(pc7.gray(message));
10951
11320
  }
10952
11321
  function printTitle(title, subtitle) {
10953
- console.log(pc6.bold(title));
11322
+ console.log(pc7.bold(title));
10954
11323
  if (subtitle) {
10955
- console.log(pc6.gray(subtitle));
11324
+ console.log(pc7.gray(subtitle));
10956
11325
  }
10957
11326
  }
10958
11327
  function printKeyValue(label, value) {
10959
- console.log(`${pc6.gray(`${label}:`.padEnd(13))} ${value}`);
11328
+ console.log(`${pc7.gray(`${label}:`.padEnd(13))} ${value}`);
10960
11329
  }
10961
11330
  function printChecks(items) {
10962
11331
  for (const item of items) {
10963
- console.log(` ${pc6.green("\u2713")} ${item}`);
11332
+ console.log(` ${pc7.green("\u2713")} ${item}`);
10964
11333
  }
10965
11334
  }
10966
11335
  function printNextSteps(steps) {
10967
11336
  if (steps.length === 0) return;
10968
11337
  console.log(`
10969
- ${pc6.bold("Next steps")}`);
11338
+ ${pc7.bold("Next steps")}`);
10970
11339
  for (const step of steps) {
10971
- console.log(` ${pc6.cyan("\u2022")} ${step}`);
11340
+ console.log(` ${pc7.cyan("\u2022")} ${step}`);
10972
11341
  }
10973
11342
  }
10974
11343
  function printValidationErrors(messages) {
10975
11344
  logError("Tileflow config has errors.");
10976
11345
  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}`);
11346
+ const path = message.path ? pc7.cyan(message.path) : pc7.cyan("(root)");
11347
+ console.log(` ${pc7.red("\u2715")} ${path} ${pc7.gray("-")} ${message.message}`);
10979
11348
  }
10980
11349
  }
10981
11350
  function printAccountDetails(session) {
10982
- printKeyValue("Account", pc6.bold(session.account.name));
11351
+ printKeyValue("Account", pc7.bold(session.account.name));
10983
11352
  printKeyValue("Email", session.account.email);
10984
11353
  printKeyValue("API", link(session.apiOrigin));
10985
11354
  printKeyValue("Expires", formatDate(session.expiresAt));
@@ -11008,11 +11377,11 @@ function printDeployedMaps(maps) {
11008
11377
  const entries = Object.entries(maps);
11009
11378
  if (entries.length === 0) return;
11010
11379
  console.log(`
11011
- ${pc6.bold("Maps")}`);
11380
+ ${pc7.bold("Maps")}`);
11012
11381
  for (const [name, map] of entries) {
11013
11382
  for (const [themeName, theme] of Object.entries(map.themes)) {
11014
11383
  const label = `${name}/${themeName}`;
11015
- console.log(` ${pc6.green("\u2713")} ${label.padEnd(24)} ${link(theme.styleUrl)}`);
11384
+ console.log(` ${pc7.green("\u2713")} ${label.padEnd(24)} ${link(theme.styleUrl)}`);
11016
11385
  }
11017
11386
  }
11018
11387
  }
@@ -11030,13 +11399,13 @@ function plural(count, singular, pluralForm = `${singular}s`) {
11030
11399
  return `${count} ${count === 1 ? singular : pluralForm}`;
11031
11400
  }
11032
11401
  function command(value) {
11033
- return pc6.cyan(value);
11402
+ return pc7.cyan(value);
11034
11403
  }
11035
11404
  function link(value) {
11036
- return pc6.cyan(value);
11405
+ return pc7.cyan(value);
11037
11406
  }
11038
11407
  function pathLabel(value) {
11039
- return pc6.cyan(value);
11408
+ return pc7.cyan(value);
11040
11409
  }
11041
11410
  function starterConfig() {
11042
11411
  return `import { defineMap, defineTheme, labels, poi } from "@tileflow/core";
@@ -11082,19 +11451,19 @@ function printHostedCompatibilityIssues(issues) {
11082
11451
  }
11083
11452
  function printMapStatus(status, apiUrl) {
11084
11453
  printTitle("Tileflow status");
11085
- printKeyValue("Map", pc6.bold(status.mapId));
11454
+ printKeyValue("Map", pc7.bold(status.mapId));
11086
11455
  console.log(`
11087
- ${pc6.bold("Styles")}`);
11456
+ ${pc7.bold("Styles")}`);
11088
11457
  if (status.styles.length === 0) {
11089
11458
  logMuted(" No styles deployed.");
11090
11459
  }
11091
11460
  for (const style of status.styles) {
11092
11461
  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))}`
11462
+ ` ${pc7.green("\u2713")} ${style.environment.padEnd(16)} ${link(`${apiUrl}/maps/${style.mapId}/style.json`).padEnd(36)} ${formatBytes4(style.size).padStart(10)} ${pc7.gray(formatDate(style.uploaded))}`
11094
11463
  );
11095
11464
  }
11096
11465
  }
11097
- function formatBytes3(bytes) {
11466
+ function formatBytes4(bytes) {
11098
11467
  if (bytes < 1024) return `${bytes} B`;
11099
11468
  const units = ["KB", "MB", "GB", "TB"];
11100
11469
  let value = bytes;