statelyai 0.7.6 → 0.7.7

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
@@ -1,8 +1,8 @@
1
1
  # statelyai
2
2
 
3
- `statelyai` connects local machine source files to Stately Studio. Use it to
4
- open a file in the visual editor, compare local and remote machines, or sync a
5
- project in both directions.
3
+ `statelyai` discovers local machine source files and can connect them to
4
+ Stately Studio. Use it locally to inventory machines, or connect a project to
5
+ open, compare, push, and pull machines.
6
6
 
7
7
  ## Run the CLI
8
8
 
@@ -26,8 +26,10 @@ npm install --global statelyai
26
26
 
27
27
  | Goal | Command | Writes |
28
28
  | ---------------------------- | ---------------------------------------------------- | ------------------------------------------------------- |
29
+ | List local machines | `statelyai scan` | Nothing |
29
30
  | Edit one local file visually | `statelyai open <file>` | Local file, when you save in the editor |
30
- | Set up project sync | `statelyai init [--scan]` | Remote project and local `statelyai.json` |
31
+ | Set up local discovery | `statelyai init --local --scan` | Local `statelyai.json` |
32
+ | Set up project sync | `statelyai init --scan` | Remote project and local `statelyai.json` |
31
33
  | Inspect project machines | `statelyai status` | Nothing |
32
34
  | Preview project uploads | `statelyai push --dry-run` | Nothing |
33
35
  | Upload local machines | `statelyai push [file]` | Remote machines and local `@statelyai` IDs |
@@ -53,6 +55,24 @@ statelyai init --scan
53
55
 
54
56
  `--scan` suggests `include` globs and asks before saving them. Without
55
57
  `--scan`, `init` creates `statelyai.json` with an empty `include` list.
58
+ It also lists every discovered machine with its symbol, file, line, and linked
59
+ Stately ID when present.
60
+
61
+ For local discovery without authentication or a Studio project:
62
+
63
+ ```bash
64
+ statelyai init --local --scan
65
+ statelyai status
66
+ ```
67
+
68
+ Run `statelyai init` later to connect the local config to a Studio project
69
+ without losing its discovery globs.
70
+
71
+ To list machines without creating any config:
72
+
73
+ ```bash
74
+ statelyai scan
75
+ ```
56
76
 
57
77
  Preview the files and remote operations:
58
78
 
@@ -114,6 +134,10 @@ environment variables.
114
134
  Stored credentials use macOS Keychain or Linux Secret Service when available,
115
135
  with a private config file as fallback. Set `STATELYAI_CREDENTIALS_BACKEND=file`
116
136
  to force file storage, or `STATELYAI_CONFIG_DIR` to choose its directory.
137
+ Stored OAuth sessions refresh automatically before expiration. If the OAuth
138
+ server rejects the refresh token, the CLI removes the unusable credential and
139
+ directs you to run `npx statelyai login`. Transient refresh failures preserve
140
+ the credential so a later command can retry.
117
141
 
118
142
  ## `statelyai.json`
119
143
 
@@ -139,16 +163,16 @@ to force file storage, or `STATELYAI_CONFIG_DIR` to choose its directory.
139
163
  | ---------------------- | --------------------------------------------------------------- |
140
164
  | `$schema` | Published JSON Schema URL. |
141
165
  | `version` | Config format version. Currently `1.0.0`. |
142
- | `projectId` | Remote Studio project ID. |
143
- | `studioUrl` | Studio API origin. |
166
+ | `projectId` | Remote Studio project ID. Omitted for local-only projects. |
167
+ | `studioUrl` | Studio API origin. Omitted for local-only projects. |
144
168
  | `defaultXStateVersion` | XState version used when creating remote machines. Minimum `5`. |
145
169
  | `include` | Source globs used by project-wide `push` and `pull`. |
146
170
  | `exclude` | Globs removed from discovery. Defaults to tests and specs. |
147
171
  | `newMachinesDir` | Destination for remote-only machines created by `pull`. |
148
172
 
149
- Project-wide `push` currently discovers machine-bearing JavaScript and
150
- TypeScript files configured as `xstate` or `auto`. The config schema accepts
151
- other format names, but project discovery does not push them.
173
+ Project-wide discovery identifies every `createMachine(...)` and
174
+ `setup(...).createMachine(...)` call in configured JavaScript and TypeScript
175
+ files. `push` currently supports files configured as `xstate` or `auto`.
152
176
 
153
177
  Mutating project commands rewrite legacy configs with one `sources` entry to
154
178
  the current top-level shape. `status` and `push --dry-run` normalize legacy
@@ -187,6 +211,7 @@ statelyai status
187
211
  ```
188
212
 
189
213
  Statuses are `linked`, `local-only`, `remote-only`, and `missing-remote`.
214
+ Local-only configs require no credentials and never contact Studio.
190
215
  Use `--json` for automation or `--auth` to show only credential resolution.
191
216
  `status` is read-only and never migrates `statelyai.json`.
192
217
 
@@ -194,10 +219,11 @@ Flags: `--config <path>`, `--base-url <url>`, `--json`, `--auth`.
194
219
 
195
220
  ### `init`
196
221
 
197
- Create or reuse a Studio project and write `statelyai.json`.
222
+ Create a local config or reuse a Studio project and write `statelyai.json`.
198
223
 
199
224
  ```bash
200
225
  statelyai init --name "Checkout" --visibility Private --scan
226
+ statelyai init --local --scan
201
227
  ```
202
228
 
203
229
  Flags:
@@ -205,9 +231,22 @@ Flags:
205
231
  - `--name <name>` sets the remote project name.
206
232
  - `--visibility Private|Public|Unlisted` defaults to `Private`.
207
233
  - `--scan` proposes source globs interactively.
234
+ - `--local` skips authentication and remote project creation.
208
235
  - `--force` replaces an existing config.
209
236
  - `--base-url <url>` overrides the Studio API origin.
210
237
 
238
+ ### `scan`
239
+
240
+ List every local XState machine without authentication or `statelyai.json`:
241
+
242
+ ```bash
243
+ statelyai scan
244
+ statelyai scan --json
245
+ ```
246
+
247
+ The output includes the machine symbol, source file, line, and linked Stately
248
+ ID when present.
249
+
211
250
  ### `open`
212
251
 
213
252
  Start a local bridge and open one source file in the browser editor:
@@ -317,6 +356,7 @@ secret environment.
317
356
  Check project discovery without network credentials or writes:
318
357
 
319
358
  ```bash
359
+ npx statelyai scan
320
360
  npx statelyai push --dry-run
321
361
  ```
322
362
 
@@ -330,6 +370,8 @@ Set `NO_COLOR=1` or `CI=true` for plain output.
330
370
  `STATELY_ACCESS_TOKEN` or `STATELY_API_KEY`.
331
371
  - **`push` finds no files:** check `include` and `exclude`; `init` without
332
372
  `--scan` intentionally leaves `include` empty.
373
+ - **You only need local inventory:** run `statelyai scan` or initialize with
374
+ `statelyai init --local --scan`; neither requires authentication.
333
375
  - **`push` matches files but finds no machines:** project discovery currently
334
376
  requires XState imports plus `createMachine(...)` or
335
377
  `setup(...).createMachine(...)`.
package/dist/bin.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { b as run } from "./cli-D0TeuFzR.mjs";
2
+ import { S as run } from "./cli-DicCA4VI.mjs";
3
3
 
4
4
  //#region src/bin.ts
5
5
  run();
@@ -11,7 +11,7 @@ import { promisify } from "node:util";
11
11
  import { Args, Command, Flags, flush, handle, run } from "@oclif/core";
12
12
  import * as http from "node:http";
13
13
  import os from "node:os";
14
- import { StudioApiError, buildAuthorizeUrl, createPkcePair, createStatelyClient, discoverAuthorizationServer, discoverProtectedResource, exchangeCodeForToken, getStatelyPragma, registerOAuthClient } from "@statelyai/sdk";
14
+ import { StudioApiError, buildAuthorizeUrl, createPkcePair, createStatelyClient, discoverAuthorizationServer, discoverProtectedResource, exchangeCodeForToken, findStatelyPragmaAttachments, getStatelyPragma, refreshToken, registerOAuthClient } from "@statelyai/sdk";
15
15
  import { planSync, pullSync, pushLocalMachineLinks } from "@statelyai/sdk/sync";
16
16
 
17
17
  //#region src/cliHost.ts
@@ -801,7 +801,9 @@ function parseStoredCredential(raw, backend, location) {
801
801
  type: "oauth",
802
802
  accessToken: credential.accessToken,
803
803
  ...typeof credential.refreshToken === "string" ? { refreshToken: credential.refreshToken } : {},
804
- ...typeof credential.expiresAt === "number" ? { expiresAt: credential.expiresAt } : {}
804
+ ...typeof credential.expiresAt === "number" ? { expiresAt: credential.expiresAt } : {},
805
+ ...typeof credential.clientId === "string" ? { clientId: credential.clientId } : {},
806
+ ...typeof credential.tokenEndpoint === "string" ? { tokenEndpoint: credential.tokenEndpoint } : {}
805
807
  },
806
808
  backend,
807
809
  location
@@ -1072,13 +1074,21 @@ function createStatelyProjectConfig(options) {
1072
1074
  return {
1073
1075
  $schema: STATELY_CONFIG_SCHEMA_URL,
1074
1076
  version: STATELY_CONFIG_VERSION,
1075
- projectId: options.projectId,
1076
- studioUrl: options.studioUrl,
1077
+ projectId: options.projectId ?? "",
1078
+ studioUrl: options.studioUrl ?? "https://stately.ai",
1077
1079
  defaultXStateVersion,
1078
1080
  include: [],
1079
1081
  exclude: [...DEFAULT_SOURCE_EXCLUDES]
1080
1082
  };
1081
1083
  }
1084
+ async function writeStatelyProjectConfig(configPath, config) {
1085
+ const serialized = { ...config };
1086
+ if (!config.projectId) {
1087
+ delete serialized.projectId;
1088
+ delete serialized.studioUrl;
1089
+ }
1090
+ await fsPromises.writeFile(configPath, `${JSON.stringify(serialized, null, 2)}\n`, "utf8");
1091
+ }
1082
1092
  function normalizeProjectConfig(config) {
1083
1093
  const firstSource = config.sources?.[0];
1084
1094
  return {
@@ -1111,7 +1121,7 @@ async function readStatelyProjectConfig(options = {}) {
1111
1121
  const config = normalizeProjectConfig(parsed);
1112
1122
  const rewriteStatus = getConfigRewriteStatus(parsed);
1113
1123
  if (options.rewriteIfNeeded && rewriteStatus.reason === "legacy-sources") {
1114
- await fsPromises.writeFile(configPath, `${JSON.stringify(config, null, 2)}\n`, "utf8");
1124
+ await writeStatelyProjectConfig(configPath, config);
1115
1125
  return {
1116
1126
  config,
1117
1127
  configPath,
@@ -1359,7 +1369,61 @@ function getEnvCredential() {
1359
1369
  variable: "NEXT_PUBLIC_STATELY_API_KEY"
1360
1370
  };
1361
1371
  }
1362
- async function resolveApiKey() {
1372
+ const OAUTH_EXPIRY_SKEW_MS = 6e4;
1373
+ function decodeJwtPayload(token) {
1374
+ const [, payload] = token.split(".");
1375
+ if (!payload) return;
1376
+ try {
1377
+ const parsed = JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
1378
+ return typeof parsed === "object" && parsed !== null ? parsed : void 0;
1379
+ } catch {
1380
+ return;
1381
+ }
1382
+ }
1383
+ function getOAuthExpiresAt(credential) {
1384
+ if (credential.expiresAt !== void 0) return credential.expiresAt;
1385
+ const exp = decodeJwtPayload(credential.accessToken)?.exp;
1386
+ return typeof exp === "number" ? exp * 1e3 : void 0;
1387
+ }
1388
+ function isTerminalOAuthRefreshError(error) {
1389
+ return error instanceof Error && /Token refresh failed with HTTP (?:400|401|403)\b/.test(error.message);
1390
+ }
1391
+ async function invalidateExpiredOAuthCredential() {
1392
+ await deleteStoredApiKey();
1393
+ throw new Error("Your Stately session expired. Run `npx statelyai login` to continue.");
1394
+ }
1395
+ async function refreshStoredOAuthCredential(credential, options) {
1396
+ if (!credential.refreshToken) return invalidateExpiredOAuthCredential();
1397
+ const claims = decodeJwtPayload(credential.accessToken);
1398
+ const clientId = credential.clientId ?? (typeof claims?.client_id === "string" ? claims.client_id : void 0) ?? getConfiguredOauthClientId();
1399
+ if (!clientId) return invalidateExpiredOAuthCredential();
1400
+ let tokenEndpoint = credential.tokenEndpoint;
1401
+ try {
1402
+ if (!tokenEndpoint) tokenEndpoint = (await discoverAuthorizationServer(typeof claims?.iss === "string" ? claims.iss : getDefaultOAuthIssuer(), { fetch: options.fetch })).token_endpoint;
1403
+ } catch (error) {
1404
+ throw new Error("Could not refresh your expired Stately session. Retry, or run `npx statelyai login`.", { cause: error });
1405
+ }
1406
+ if (!tokenEndpoint) return invalidateExpiredOAuthCredential();
1407
+ try {
1408
+ const refreshedCredential = createOAuthCredential(await refreshToken({
1409
+ clientId,
1410
+ fetch: options.fetch,
1411
+ refreshToken: credential.refreshToken,
1412
+ tokenEndpoint
1413
+ }), {
1414
+ clientId,
1415
+ fallbackRefreshToken: credential.refreshToken,
1416
+ now: options.now,
1417
+ tokenEndpoint
1418
+ });
1419
+ await setStoredCredential(refreshedCredential);
1420
+ return refreshedCredential;
1421
+ } catch (error) {
1422
+ if (isTerminalOAuthRefreshError(error)) return invalidateExpiredOAuthCredential();
1423
+ throw new Error("Could not refresh your expired Stately session. Retry, or run `npx statelyai login`.", { cause: error });
1424
+ }
1425
+ }
1426
+ async function resolveApiKey(options = {}) {
1363
1427
  const envApiKey = getEnvApiKey();
1364
1428
  if (envApiKey) return {
1365
1429
  apiKey: envApiKey.apiKey,
@@ -1367,11 +1431,19 @@ async function resolveApiKey() {
1367
1431
  detail: envApiKey.variable
1368
1432
  };
1369
1433
  const storedCredential = await getStoredCredential();
1370
- if (storedCredential?.credential) return {
1371
- apiKey: storedCredential.credential.type === "oauth" ? storedCredential.credential.accessToken : storedCredential.credential.token,
1372
- source: "stored",
1373
- detail: describeCredentialBackend(storedCredential.backend, storedCredential.location)
1374
- };
1434
+ if (storedCredential?.credential) {
1435
+ let credential = storedCredential.credential;
1436
+ const now = options.now ?? Date.now();
1437
+ if (credential.type === "oauth" && (getOAuthExpiresAt(credential) ?? Infinity) <= now + OAUTH_EXPIRY_SKEW_MS) credential = await refreshStoredOAuthCredential(credential, {
1438
+ ...options,
1439
+ now
1440
+ });
1441
+ return {
1442
+ apiKey: credential.type === "oauth" ? credential.accessToken : credential.token,
1443
+ source: "stored",
1444
+ detail: describeCredentialBackend(storedCredential.backend, storedCredential.location)
1445
+ };
1446
+ }
1375
1447
  return { source: "missing" };
1376
1448
  }
1377
1449
  function getDefaultBaseUrl() {
@@ -1538,14 +1610,17 @@ function parseOAuthCallback(value) {
1538
1610
  return { code: value };
1539
1611
  }
1540
1612
  }
1541
- function createOAuthCredential(tokenResponse, now = Date.now()) {
1613
+ function createOAuthCredential(tokenResponse, options = {}) {
1542
1614
  if (!tokenResponse.access_token) throw new Error("Token response did not include an access_token.");
1615
+ const now = options.now ?? Date.now();
1543
1616
  const expiresAt = typeof tokenResponse.expires_at === "number" ? tokenResponse.expires_at * 1e3 : typeof tokenResponse.expires_in === "number" ? now + tokenResponse.expires_in * 1e3 : void 0;
1544
1617
  return {
1545
1618
  type: "oauth",
1546
1619
  accessToken: tokenResponse.access_token,
1547
- ...tokenResponse.refresh_token ? { refreshToken: tokenResponse.refresh_token } : {},
1548
- ...expiresAt ? { expiresAt } : {}
1620
+ ...tokenResponse.refresh_token ?? options.fallbackRefreshToken ? { refreshToken: tokenResponse.refresh_token ?? options.fallbackRefreshToken } : {},
1621
+ ...expiresAt ? { expiresAt } : {},
1622
+ ...options.clientId ? { clientId: options.clientId } : {},
1623
+ ...options.tokenEndpoint ? { tokenEndpoint: options.tokenEndpoint } : {}
1549
1624
  };
1550
1625
  }
1551
1626
  async function buildOAuthLoginStart(options) {
@@ -1605,7 +1680,10 @@ async function runOAuthBrowserLogin(options) {
1605
1680
  codeVerifier,
1606
1681
  redirectUri: callbackServer.redirectUri,
1607
1682
  clientId: discovery.clientId
1608
- })));
1683
+ }), {
1684
+ clientId: discovery.clientId,
1685
+ tokenEndpoint: discovery.tokenEndpoint
1686
+ }));
1609
1687
  } finally {
1610
1688
  await callbackServer.close();
1611
1689
  }
@@ -1816,16 +1894,38 @@ async function initProject(options) {
1816
1894
  configPath: options.configPath,
1817
1895
  rewriteIfNeeded: true
1818
1896
  }).catch(() => void 0) : void 0;
1897
+ if (existingConfig) {
1898
+ if (options.local) return {
1899
+ config: existingConfig.config,
1900
+ configPath: existingConfig.configPath,
1901
+ reusedExistingConfig: true
1902
+ };
1903
+ if (existingConfig.config.projectId) {
1904
+ const client = options.client ?? createStatelyClient({
1905
+ apiKey: options.apiKey,
1906
+ baseUrl: studioUrl
1907
+ });
1908
+ return {
1909
+ config: existingConfig.config,
1910
+ configPath: existingConfig.configPath,
1911
+ project: await client.projects.get(existingConfig.config.projectId),
1912
+ reusedExistingConfig: true
1913
+ };
1914
+ }
1915
+ }
1916
+ if (options.local) {
1917
+ const config = createStatelyProjectConfig({ defaultXStateVersion });
1918
+ await writeStatelyProjectConfig(configPath, config);
1919
+ return {
1920
+ config,
1921
+ configPath,
1922
+ reusedExistingConfig: false
1923
+ };
1924
+ }
1819
1925
  const client = options.client ?? createStatelyClient({
1820
1926
  apiKey: options.apiKey,
1821
1927
  baseUrl: studioUrl
1822
1928
  });
1823
- if (existingConfig) return {
1824
- config: existingConfig.config,
1825
- configPath: existingConfig.configPath,
1826
- project: await client.projects.get(existingConfig.config.projectId),
1827
- reusedExistingConfig: true
1828
- };
1829
1929
  const inferredRepo = options.project?.repo ?? await inferConnectedRepoFromCwd(cwd);
1830
1930
  const projectInput = {
1831
1931
  name: options.project?.name ?? inferInitProjectName(cwd, inferredRepo),
@@ -1835,12 +1935,16 @@ async function initProject(options) {
1835
1935
  ...inferredRepo ? { repo: inferredRepo } : {}
1836
1936
  };
1837
1937
  const project = await client.projects.ensure(projectInput);
1838
- const config = createStatelyProjectConfig({
1938
+ const config = existingConfig ? {
1939
+ ...existingConfig.config,
1940
+ projectId: project.projectId,
1941
+ studioUrl
1942
+ } : createStatelyProjectConfig({
1839
1943
  projectId: project.projectId,
1840
1944
  studioUrl,
1841
1945
  defaultXStateVersion
1842
1946
  });
1843
- await fsPromises.writeFile(configPath, `${JSON.stringify(config, null, 2)}\n`, "utf8");
1947
+ await writeStatelyProjectConfig(configPath, config);
1844
1948
  return {
1845
1949
  config,
1846
1950
  configPath,
@@ -1848,15 +1952,66 @@ async function initProject(options) {
1848
1952
  reusedExistingConfig: false
1849
1953
  };
1850
1954
  }
1955
+ function inferDiscoveredMachineName(options) {
1956
+ const declaration = options.source.slice(options.attachStart, options.machineStart);
1957
+ const variableName = [...declaration.matchAll(/\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=/g)].at(-1)?.[1];
1958
+ if (variableName) return variableName;
1959
+ if (/\bexport\s+default\b/.test(declaration)) return "default";
1960
+ const configuredId = options.source.slice(options.machineStart, options.machineStart + 500).match(/\bid\s*:\s*['"]([^'"]+)['"]/)?.[1];
1961
+ if (configuredId) return configuredId;
1962
+ return `${path.basename(options.relativePath, path.extname(options.relativePath))}#${options.machineIndex + 1}`;
1963
+ }
1964
+ function discoverMachinesInSource(options) {
1965
+ if (!looksLikeXStateMachineSource(options.source)) return [];
1966
+ return findStatelyPragmaAttachments(options.source, options.filePath).map((attachment, machineIndex) => ({
1967
+ filePath: options.filePath,
1968
+ ...attachment.pragma?.id ? { id: attachment.pragma.id } : {},
1969
+ line: options.source.slice(0, attachment.machineStart).split(/\r?\n/).length,
1970
+ machineIndex,
1971
+ name: inferDiscoveredMachineName({
1972
+ attachStart: attachment.attachStart,
1973
+ machineIndex,
1974
+ machineStart: attachment.machineStart,
1975
+ relativePath: options.relativePath,
1976
+ source: options.source
1977
+ }),
1978
+ relativePath: options.relativePath
1979
+ }));
1980
+ }
1981
+ async function discoverMachinesInFiles(files) {
1982
+ const machines = [];
1983
+ for (const file of files) {
1984
+ const source = await fsPromises.readFile(file.filePath, "utf8");
1985
+ machines.push(...discoverMachinesInSource({
1986
+ filePath: file.filePath,
1987
+ relativePath: file.relativePath,
1988
+ source
1989
+ }));
1990
+ }
1991
+ return machines;
1992
+ }
1993
+ async function scanProjectMachines(options = {}) {
1994
+ const cwd = path.resolve(options.cwd ?? process.cwd());
1995
+ return discoverMachinesInFiles((await discoverCodeSourceFiles({ cwd })).map((relativePath) => ({
1996
+ filePath: path.join(cwd, relativePath),
1997
+ relativePath,
1998
+ source: {
1999
+ include: [relativePath],
2000
+ exclude: [],
2001
+ format: "xstate",
2002
+ xstateVersion: 5
2003
+ }
2004
+ })));
2005
+ }
2006
+ function formatDiscoveredMachines(machines) {
2007
+ const fileCount = new Set(machines.map((machine) => machine.relativePath)).size;
2008
+ const lines = [`Found ${machines.length} ${pluralize(machines.length, "machine")} in ${fileCount} ${pluralize(fileCount, "file")}:`];
2009
+ for (const machine of machines) lines.push(` ${machine.name} ${machine.relativePath}:${machine.line}${machine.id ? ` ${machine.id}` : ""}`);
2010
+ return lines.join("\n");
2011
+ }
1851
2012
  async function scanProjectSources(options) {
1852
2013
  const cwd = path.resolve(options.cwd ?? process.cwd());
1853
- const candidateRelativePaths = await discoverCodeSourceFiles({ cwd });
1854
- const machineRelativePaths = [];
1855
- for (const relativePath of candidateRelativePaths) {
1856
- const filePath = path.join(cwd, relativePath);
1857
- if (looksLikeXStateMachineSource(await fsPromises.readFile(filePath, "utf8"))) machineRelativePaths.push(relativePath);
1858
- }
1859
- return suggestStatelySourceConfigs(machineRelativePaths);
2014
+ return suggestStatelySourceConfigs([...new Set((await scanProjectMachines({ cwd })).map((machine) => machine.relativePath))]);
1860
2015
  }
1861
2016
  function looksLikeXStateMachineSource(source) {
1862
2017
  const hasXStateImport = /from\s+['"]xstate(?:\/[^'"]+)?['"]/.test(source) || /require\(\s*['"]xstate(?:\/[^'"]+)?['"]\s*\)/.test(source);
@@ -1867,7 +2022,12 @@ async function classifyPushCandidates(files) {
1867
2022
  const machineFiles = [];
1868
2023
  const nonMachineFiles = [];
1869
2024
  for (const file of files) {
1870
- if (looksLikeXStateMachineSource(await fsPromises.readFile(file.filePath, "utf8"))) {
2025
+ const contents = await fsPromises.readFile(file.filePath, "utf8");
2026
+ if (discoverMachinesInSource({
2027
+ filePath: file.filePath,
2028
+ relativePath: file.relativePath,
2029
+ source: contents
2030
+ }).length > 0) {
1871
2031
  machineFiles.push(file);
1872
2032
  continue;
1873
2033
  }
@@ -1887,6 +2047,7 @@ async function resolveConfiguredProject(options) {
1887
2047
  configPath: options.configPath,
1888
2048
  rewriteIfNeeded: options.rewriteIfNeeded ?? true
1889
2049
  });
2050
+ if (!config.projectId) throw new Error("This is a local-only project. Run `statelyai init` to connect it to Stately Studio.");
1890
2051
  const studioUrl = options.baseUrl ?? config.studioUrl;
1891
2052
  return {
1892
2053
  client: options.client ?? createStatelyClient({
@@ -1958,34 +2119,68 @@ function formatCredentialStatus(status) {
1958
2119
  return status.type === "none" ? `Auth: none (${status.detail})` : `Auth: ${status.type} from ${status.source} (${status.detail})`;
1959
2120
  }
1960
2121
  async function resolveProjectStatus(options = {}) {
1961
- const { client, config, configPath, files } = await resolveConfiguredProject({
1962
- ...options,
2122
+ const { config, configPath, rootDir } = await readStatelyProjectConfig({
2123
+ cwd: options.cwd,
2124
+ configPath: options.configPath,
1963
2125
  rewriteIfNeeded: false
1964
2126
  });
1965
- const project = await client.projects.get(config.projectId);
1966
- const { machineFiles } = await classifyPushCandidates(files.filter(supportsMachineDiscovery));
1967
- const linkedTargets = await discoverLinkedPullTargets(machineFiles);
1968
- const linkedByPath = new Map(linkedTargets.map((target) => [target.file.filePath, target]));
2127
+ const localMachines = await discoverMachinesInFiles((await discoverStatelySourceFiles({
2128
+ cwd: rootDir,
2129
+ config
2130
+ })).filter(supportsMachineDiscovery));
2131
+ const studioUrl = options.baseUrl ?? config.studioUrl;
2132
+ if (!config.projectId) {
2133
+ const machines = localMachines.map((machine) => ({
2134
+ line: machine.line,
2135
+ machineIndex: machine.machineIndex,
2136
+ name: machine.name,
2137
+ path: machine.relativePath,
2138
+ status: "local-only"
2139
+ }));
2140
+ return {
2141
+ auth: {
2142
+ detail: "not required for local projects",
2143
+ source: "none",
2144
+ type: "none"
2145
+ },
2146
+ configPath,
2147
+ counts: {
2148
+ linked: 0,
2149
+ "local-only": machines.length,
2150
+ "missing-remote": 0,
2151
+ "remote-only": 0
2152
+ },
2153
+ machines,
2154
+ mode: "local"
2155
+ };
2156
+ }
2157
+ const apiKey = options.apiKey ?? (options.client ? void 0 : (await resolveApiKey()).apiKey);
2158
+ const project = await (options.client ?? createStatelyClient({
2159
+ apiKey,
2160
+ baseUrl: studioUrl
2161
+ })).projects.get(config.projectId);
1969
2162
  const remoteById = new Map(project.machines.flatMap((machine) => {
1970
2163
  const id = getProjectMachineId(machine);
1971
2164
  return id ? [[id, machine]] : [];
1972
2165
  }));
1973
- const linkedIds = new Set(linkedTargets.map((target) => target.machineId));
1974
- const studioUrl = options.baseUrl ?? config.studioUrl;
1975
- const machines = machineFiles.map((file) => {
1976
- const linked = linkedByPath.get(file.filePath);
1977
- if (!linked) return {
1978
- name: path.basename(file.relativePath),
1979
- path: file.relativePath,
2166
+ const linkedIds = new Set(localMachines.flatMap((machine) => machine.id ? [machine.id] : []));
2167
+ const machines = localMachines.map((machine) => {
2168
+ if (!machine.id) return {
2169
+ line: machine.line,
2170
+ machineIndex: machine.machineIndex,
2171
+ name: machine.name,
2172
+ path: machine.relativePath,
1980
2173
  status: "local-only"
1981
2174
  };
1982
- const remote = remoteById.get(linked.machineId);
2175
+ const remote = remoteById.get(machine.id);
1983
2176
  return {
1984
- id: linked.machineId,
1985
- name: remote?.name || path.basename(file.relativePath),
1986
- path: file.relativePath,
2177
+ id: machine.id,
2178
+ line: machine.line,
2179
+ machineIndex: machine.machineIndex,
2180
+ name: remote?.name || machine.name,
2181
+ path: machine.relativePath,
1987
2182
  status: remote ? "linked" : "missing-remote",
1988
- url: buildMachineEditorUrl(studioUrl, config.projectId, linked.machineId)
2183
+ url: buildMachineEditorUrl(studioUrl, config.projectId, machine.id)
1989
2184
  };
1990
2185
  });
1991
2186
  for (const remote of project.machines) {
@@ -2009,6 +2204,7 @@ async function resolveProjectStatus(options = {}) {
2009
2204
  "remote-only": machines.filter((machine) => machine.status === "remote-only").length
2010
2205
  },
2011
2206
  machines,
2207
+ mode: "studio",
2012
2208
  project: {
2013
2209
  id: config.projectId,
2014
2210
  ...project.name ? { name: project.name } : {},
@@ -2283,10 +2479,10 @@ var PushCommand = class PushCommand extends Command {
2283
2479
  if (flags["dry-run"]) {
2284
2480
  const wouldLink = [];
2285
2481
  const wouldUpdate = [];
2286
- for (const file of machineFiles) {
2287
- const pragma = getStatelyPragma(await fsPromises.readFile(file.filePath, "utf8"), file.filePath);
2288
- if (pragma?.id) wouldUpdate.push(`${file.relativePath}: ${formatSecondaryText(pragma.id)}`);
2289
- else wouldLink.push(file.relativePath);
2482
+ for (const machine of await discoverMachinesInFiles(machineFiles)) {
2483
+ const location = `${machine.relativePath}:${machine.line}`;
2484
+ if (machine.id) wouldUpdate.push(`${machine.name} ${location} ${formatSecondaryText(machine.id)}`);
2485
+ else wouldLink.push(`${machine.name} ${location}`);
2290
2486
  }
2291
2487
  if (wouldLink.length > 0) this.log(`${formatSectionLabel("Would link:")}\n${wouldLink.join("\n")}`);
2292
2488
  if (wouldUpdate.length > 0) this.log(`${formatSectionLabel("Would update:")}\n${wouldUpdate.join("\n")}`);
@@ -2389,8 +2585,8 @@ var OpenCommand = class OpenCommand extends Command {
2389
2585
  };
2390
2586
  var InitCommand = class InitCommand extends Command {
2391
2587
  static enableJsonFlag = false;
2392
- static summary = "Create a Stately project and write statelyai.json.";
2393
- static description = "Creates or reuses a remote Studio project for the current working directory and writes a local statelyai.json configuration file.";
2588
+ static summary = "Create a local or Studio-connected statelyai.json.";
2589
+ static description = "Creates a local configuration or reuses a remote Studio project for the current working directory.";
2394
2590
  static flags = {
2395
2591
  help: Flags.help({ char: "h" }),
2396
2592
  "base-url": Flags.string({ description: "Base URL for Stately Studio or a self-hosted deployment" }),
@@ -2408,6 +2604,10 @@ var InitCommand = class InitCommand extends Command {
2408
2604
  description: "Overwrite an existing statelyai.json file",
2409
2605
  default: false
2410
2606
  }),
2607
+ local: Flags.boolean({
2608
+ description: "Create a local-only config without authentication or Studio access",
2609
+ default: false
2610
+ }),
2411
2611
  scan: Flags.boolean({
2412
2612
  description: "Scan the repo for machine-bearing files and suggest source globs to save into statelyai.json",
2413
2613
  default: false
@@ -2415,21 +2615,25 @@ var InitCommand = class InitCommand extends Command {
2415
2615
  };
2416
2616
  async run() {
2417
2617
  const { flags } = await this.parse(InitCommand);
2418
- const resolvedApiKey = await resolveApiKey();
2419
- this.log("Creating or reusing remote project...");
2618
+ const resolvedApiKey = flags.local ? { source: "missing" } : await resolveApiKey();
2619
+ this.log(flags.local ? "Creating local project config..." : "Creating or reusing remote project...");
2420
2620
  const result = await initProject({
2421
2621
  apiKey: resolvedApiKey.apiKey,
2422
2622
  baseUrl: flags["base-url"],
2423
2623
  force: flags.force,
2624
+ local: flags.local,
2424
2625
  project: {
2425
2626
  ...flags.name ? { name: flags.name } : {},
2426
2627
  visibility: flags.visibility
2427
2628
  }
2428
2629
  });
2429
- const projectEditorUrl = buildProjectEditorUrl(result.config.studioUrl, result.project.projectId);
2430
- const initMessage = result.reusedExistingConfig ? `Using existing statelyai.json at ${result.configPath} for project ${result.project.projectId}.\nEditor: ${projectEditorUrl}` : `Initialized project ${result.project.projectId} and wrote ${result.configPath}.\nEditor: ${projectEditorUrl}`;
2630
+ const initMessage = result.project ? (() => {
2631
+ const projectEditorUrl = buildProjectEditorUrl(result.config.studioUrl, result.project.projectId);
2632
+ return result.reusedExistingConfig ? `Using existing statelyai.json at ${result.configPath} for project ${result.project.projectId}.\nEditor: ${projectEditorUrl}` : `Initialized project ${result.project.projectId} and wrote ${result.configPath}.\nEditor: ${projectEditorUrl}`;
2633
+ })() : result.reusedExistingConfig ? `Using existing local statelyai.json at ${result.configPath}.` : `Initialized local project and wrote ${result.configPath}.`;
2431
2634
  if (flags.scan) {
2432
2635
  this.log("Scanning local source files...");
2636
+ const machines = await scanProjectMachines({ cwd: path.dirname(result.configPath) });
2433
2637
  const suggestions = await scanProjectSources({
2434
2638
  cwd: path.dirname(result.configPath),
2435
2639
  defaultXStateVersion: result.config.defaultXStateVersion
@@ -2439,6 +2643,7 @@ var InitCommand = class InitCommand extends Command {
2439
2643
  return;
2440
2644
  }
2441
2645
  this.log(initMessage);
2646
+ this.log(formatDiscoveredMachines(machines));
2442
2647
  this.log(`Suggested source globs:\n${suggestions.map((source) => `- ${source.include.join(", ")}`).join("\n")}`);
2443
2648
  if (await promptYesNo("Save these source globs to statelyai.json?", true)) {
2444
2649
  const inferredNewMachineDir = inferNewMachineDirFromSuggestions(suggestions);
@@ -2448,7 +2653,7 @@ var InitCommand = class InitCommand extends Command {
2448
2653
  exclude: [...new Set(suggestions.flatMap((suggestion) => suggestion.exclude ?? []))],
2449
2654
  ...inferredNewMachineDir ? { newMachinesDir: inferredNewMachineDir } : {}
2450
2655
  };
2451
- await fsPromises.writeFile(result.configPath, `${JSON.stringify(nextConfig, null, 2)}\n`, "utf8");
2656
+ await writeStatelyProjectConfig(result.configPath, nextConfig);
2452
2657
  this.log(inferredNewMachineDir ? `Saved scanned source globs to statelyai.json.\nNew remote machines will be created under ${inferredNewMachineDir}/ by default.` : "Saved scanned source globs to statelyai.json.");
2453
2658
  } else this.log("Left statelyai.json unchanged. Edit it before running statelyai push.");
2454
2659
  return;
@@ -2456,6 +2661,23 @@ var InitCommand = class InitCommand extends Command {
2456
2661
  this.log(`${initMessage}\nNo include globs configured yet. Edit statelyai.json before running statelyai push, or rerun with --scan.`);
2457
2662
  }
2458
2663
  };
2664
+ var ScanCommand = class ScanCommand extends Command {
2665
+ static enableJsonFlag = false;
2666
+ static summary = "List local state machines without authentication or config.";
2667
+ static description = "Scans source files in the current repository and reports every discovered XState machine.";
2668
+ static flags = {
2669
+ help: Flags.help({ char: "h" }),
2670
+ json: Flags.boolean({
2671
+ description: "Print machine-readable JSON",
2672
+ default: false
2673
+ })
2674
+ };
2675
+ async run() {
2676
+ const { flags } = await this.parse(ScanCommand);
2677
+ const machines = await scanProjectMachines();
2678
+ this.log(flags.json ? JSON.stringify({ machines }, null, 2) : formatDiscoveredMachines(machines));
2679
+ }
2680
+ };
2459
2681
  var LoginCommand = class LoginCommand extends Command {
2460
2682
  static enableJsonFlag = false;
2461
2683
  static summary = "Store Stately credentials for future CLI use.";
@@ -2503,16 +2725,20 @@ var LogoutCommand = class extends Command {
2503
2725
  function formatProjectStatus(result) {
2504
2726
  const lines = [
2505
2727
  formatCredentialStatus(result.auth),
2506
- `Project: ${result.project.name ? `${result.project.name} ` : ""}(${result.project.id})`,
2507
- `Studio: ${result.project.url}`,
2728
+ `Mode: ${result.mode}`,
2729
+ ...result.project ? [`Project: ${result.project.name ? `${result.project.name} ` : ""}(${result.project.id})`, `Studio: ${result.project.url}`] : [],
2508
2730
  `Config: ${result.configPath}`,
2509
2731
  `Machines: ${result.machines.length} (${result.counts.linked} linked, ${result.counts["local-only"]} local-only, ${result.counts["remote-only"]} remote-only, ${result.counts["missing-remote"]} missing-remote)`
2510
2732
  ];
2511
2733
  for (const machine of result.machines) {
2512
- const details = [machine.id, machine.path].filter(Boolean).join(" ");
2734
+ const details = [machine.path ? `${machine.path}${machine.line ? `:${machine.line}` : ""}` : void 0, machine.id].filter(Boolean).join(" ");
2513
2735
  lines.push(` [${machine.status}] ${machine.name}${details ? ` ${details}` : ""}`);
2514
2736
  }
2515
2737
  const next = [];
2738
+ if (result.mode === "local") {
2739
+ lines.push("Next: statelyai init (connect to Studio)");
2740
+ return lines.join("\n");
2741
+ }
2516
2742
  if (result.counts["local-only"] > 0 || result.counts["missing-remote"] > 0) next.push("statelyai push");
2517
2743
  if (result.counts["remote-only"] > 0) next.push("statelyai pull");
2518
2744
  if (next.length > 0) lines.push(`Next: ${next.join(", ")}`);
@@ -2541,9 +2767,7 @@ var StatusCommand = class StatusCommand extends Command {
2541
2767
  this.log(flags.json ? JSON.stringify({ auth }, null, 2) : formatCredentialStatus(auth));
2542
2768
  return;
2543
2769
  }
2544
- const apiKey = (await resolveApiKey()).apiKey;
2545
2770
  const result = await resolveProjectStatus({
2546
- apiKey,
2547
2771
  baseUrl: flags["base-url"],
2548
2772
  configPath: flags.config
2549
2773
  });
@@ -2566,6 +2790,7 @@ const COMMANDS = {
2566
2790
  pull: PullCommand,
2567
2791
  push: PushCommand,
2568
2792
  open: OpenCommand,
2793
+ scan: ScanCommand,
2569
2794
  status: StatusCommand,
2570
2795
  init: InitCommand,
2571
2796
  login: LoginCommand,
@@ -2594,4 +2819,4 @@ function isDirectExecution() {
2594
2819
  if (isDirectExecution()) run$1();
2595
2820
 
2596
2821
  //#endregion
2597
- export { createStatelyProjectConfig as S, resolveConfiguredPullTargets as _, discoverOAuthLogin as a, run$1 as b, formatPlanSummary as c, getEnvApiKey as d, getEnvCredential as f, resolveApiKey as g, isFileGitDirty as h, discoverLinkedPullTargets as i, formatProjectStatus as l, initProject as m, buildOAuthLoginStart as n, discoverRemoteProjectMachineTargets as o, inferInitProjectName as p, classifyPushCandidates as r, formatMissingNewMachineDirMessage as s, COMMANDS as t, formatStoredCredentialSource as u, resolveCredentialStatus as v, scanProjectSources as x, resolveProjectStatus as y };
2822
+ export { scanProjectMachines as C, run$1 as S, createStatelyProjectConfig as T, isFileGitDirty as _, discoverMachinesInSource as a, resolveCredentialStatus as b, formatDiscoveredMachines as c, formatProjectStatus as d, formatStoredCredentialSource as f, initProject as g, inferInitProjectName as h, discoverLinkedPullTargets as i, formatMissingNewMachineDirMessage as l, getEnvCredential as m, buildOAuthLoginStart as n, discoverOAuthLogin as o, getEnvApiKey as p, classifyPushCandidates as r, discoverRemoteProjectMachineTargets as s, COMMANDS as t, formatPlanSummary as u, resolveApiKey as v, scanProjectSources as w, resolveProjectStatus as x, resolveConfiguredPullTargets as y };
package/dist/index.d.mts CHANGED
@@ -12,6 +12,8 @@ type StatelyCredential = {
12
12
  accessToken: string;
13
13
  refreshToken?: string;
14
14
  expiresAt?: number;
15
+ clientId?: string;
16
+ tokenEndpoint?: string;
15
17
  };
16
18
  type CredentialBackend = 'file' | 'linux-secret-tool' | 'macos-keychain';
17
19
  type StoredCredential = {
@@ -44,8 +46,8 @@ interface DiscoveredSourceFile {
44
46
  source: StatelySourceConfig;
45
47
  }
46
48
  declare function createStatelyProjectConfig(options: {
47
- projectId: string;
48
- studioUrl: string;
49
+ projectId?: string;
50
+ studioUrl?: string;
49
51
  defaultXStateVersion?: number;
50
52
  }): StatelyProjectConfig;
51
53
  //#endregion
@@ -58,18 +60,27 @@ interface InitProjectOptions {
58
60
  configPath?: string;
59
61
  defaultXStateVersion?: number;
60
62
  force?: boolean;
63
+ local?: boolean;
61
64
  project?: Partial<CreateProjectInput>;
62
65
  }
63
66
  interface InitProjectResult {
64
67
  config: StatelyProjectConfig;
65
68
  configPath: string;
66
- project: ProjectData;
69
+ project?: ProjectData;
67
70
  reusedExistingConfig?: boolean;
68
71
  }
69
72
  interface ScanProjectSourcesOptions {
70
73
  cwd?: string;
71
74
  defaultXStateVersion?: number;
72
75
  }
76
+ interface DiscoveredMachine {
77
+ filePath: string;
78
+ id?: string;
79
+ line: number;
80
+ machineIndex: number;
81
+ name: string;
82
+ relativePath: string;
83
+ }
73
84
  interface LinkedPullTarget {
74
85
  file: DiscoveredSourceFile;
75
86
  machineId: string;
@@ -100,6 +111,8 @@ interface ResolvedConfiguredPullTargets {
100
111
  type ProjectMachineStatus = 'linked' | 'local-only' | 'missing-remote' | 'remote-only';
101
112
  interface ProjectStatusMachine {
102
113
  id?: string;
114
+ line?: number;
115
+ machineIndex?: number;
103
116
  name: string;
104
117
  path?: string;
105
118
  status: ProjectMachineStatus;
@@ -110,17 +123,22 @@ interface CredentialStatus {
110
123
  source: 'environment' | 'none' | 'stored';
111
124
  type: 'api_key' | 'none' | 'oauth';
112
125
  }
126
+ interface ResolveApiKeyOptions {
127
+ fetch?: typeof fetch;
128
+ now?: number;
129
+ }
113
130
  interface ProjectStatusResult {
114
131
  auth: CredentialStatus;
115
132
  configPath: string;
116
133
  counts: Record<ProjectMachineStatus, number>;
117
134
  machines: ProjectStatusMachine[];
118
- project: {
135
+ mode: 'local' | 'studio';
136
+ project?: {
119
137
  id: string;
120
138
  name?: string;
121
139
  url: string;
122
140
  };
123
- studioUrl: string;
141
+ studioUrl?: string;
124
142
  }
125
143
  type ApiKeyResolution = {
126
144
  apiKey: string;
@@ -139,7 +157,7 @@ declare function getEnvCredential(): {
139
157
  credential: StatelyCredential;
140
158
  variable: 'NEXT_PUBLIC_STATELY_API_KEY' | 'STATELY_API_KEY' | 'STATELY_ACCESS_TOKEN';
141
159
  } | undefined;
142
- declare function resolveApiKey(): Promise<ApiKeyResolution>;
160
+ declare function resolveApiKey(options?: ResolveApiKeyOptions): Promise<ApiKeyResolution>;
143
161
  interface OAuthLoginDiscovery {
144
162
  authorizationServer: OAuthAuthorizationServerMetadata;
145
163
  authorizationEndpoint: string;
@@ -182,6 +200,15 @@ declare function discoverRemoteProjectMachineTargets(options: {
182
200
  client?: Pick<StudioClient, 'machines'>;
183
201
  }): Promise<RemoteProjectMachineTarget[]>;
184
202
  declare function initProject(options: InitProjectOptions): Promise<InitProjectResult>;
203
+ declare function discoverMachinesInSource(options: {
204
+ filePath: string;
205
+ relativePath: string;
206
+ source: string;
207
+ }): DiscoveredMachine[];
208
+ declare function scanProjectMachines(options?: {
209
+ cwd?: string;
210
+ }): Promise<DiscoveredMachine[]>;
211
+ declare function formatDiscoveredMachines(machines: DiscoveredMachine[]): string;
185
212
  declare function scanProjectSources(options: ScanProjectSourcesOptions): Promise<Array<Pick<StatelyProjectConfig, 'include' | 'exclude'>>>;
186
213
  declare function classifyPushCandidates(files: DiscoveredSourceFile[]): Promise<PushCandidateClassification>;
187
214
  declare function resolveConfiguredPullTargets(options: {
@@ -295,10 +322,21 @@ declare class InitCommand extends Command {
295
322
  name: _oclif_core_interfaces0.OptionFlag<string | undefined, _oclif_core_interfaces0.CustomOptions>;
296
323
  visibility: _oclif_core_interfaces0.OptionFlag<string, _oclif_core_interfaces0.CustomOptions>;
297
324
  force: _oclif_core_interfaces0.BooleanFlag<boolean>;
325
+ local: _oclif_core_interfaces0.BooleanFlag<boolean>;
298
326
  scan: _oclif_core_interfaces0.BooleanFlag<boolean>;
299
327
  };
300
328
  run(): Promise<void>;
301
329
  }
330
+ declare class ScanCommand extends Command {
331
+ static enableJsonFlag: boolean;
332
+ static summary: string;
333
+ static description: string;
334
+ static flags: {
335
+ help: _oclif_core_interfaces0.BooleanFlag<void>;
336
+ json: _oclif_core_interfaces0.BooleanFlag<boolean>;
337
+ };
338
+ run(): Promise<void>;
339
+ }
302
340
  declare class LoginCommand extends Command {
303
341
  static enableJsonFlag: boolean;
304
342
  static summary: string;
@@ -350,6 +388,7 @@ declare const COMMANDS: {
350
388
  pull: typeof PullCommand;
351
389
  push: typeof PushCommand;
352
390
  open: typeof OpenCommand;
391
+ scan: typeof ScanCommand;
353
392
  status: typeof StatusCommand;
354
393
  init: typeof InitCommand;
355
394
  login: typeof LoginCommand;
@@ -358,4 +397,4 @@ declare const COMMANDS: {
358
397
  };
359
398
  declare function run(argv?: any, entryUrl?: string): Promise<void>;
360
399
  //#endregion
361
- export { ApiKeyResolution, COMMANDS, CredentialStatus, InitProjectOptions, InitProjectResult, LinkedPullTarget, OAuthLoginDiscovery, ProjectMachineStatus, ProjectStatusMachine, ProjectStatusResult, PushCandidateClassification, RemoteProjectMachineTarget, ResolvedConfiguredPullTargets, ScanProjectSourcesOptions, buildOAuthLoginStart, classifyPushCandidates, createStatelyProjectConfig, discoverLinkedPullTargets, discoverOAuthLogin, discoverRemoteProjectMachineTargets, formatMissingNewMachineDirMessage, formatPlanSummary, formatProjectStatus, formatStoredCredentialSource, getEnvApiKey, getEnvCredential, inferInitProjectName, initProject, isFileGitDirty, resolveApiKey, resolveConfiguredPullTargets, resolveCredentialStatus, resolveProjectStatus, run, scanProjectSources };
400
+ export { ApiKeyResolution, COMMANDS, CredentialStatus, DiscoveredMachine, InitProjectOptions, InitProjectResult, LinkedPullTarget, OAuthLoginDiscovery, ProjectMachineStatus, ProjectStatusMachine, ProjectStatusResult, PushCandidateClassification, RemoteProjectMachineTarget, ResolveApiKeyOptions, ResolvedConfiguredPullTargets, ScanProjectSourcesOptions, buildOAuthLoginStart, classifyPushCandidates, createStatelyProjectConfig, discoverLinkedPullTargets, discoverMachinesInSource, discoverOAuthLogin, discoverRemoteProjectMachineTargets, formatDiscoveredMachines, formatMissingNewMachineDirMessage, formatPlanSummary, formatProjectStatus, formatStoredCredentialSource, getEnvApiKey, getEnvCredential, inferInitProjectName, initProject, isFileGitDirty, resolveApiKey, resolveConfiguredPullTargets, resolveCredentialStatus, resolveProjectStatus, run, scanProjectMachines, scanProjectSources };
package/dist/index.mjs CHANGED
@@ -1,3 +1,3 @@
1
- import { S as createStatelyProjectConfig, _ as resolveConfiguredPullTargets, a as discoverOAuthLogin, b as run, c as formatPlanSummary, d as getEnvApiKey, f as getEnvCredential, g as resolveApiKey, h as isFileGitDirty, i as discoverLinkedPullTargets, l as formatProjectStatus, m as initProject, n as buildOAuthLoginStart, o as discoverRemoteProjectMachineTargets, p as inferInitProjectName, r as classifyPushCandidates, s as formatMissingNewMachineDirMessage, t as COMMANDS, u as formatStoredCredentialSource, v as resolveCredentialStatus, x as scanProjectSources, y as resolveProjectStatus } from "./cli-D0TeuFzR.mjs";
1
+ import { C as scanProjectMachines, S as run, T as createStatelyProjectConfig, _ as isFileGitDirty, a as discoverMachinesInSource, b as resolveCredentialStatus, c as formatDiscoveredMachines, d as formatProjectStatus, f as formatStoredCredentialSource, g as initProject, h as inferInitProjectName, i as discoverLinkedPullTargets, l as formatMissingNewMachineDirMessage, m as getEnvCredential, n as buildOAuthLoginStart, o as discoverOAuthLogin, p as getEnvApiKey, r as classifyPushCandidates, s as discoverRemoteProjectMachineTargets, t as COMMANDS, u as formatPlanSummary, v as resolveApiKey, w as scanProjectSources, x as resolveProjectStatus, y as resolveConfiguredPullTargets } from "./cli-DicCA4VI.mjs";
2
2
 
3
- export { COMMANDS, buildOAuthLoginStart, classifyPushCandidates, createStatelyProjectConfig, discoverLinkedPullTargets, discoverOAuthLogin, discoverRemoteProjectMachineTargets, formatMissingNewMachineDirMessage, formatPlanSummary, formatProjectStatus, formatStoredCredentialSource, getEnvApiKey, getEnvCredential, inferInitProjectName, initProject, isFileGitDirty, resolveApiKey, resolveConfiguredPullTargets, resolveCredentialStatus, resolveProjectStatus, run, scanProjectSources };
3
+ export { COMMANDS, buildOAuthLoginStart, classifyPushCandidates, createStatelyProjectConfig, discoverLinkedPullTargets, discoverMachinesInSource, discoverOAuthLogin, discoverRemoteProjectMachineTargets, formatDiscoveredMachines, formatMissingNewMachineDirMessage, formatPlanSummary, formatProjectStatus, formatStoredCredentialSource, getEnvApiKey, getEnvCredential, inferInitProjectName, initProject, isFileGitDirty, resolveApiKey, resolveConfiguredPullTargets, resolveCredentialStatus, resolveProjectStatus, run, scanProjectMachines, scanProjectSources };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "statelyai",
3
- "version": "0.7.6",
3
+ "version": "0.7.7",
4
4
  "description": "Command-line tools for Stately",
5
5
  "license": "MIT",
6
6
  "type": "module",