statelyai 0.7.5 → 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-DRKY520A.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
@@ -168,7 +168,13 @@ var RemoteEditorSession = class {
168
168
  activePanels: []
169
169
  });
170
170
  } catch (error) {
171
- this.showError(formatError(error, "Failed to initialize the visual editor."));
171
+ const message = formatError(error, "Failed to initialize the visual editor.");
172
+ this.postMessage({
173
+ type: "@statelyai.error",
174
+ code: "initialization_failed",
175
+ message
176
+ });
177
+ this.showError(message);
172
178
  return;
173
179
  }
174
180
  for (const pending of this.pendingMessages) this.sendMessage(pending);
@@ -530,6 +536,8 @@ function watchWorkspace(rootDir, listener) {
530
536
  }
531
537
  function getCliWebviewContent(options) {
532
538
  const baseUrl = normalizedBaseUrl(options.editorUrl);
539
+ const url = new URL(`${baseUrl}/embed`);
540
+ url.searchParams.set("auth", "message");
533
541
  return `<!DOCTYPE html>
534
542
  <html lang="en" style="height:100%;margin:0">
535
543
  <head>
@@ -543,7 +551,7 @@ function getCliWebviewContent(options) {
543
551
  <body>
544
552
  <iframe
545
553
  id="stately-editor"
546
- src="${escapeAttribute(new URL(`${baseUrl}/embed`).toString())}"
554
+ src="${escapeAttribute(url.toString())}"
547
555
  allow="clipboard-read; clipboard-write"
548
556
  ></iframe>
549
557
  <script>
@@ -793,7 +801,9 @@ function parseStoredCredential(raw, backend, location) {
793
801
  type: "oauth",
794
802
  accessToken: credential.accessToken,
795
803
  ...typeof credential.refreshToken === "string" ? { refreshToken: credential.refreshToken } : {},
796
- ...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 } : {}
797
807
  },
798
808
  backend,
799
809
  location
@@ -1064,13 +1074,21 @@ function createStatelyProjectConfig(options) {
1064
1074
  return {
1065
1075
  $schema: STATELY_CONFIG_SCHEMA_URL,
1066
1076
  version: STATELY_CONFIG_VERSION,
1067
- projectId: options.projectId,
1068
- studioUrl: options.studioUrl,
1077
+ projectId: options.projectId ?? "",
1078
+ studioUrl: options.studioUrl ?? "https://stately.ai",
1069
1079
  defaultXStateVersion,
1070
1080
  include: [],
1071
1081
  exclude: [...DEFAULT_SOURCE_EXCLUDES]
1072
1082
  };
1073
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
+ }
1074
1092
  function normalizeProjectConfig(config) {
1075
1093
  const firstSource = config.sources?.[0];
1076
1094
  return {
@@ -1103,7 +1121,7 @@ async function readStatelyProjectConfig(options = {}) {
1103
1121
  const config = normalizeProjectConfig(parsed);
1104
1122
  const rewriteStatus = getConfigRewriteStatus(parsed);
1105
1123
  if (options.rewriteIfNeeded && rewriteStatus.reason === "legacy-sources") {
1106
- await fsPromises.writeFile(configPath, `${JSON.stringify(config, null, 2)}\n`, "utf8");
1124
+ await writeStatelyProjectConfig(configPath, config);
1107
1125
  return {
1108
1126
  config,
1109
1127
  configPath,
@@ -1351,7 +1369,61 @@ function getEnvCredential() {
1351
1369
  variable: "NEXT_PUBLIC_STATELY_API_KEY"
1352
1370
  };
1353
1371
  }
1354
- 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 = {}) {
1355
1427
  const envApiKey = getEnvApiKey();
1356
1428
  if (envApiKey) return {
1357
1429
  apiKey: envApiKey.apiKey,
@@ -1359,11 +1431,19 @@ async function resolveApiKey() {
1359
1431
  detail: envApiKey.variable
1360
1432
  };
1361
1433
  const storedCredential = await getStoredCredential();
1362
- if (storedCredential?.credential) return {
1363
- apiKey: storedCredential.credential.type === "oauth" ? storedCredential.credential.accessToken : storedCredential.credential.token,
1364
- source: "stored",
1365
- detail: describeCredentialBackend(storedCredential.backend, storedCredential.location)
1366
- };
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
+ }
1367
1447
  return { source: "missing" };
1368
1448
  }
1369
1449
  function getDefaultBaseUrl() {
@@ -1530,14 +1610,17 @@ function parseOAuthCallback(value) {
1530
1610
  return { code: value };
1531
1611
  }
1532
1612
  }
1533
- function createOAuthCredential(tokenResponse, now = Date.now()) {
1613
+ function createOAuthCredential(tokenResponse, options = {}) {
1534
1614
  if (!tokenResponse.access_token) throw new Error("Token response did not include an access_token.");
1615
+ const now = options.now ?? Date.now();
1535
1616
  const expiresAt = typeof tokenResponse.expires_at === "number" ? tokenResponse.expires_at * 1e3 : typeof tokenResponse.expires_in === "number" ? now + tokenResponse.expires_in * 1e3 : void 0;
1536
1617
  return {
1537
1618
  type: "oauth",
1538
1619
  accessToken: tokenResponse.access_token,
1539
- ...tokenResponse.refresh_token ? { refreshToken: tokenResponse.refresh_token } : {},
1540
- ...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 } : {}
1541
1624
  };
1542
1625
  }
1543
1626
  async function buildOAuthLoginStart(options) {
@@ -1597,7 +1680,10 @@ async function runOAuthBrowserLogin(options) {
1597
1680
  codeVerifier,
1598
1681
  redirectUri: callbackServer.redirectUri,
1599
1682
  clientId: discovery.clientId
1600
- })));
1683
+ }), {
1684
+ clientId: discovery.clientId,
1685
+ tokenEndpoint: discovery.tokenEndpoint
1686
+ }));
1601
1687
  } finally {
1602
1688
  await callbackServer.close();
1603
1689
  }
@@ -1808,16 +1894,38 @@ async function initProject(options) {
1808
1894
  configPath: options.configPath,
1809
1895
  rewriteIfNeeded: true
1810
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
+ }
1811
1925
  const client = options.client ?? createStatelyClient({
1812
1926
  apiKey: options.apiKey,
1813
1927
  baseUrl: studioUrl
1814
1928
  });
1815
- if (existingConfig) return {
1816
- config: existingConfig.config,
1817
- configPath: existingConfig.configPath,
1818
- project: await client.projects.get(existingConfig.config.projectId),
1819
- reusedExistingConfig: true
1820
- };
1821
1929
  const inferredRepo = options.project?.repo ?? await inferConnectedRepoFromCwd(cwd);
1822
1930
  const projectInput = {
1823
1931
  name: options.project?.name ?? inferInitProjectName(cwd, inferredRepo),
@@ -1827,12 +1935,16 @@ async function initProject(options) {
1827
1935
  ...inferredRepo ? { repo: inferredRepo } : {}
1828
1936
  };
1829
1937
  const project = await client.projects.ensure(projectInput);
1830
- const config = createStatelyProjectConfig({
1938
+ const config = existingConfig ? {
1939
+ ...existingConfig.config,
1940
+ projectId: project.projectId,
1941
+ studioUrl
1942
+ } : createStatelyProjectConfig({
1831
1943
  projectId: project.projectId,
1832
1944
  studioUrl,
1833
1945
  defaultXStateVersion
1834
1946
  });
1835
- await fsPromises.writeFile(configPath, `${JSON.stringify(config, null, 2)}\n`, "utf8");
1947
+ await writeStatelyProjectConfig(configPath, config);
1836
1948
  return {
1837
1949
  config,
1838
1950
  configPath,
@@ -1840,15 +1952,66 @@ async function initProject(options) {
1840
1952
  reusedExistingConfig: false
1841
1953
  };
1842
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
+ }
1843
2012
  async function scanProjectSources(options) {
1844
2013
  const cwd = path.resolve(options.cwd ?? process.cwd());
1845
- const candidateRelativePaths = await discoverCodeSourceFiles({ cwd });
1846
- const machineRelativePaths = [];
1847
- for (const relativePath of candidateRelativePaths) {
1848
- const filePath = path.join(cwd, relativePath);
1849
- if (looksLikeXStateMachineSource(await fsPromises.readFile(filePath, "utf8"))) machineRelativePaths.push(relativePath);
1850
- }
1851
- return suggestStatelySourceConfigs(machineRelativePaths);
2014
+ return suggestStatelySourceConfigs([...new Set((await scanProjectMachines({ cwd })).map((machine) => machine.relativePath))]);
1852
2015
  }
1853
2016
  function looksLikeXStateMachineSource(source) {
1854
2017
  const hasXStateImport = /from\s+['"]xstate(?:\/[^'"]+)?['"]/.test(source) || /require\(\s*['"]xstate(?:\/[^'"]+)?['"]\s*\)/.test(source);
@@ -1859,7 +2022,12 @@ async function classifyPushCandidates(files) {
1859
2022
  const machineFiles = [];
1860
2023
  const nonMachineFiles = [];
1861
2024
  for (const file of files) {
1862
- 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) {
1863
2031
  machineFiles.push(file);
1864
2032
  continue;
1865
2033
  }
@@ -1879,6 +2047,7 @@ async function resolveConfiguredProject(options) {
1879
2047
  configPath: options.configPath,
1880
2048
  rewriteIfNeeded: options.rewriteIfNeeded ?? true
1881
2049
  });
2050
+ if (!config.projectId) throw new Error("This is a local-only project. Run `statelyai init` to connect it to Stately Studio.");
1882
2051
  const studioUrl = options.baseUrl ?? config.studioUrl;
1883
2052
  return {
1884
2053
  client: options.client ?? createStatelyClient({
@@ -1950,34 +2119,68 @@ function formatCredentialStatus(status) {
1950
2119
  return status.type === "none" ? `Auth: none (${status.detail})` : `Auth: ${status.type} from ${status.source} (${status.detail})`;
1951
2120
  }
1952
2121
  async function resolveProjectStatus(options = {}) {
1953
- const { client, config, configPath, files } = await resolveConfiguredProject({
1954
- ...options,
2122
+ const { config, configPath, rootDir } = await readStatelyProjectConfig({
2123
+ cwd: options.cwd,
2124
+ configPath: options.configPath,
1955
2125
  rewriteIfNeeded: false
1956
2126
  });
1957
- const project = await client.projects.get(config.projectId);
1958
- const { machineFiles } = await classifyPushCandidates(files.filter(supportsMachineDiscovery));
1959
- const linkedTargets = await discoverLinkedPullTargets(machineFiles);
1960
- 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);
1961
2162
  const remoteById = new Map(project.machines.flatMap((machine) => {
1962
2163
  const id = getProjectMachineId(machine);
1963
2164
  return id ? [[id, machine]] : [];
1964
2165
  }));
1965
- const linkedIds = new Set(linkedTargets.map((target) => target.machineId));
1966
- const studioUrl = options.baseUrl ?? config.studioUrl;
1967
- const machines = machineFiles.map((file) => {
1968
- const linked = linkedByPath.get(file.filePath);
1969
- if (!linked) return {
1970
- name: path.basename(file.relativePath),
1971
- 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,
1972
2173
  status: "local-only"
1973
2174
  };
1974
- const remote = remoteById.get(linked.machineId);
2175
+ const remote = remoteById.get(machine.id);
1975
2176
  return {
1976
- id: linked.machineId,
1977
- name: remote?.name || path.basename(file.relativePath),
1978
- 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,
1979
2182
  status: remote ? "linked" : "missing-remote",
1980
- url: buildMachineEditorUrl(studioUrl, config.projectId, linked.machineId)
2183
+ url: buildMachineEditorUrl(studioUrl, config.projectId, machine.id)
1981
2184
  };
1982
2185
  });
1983
2186
  for (const remote of project.machines) {
@@ -2001,6 +2204,7 @@ async function resolveProjectStatus(options = {}) {
2001
2204
  "remote-only": machines.filter((machine) => machine.status === "remote-only").length
2002
2205
  },
2003
2206
  machines,
2207
+ mode: "studio",
2004
2208
  project: {
2005
2209
  id: config.projectId,
2006
2210
  ...project.name ? { name: project.name } : {},
@@ -2275,10 +2479,10 @@ var PushCommand = class PushCommand extends Command {
2275
2479
  if (flags["dry-run"]) {
2276
2480
  const wouldLink = [];
2277
2481
  const wouldUpdate = [];
2278
- for (const file of machineFiles) {
2279
- const pragma = getStatelyPragma(await fsPromises.readFile(file.filePath, "utf8"), file.filePath);
2280
- if (pragma?.id) wouldUpdate.push(`${file.relativePath}: ${formatSecondaryText(pragma.id)}`);
2281
- 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}`);
2282
2486
  }
2283
2487
  if (wouldLink.length > 0) this.log(`${formatSectionLabel("Would link:")}\n${wouldLink.join("\n")}`);
2284
2488
  if (wouldUpdate.length > 0) this.log(`${formatSectionLabel("Would update:")}\n${wouldUpdate.join("\n")}`);
@@ -2381,8 +2585,8 @@ var OpenCommand = class OpenCommand extends Command {
2381
2585
  };
2382
2586
  var InitCommand = class InitCommand extends Command {
2383
2587
  static enableJsonFlag = false;
2384
- static summary = "Create a Stately project and write statelyai.json.";
2385
- 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.";
2386
2590
  static flags = {
2387
2591
  help: Flags.help({ char: "h" }),
2388
2592
  "base-url": Flags.string({ description: "Base URL for Stately Studio or a self-hosted deployment" }),
@@ -2400,6 +2604,10 @@ var InitCommand = class InitCommand extends Command {
2400
2604
  description: "Overwrite an existing statelyai.json file",
2401
2605
  default: false
2402
2606
  }),
2607
+ local: Flags.boolean({
2608
+ description: "Create a local-only config without authentication or Studio access",
2609
+ default: false
2610
+ }),
2403
2611
  scan: Flags.boolean({
2404
2612
  description: "Scan the repo for machine-bearing files and suggest source globs to save into statelyai.json",
2405
2613
  default: false
@@ -2407,21 +2615,25 @@ var InitCommand = class InitCommand extends Command {
2407
2615
  };
2408
2616
  async run() {
2409
2617
  const { flags } = await this.parse(InitCommand);
2410
- const resolvedApiKey = await resolveApiKey();
2411
- 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...");
2412
2620
  const result = await initProject({
2413
2621
  apiKey: resolvedApiKey.apiKey,
2414
2622
  baseUrl: flags["base-url"],
2415
2623
  force: flags.force,
2624
+ local: flags.local,
2416
2625
  project: {
2417
2626
  ...flags.name ? { name: flags.name } : {},
2418
2627
  visibility: flags.visibility
2419
2628
  }
2420
2629
  });
2421
- const projectEditorUrl = buildProjectEditorUrl(result.config.studioUrl, result.project.projectId);
2422
- 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}.`;
2423
2634
  if (flags.scan) {
2424
2635
  this.log("Scanning local source files...");
2636
+ const machines = await scanProjectMachines({ cwd: path.dirname(result.configPath) });
2425
2637
  const suggestions = await scanProjectSources({
2426
2638
  cwd: path.dirname(result.configPath),
2427
2639
  defaultXStateVersion: result.config.defaultXStateVersion
@@ -2431,6 +2643,7 @@ var InitCommand = class InitCommand extends Command {
2431
2643
  return;
2432
2644
  }
2433
2645
  this.log(initMessage);
2646
+ this.log(formatDiscoveredMachines(machines));
2434
2647
  this.log(`Suggested source globs:\n${suggestions.map((source) => `- ${source.include.join(", ")}`).join("\n")}`);
2435
2648
  if (await promptYesNo("Save these source globs to statelyai.json?", true)) {
2436
2649
  const inferredNewMachineDir = inferNewMachineDirFromSuggestions(suggestions);
@@ -2440,7 +2653,7 @@ var InitCommand = class InitCommand extends Command {
2440
2653
  exclude: [...new Set(suggestions.flatMap((suggestion) => suggestion.exclude ?? []))],
2441
2654
  ...inferredNewMachineDir ? { newMachinesDir: inferredNewMachineDir } : {}
2442
2655
  };
2443
- await fsPromises.writeFile(result.configPath, `${JSON.stringify(nextConfig, null, 2)}\n`, "utf8");
2656
+ await writeStatelyProjectConfig(result.configPath, nextConfig);
2444
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.");
2445
2658
  } else this.log("Left statelyai.json unchanged. Edit it before running statelyai push.");
2446
2659
  return;
@@ -2448,6 +2661,23 @@ var InitCommand = class InitCommand extends Command {
2448
2661
  this.log(`${initMessage}\nNo include globs configured yet. Edit statelyai.json before running statelyai push, or rerun with --scan.`);
2449
2662
  }
2450
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
+ };
2451
2681
  var LoginCommand = class LoginCommand extends Command {
2452
2682
  static enableJsonFlag = false;
2453
2683
  static summary = "Store Stately credentials for future CLI use.";
@@ -2495,16 +2725,20 @@ var LogoutCommand = class extends Command {
2495
2725
  function formatProjectStatus(result) {
2496
2726
  const lines = [
2497
2727
  formatCredentialStatus(result.auth),
2498
- `Project: ${result.project.name ? `${result.project.name} ` : ""}(${result.project.id})`,
2499
- `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}`] : [],
2500
2730
  `Config: ${result.configPath}`,
2501
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)`
2502
2732
  ];
2503
2733
  for (const machine of result.machines) {
2504
- 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(" ");
2505
2735
  lines.push(` [${machine.status}] ${machine.name}${details ? ` ${details}` : ""}`);
2506
2736
  }
2507
2737
  const next = [];
2738
+ if (result.mode === "local") {
2739
+ lines.push("Next: statelyai init (connect to Studio)");
2740
+ return lines.join("\n");
2741
+ }
2508
2742
  if (result.counts["local-only"] > 0 || result.counts["missing-remote"] > 0) next.push("statelyai push");
2509
2743
  if (result.counts["remote-only"] > 0) next.push("statelyai pull");
2510
2744
  if (next.length > 0) lines.push(`Next: ${next.join(", ")}`);
@@ -2533,9 +2767,7 @@ var StatusCommand = class StatusCommand extends Command {
2533
2767
  this.log(flags.json ? JSON.stringify({ auth }, null, 2) : formatCredentialStatus(auth));
2534
2768
  return;
2535
2769
  }
2536
- const apiKey = (await resolveApiKey()).apiKey;
2537
2770
  const result = await resolveProjectStatus({
2538
- apiKey,
2539
2771
  baseUrl: flags["base-url"],
2540
2772
  configPath: flags.config
2541
2773
  });
@@ -2558,6 +2790,7 @@ const COMMANDS = {
2558
2790
  pull: PullCommand,
2559
2791
  push: PushCommand,
2560
2792
  open: OpenCommand,
2793
+ scan: ScanCommand,
2561
2794
  status: StatusCommand,
2562
2795
  init: InitCommand,
2563
2796
  login: LoginCommand,
@@ -2586,4 +2819,4 @@ function isDirectExecution() {
2586
2819
  if (isDirectExecution()) run$1();
2587
2820
 
2588
2821
  //#endregion
2589
- 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-DRKY520A.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.5",
3
+ "version": "0.7.7",
4
4
  "description": "Command-line tools for Stately",
5
5
  "license": "MIT",
6
6
  "type": "module",