@statelyai/sdk 0.7.0 → 0.7.1

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.
@@ -1,4 +1,4 @@
1
- import { m as UploadResult } from "./protocol-DN4mH4jR.mjs";
1
+ import { m as UploadResult } from "./protocol-s9zwsiCW.mjs";
2
2
 
3
3
  //#region src/assetStorage.d.ts
4
4
  interface AssetUploadContext {
package/dist/cli.d.mts CHANGED
@@ -1,5 +1,4 @@
1
1
  import { ConnectedRepo, CreateProjectInput, ProjectData, StudioClient } from "./studio.mjs";
2
- import "./graph-DpBGHZwl.mjs";
3
2
  import { SyncPlan } from "./sync.mjs";
4
3
  import { Command } from "@oclif/core";
5
4
  import * as _oclif_core_interfaces0 from "@oclif/core/interfaces";
@@ -43,7 +42,6 @@ interface InitProjectResult {
43
42
  }
44
43
  interface ScanProjectSourcesOptions {
45
44
  cwd?: string;
46
- client: StudioClient;
47
45
  defaultXStateVersion?: number;
48
46
  }
49
47
  type ApiKeyResolution = {
package/dist/cli.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { createStatelyClient } from "./studio.mjs";
3
3
  import { u as getStatelyPragma } from "./graphToXStateTS-Gzh0ZqbN.mjs";
4
- import { planSync, pullSync, pushLocalMachineLinks } from "./sync.mjs";
4
+ import { n as pullSync, r as pushLocalMachineLinks, t as planSync } from "./sync-DLkTmSyA.mjs";
5
5
  import fs from "node:fs/promises";
6
6
  import * as path$1 from "node:path";
7
7
  import path from "node:path";
@@ -951,6 +951,7 @@ const CODE_SOURCE_EXTENSIONS = new Set([
951
951
  ".mjs",
952
952
  ".cjs"
953
953
  ]);
954
+ const execFileAsync$1 = promisify(execFile);
954
955
  function createStatelyProjectConfig(options) {
955
956
  const defaultXStateVersion = options.defaultXStateVersion ?? 5;
956
957
  return {
@@ -962,12 +963,26 @@ function createStatelyProjectConfig(options) {
962
963
  sources: []
963
964
  };
964
965
  }
966
+ function normalizeSourceConfig(source) {
967
+ return {
968
+ include: [...source.include ?? []],
969
+ exclude: source.exclude == null ? [...DEFAULT_SOURCE_EXCLUDES] : [...source.exclude],
970
+ format: source.format ?? "xstate",
971
+ ...source.xstateVersion == null ? {} : { xstateVersion: source.xstateVersion }
972
+ };
973
+ }
974
+ function normalizeProjectConfig(config) {
975
+ return {
976
+ ...config,
977
+ sources: (config.sources ?? []).map((source) => normalizeSourceConfig(source))
978
+ };
979
+ }
965
980
  async function readStatelyProjectConfig(options = {}) {
966
981
  const rootDir = path.resolve(options.cwd ?? process.cwd());
967
982
  const configPath = path.resolve(rootDir, options.configPath ?? STATELY_CONFIG_FILE);
968
983
  const raw = await fs.readFile(configPath, "utf8");
969
984
  return {
970
- config: JSON.parse(raw),
985
+ config: normalizeProjectConfig(JSON.parse(raw)),
971
986
  configPath,
972
987
  rootDir
973
988
  };
@@ -987,6 +1002,27 @@ async function walkFiles(rootDir, currentDir = rootDir) {
987
1002
  }
988
1003
  return files;
989
1004
  }
1005
+ async function filterGitIgnoredPaths(rootDir, relativeFiles) {
1006
+ try {
1007
+ const { stdout } = await execFileAsync$1("git", [
1008
+ "ls-files",
1009
+ "--others",
1010
+ "--ignored",
1011
+ "--exclude-standard",
1012
+ "--directory"
1013
+ ], { cwd: rootDir });
1014
+ const ignoredEntries = stdout.split(/\r?\n/).map((entry) => entry.trim()).filter(Boolean);
1015
+ if (ignoredEntries.length === 0) return relativeFiles;
1016
+ return relativeFiles.filter((relativePath) => {
1017
+ return !ignoredEntries.some((ignoredEntry) => {
1018
+ if (ignoredEntry.endsWith("/")) return relativePath.startsWith(ignoredEntry);
1019
+ return relativePath === ignoredEntry;
1020
+ });
1021
+ });
1022
+ } catch {
1023
+ return relativeFiles;
1024
+ }
1025
+ }
990
1026
  function isCodeSourceFile(relativePath) {
991
1027
  return CODE_SOURCE_EXTENSIONS.has(path.extname(relativePath).toLowerCase());
992
1028
  }
@@ -1031,7 +1067,8 @@ function matchesAny(patterns, relativePath) {
1031
1067
  return (patterns ?? []).some((pattern) => matchesGlob(relativePath, pattern));
1032
1068
  }
1033
1069
  async function discoverCodeSourceFiles(options = {}) {
1034
- return (await walkFiles(path.resolve(options.cwd ?? process.cwd()))).filter((relativePath) => isCodeSourceFile(relativePath)).filter((relativePath) => !matchesAny(DEFAULT_SOURCE_EXCLUDES, relativePath)).sort((left, right) => left.localeCompare(right));
1070
+ const rootDir = path.resolve(options.cwd ?? process.cwd());
1071
+ return (await filterGitIgnoredPaths(rootDir, await walkFiles(rootDir))).filter((relativePath) => isCodeSourceFile(relativePath)).filter((relativePath) => !matchesAny(DEFAULT_SOURCE_EXCLUDES, relativePath)).sort((left, right) => left.localeCompare(right));
1035
1072
  }
1036
1073
  function createSuggestedSource(include) {
1037
1074
  return {
@@ -1080,7 +1117,7 @@ async function discoverStatelySourceFiles(options = {}) {
1080
1117
  config: options.config,
1081
1118
  rootDir: path.resolve(options.cwd ?? process.cwd())
1082
1119
  } : await readStatelyProjectConfig(options);
1083
- const relativeFiles = await walkFiles(rootDir);
1120
+ const relativeFiles = await filterGitIgnoredPaths(rootDir, await walkFiles(rootDir));
1084
1121
  const discovered = /* @__PURE__ */ new Map();
1085
1122
  for (const source of config.sources) for (const relativePath of relativeFiles) {
1086
1123
  if (!matchesAny(source.include, relativePath)) continue;
@@ -1098,6 +1135,7 @@ async function discoverStatelySourceFiles(options = {}) {
1098
1135
  //#endregion
1099
1136
  //#region src/cli.ts
1100
1137
  const execFileAsync = promisify(execFile);
1138
+ const STATELY_API_KEY_SETTINGS_URL = "https://stately.ai/registry/user/my-settings?tab=API+Key";
1101
1139
  function loadLocalEnv() {
1102
1140
  if (typeof process.loadEnvFile !== "function") return;
1103
1141
  const cwdEnvPath = path.join(process.cwd(), ".env.local");
@@ -1248,6 +1286,12 @@ function normalizeApiKey(value) {
1248
1286
  const trimmed = value?.trim();
1249
1287
  return trimmed ? trimmed : void 0;
1250
1288
  }
1289
+ function pluralize(count, singular, plural = `${singular}s`) {
1290
+ return count === 1 ? singular : plural;
1291
+ }
1292
+ function getMissingApiKeyMessage() {
1293
+ return `No API key configured. Use \`statelyai login\`, set \`STATELY_API_KEY\`, or pass \`--api-key\`.\nGet or create an API key at ${STATELY_API_KEY_SETTINGS_URL}`;
1294
+ }
1251
1295
  async function fileExists(filePath) {
1252
1296
  try {
1253
1297
  await fs.access(filePath);
@@ -1293,17 +1337,15 @@ async function scanProjectSources(options) {
1293
1337
  const machineRelativePaths = [];
1294
1338
  for (const relativePath of candidateRelativePaths) {
1295
1339
  const filePath = path.join(cwd, relativePath);
1296
- const contents = await fs.readFile(filePath, "utf8");
1297
- let extracted;
1298
- try {
1299
- extracted = await options.client.code.extractMachines(contents);
1300
- } catch {
1301
- continue;
1302
- }
1303
- if (extracted.machines.length > 0) machineRelativePaths.push(relativePath);
1340
+ if (looksLikeXStateMachineSource(await fs.readFile(filePath, "utf8"))) machineRelativePaths.push(relativePath);
1304
1341
  }
1305
1342
  return suggestStatelySourceConfigs(machineRelativePaths, Math.max(5, options.defaultXStateVersion ?? 5));
1306
1343
  }
1344
+ function looksLikeXStateMachineSource(source) {
1345
+ const hasXStateImport = /from\s+['"]xstate(?:\/[^'"]+)?['"]/.test(source) || /require\(\s*['"]xstate(?:\/[^'"]+)?['"]\s*\)/.test(source);
1346
+ const hasMachineFactory = /\bcreateMachine\s*\(/.test(source) || /\.createMachine\s*\(/.test(source);
1347
+ return hasXStateImport && hasMachineFactory;
1348
+ }
1307
1349
  function supportsMachineDiscovery(file) {
1308
1350
  return file.source.format === "xstate" || file.source.format === "auto";
1309
1351
  }
@@ -1441,6 +1483,7 @@ var PullCommand = class PullCommand extends ParsedSyncCommand {
1441
1483
  target = localCandidate;
1442
1484
  }
1443
1485
  if (!target) this.error("Missing target path. Pass `statelyai pull <machine-id|url> <file>` or `statelyai pull <linked-file>`.");
1486
+ this.log(`Pulling ${source} into ${target}...`);
1444
1487
  const result = await pullSync({
1445
1488
  source,
1446
1489
  target,
@@ -1467,7 +1510,8 @@ var PushCommand = class PushCommand extends Command {
1467
1510
  async run() {
1468
1511
  const { args, flags } = await this.parse(PushCommand);
1469
1512
  const resolvedApiKey = await resolveApiKey(flags["api-key"]);
1470
- if (!resolvedApiKey.apiKey) this.error("No API key configured. Use `statelyai login`, set `STATELY_API_KEY`, or pass `--api-key`.");
1513
+ if (!resolvedApiKey.apiKey) this.error(getMissingApiKeyMessage());
1514
+ this.log("Resolving configured project and source files...");
1471
1515
  const { client, config, files } = await resolveConfiguredProject({
1472
1516
  apiKey: resolvedApiKey.apiKey,
1473
1517
  baseUrl: flags["base-url"],
@@ -1486,10 +1530,12 @@ var PushCommand = class PushCommand extends Command {
1486
1530
  this.log("No matching local machine source files were discovered.");
1487
1531
  return;
1488
1532
  }
1533
+ this.log(`Processing ${candidateFiles.length} ${pluralize(candidateFiles.length, "source file")}...`);
1489
1534
  const linked = [];
1490
1535
  const refreshed = [];
1491
1536
  const skipped = [];
1492
1537
  for (const file of candidateFiles) {
1538
+ this.log(`Pushing ${file.relativePath}...`);
1493
1539
  if (!supportsMachineDiscovery(file)) {
1494
1540
  skipped.push(`${file.relativePath}: unsupported format ${file.source.format}`);
1495
1541
  continue;
@@ -1586,7 +1632,8 @@ var InitCommand = class InitCommand extends Command {
1586
1632
  async run() {
1587
1633
  const { flags } = await this.parse(InitCommand);
1588
1634
  const resolvedApiKey = await resolveApiKey(flags["api-key"]);
1589
- if (!resolvedApiKey.apiKey) this.error("No API key configured. Use `statelyai login`, set `STATELY_API_KEY`, or pass `--api-key`.");
1635
+ if (!resolvedApiKey.apiKey) this.error(getMissingApiKeyMessage());
1636
+ this.log("Creating or reusing remote project...");
1590
1637
  const result = await initProject({
1591
1638
  apiKey: resolvedApiKey.apiKey,
1592
1639
  baseUrl: flags["base-url"],
@@ -1597,13 +1644,9 @@ var InitCommand = class InitCommand extends Command {
1597
1644
  }
1598
1645
  });
1599
1646
  if (flags.scan) {
1600
- const scanClient = createStatelyClient({
1601
- apiKey: resolvedApiKey.apiKey,
1602
- baseUrl: flags["base-url"] ?? result.config.studioUrl
1603
- });
1647
+ this.log("Scanning local source files...");
1604
1648
  const suggestions = await scanProjectSources({
1605
1649
  cwd: path.dirname(result.configPath),
1606
- client: scanClient,
1607
1650
  defaultXStateVersion: result.config.defaultXStateVersion
1608
1651
  });
1609
1652
  if (suggestions.length === 0) {
@@ -1640,8 +1683,9 @@ var LoginCommand = class LoginCommand extends Command {
1640
1683
  async run() {
1641
1684
  const { flags } = await this.parse(LoginCommand);
1642
1685
  if (flags.stdin && flags["api-key"]) this.error("Pass either --api-key or --stdin, not both.");
1686
+ if (!flags["api-key"] && !flags.stdin && process.stdin.isTTY && process.stdout.isTTY) this.log(`Get or create an API key at ${STATELY_API_KEY_SETTINGS_URL}`);
1643
1687
  const apiKey = normalizeApiKey(flags["api-key"] ?? (!process.stdin.isTTY || flags.stdin ? await readApiKeyFromStdin() : await promptForApiKey()));
1644
- if (!apiKey) this.error("API key cannot be empty.");
1688
+ if (!apiKey) this.error(`API key cannot be empty.\nGet or create an API key at ${STATELY_API_KEY_SETTINGS_URL}`);
1645
1689
  const stored = await setStoredApiKey(apiKey);
1646
1690
  this.log(`Stored API key in ${describeCredentialBackend(stored.backend, stored.location)}.`);
1647
1691
  }
@@ -1677,7 +1721,7 @@ var AuthStatusCommand = class extends Command {
1677
1721
  this.log(`API key source: stored credential (${describeCredentialBackend(storedApiKey.backend, storedApiKey.location)}).`);
1678
1722
  return;
1679
1723
  }
1680
- this.log("No API key configured. Use `statelyai login`, set `STATELY_API_KEY`, or pass `--api-key`.");
1724
+ this.log(getMissingApiKeyMessage());
1681
1725
  }
1682
1726
  };
1683
1727
  const COMMANDS = {
package/dist/embed.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { a as EmbedMode, c as ExportFormatMap, d as MachineSourceLocations, f as ProjectEmbedMachine, i as EmbedEventName, l as InitOptions, m as UploadResult, n as EmbedEventHandler, o as ExportCallOptions, r as EmbedEventMap, s as ExportFormat, t as CommentsConfig, u as MachineInitOptions } from "./protocol-DN4mH4jR.mjs";
1
+ import { a as EmbedMode, c as ExportFormatMap, d as MachineSourceLocations, f as ProjectEmbedMachine, i as EmbedEventName, l as InitOptions, m as UploadResult, n as EmbedEventHandler, o as ExportCallOptions, r as EmbedEventMap, s as ExportFormat, t as CommentsConfig, u as MachineInitOptions } from "./protocol-s9zwsiCW.mjs";
2
2
  import { AssetUploadAdapter } from "./assetStorage.mjs";
3
3
 
4
4
  //#region src/embed.d.ts
package/dist/graph.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- import { _ as studioMachineConverter, a as StatelyGraphData, c as StatelyInvoke, d as StudioAction, f as StudioEdge, g as fromStudioMachine, h as StudioNode, i as StatelyGraph, l as StatelyNodeData, m as StudioMachine, n as StatelyActorImplementation, o as StatelyGuard, p as StudioEventTypeData, r as StatelyEdgeData, s as StatelyImplementation, t as StatelyAction, u as StatelyTagImplementation, v as toStudioMachine } from "./graph-DpBGHZwl.mjs";
1
+ import { _ as studioMachineConverter, a as StatelyGraphData, c as StatelyInvoke, d as StudioAction, f as StudioEdge, g as fromStudioMachine, h as StudioNode, i as StatelyGraph, l as StatelyNodeData, m as StudioMachine, n as StatelyActorImplementation, o as StatelyGuard, p as StudioEventTypeData, r as StatelyEdgeData, s as StatelyImplementation, t as StatelyAction, u as StatelyTagImplementation, v as toStudioMachine } from "./graph-zuNj3kfa.mjs";
2
2
  export { StatelyAction, StatelyActorImplementation, StatelyEdgeData, StatelyGraph, StatelyGraphData, StatelyGuard, StatelyImplementation, StatelyInvoke, StatelyNodeData, StatelyTagImplementation, StudioAction, StudioEdge, StudioEventTypeData, StudioMachine, StudioNode, fromStudioMachine, studioMachineConverter, toStudioMachine };
package/dist/index.d.mts CHANGED
@@ -1,14 +1,14 @@
1
1
  import { StatelyApiClientOptions, StatelyApiError, StatelyApiUrlOptions, createStatelyApiClient, createStatelyApiUrl } from "./api.mjs";
2
- import { a as EmbedMode, c as ExportFormatMap, f as ProjectEmbedMachine, i as EmbedEventName, l as InitOptions, m as UploadResult, n as EmbedEventHandler, o as ExportCallOptions, r as EmbedEventMap, s as ExportFormat, t as CommentsConfig } from "./protocol-DN4mH4jR.mjs";
2
+ import { a as EmbedMode, c as ExportFormatMap, f as ProjectEmbedMachine, i as EmbedEventName, l as InitOptions, m as UploadResult, n as EmbedEventHandler, o as ExportCallOptions, r as EmbedEventMap, s as ExportFormat, t as CommentsConfig } from "./protocol-s9zwsiCW.mjs";
3
3
  import { AssetUploadAdapter, AssetUploadContext, AssetUploadRequest, CreateS3AssetUploadAdapterOptions, CreateSupabaseAssetUploadAdapterOptions, S3UploadTarget, SupabaseStorageClient, createS3AssetUploadAdapter, createSupabaseAssetUploadAdapter } from "./assetStorage.mjs";
4
4
  import { ConnectedRepo, CreateMachineFromDefinitionInput, CreateMachineFromTemplateInput, CreateMachineInput, CreateMachineTemplate, CreateProjectInput, EnsureProjectInput, ExtractMachinesResponse, ExtractedMachine, GetMachineOptions, ProjectData, ProjectMachine, ProjectVisibility, RepoType, StudioApiError, StudioClient, StudioClientOptions, StudioMachineRecord, UpdateMachineInput, VerifyApiKeyResponse, XStateVersion, createStatelyClient } from "./studio.mjs";
5
- import { C as EventTypeData, S as DigraphNodeConfig, _ as studioMachineConverter, a as StatelyGraphData, b as DigraphConfig, c as StatelyInvoke, d as StudioAction, f as StudioEdge, g as fromStudioMachine, h as StudioNode, i as StatelyGraph, l as StatelyNodeData, m as StudioMachine, o as StatelyGuard, r as StatelyEdgeData, t as StatelyAction, v as toStudioMachine, w as StateNodeJSONData, x as DigraphEdgeConfig, y as DigraphAction } from "./graph-DpBGHZwl.mjs";
5
+ import { C as EventTypeData, S as DigraphNodeConfig, _ as studioMachineConverter, a as StatelyGraphData, b as DigraphConfig, c as StatelyInvoke, d as StudioAction, f as StudioEdge, g as fromStudioMachine, h as StudioNode, i as StatelyGraph, l as StatelyNodeData, m as StudioMachine, o as StatelyGuard, r as StatelyEdgeData, t as StatelyAction, v as toStudioMachine, w as StateNodeJSONData, x as DigraphEdgeConfig, y as DigraphAction } from "./graph-zuNj3kfa.mjs";
6
6
  import { PlanSyncOptions, PullSyncResult, PushLocalMachineLinksResult, PushSyncOptions, PushSyncProjectOptions, PushSyncResult, ResolvedSyncInput, SyncInputFormat, SyncPlan, SyncPlanSummary } from "./sync.mjs";
7
7
  import { AssetConfig, StatelyEmbed, StatelyEmbedOptions, createStatelyEmbed } from "./embed.mjs";
8
- import { a as ManualActorOptions, c as createPostMessageTransport, i as InspectorEvents, l as createWebSocketTransport, n as CreateInspectorOptions, o as createStatelyInspector, r as Inspector, s as Transport, t as AdoptedActor } from "./inspect-Bg9FTvb3.mjs";
8
+ import { a as ManualActorOptions, c as createPostMessageTransport, i as InspectorEvents, l as createWebSocketTransport, n as CreateInspectorOptions, o as createStatelyInspector, r as Inspector, s as Transport, t as AdoptedActor } from "./inspect-YoEwfiKb.mjs";
9
9
  import { ActionLocation, GraphPatch } from "./patchTypes.mjs";
10
- import { JSONSchema7 } from "json-schema";
11
10
  import { UnknownMachineConfig } from "xstate";
11
+ import { JSONSchema7 } from "json-schema";
12
12
 
13
13
  //#region src/statelyPragma.d.ts
14
14
  interface StatelyPragma {
@@ -1,4 +1,4 @@
1
- import { c as ExportFormatMap, o as ExportCallOptions, p as ProtocolMessage, r as EmbedEventMap, s as ExportFormat } from "./protocol-DN4mH4jR.mjs";
1
+ import { c as ExportFormatMap, o as ExportCallOptions, p as ProtocolMessage, r as EmbedEventMap, s as ExportFormat } from "./protocol-s9zwsiCW.mjs";
2
2
 
3
3
  //#region src/transport.d.ts
4
4
  interface Transport {
@@ -1,3 +1,3 @@
1
- import { a as EmbedMode, c as ExportFormatMap, i as EmbedEventName, n as EmbedEventHandler, o as ExportCallOptions, r as EmbedEventMap, s as ExportFormat } from "./protocol-DN4mH4jR.mjs";
2
- import { a as ManualActorOptions, i as InspectorEvents, n as CreateInspectorOptions, o as createStatelyInspector, r as Inspector, s as Transport, t as AdoptedActor } from "./inspect-Bg9FTvb3.mjs";
1
+ import { a as EmbedMode, c as ExportFormatMap, i as EmbedEventName, n as EmbedEventHandler, o as ExportCallOptions, r as EmbedEventMap, s as ExportFormat } from "./protocol-s9zwsiCW.mjs";
2
+ import { a as ManualActorOptions, i as InspectorEvents, n as CreateInspectorOptions, o as createStatelyInspector, r as Inspector, s as Transport, t as AdoptedActor } from "./inspect-YoEwfiKb.mjs";
3
3
  export { AdoptedActor, CreateInspectorOptions, EmbedEventHandler, EmbedEventMap, EmbedEventName, EmbedMode, ExportCallOptions, ExportFormat, ExportFormatMap, Inspector, InspectorEvents, ManualActorOptions, Transport, createStatelyInspector };
package/dist/studio.mjs CHANGED
@@ -40,6 +40,14 @@ function unwrapResponseData(data) {
40
40
  if (typeof data === "object" && data !== null && "data" in data) return data.data;
41
41
  return data;
42
42
  }
43
+ function normalizeProjectData(project) {
44
+ if (project.projectVersionId) return project;
45
+ if (project.projectId && project.id) return {
46
+ ...project,
47
+ projectVersionId: project.id
48
+ };
49
+ return project;
50
+ }
43
51
  function normalizeRepoUrl(url) {
44
52
  if (!url) return;
45
53
  return url.replace(/\.git$/i, "").replace(/\/+$/, "");
@@ -71,14 +79,14 @@ function createStatelyClient(options = {}) {
71
79
  } },
72
80
  projects: {
73
81
  list() {
74
- return request("/projects", { method: "GET" });
82
+ return request("/projects", { method: "GET" }).then((projects) => projects.map(normalizeProjectData));
75
83
  },
76
84
  create(input) {
77
85
  return request("/projects/create", {
78
86
  method: "POST",
79
87
  headers: { "Content-Type": "application/json" },
80
88
  body: JSON.stringify(input)
81
- });
89
+ }).then(normalizeProjectData);
82
90
  },
83
91
  async ensure(input) {
84
92
  if (input.repo && input.matchConnectedRepo !== false) {
@@ -88,7 +96,7 @@ function createStatelyClient(options = {}) {
88
96
  return this.create(input);
89
97
  },
90
98
  get(projectId) {
91
- return request(`/projects/${encodeURIComponent(projectId)}`, { method: "GET" });
99
+ return request(`/projects/${encodeURIComponent(projectId)}`, { method: "GET" }).then(normalizeProjectData);
92
100
  }
93
101
  },
94
102
  machines: {