tileflow 0.1.0-alpha.1 → 0.1.0-alpha.3

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/README.md CHANGED
@@ -131,6 +131,41 @@ Each manifest command entry declares its `outputKind`, `outputVersion`, and stab
131
131
  `outputSchemaRef`. `raw-authoring-manifest` and `raw-config-reference` identify the two bootstrap
132
132
  documents above; `command-envelope` identifies the ordinary version-1 CLI contract.
133
133
 
134
+ ## Coordinates
135
+
136
+ These local commands require an explicitly provided compatible runtime distribution. No public
137
+ native runtime download is currently offered; package installation or the default setup URL does
138
+ not supply one. The verified production profile is Linux x64 on Debian 12, glibc 2.36 and Node 24.
139
+ The examples below use development artifacts and do not establish public platform support.
140
+
141
+ Coordinates commands use a locally provisioned execution release. They do not use Map
142
+ configuration, account state, or a Hosted fallback. Provision first from a distribution source, then
143
+ run the local commands against the active cache entry or an explicit runtime directory.
144
+
145
+ ```sh
146
+ tileflow setup coordinates --source /path/to/distribution.json --cache-dir /path/to/coordinates-cache --development --json
147
+ tileflow coordinates search --query ETRS89 --runtime-dir /path/to/installed-release --development --json
148
+ tileflow coordinates describe --id EPSG:25830 --runtime-dir /path/to/installed-release --development --json
149
+ tileflow coordinates operations --from EPSG:4258 --to EPSG:25832 --runtime-dir /path/to/installed-release --development --json
150
+ tileflow coordinates transform --from EPSG:4258 --to EPSG:25832 --positions '[[12,55]]' --runtime-dir /path/to/installed-release --development --json
151
+ ```
152
+
153
+ `setup coordinates --source` accepts a distribution URL, a local `distribution.json`, or a directory
154
+ that contains one. `--archive` is a local archive alias and rejects URLs. Setup verifies every asset
155
+ declared by the selected distribution before it records the active release. `--development` is
156
+ required for a development distribution.
157
+
158
+ Every execution command accepts `--runtime-dir`, `--release`, and `--development`. With no
159
+ `--runtime-dir`, it opens the cache entry selected by setup. `search` accepts `--query`, `describe`
160
+ accepts `--id`, `operations` accepts `--from` and `--to`, and `transform` also accepts JSON
161
+ `--positions`.
162
+
163
+ For a complete request, use exactly one of `--request '<json>'` or `--input <file|->`. These forms
164
+ cannot be combined with the command's convenience request flags. Input is limited to 32 KiB, must be
165
+ valid UTF-8 JSON, and `-` reads standard input. Success writes one JSON document to stdout. A
166
+ structured failure writes one JSON document to stderr and exits with status 1. Plain `--help` remains
167
+ human-readable; `--help --json` is a structured failure.
168
+
134
169
  `tileflow explain` compiles exactly one map/theme selection through `createStyleResult()` and emits
135
170
  its structured `report` and safe compiler `diagnostics`; it deliberately omits the usually large
136
171
  MapLibre `style`. In a singular config, `--map` is optional; a multi-map workbench requires it.
@@ -490,6 +525,11 @@ npx tileflow status --map-id map_AbCdEfGhIjKlMnOp
490
525
  npx tileflow logout
491
526
  ```
492
527
 
528
+ Changing or resetting the hosted account password revokes saved personal CLI sessions. The next
529
+ account or hosted command stops with a clear message and asks you to run `tileflow login` again.
530
+ Project API and CI keys are independent project credentials and are not removed by a personal
531
+ password change.
532
+
493
533
  The dashboard creates the Map and browser-access policy before repository setup. Keep that exact
494
534
  Map ID in the deploy action. If the config contains several maps, select the authored map explicitly:
495
535
 
package/dist/index.js CHANGED
@@ -10,6 +10,7 @@ import { hostname } from "os";
10
10
  import { dirname as dirname10, isAbsolute as isAbsolute4, relative as relative5, resolve as resolve7, sep as sep5 } from "path";
11
11
  import { createInterface } from "readline/promises";
12
12
  import pc3 from "picocolors";
13
+ import { createLocalCoordinates, setupCoordinates } from "@tileflow/coordinates-runtime";
13
14
  import {
14
15
  auditTileflowMapThemeValues,
15
16
  getTileflowStyleFontFaces,
@@ -64,6 +65,7 @@ import { chmod, mkdir, open, readFile, rename, unlink } from "fs/promises";
64
65
  import { homedir } from "os";
65
66
  import { dirname, resolve } from "path";
66
67
  import { z } from "zod";
68
+ var rejectedAccountSessionMessage = "The saved Tileflow account session is no longer valid. Run tileflow login to authorize this machine again.";
67
69
  var maximumAuthFileBytes = 1024 * 1024;
68
70
  var maximumOrigins = 16;
69
71
  var accountSchema = z.object({
@@ -1588,6 +1590,353 @@ function printHumanConfigInspection(inspection) {
1588
1590
  console.log("Run tileflow inspect --json for the complete machine-readable result.");
1589
1591
  }
1590
1592
 
1593
+ // src/coordinates-command.ts
1594
+ import { lstat as lstat2, open as open3 } from "fs/promises";
1595
+ import { CoordinatesSetupError } from "@tileflow/coordinates-runtime";
1596
+ import {
1597
+ CoordinatesContractError,
1598
+ coordinatesFailureSchema,
1599
+ coordinatesLimits,
1600
+ parseCoordinatesJsonRequest,
1601
+ parseCoordinatesRequest
1602
+ } from "@tileflow/coordinates/contract";
1603
+ var CoordinatesInputError = class extends Error {
1604
+ constructor(reason) {
1605
+ super("Coordinates input could not be read.");
1606
+ this.reason = reason;
1607
+ }
1608
+ reason;
1609
+ };
1610
+ function registerCoordinatesCommands(program2, dependencies) {
1611
+ const setup = program2.commands.find((command2) => command2.name() === "setup") ?? program2.command("setup").description("Provision local Tileflow tooling");
1612
+ configureCommand(
1613
+ setup.command("coordinates").description("Provision an offline Coordinates execution release").option("--source <url-or-path>", "distribution manifest URL or offline path").option("--archive <path>", "offline archive path alias").option("--cache-dir <path>", "Coordinates cache directory").option("--release <release-id>", "required Coordinates release identity").option("--development", "allow a development distribution").option("--json", "print one JSON receipt")
1614
+ ).action(async (options, command2) => {
1615
+ if (options.help) {
1616
+ printHelp(command2, options.json, () => printSetupFailure());
1617
+ return;
1618
+ }
1619
+ if (command2.args.length > 0 || options.source && options.archive || options.archive && isRemoteLocation(options.archive)) {
1620
+ printSetupFailure(new CoordinatesSetupError("COORDINATES_SETUP_INVALID_REQUEST"));
1621
+ return;
1622
+ }
1623
+ const controller = (dependencies.createSignalController ?? installSignalAbortController)();
1624
+ try {
1625
+ const receipt = await dependencies.setupCoordinates({
1626
+ source: options.archive ?? options.source,
1627
+ cacheDirectory: options.cacheDir,
1628
+ requiredReleaseId: options.release,
1629
+ allowDevelopment: options.development,
1630
+ signal: controller.signal
1631
+ });
1632
+ printDocument(receipt);
1633
+ } catch (error) {
1634
+ printSetupFailure(error);
1635
+ } finally {
1636
+ controller.close();
1637
+ }
1638
+ });
1639
+ const coordinates = program2.command("coordinates").description("Inspect and execute Coordinates requests using a provisioned local release");
1640
+ configureCommand(coordinates).option("--json", "print one JSON response");
1641
+ coordinates.action((options, command2) => {
1642
+ if (options.help) {
1643
+ printHelp(
1644
+ command2,
1645
+ options.json,
1646
+ () => printFailure(invalidRequest(null, "INVALID_VALUE", { path: ["command"] }))
1647
+ );
1648
+ return;
1649
+ }
1650
+ const reason = command2.args.length === 0 ? "INVALID_VALUE" : "UNKNOWN_FIELD";
1651
+ printFailure(invalidRequest(null, reason, { path: ["command"] }));
1652
+ });
1653
+ registerOperation(
1654
+ coordinates,
1655
+ "search",
1656
+ dependencies,
1657
+ (options) => parseRequest("search", options, { query: options.query })
1658
+ );
1659
+ registerOperation(
1660
+ coordinates,
1661
+ "describe",
1662
+ dependencies,
1663
+ (options) => parseRequest("describe", options, { id: options.id })
1664
+ );
1665
+ registerOperation(
1666
+ coordinates,
1667
+ "operations",
1668
+ dependencies,
1669
+ (options) => parseRequest("operations", options, { from: options.from, to: options.to })
1670
+ );
1671
+ registerOperation(
1672
+ coordinates,
1673
+ "transform",
1674
+ dependencies,
1675
+ (options) => parseTransformRequest(options)
1676
+ );
1677
+ }
1678
+ function registerOperation(coordinates, name, dependencies, parse3) {
1679
+ const command2 = configureCommand(
1680
+ coordinates.command(name).description(`Run the local Coordinates ${name} operation`).option("--request <json>", "complete JSON request").option("--input <path-or-dash>", "complete JSON request file, or - for standard input").option("--runtime-dir <path>", "prepared Coordinates runtime directory").option("--release <release-id>", "required Coordinates release identity").option("--development", "allow a development runtime").option("--json", "print one JSON response")
1681
+ );
1682
+ if (name === "search") command2.option("--query <text>", "search query");
1683
+ if (name === "describe") command2.option("--id <crs-id>", "CRS identifier");
1684
+ if (name === "operations" || name === "transform") {
1685
+ command2.option("--from <crs-id>", "source CRS identifier").option("--to <crs-id>", "target CRS identifier");
1686
+ }
1687
+ if (name === "transform") command2.option("--positions <json>", "JSON coordinate positions");
1688
+ command2.action(async (options, invoked) => {
1689
+ const parent = invoked.parent?.opts();
1690
+ const wantsHelp = options.help ?? parent?.help;
1691
+ const wantsJson = options.json ?? parent?.json;
1692
+ if (wantsHelp) {
1693
+ printHelp(
1694
+ invoked,
1695
+ wantsJson,
1696
+ () => printFailure(invalidRequest(name, "INVALID_VALUE", { path: ["command"] }))
1697
+ );
1698
+ return;
1699
+ }
1700
+ if (invoked.args.length > 0) {
1701
+ printFailure(invalidRequest(name, "UNKNOWN_FIELD", { path: ["options"] }));
1702
+ return;
1703
+ }
1704
+ const controller = (dependencies.createSignalController ?? installSignalAbortController)();
1705
+ let local;
1706
+ try {
1707
+ const request = await resolveRequest(
1708
+ name,
1709
+ options,
1710
+ parse3,
1711
+ dependencies.readInput,
1712
+ controller.signal
1713
+ );
1714
+ local = await dependencies.createLocalCoordinates({
1715
+ directory: options.runtimeDir,
1716
+ requiredReleaseId: options.release,
1717
+ allowDevelopment: options.development,
1718
+ signal: controller.signal
1719
+ });
1720
+ const response = await local[name](request, { signal: controller.signal });
1721
+ await local.close();
1722
+ local = void 0;
1723
+ printDocument(response);
1724
+ } catch (error) {
1725
+ printFailure(error, name);
1726
+ } finally {
1727
+ try {
1728
+ await local?.close();
1729
+ } catch {
1730
+ } finally {
1731
+ controller.close();
1732
+ }
1733
+ }
1734
+ });
1735
+ }
1736
+ function configureCommand(command2) {
1737
+ return command2.allowUnknownOption(true).allowExcessArguments(true).helpOption(false).option("-h, --help", "display help");
1738
+ }
1739
+ async function resolveRequest(command2, options, parse3, readInput = readCoordinatesInput, signal) {
1740
+ const rawRequest = options.request !== void 0 || options.input !== void 0;
1741
+ if (options.request !== void 0 && options.input !== void 0 || rawRequest && hasConvenienceOptions(command2, options)) {
1742
+ throw invalidRequest(command2, "INVALID_VALUE", { path: ["request"] });
1743
+ }
1744
+ if (options.request !== void 0) return parseCoordinatesJsonRequest(command2, options.request);
1745
+ if (options.input !== void 0) {
1746
+ try {
1747
+ const input = await readInput(options.input, signal ?? new AbortController().signal);
1748
+ if (signal?.aborted) throw cancelledRequest(command2);
1749
+ return parseCoordinatesJsonRequest(command2, input);
1750
+ } catch (error) {
1751
+ if (signal?.aborted) throw cancelledRequest(command2);
1752
+ if (error instanceof CoordinatesInputError) throw invalidRequest(command2, error.reason);
1753
+ throw error;
1754
+ }
1755
+ }
1756
+ return parse3(options);
1757
+ }
1758
+ function hasConvenienceOptions(command2, options) {
1759
+ if (command2 === "search") return options.query !== void 0;
1760
+ if (command2 === "describe") return options.id !== void 0;
1761
+ if (command2 === "operations") {
1762
+ const pair = options;
1763
+ return pair.from !== void 0 || pair.to !== void 0;
1764
+ }
1765
+ const transform = options;
1766
+ return transform.from !== void 0 || transform.to !== void 0 || transform.positions !== void 0;
1767
+ }
1768
+ function parseRequest(command2, options, request) {
1769
+ return parseCoordinatesRequest(command2, request);
1770
+ }
1771
+ function parseTransformRequest(options) {
1772
+ let positions;
1773
+ try {
1774
+ positions = options.positions === void 0 ? void 0 : JSON.parse(options.positions);
1775
+ } catch {
1776
+ throw invalidRequest("transform", "INVALID_JSON");
1777
+ }
1778
+ return parseCoordinatesRequest("transform", { from: options.from, to: options.to, positions });
1779
+ }
1780
+ async function readCoordinatesInput(input, signal) {
1781
+ if (input === "-") return readStandardInput(signal);
1782
+ assertNotAborted(signal);
1783
+ let info;
1784
+ try {
1785
+ info = await lstat2(input);
1786
+ } catch {
1787
+ throw new CoordinatesInputError("INVALID_VALUE");
1788
+ }
1789
+ if (!info.isFile() || info.isSymbolicLink()) throw new CoordinatesInputError("INVALID_VALUE");
1790
+ if (info.size > coordinatesLimits.maximumRequestBytes) {
1791
+ throw new CoordinatesInputError("REQUEST_TOO_LARGE");
1792
+ }
1793
+ const handle = await open3(input, "r");
1794
+ try {
1795
+ let length = 0;
1796
+ const bytes = Buffer.alloc(coordinatesLimits.maximumRequestBytes + 1);
1797
+ while (length < bytes.length) {
1798
+ assertNotAborted(signal);
1799
+ const { bytesRead } = await handle.read(bytes, length, bytes.length - length, length);
1800
+ if (bytesRead === 0) break;
1801
+ length += bytesRead;
1802
+ }
1803
+ assertNotAborted(signal);
1804
+ if (length > coordinatesLimits.maximumRequestBytes) {
1805
+ throw new CoordinatesInputError("REQUEST_TOO_LARGE");
1806
+ }
1807
+ return decodeInput(bytes.subarray(0, length));
1808
+ } finally {
1809
+ await handle.close();
1810
+ }
1811
+ }
1812
+ async function readStandardInput(signal) {
1813
+ const chunks = [];
1814
+ let length = 0;
1815
+ return new Promise((resolve8, reject) => {
1816
+ const finish = (result) => {
1817
+ process.stdin.removeListener("data", onData);
1818
+ process.stdin.removeListener("end", onEnd);
1819
+ process.stdin.removeListener("error", onError);
1820
+ signal.removeEventListener("abort", onAbort);
1821
+ result();
1822
+ };
1823
+ const onAbort = () => {
1824
+ process.stdin.pause();
1825
+ finish(() => reject(new CoordinatesInputError("INVALID_VALUE")));
1826
+ };
1827
+ const onData = (chunk) => {
1828
+ const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
1829
+ length += bytes.length;
1830
+ if (length > coordinatesLimits.maximumRequestBytes) {
1831
+ process.stdin.pause();
1832
+ finish(() => reject(new CoordinatesInputError("REQUEST_TOO_LARGE")));
1833
+ return;
1834
+ }
1835
+ chunks.push(bytes);
1836
+ };
1837
+ const onEnd = () => finish(() => resolve8(decodeInput(Buffer.concat(chunks))));
1838
+ const onError = () => finish(() => reject(new CoordinatesInputError("INVALID_VALUE")));
1839
+ if (signal.aborted) {
1840
+ onAbort();
1841
+ return;
1842
+ }
1843
+ process.stdin.on("data", onData);
1844
+ process.stdin.once("end", onEnd);
1845
+ process.stdin.once("error", onError);
1846
+ signal.addEventListener("abort", onAbort, { once: true });
1847
+ process.stdin.resume();
1848
+ });
1849
+ }
1850
+ function invalidRequest(command2, reason, details = {}) {
1851
+ return new CoordinatesContractError({
1852
+ schemaVersion: 1,
1853
+ ok: false,
1854
+ command: command2,
1855
+ releaseId: null,
1856
+ provenance: null,
1857
+ warnings: [],
1858
+ usage: null,
1859
+ error: { code: "COORDINATES_INVALID_REQUEST", reason, details, phase: "input" }
1860
+ });
1861
+ }
1862
+ function cancelledRequest(command2) {
1863
+ return new CoordinatesContractError({
1864
+ schemaVersion: 1,
1865
+ ok: false,
1866
+ command: command2,
1867
+ releaseId: null,
1868
+ provenance: null,
1869
+ warnings: [],
1870
+ usage: null,
1871
+ error: { code: "COORDINATES_CANCELLED", reason: "CANCELLED", details: {}, phase: "input" }
1872
+ });
1873
+ }
1874
+ function printDocument(document) {
1875
+ process.stdout.write(`${JSON.stringify(document)}
1876
+ `);
1877
+ }
1878
+ function printFailure(error, command2) {
1879
+ const document = serializeCoordinatesFailure(error, command2) ?? (command2 ? invalidRequest(command2, "INVALID_VALUE").toJSON() : setupFailureDocument());
1880
+ process.stderr.write(`${JSON.stringify(document)}
1881
+ `);
1882
+ process.exitCode = 1;
1883
+ }
1884
+ function printSetupFailure(error) {
1885
+ process.stderr.write(
1886
+ `${JSON.stringify(serializeSetupFailure(error) ?? setupFailureDocument())}
1887
+ `
1888
+ );
1889
+ process.exitCode = 1;
1890
+ }
1891
+ function serializeCoordinatesFailure(error, command2) {
1892
+ if (!(error instanceof CoordinatesContractError)) return void 0;
1893
+ const parsed = coordinatesFailureSchema.safeParse(error.toJSON());
1894
+ if (!parsed.success) return void 0;
1895
+ const failure = parsed.data;
1896
+ return new CoordinatesContractError({
1897
+ ...failure,
1898
+ command: failure.command ?? command2 ?? null
1899
+ }).toJSON();
1900
+ }
1901
+ function serializeSetupFailure(error) {
1902
+ if (!(error instanceof CoordinatesSetupError)) return void 0;
1903
+ const codes = /* @__PURE__ */ new Set([
1904
+ "COORDINATES_SETUP_INVALID_REQUEST",
1905
+ "COORDINATES_SETUP_RELEASE_UNAVAILABLE",
1906
+ "COORDINATES_SETUP_MANIFEST_INVALID",
1907
+ "COORDINATES_SETUP_ARTIFACT_UNAVAILABLE",
1908
+ "COORDINATES_SETUP_INTEGRITY_FAILED",
1909
+ "COORDINATES_SETUP_INSTALLATION_FAILED",
1910
+ "COORDINATES_SETUP_CANCELLED",
1911
+ "COORDINATES_SETUP_TIMEOUT"
1912
+ ]);
1913
+ if (!codes.has(error.code)) return void 0;
1914
+ return new CoordinatesSetupError(error.code, error.releaseId, error.assetId).toJSON();
1915
+ }
1916
+ function setupFailureDocument() {
1917
+ return new CoordinatesSetupError("COORDINATES_SETUP_INVALID_REQUEST").toJSON();
1918
+ }
1919
+ function printHelp(command2, json, printJson) {
1920
+ if (json) {
1921
+ printJson();
1922
+ return;
1923
+ }
1924
+ command2.outputHelp();
1925
+ }
1926
+ function isRemoteLocation(input) {
1927
+ return /^[A-Za-z][A-Za-z0-9+.-]*:\/\//u.test(input);
1928
+ }
1929
+ function assertNotAborted(signal) {
1930
+ if (signal.aborted) throw new CoordinatesInputError("INVALID_VALUE");
1931
+ }
1932
+ function decodeInput(bytes) {
1933
+ try {
1934
+ return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
1935
+ } catch {
1936
+ throw new CoordinatesInputError("INVALID_VALUE");
1937
+ }
1938
+ }
1939
+
1591
1940
  // src/deploy-source.ts
1592
1941
  var overrideFields = [
1593
1942
  ["TILEFLOW_DEPLOY_REPOSITORY", "repository", 255],
@@ -2018,7 +2367,10 @@ async function validateAccountSession(session, options = {}) {
2018
2367
  options
2019
2368
  );
2020
2369
  if (!response.ok) {
2021
- return { error: `Account session validation failed (${response.status}).`, ok: false };
2370
+ return {
2371
+ error: response.status === 401 ? rejectedAccountSessionMessage : `Account session validation failed (${response.status}).`,
2372
+ ok: false
2373
+ };
2022
2374
  }
2023
2375
  const body = asRecord(response.body);
2024
2376
  const account = asRecord(body.account);
@@ -2110,7 +2462,10 @@ async function requestProjectCapability(session, project, scopes, options = {})
2110
2462
  };
2111
2463
  }
2112
2464
  if (!response.ok) {
2113
- return { error: `Project capability request failed (${response.status}).`, ok: false };
2465
+ return {
2466
+ error: response.status === 401 ? rejectedAccountSessionMessage : `Project capability request failed (${response.status}).`,
2467
+ ok: false
2468
+ };
2114
2469
  }
2115
2470
  const body = asRecord(response.body);
2116
2471
  if (!response.json || typeof body.capability !== "string" || !body.capability.startsWith("tf_cap_") || body.capability.length > 8192 || body.reference !== project || !validIsoDate(body.expiresAt) || !Array.isArray(body.scopes) || body.scopes.join("\0") !== [...scopes].sort().join("\0")) {
@@ -3661,7 +4016,7 @@ async function accountRequest(session, path, init) {
3661
4016
  }
3662
4017
  return response.ok ? { body: response.body, ok: true, status: response.status } : {
3663
4018
  body: response.body,
3664
- error: `Request failed (${response.status}).`,
4019
+ error: response.status === 401 ? rejectedAccountSessionMessage : `Request failed (${response.status}).`,
3665
4020
  ok: false,
3666
4021
  status: response.status
3667
4022
  };
@@ -3745,7 +4100,7 @@ import { z as z6 } from "zod";
3745
4100
 
3746
4101
  // src/tileset-upload.ts
3747
4102
  import { createHash } from "crypto";
3748
- import { open as open3, stat } from "fs/promises";
4103
+ import { open as open4, stat } from "fs/promises";
3749
4104
  import { z as z5 } from "zod";
3750
4105
  import { serializeCanonicalJson as serializeCanonicalJson2 } from "@tileflow/core";
3751
4106
  var teamTilesetPartSize = 16777216;
@@ -3809,7 +4164,7 @@ async function inspectNodeTeamTilesetArchive(path) {
3809
4164
  return checkedIdentity(value.dev, value.ino, value.mtimeNs, value.size);
3810
4165
  }
3811
4166
  async function openNodeTeamTilesetArchive(path, expectedIdentity) {
3812
- const handle = await open3(path, "r");
4167
+ const handle = await open4(path, "r");
3813
4168
  const identity = async () => {
3814
4169
  let pathStat;
3815
4170
  let handleStat;
@@ -4824,7 +5179,7 @@ var tilesetPurgeResponseSchema = z6.object({
4824
5179
  }).strict();
4825
5180
 
4826
5181
  // src/visual-command.ts
4827
- import { lstat as lstat3, readFile as readFile6 } from "fs/promises";
5182
+ import { lstat as lstat4, readFile as readFile6 } from "fs/promises";
4828
5183
  import { dirname as dirname9, isAbsolute as isAbsolute3, parse as parse2, relative as relative4, resolve as resolve6, sep as sep4 } from "path";
4829
5184
  import {
4830
5185
  analyzeTileflowCaptureReference,
@@ -4843,7 +5198,7 @@ import { compareCodeUnits as compareCodeUnits8 } from "@tileflow/core";
4843
5198
 
4844
5199
  // src/visual-compare-command.ts
4845
5200
  import { createHash as createHash3 } from "crypto";
4846
- import { lstat as lstat2, readFile as readFile5, realpath } from "fs/promises";
5201
+ import { lstat as lstat3, readFile as readFile5, realpath } from "fs/promises";
4847
5202
  import { basename as basename3, dirname as dirname8, extname, isAbsolute as isAbsolute2, parse, relative as relative3, resolve as resolve5, sep as sep3 } from "path";
4848
5203
  import {
4849
5204
  compareTileflowCapturesForReview,
@@ -5793,7 +6148,7 @@ async function readVisualCompareInventory(assetDirectory, inventoryPath, boundar
5793
6148
  await assertNoSymlinkComponents(dirname8(inventoryPath), boundary);
5794
6149
  let info;
5795
6150
  try {
5796
- info = await lstat2(inventoryPath);
6151
+ info = await lstat3(inventoryPath);
5797
6152
  } catch (error) {
5798
6153
  if (isMissingFile4(error)) return [];
5799
6154
  throw error;
@@ -5817,7 +6172,7 @@ async function readVisualCompareInventory(assetDirectory, inventoryPath, boundar
5817
6172
  await assertNoSymlinkComponents(dirname8(path), boundary);
5818
6173
  let assetInfo;
5819
6174
  try {
5820
- assetInfo = await lstat2(path);
6175
+ assetInfo = await lstat3(path);
5821
6176
  } catch (error) {
5822
6177
  if (isMissingFile4(error)) continue;
5823
6178
  throw error;
@@ -5979,7 +6334,7 @@ function pathsOverlap(left, right) {
5979
6334
  }
5980
6335
  async function assertDirectoryIfPresent(path) {
5981
6336
  try {
5982
- const info = await lstat2(path);
6337
+ const info = await lstat3(path);
5983
6338
  if (info.isSymbolicLink() || !info.isDirectory()) {
5984
6339
  throw new Error("Visual compare asset path must be a non-symlink directory.");
5985
6340
  }
@@ -5989,7 +6344,7 @@ async function assertDirectoryIfPresent(path) {
5989
6344
  }
5990
6345
  async function pathExists(path) {
5991
6346
  try {
5992
- await lstat2(path);
6347
+ await lstat3(path);
5993
6348
  return true;
5994
6349
  } catch (error) {
5995
6350
  if (isMissingFile4(error)) return false;
@@ -6343,7 +6698,7 @@ async function readOptionalArtifact(path, boundary, maximumBytes) {
6343
6698
  await assertNoSymlinkComponents(dirname9(path), boundary);
6344
6699
  let info;
6345
6700
  try {
6346
- info = await lstat3(path);
6701
+ info = await lstat4(path);
6347
6702
  } catch (error) {
6348
6703
  if (isMissingFile5(error)) return void 0;
6349
6704
  throw error;
@@ -6363,7 +6718,7 @@ async function readOptionalArtifact(path, boundary, maximumBytes) {
6363
6718
  async function readVisualReference(path) {
6364
6719
  let info;
6365
6720
  try {
6366
- info = await lstat3(path);
6721
+ info = await lstat4(path);
6367
6722
  } catch (error) {
6368
6723
  throw new TileflowCaptureError2("BASELINE_INVALID", "The visual reference PNG cannot be read.", {
6369
6724
  cause: error
@@ -6399,7 +6754,7 @@ async function resolveVisualDirectory(cwd, value, kind) {
6399
6754
  const boundary = isInside2(cwd, path) ? cwd : path;
6400
6755
  await assertNoSymlinkComponents(path, boundary);
6401
6756
  try {
6402
- const info = await lstat3(path);
6757
+ const info = await lstat4(path);
6403
6758
  if (info.isSymbolicLink() || !info.isDirectory()) {
6404
6759
  throw new Error(`Visual ${kind} path must be a non-symlink directory.`);
6405
6760
  }
@@ -7341,6 +7696,7 @@ registerConfigInspectCommand(inspectCommand, { defaultConfigPath });
7341
7696
  registerFeatureInspectCommand(inspectCommand, { defaultConfigPath });
7342
7697
  registerAiCommands(program, { defaultApiUrl, defaultConfigPath });
7343
7698
  registerCaptureCommands(program, { defaultConfigPath });
7699
+ registerCoordinatesCommands(program, { createLocalCoordinates, setupCoordinates });
7344
7700
  registerVisualCommands(program, {
7345
7701
  defaultConfigPath,
7346
7702
  openReport: (path) => openBrowser(path, true)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tileflow",
3
- "version": "0.1.0-alpha.1",
3
+ "version": "0.1.0-alpha.3",
4
4
  "license": "Apache-2.0",
5
5
  "description": "CLI-only tools for Tileflow config, capture, visual testing, and deploy.",
6
6
  "type": "module",
@@ -25,10 +25,7 @@
25
25
  },
26
26
  "files": [
27
27
  "dist",
28
- "LICENSE",
29
- "NOTICE",
30
- "GENERATED_OUTPUT_LICENSE.md",
31
- "TRADEMARKS.md"
28
+ "LICENSE"
32
29
  ],
33
30
  "bin": {
34
31
  "tileflow": "./dist/index.js"
@@ -38,12 +35,14 @@
38
35
  },
39
36
  "dependencies": {
40
37
  "@hono/node-server": "2.0.10",
41
- "@tileflow/capture": ">=0.1.0-alpha.20 <0.1.0-beta.0",
42
- "@tileflow/core": ">=0.1.0-alpha.23 <0.1.0-beta.0",
43
- "@tileflow/dev": ">=0.1.0-alpha.21 <0.1.0-beta.0",
44
- "@tileflow/maps": ">=0.1.0-alpha.1 <0.1.0-beta.0",
45
- "hono": "4.12.34",
38
+ "@tileflow/capture": ">=0.1.0-alpha.21 <0.1.0-beta.0",
39
+ "@tileflow/coordinates": ">=0.1.0-alpha.0 <0.1.0-beta.0",
40
+ "@tileflow/coordinates-runtime": ">=0.1.0-alpha.0 <0.1.0-beta.0",
41
+ "@tileflow/core": ">=0.1.0-alpha.24 <0.1.0-beta.0",
42
+ "@tileflow/dev": ">=0.1.0-alpha.22 <0.1.0-beta.0",
43
+ "@tileflow/maps": ">=0.1.0-alpha.2 <0.1.0-beta.0",
46
44
  "commander": "^15.0.0",
45
+ "hono": "4.13.5",
47
46
  "picocolors": "^1.1.1",
48
47
  "zod": "^4.4.3"
49
48
  },
@@ -1,21 +0,0 @@
1
- # Tileflow generated-output grant
2
-
3
- In addition to the rights granted by the Apache License, Version 2.0, Tileflow.dev contributors
4
- grant recipients a perpetual, worldwide, non-exclusive, no-charge, royalty-free right to use,
5
- reproduce, modify, display, perform, distribute, sublicense, and commercialize the Tileflow-owned
6
- parts of output generated by this SDK.
7
-
8
- Covered output includes compiled map styles, manifests, receipts, sprites, glyph artifacts, static
9
- render descriptions, and other build artifacts produced from Tileflow maps or application maps.
10
- Original Tileflow map definitions, icons, and patterns distributed by `@tileflow/maps` may likewise
11
- be used, modified, embedded, deployed, and redistributed as part of personal or commercial maps and
12
- applications.
13
-
14
- This grant does not relicense third-party software, fonts, icons, map data, datasets, imagery, or
15
- other input material. Their licenses, attribution requirements, notices, and use restrictions
16
- continue to apply to the relevant source and generated output. You are responsible for having the
17
- rights necessary to process and distribute your own inputs.
18
-
19
- This grant does not provide access to Tileflow hosted services or grant rights in Tileflow names,
20
- logos, or marks. The SDK and generated output are provided without warranties or conditions beyond
21
- those required by applicable law.
package/NOTICE DELETED
@@ -1,9 +0,0 @@
1
- Tileflow SDK
2
- Copyright 2026 Tileflow.dev contributors
3
-
4
- This product includes software and original map artwork developed by Tileflow.dev contributors.
5
-
6
- Third-party materials distributed with a package remain subject to their own license and notice
7
- files. See THIRD_PARTY_NOTICES.md and asset-adjacent license files where present.
8
-
9
- The Tileflow names and marks are not licensed under the Apache License 2.0. See TRADEMARKS.md.
package/TRADEMARKS.md DELETED
@@ -1,10 +0,0 @@
1
- # Tileflow trademarks
2
-
3
- The Apache License, Version 2.0, and the Tileflow generated-output grant do not grant permission to
4
- use the Tileflow name, `tileflow.dev`, logos, service marks, product names, or other Tileflow brand
5
- features except for reasonable and customary use that truthfully describes the origin of the SDK or
6
- compatibility with Tileflow, and for reproducing required notices.
7
-
8
- You may not use Tileflow brand features in a way that suggests sponsorship, endorsement, or an
9
- official Tileflow product when none exists. Third-party names and marks remain the property of their
10
- respective owners.