statelyai 0.10.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,14 +1,15 @@
1
- import * as crypto from "node:crypto";
2
- import fs, { promises, watch } from "node:fs";
1
+ import { Args, Command, Flags, flush, handle, run } from "@oclif/core";
3
2
  import fsPromises from "node:fs/promises";
4
- import { execFile, execFileSync, spawn } from "node:child_process";
5
3
  import * as path$1 from "node:path";
6
4
  import path from "node:path";
5
+ import { z } from "zod";
6
+ import { execFile, execFileSync, spawn } from "node:child_process";
7
+ import { promisify } from "node:util";
8
+ import * as crypto from "node:crypto";
9
+ import fs, { promises, watch } from "node:fs";
7
10
  import { createInterface } from "node:readline/promises";
8
11
  import { Writable } from "node:stream";
9
12
  import { fileURLToPath, pathToFileURL } from "node:url";
10
- import { promisify } from "node:util";
11
- import { Args, Command, Flags, flush, handle, run } from "@oclif/core";
12
13
  import * as http from "node:http";
13
14
  import * as ts from "typescript";
14
15
  import { EditorHostWorkspaceSession, StudioApiError, buildAuthorizeUrl, createPkcePair, createRemoteEditorSyncBackend, createStatelyClient, discoverAuthorizationServer, discoverProtectedResource, exchangeCodeForToken, findStatelyPragmaAttachments, getStatelyPragma, refreshToken, registerOAuthClient } from "@statelyai/sdk";
@@ -16,121 +17,814 @@ import { WebSocket, WebSocketServer } from "ws";
16
17
  import os from "node:os";
17
18
  import { PullSourceReconciliationError, planSync, pullSync, pushLocalMachineLinks } from "@statelyai/sdk/sync";
18
19
 
19
- //#region src/version.ts
20
- const CLI_VERSION = "0.10.0";
20
+ //#region ../api-contract/dist/verification.mjs
21
+ const count = z.number().int().nonnegative();
22
+ const metric = (value) => z.object({
23
+ value: z.union([value, z.literal("n/a")]),
24
+ detail: z.string()
25
+ });
26
+ const coverageBucket = z.object({
27
+ covered: count,
28
+ total: count,
29
+ uncovered: z.array(z.string())
30
+ }).refine((bucket) => bucket.covered <= bucket.total, "Covered count exceeds total");
31
+ const verificationCoverageSchema = z.object({
32
+ states: coverageBucket,
33
+ transitions: coverageBucket,
34
+ eventCases: coverageBucket,
35
+ percent: z.object({
36
+ states: z.number().min(0).max(100),
37
+ transitions: z.number().min(0).max(100),
38
+ eventCases: z.number().min(0).max(100),
39
+ overall: z.number().min(0).max(100)
40
+ })
41
+ });
42
+ const witnessStep = z.object({
43
+ index: count,
44
+ event: z.object({ type: z.string() }).catchall(z.unknown()),
45
+ expectedState: z.unknown()
46
+ });
47
+ const predicateResult = z.object({
48
+ state: z.string(),
49
+ status: z.enum(["passed", "failed"]),
50
+ signals: z.array(z.string()),
51
+ message: z.string().optional()
52
+ });
53
+ const verificationDivergenceSchema = z.object({
54
+ version: z.literal(1),
55
+ id: z.string(),
56
+ kind: z.enum(["expectation", "negative-probe"]),
57
+ classification: z.enum([
58
+ "unclassified",
59
+ "model-gap",
60
+ "behavior-gap",
61
+ "flake"
62
+ ]),
63
+ pathId: z.string().optional(),
64
+ witness: z.object({
65
+ path: z.array(witnessStep),
66
+ stepCount: count
67
+ }),
68
+ minimalWitness: z.object({
69
+ steps: z.array(witnessStep),
70
+ pathLength: count,
71
+ candidatesTried: count,
72
+ reproduced: z.boolean()
73
+ }).optional(),
74
+ expected: z.object({
75
+ state: z.string(),
76
+ predicateResults: z.array(predicateResult),
77
+ refusal: z.object({
78
+ stateUnchanged: z.literal(true),
79
+ noNavigationOrMutation: z.literal(true)
80
+ }).optional()
81
+ }),
82
+ observed: z.object({
83
+ predicateResults: z.array(predicateResult),
84
+ snapshot: z.object({
85
+ url: z.string(),
86
+ title: z.string(),
87
+ visibleText: z.string(),
88
+ domHash: z.string(),
89
+ mutationCount: count.optional(),
90
+ requestCount: count.optional()
91
+ }),
92
+ error: z.string().optional()
93
+ }),
94
+ screenshotPath: z.string(),
95
+ provenance: z.object({
96
+ suite: z.string(),
97
+ modelVersion: z.string(),
98
+ modelHash: z.string(),
99
+ appBaseUrl: z.string(),
100
+ timestamp: z.string(),
101
+ traceFileRef: z.string()
102
+ }),
103
+ weakOracles: z.boolean()
104
+ }).passthrough();
105
+ const verificationPathSchema = z.object({
106
+ type: z.literal("path-result"),
107
+ id: z.string(),
108
+ status: z.enum(["passed", "failed"]),
109
+ witnessedStates: z.array(z.string()),
110
+ witnessedTransitions: z.array(z.string())
111
+ }).passthrough();
112
+ /** MBT version-one run report. Optional path/probe records support older reports. */
113
+ const verificationRunReportSchema = z.object({
114
+ version: z.literal(1),
115
+ suite: z.string(),
116
+ status: z.enum(["passed", "failed"]),
117
+ verdict: z.enum([
118
+ "conformant",
119
+ "divergent",
120
+ "inconclusive"
121
+ ]),
122
+ coverage: z.enum(["shortest", "simple"]),
123
+ scenario: z.string().optional(),
124
+ startedAt: z.iso.datetime({ offset: true }),
125
+ completedAt: z.iso.datetime({ offset: true }),
126
+ durationMs: z.number().nonnegative(),
127
+ exitCode: z.number().int(),
128
+ weakOracles: z.boolean(),
129
+ phases: z.array(z.object({
130
+ name: z.enum(["exploration", "verification"]),
131
+ status: z.enum(["completed", "skipped"]),
132
+ aiFree: z.boolean()
133
+ })),
134
+ probePlan: z.object({
135
+ cap: count,
136
+ totalCandidates: count,
137
+ samplingRule: z.string()
138
+ }),
139
+ metrics: z.object({
140
+ fitness: metric(z.number()).optional(),
141
+ precision: metric(z.number()),
142
+ soundnessRatio: metric(z.number()),
143
+ frontierSize: metric(z.number()),
144
+ determinism: metric(z.boolean())
145
+ }).passthrough(),
146
+ modelCoverage: verificationCoverageSchema,
147
+ executedCoverage: verificationCoverageSchema,
148
+ paths: z.array(verificationPathSchema).optional(),
149
+ probes: z.array(z.object({
150
+ type: z.literal("probe-result"),
151
+ id: z.string(),
152
+ state: z.string(),
153
+ event: z.string(),
154
+ status: z.enum(["passed", "failed"])
155
+ })).optional(),
156
+ divergences: z.array(verificationDivergenceSchema),
157
+ ledger: z.array(z.object({
158
+ type: z.literal("divergence"),
159
+ timestamp: z.string(),
160
+ divergence: verificationDivergenceSchema
161
+ }))
162
+ }).passthrough().superRefine((report, context) => {
163
+ if (report.divergences.length > 0 !== (report.verdict === "divergent")) context.addIssue({
164
+ code: "custom",
165
+ path: ["verdict"],
166
+ message: "Verdict must agree with recorded divergences"
167
+ });
168
+ if (report.verdict === "conformant" && (report.status !== "passed" || report.exitCode !== 0 || report.paths?.some((item) => item.status === "failed") || report.probes?.some((item) => item.status === "failed") || report.metrics.determinism.value === false)) context.addIssue({
169
+ code: "custom",
170
+ path: ["verdict"],
171
+ message: "Conformant reports cannot contain failed execution results"
172
+ });
173
+ if (report.status === "passed" && (report.verdict !== "conformant" || report.exitCode !== 0)) context.addIssue({
174
+ code: "custom",
175
+ path: ["status"],
176
+ message: "A passed report requires a conformant verdict and zero exit code"
177
+ });
178
+ });
179
+ const verificationFailOnSchema = z.enum(["divergent", "inconclusive"]);
180
+ /** References are inert data. The host resolves them inside its execution boundary. */
181
+ const verificationRequestSchema = z.object({
182
+ model: z.discriminatedUnion("kind", [z.object({
183
+ kind: z.literal("source"),
184
+ content: z.string().min(1),
185
+ name: z.string().optional()
186
+ }), z.object({
187
+ kind: z.literal("machine"),
188
+ machineId: z.string().min(1)
189
+ })]),
190
+ bindings: z.object({
191
+ content: z.string(),
192
+ name: z.string().optional()
193
+ }).optional(),
194
+ baseline: z.discriminatedUnion("kind", [z.object({
195
+ kind: z.literal("report"),
196
+ report: verificationRunReportSchema
197
+ }), z.object({
198
+ kind: z.literal("reference"),
199
+ id: z.string().min(1)
200
+ })]).optional()
201
+ });
202
+ const verificationExecutionPolicy = {
203
+ aiFree: true,
204
+ network: "deny",
205
+ env: { MBT_VERIFY_AI_FREE: "1" }
206
+ };
207
+ /** Strict mode fails on divergences and inconclusive runs; default fails on divergences. */
208
+ function verificationExitCode(report, failOn = "divergent") {
209
+ return report.verdict === "divergent" || failOn === "inconclusive" && report.verdict === "inconclusive" ? 1 : 0;
210
+ }
211
+ function verificationWitnessEvents(divergence) {
212
+ return (divergence.minimalWitness?.reproduced ? divergence.minimalWitness.steps : divergence.witness.path).map((step) => step.event.type);
213
+ }
214
+ function formatVerificationSummary(report) {
215
+ const violations = report.divergences.filter((item) => item.kind === "negative-probe").length;
216
+ return [`${report.verdict}: ${report.divergences.length} divergence(s), ${violations} negative-probe violation(s), ${report.executedCoverage.percent.overall}% executed coverage`, ...report.divergences.map((item) => `${item.id}: witness: [${verificationWitnessEvents(item).join(", ")}]`)].join("\n");
217
+ }
21
218
 
22
219
  //#endregion
23
- //#region src/cliHost.ts
24
- const DEFAULT_SYNC_MAX_FILES = 150;
25
- const DEFAULT_SYNC_MAX_BYTES = 2e6;
26
- const supportedDocumentPattern = /\.(?:c|m)?(?:jsx?|tsx?)$|\.jsonc?$|\.ya?ml$|\.mmd$|\.mermaid$/i;
27
- var WebSocketClient = class {
28
- listeners = /* @__PURE__ */ new Set();
29
- constructor(socket) {
30
- this.socket = socket;
31
- socket.on("message", (data, isBinary) => {
32
- if (isBinary) return;
33
- let message;
34
- try {
35
- message = JSON.parse(rawDataToBuffer(data).toString("utf8"));
36
- } catch {
37
- socket.close(1007, "Invalid JSON payload");
38
- return;
39
- }
40
- for (const listener of this.listeners) listener(message);
41
- });
42
- }
43
- onMessage(listener) {
44
- this.listeners.add(listener);
45
- }
46
- send(message) {
47
- if (this.socket.readyState === WebSocket.OPEN) this.socket.send(JSON.stringify(message));
220
+ //#region src/projectConfig.ts
221
+ const STATELY_CONFIG_FILE = "statelyai.json";
222
+ const STATELY_CONFIG_SCHEMA_URL = "https://stately.ai/schemas/statelyai.json";
223
+ const STATELY_CONFIG_VERSION = "1.0.0";
224
+ /**
225
+ * Base URL of the deployment that hosts the owned resource contract
226
+ * (`/api/v1/*`). Matches the SDK default.
227
+ */
228
+ const DEFAULT_STATELY_API_BASE_URL = "https://editor.stately.ai";
229
+ /**
230
+ * Hosts of the legacy hosted Studio. `statelyai.json#studioUrl` values that
231
+ * point at them predate the editor-hosted API and must be redirected.
232
+ */
233
+ const HOSTED_STUDIO_HOSTNAMES = new Set(["stately.ai", "www.stately.ai"]);
234
+ /**
235
+ * Maps a configured `statelyai.json#studioUrl` (or a `--base-url` flag) to the
236
+ * base URL of the editor API that serves our owned contract.
237
+ *
238
+ * Hosted legacy values (`https://stately.ai`, `http://www.stately.ai/`, ...)
239
+ * resolve to {@link DEFAULT_STATELY_API_BASE_URL}. Anything else is used as-is
240
+ * (minus trailing slashes): self-hosted editor deployments already point at the
241
+ * server that hosts `/api/v1`.
242
+ *
243
+ * This is a read-only compatibility shim: the CLI never rewrites
244
+ * `statelyai.json` because of it.
245
+ */
246
+ function resolveApiBaseUrl(studioUrl) {
247
+ const trimmed = studioUrl?.trim();
248
+ if (!trimmed) return DEFAULT_STATELY_API_BASE_URL;
249
+ const parsed = tryParseUrl(trimmed) ?? tryParseUrl(`https://${trimmed}`);
250
+ if (parsed && HOSTED_STUDIO_HOSTNAMES.has(parsed.hostname.toLowerCase())) return DEFAULT_STATELY_API_BASE_URL;
251
+ return trimmed.replace(/\/+$/, "");
252
+ }
253
+ function tryParseUrl(value) {
254
+ try {
255
+ return new URL(value);
256
+ } catch {
257
+ return;
48
258
  }
49
- close() {
50
- if (this.socket.readyState === WebSocket.OPEN || this.socket.readyState === WebSocket.CONNECTING) this.socket.close();
259
+ }
260
+ const DEFAULT_SOURCE_EXCLUDES = ["**/*.test.*", "**/*.spec.*"];
261
+ const CODE_SOURCE_EXTENSIONS = new Set([
262
+ ".ts",
263
+ ".tsx",
264
+ ".js",
265
+ ".jsx",
266
+ ".mts",
267
+ ".cts",
268
+ ".mjs",
269
+ ".cjs"
270
+ ]);
271
+ const execFileAsync$1 = promisify(execFile);
272
+ const ALWAYS_IGNORED_DIRECTORIES = new Set([
273
+ ".git",
274
+ "node_modules",
275
+ "dist",
276
+ "build",
277
+ "out",
278
+ "coverage",
279
+ ".next"
280
+ ]);
281
+ function createStatelyProjectConfig(options) {
282
+ const defaultXStateVersion = options.defaultXStateVersion ?? 5;
283
+ return {
284
+ $schema: STATELY_CONFIG_SCHEMA_URL,
285
+ version: STATELY_CONFIG_VERSION,
286
+ projectId: options.projectId ?? "",
287
+ studioUrl: options.studioUrl ?? "https://stately.ai",
288
+ defaultXStateVersion,
289
+ include: [],
290
+ exclude: [...DEFAULT_SOURCE_EXCLUDES]
291
+ };
292
+ }
293
+ async function writeStatelyProjectConfig(configPath, config) {
294
+ const serialized = { ...config };
295
+ if (!config.projectId) {
296
+ delete serialized.projectId;
297
+ delete serialized.studioUrl;
51
298
  }
52
- };
53
- function rawDataToBuffer(data) {
54
- if (Array.isArray(data)) return Buffer.concat(data);
55
- if (data instanceof ArrayBuffer) return Buffer.from(data);
56
- return Buffer.from(data);
299
+ await fsPromises.writeFile(configPath, `${JSON.stringify(serialized, null, 2)}\n`, "utf8");
57
300
  }
58
- var RemoteEditorSession = class {
59
- session;
60
- workspaceWatcher;
61
- constructor(options, sendMessage) {
62
- this.options = options;
63
- const send = (message) => {
64
- this.log("send", message.type, message);
65
- sendMessage(message);
301
+ function normalizeProjectConfig(config) {
302
+ const firstSource = config.sources?.[0];
303
+ return {
304
+ $schema: config.$schema ?? STATELY_CONFIG_SCHEMA_URL,
305
+ version: config.version ?? STATELY_CONFIG_VERSION,
306
+ projectId: config.projectId ?? "",
307
+ studioUrl: config.studioUrl ?? "https://stately.ai",
308
+ defaultXStateVersion: Math.max(5, config.defaultXStateVersion ?? 5),
309
+ ...config.sourceUpdateStrategy === "preserve" || config.sourceUpdateStrategy === "rewrite" ? { sourceUpdateStrategy: config.sourceUpdateStrategy } : {},
310
+ include: [...config.include ?? firstSource?.include ?? []],
311
+ exclude: config.exclude == null ? [...firstSource?.exclude ?? DEFAULT_SOURCE_EXCLUDES] : [...config.exclude],
312
+ ...config.newMachinesDir ? { newMachinesDir: config.newMachinesDir } : {}
313
+ };
314
+ }
315
+ function getConfigRewriteStatus(config) {
316
+ if (!config.sources) return { needsRewrite: false };
317
+ if (config.sources.length > 1) return {
318
+ needsRewrite: true,
319
+ reason: "multiple-legacy-sources"
320
+ };
321
+ return {
322
+ needsRewrite: true,
323
+ reason: "legacy-sources"
324
+ };
325
+ }
326
+ async function readStatelyProjectConfig(options = {}) {
327
+ const rootDir = path.resolve(options.cwd ?? process.cwd());
328
+ const configPath = path.resolve(rootDir, options.configPath ?? STATELY_CONFIG_FILE);
329
+ const raw = await fsPromises.readFile(configPath, "utf8");
330
+ const parsed = JSON.parse(raw);
331
+ const config = normalizeProjectConfig(parsed);
332
+ const rewriteStatus = getConfigRewriteStatus(parsed);
333
+ if (options.rewriteIfNeeded && rewriteStatus.reason === "legacy-sources") {
334
+ await writeStatelyProjectConfig(configPath, config);
335
+ return {
336
+ config,
337
+ configPath,
338
+ rootDir,
339
+ rewriteStatus: {
340
+ needsRewrite: false,
341
+ rewritten: true
342
+ }
66
343
  };
67
- const machines = options.machines ?? [{
68
- id: `${options.rootUri}#0`,
69
- fileName: options.fileName,
70
- machineIndex: 0
71
- }];
72
- this.workspaceWatcher = createSharedWorkspaceWatcher(options.rootDir);
73
- this.session = new EditorHostWorkspaceSession({
74
- machines: machines.map((machine) => ({
75
- id: machine.id,
76
- name: machine.name,
77
- source: {
78
- rootUri: fileNameToUri(machine.fileName),
79
- rootFileName: machine.fileName,
80
- rootDir: options.rootDir,
81
- machineIndex: machine.machineIndex,
82
- collectDocuments: (extraUris) => collectSourceDocuments({
83
- rootFileName: machine.fileName,
84
- extraUris: [...extraUris ?? []]
85
- }),
86
- applyReplacements: async (replacements, rootUri) => {
87
- await applyFileReplacements(replacements, rootUri);
88
- return [...new Set(replacements.map((replacement) => replacement.uri ?? rootUri))];
89
- },
90
- watchSavedDocuments: (listener) => this.workspaceWatcher.subscribe(listener)
91
- }
92
- })),
93
- currentMachineId: machines[0]?.id,
94
- workspaceName: path$1.basename(options.rootDir),
95
- projectWorkspaceOpen: false,
96
- sourceUpdateStrategy: options.sourceUpdateStrategy ?? "preserve",
97
- sync: createRemoteEditorSyncBackend({
98
- editorUrl: options.editorUrl,
99
- apiKey: options.apiKey,
100
- fetch: (input, init) => fetchEditorHost(input instanceof URL ? input : new URL(String(input)), init)
101
- }),
102
- send,
103
- apiKey: options.apiKey,
104
- client: {
105
- name: "statelyai",
106
- version: CLI_VERSION
107
- },
108
- showError: (message) => console.error(`Stately Editor: ${message}`)
109
- });
110
- }
111
- receiveMessage(message) {
112
- this.log("recv", message.type, message);
113
- return this.session.receive(message);
114
344
  }
115
- dispose() {
116
- this.session.dispose();
117
- this.workspaceWatcher.dispose();
118
- }
119
- log(direction, type, payload) {
120
- if (this.options.debug) console.error(`[${direction}] ${type}`, redactDebugPayload(payload) ?? "");
121
- }
122
- };
123
- function redactDebugPayload(payload) {
124
- if (Array.isArray(payload)) return payload.map(redactDebugPayload);
125
- if (!payload || typeof payload !== "object") return payload;
126
- return Object.fromEntries(Object.entries(payload).map(([key, value]) => [key, /^(?:apiKey|accessToken|refreshToken|token|authorization)$/i.test(key) ? "[REDACTED]" : redactDebugPayload(value)]));
345
+ return {
346
+ config,
347
+ configPath,
348
+ rootDir,
349
+ rewriteStatus
350
+ };
127
351
  }
128
- async function openEditor(options) {
129
- const fileName = path$1.resolve(options.fileName);
130
- const machines = (options.machines ?? [{
131
- id: `${fileNameToUri(fileName)}#0`,
132
- fileName,
133
- machineIndex: 0
352
+ async function walkFiles(rootDir, currentDir = rootDir) {
353
+ const entries = await fsPromises.readdir(currentDir, { withFileTypes: true });
354
+ const files = [];
355
+ for (const entry of entries) {
356
+ if (ALWAYS_IGNORED_DIRECTORIES.has(entry.name)) continue;
357
+ const absolutePath = path.join(currentDir, entry.name);
358
+ if (entry.isDirectory()) {
359
+ files.push(...await walkFiles(rootDir, absolutePath));
360
+ continue;
361
+ }
362
+ if (!entry.isFile()) continue;
363
+ files.push(path.relative(rootDir, absolutePath).replace(/\\/g, "/"));
364
+ }
365
+ return files;
366
+ }
367
+ async function filterGitIgnoredPaths(rootDir, relativeFiles) {
368
+ if (relativeFiles.length === 0) return relativeFiles;
369
+ try {
370
+ const { stdout } = await execFileAsync$1("git", ["check-ignore", ...relativeFiles], { cwd: rootDir });
371
+ const ignoredEntries = stdout.split(/\r?\n/).map((entry) => entry.trim()).filter(Boolean);
372
+ if (ignoredEntries.length === 0) return relativeFiles;
373
+ const ignoredPaths = new Set(ignoredEntries);
374
+ return relativeFiles.filter((relativePath) => {
375
+ return !ignoredPaths.has(relativePath);
376
+ });
377
+ } catch {
378
+ return relativeFiles;
379
+ }
380
+ }
381
+ function isCodeSourceFile(relativePath) {
382
+ return CODE_SOURCE_EXTENSIONS.has(path.extname(relativePath).toLowerCase());
383
+ }
384
+ function expandBraces(pattern) {
385
+ const match = pattern.match(/\{([^{}]+)\}/);
386
+ if (!match || match.index == null) return [pattern];
387
+ const [token, inner] = match;
388
+ return inner.split(",").flatMap((variant) => expandBraces(`${pattern.slice(0, match.index)}${variant}${pattern.slice(match.index + token.length)}`));
389
+ }
390
+ function globToRegExp(pattern) {
391
+ let regex = "^";
392
+ for (let index = 0; index < pattern.length; index += 1) {
393
+ const char = pattern[index];
394
+ const next = pattern[index + 1];
395
+ if (char === "*") {
396
+ if (next === "*") {
397
+ const slashAfterGlobstar = pattern[index + 2] === "/";
398
+ regex += slashAfterGlobstar ? "(?:.*/)?" : ".*";
399
+ index += slashAfterGlobstar ? 2 : 1;
400
+ continue;
401
+ }
402
+ regex += "[^/]*";
403
+ continue;
404
+ }
405
+ if (char === "?") {
406
+ regex += "[^/]";
407
+ continue;
408
+ }
409
+ if ("\\.[]{}()+-^$|".includes(char)) {
410
+ regex += `\\${char}`;
411
+ continue;
412
+ }
413
+ regex += char;
414
+ }
415
+ regex += "$";
416
+ return new RegExp(regex);
417
+ }
418
+ function matchesGlob(relativePath, pattern) {
419
+ return expandBraces(pattern).some((expanded) => globToRegExp(expanded).test(relativePath));
420
+ }
421
+ function matchesAny(patterns, relativePath) {
422
+ return (patterns ?? []).some((pattern) => matchesGlob(relativePath, pattern));
423
+ }
424
+ function isTrackedByStatelyProjectConfig(config, relativePath) {
425
+ return matchesAny(config.include, relativePath) && !matchesAny(config.exclude ?? DEFAULT_SOURCE_EXCLUDES, relativePath);
426
+ }
427
+ async function discoverCodeSourceFiles(options = {}) {
428
+ const rootDir = path.resolve(options.cwd ?? process.cwd());
429
+ return (await filterGitIgnoredPaths(rootDir, await walkFiles(rootDir))).filter((relativePath) => isCodeSourceFile(relativePath)).filter((relativePath) => !matchesAny(DEFAULT_SOURCE_EXCLUDES, relativePath)).sort((left, right) => left.localeCompare(right));
430
+ }
431
+ function createSuggestedConfig(include) {
432
+ return {
433
+ include: [include],
434
+ exclude: [...DEFAULT_SOURCE_EXCLUDES]
435
+ };
436
+ }
437
+ function toExtensionToken(extname) {
438
+ return extname.startsWith(".") ? extname.slice(1) : extname;
439
+ }
440
+ function formatExtensionPattern(extnames) {
441
+ const unique = [...new Set(extnames.map(toExtensionToken))].sort();
442
+ if (unique.length === 1) return `.${unique[0]}`;
443
+ return `.{${unique.join(",")}}`;
444
+ }
445
+ function getSuggestionRoot(relativePath) {
446
+ if (relativePath.startsWith("src/")) return "src/";
447
+ const packageMatch = relativePath.match(/^packages\/([^/]+)\/src\//);
448
+ if (packageMatch) return `packages/${packageMatch[1]}/src/`;
449
+ const appMatch = relativePath.match(/^apps\/([^/]+)\/src\//);
450
+ if (appMatch) return `apps/${appMatch[1]}/src/`;
451
+ return null;
452
+ }
453
+ function getCommonDirectory(relativePaths) {
454
+ const [firstDirectory, ...remainingDirectories] = relativePaths.map((relativePath) => path.posix.dirname(relativePath).split("/"));
455
+ if (!firstDirectory) return;
456
+ const commonSegments = [];
457
+ for (const [index, segment] of firstDirectory.entries()) {
458
+ if (!remainingDirectories.every((directory) => directory[index] === segment)) break;
459
+ commonSegments.push(segment);
460
+ }
461
+ return commonSegments.length > 0 ? commonSegments.join("/") : void 0;
462
+ }
463
+ function suggestGroupedInclude(relativePaths, root) {
464
+ const extPattern = formatExtensionPattern(relativePaths.map((relativePath) => path.posix.extname(relativePath)));
465
+ if (relativePaths.map((relativePath) => path.posix.basename(relativePath, path.posix.extname(relativePath))).every((basename) => basename.toLowerCase().endsWith("machine"))) return `${root}**/*machine${extPattern}`;
466
+ const commonDirectory = getCommonDirectory(relativePaths);
467
+ if (commonDirectory) return `${commonDirectory}/${relativePaths.every((relativePath) => path.posix.dirname(relativePath) === commonDirectory) ? "*" : "**/*"}${extPattern}`;
468
+ return `${root}**/*${extPattern}`;
469
+ }
470
+ function suggestStatelySourceConfigs(relativePaths) {
471
+ const pending = new Set(relativePaths.map((relativePath) => relativePath.replace(/\\/g, "/")).filter(Boolean));
472
+ const suggestions = [];
473
+ const addSuggestion = (include, predicate) => {
474
+ const matched = [...pending].filter(predicate);
475
+ if (matched.length === 0) return;
476
+ suggestions.push(createSuggestedConfig(include));
477
+ for (const matchedPath of matched) pending.delete(matchedPath);
478
+ };
479
+ const groupedRoots = /* @__PURE__ */ new Map();
480
+ for (const relativePath of pending) {
481
+ const root = getSuggestionRoot(relativePath);
482
+ if (!root) continue;
483
+ const bucket = groupedRoots.get(root) ?? [];
484
+ bucket.push(relativePath);
485
+ groupedRoots.set(root, bucket);
486
+ }
487
+ for (const [root, groupedPaths] of [...groupedRoots.entries()].sort((left, right) => left[0].localeCompare(right[0]))) addSuggestion(suggestGroupedInclude(groupedPaths, root), (relativePath) => groupedPaths.includes(relativePath));
488
+ const byDirectory = /* @__PURE__ */ new Map();
489
+ for (const relativePath of pending) {
490
+ const directory = path.posix.dirname(relativePath);
491
+ const key = directory === "." ? relativePath : directory;
492
+ const bucket = byDirectory.get(key) ?? [];
493
+ bucket.push(relativePath);
494
+ byDirectory.set(key, bucket);
495
+ }
496
+ for (const [key, matchedPaths] of [...byDirectory.entries()].sort((left, right) => left[0].localeCompare(right[0]))) {
497
+ if (matchedPaths.length > 1 && key !== "." && key !== matchedPaths[0]) {
498
+ addSuggestion(`${key}/**/*.{ts,tsx,js,jsx,mts,cts,mjs,cjs}`, (relativePath) => relativePath.startsWith(`${key}/`));
499
+ continue;
500
+ }
501
+ const exactPath = matchedPaths[0];
502
+ addSuggestion(exactPath, (relativePath) => relativePath === exactPath);
503
+ }
504
+ return suggestions;
505
+ }
506
+ async function discoverStatelySourceFiles(options = {}) {
507
+ const { config, rootDir } = options.config ? {
508
+ config: options.config,
509
+ rootDir: path.resolve(options.cwd ?? process.cwd())
510
+ } : await readStatelyProjectConfig(options);
511
+ const relativeFiles = await filterGitIgnoredPaths(rootDir, await walkFiles(rootDir));
512
+ const discovered = /* @__PURE__ */ new Map();
513
+ const source = {
514
+ include: config.include,
515
+ exclude: config.exclude ?? DEFAULT_SOURCE_EXCLUDES,
516
+ format: "xstate",
517
+ xstateVersion: config.defaultXStateVersion
518
+ };
519
+ for (const relativePath of relativeFiles) {
520
+ if (!matchesAny(source.include, relativePath)) continue;
521
+ if (matchesAny(source.exclude, relativePath)) continue;
522
+ const filePath = path.join(rootDir, relativePath);
523
+ if (!discovered.has(filePath)) discovered.set(filePath, {
524
+ filePath,
525
+ relativePath,
526
+ source
527
+ });
528
+ }
529
+ return [...discovered.values()].sort((left, right) => left.relativePath.localeCompare(right.relativePath));
530
+ }
531
+
532
+ //#endregion
533
+ //#region src/verificationJUnit.ts
534
+ function xml(text) {
535
+ return text.replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\uFFFE\uFFFF]/gu, "�").replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll("\"", "&quot;").replaceAll("'", "&apos;");
536
+ }
537
+ /** One case per recorded path/probe. Unattributed divergences remain separate cases. */
538
+ function formatVerificationJUnit(report) {
539
+ if (!report.paths) throw new Error("JUnit requires path records. This report predates path reporting.");
540
+ const records = [...report.paths, ...report.probes ?? []];
541
+ const counts = /* @__PURE__ */ new Map();
542
+ for (const record of records) counts.set(record.id, (counts.get(record.id) ?? 0) + 1);
543
+ const hasUniqueRecord = (id) => id !== void 0 && counts.get(id) === 1;
544
+ const cases = [];
545
+ let errors = 0;
546
+ const failures = (items) => items.map((item) => `<failure type="${xml(item.kind)}" message="${xml(`${item.id}; witness: [${verificationWitnessEvents(item).join(", ")}]`)}">${xml(item.observed.error ?? `Expected ${item.expected.state}`)}</failure>`).join("");
547
+ for (const record of records) {
548
+ const divergences = report.divergences.filter((item) => item.pathId === record.id && hasUniqueRecord(item.pathId));
549
+ const failedWithoutWitness = record.status === "failed" && !divergences.length;
550
+ if (failedWithoutWitness) errors++;
551
+ cases.push(`<testcase name="${xml(record.id)}" classname="${xml(report.suite)}">${failures(divergences)}${failedWithoutWitness ? "<error type=\"unattributed_path_failure\" message=\"Path failed without a linked divergence; consult the run report.\"/>" : ""}</testcase>`);
552
+ }
553
+ for (const item of report.divergences.filter((item) => !hasUniqueRecord(item.pathId))) cases.push(`<testcase name="${xml(item.id)}" classname="${xml(report.suite)}">${failures([item])}</testcase>`);
554
+ if (report.verdict === "inconclusive" || report.status === "failed" && !errors && !report.divergences.length) {
555
+ errors++;
556
+ cases.push("<testcase name=\"verification incomplete\"><error type=\"inconclusive\" message=\"Verification did not produce a conclusive successful run.\"/></testcase>");
557
+ }
558
+ return `<?xml version="1.0" encoding="UTF-8"?>\n<testsuite name="${xml(report.suite)}" tests="${cases.length}" failures="${report.divergences.length}" errors="${errors}" time="${report.durationMs / 1e3}">\n${cases.join("\n")}\n</testsuite>\n`;
559
+ }
560
+
561
+ //#endregion
562
+ //#region src/verification.ts
563
+ const maxInputBytes = 10 * 1024 * 1024;
564
+ async function readInput(file) {
565
+ if ((await fsPromises.stat(file)).size > maxInputBytes) throw new Error(`Verification input exceeds ${maxInputBytes} bytes: ${file}`);
566
+ return fsPromises.readFile(file, "utf8");
567
+ }
568
+ async function localFile(root, reference) {
569
+ const file = path.resolve(root, reference);
570
+ try {
571
+ if (!(await fsPromises.stat(file)).isFile()) throw new Error(`Not a file: ${reference}`);
572
+ return file;
573
+ } catch (error) {
574
+ if (error.code !== "ENOENT") throw error;
575
+ if (reference.includes("/") || reference.includes("\\") || path.extname(reference) || reference.startsWith(".")) throw new Error(`Verification input not found: ${reference}`);
576
+ return;
577
+ }
578
+ }
579
+ /** Read local source as data; never import or execute model/bindings modules. */
580
+ async function resolveVerificationRequest(options) {
581
+ const cwd = path.resolve(options.cwd ?? process.cwd());
582
+ const inputFiles = [];
583
+ let model = options.model;
584
+ if (!model) {
585
+ const project = await readStatelyProjectConfig({
586
+ cwd,
587
+ configPath: options.configPath
588
+ });
589
+ const files = await discoverStatelySourceFiles({
590
+ cwd: project.rootDir,
591
+ config: project.config
592
+ });
593
+ if (files.length !== 1) throw new Error(`Expected one configured model, found ${files.length}. Select one with --model.`);
594
+ model = files[0].filePath;
595
+ }
596
+ const modelFile = await localFile(cwd, model);
597
+ const request = { model: modelFile ? {
598
+ kind: "source",
599
+ content: await readInput(modelFile),
600
+ name: path.basename(modelFile)
601
+ } : {
602
+ kind: "machine",
603
+ machineId: model.replace(/^machine:/, "")
604
+ } };
605
+ if (modelFile) inputFiles.push(modelFile);
606
+ if (options.bindings) {
607
+ const file = path.resolve(cwd, options.bindings);
608
+ request.bindings = {
609
+ content: await readInput(file),
610
+ name: path.basename(file)
611
+ };
612
+ inputFiles.push(file);
613
+ }
614
+ if (options.baseline) {
615
+ const file = await localFile(cwd, options.baseline);
616
+ request.baseline = file ? {
617
+ kind: "report",
618
+ report: verificationRunReportSchema.parse(JSON.parse(await readInput(file)))
619
+ } : {
620
+ kind: "reference",
621
+ id: options.baseline
622
+ };
623
+ if (file) inputFiles.push(file);
624
+ }
625
+ return {
626
+ request: verificationRequestSchema.parse(request),
627
+ inputFiles
628
+ };
629
+ }
630
+ async function verifyProject(options = {}) {
631
+ const cwd = path.resolve(options.cwd ?? process.cwd());
632
+ let report;
633
+ let inputFiles;
634
+ if (options.fromReport) {
635
+ if (options.model || options.bindings || options.baseline || options.configPath) throw new Error("--from-report cannot be combined with model, bindings, baseline, or config inputs.");
636
+ const file = path.resolve(cwd, options.fromReport);
637
+ report = verificationRunReportSchema.parse(JSON.parse(await readInput(file)));
638
+ inputFiles = [file];
639
+ } else {
640
+ if (!options.execute) throw new Error("Live verification is not configured. Use --from-report <file> to process an existing run report, or supply a VerificationExecutor to verifyProject().");
641
+ const resolved = await resolveVerificationRequest(options);
642
+ inputFiles = resolved.inputFiles;
643
+ report = verificationRunReportSchema.parse(await options.execute(resolved.request, verificationExecutionPolicy));
644
+ }
645
+ const format = options.reportFormat ?? "json";
646
+ const output = format === "junit" ? formatVerificationJUnit(report) : `${JSON.stringify(report, null, 2)}\n`;
647
+ const destination = options.report ?? `statelyai-verify-report.${format === "junit" ? "xml" : "json"}`;
648
+ if (destination !== "-") {
649
+ const file = path.resolve(cwd, destination);
650
+ const existing = await fsPromises.realpath(file).catch((error) => {
651
+ if (error.code === "ENOENT") return file;
652
+ throw error;
653
+ });
654
+ for (const input of inputFiles) if (existing === await fsPromises.realpath(input)) throw new Error("Report output cannot overwrite a verification input.");
655
+ await fsPromises.mkdir(path.dirname(file), { recursive: true });
656
+ await fsPromises.writeFile(file, output, "utf8");
657
+ }
658
+ return {
659
+ report,
660
+ output,
661
+ destination,
662
+ summary: `${options.fromReport ? "Saved report: " : ""}${formatVerificationSummary(report)}`,
663
+ exitCode: verificationExitCode(report, options.failOn)
664
+ };
665
+ }
666
+
667
+ //#endregion
668
+ //#region src/verifyCommand.ts
669
+ var VerifyCommand = class VerifyCommand extends Command {
670
+ static enableJsonFlag = false;
671
+ static summary = "Validate a verification report, print its verdict, and export JSON or JUnit.";
672
+ static description = "Saved-report processing works offline. Live execution requires a configured verification adapter.";
673
+ static flags = {
674
+ model: Flags.string({ description: "Model file or registry machine ID (live execution)" }),
675
+ bindings: Flags.string({ description: "Bindings file (live execution)" }),
676
+ baseline: Flags.string({ description: "Baseline report file or reference ID (live execution)" }),
677
+ config: Flags.string({ description: "Path to statelyai.json (live execution)" }),
678
+ "from-report": Flags.string({ description: "Process an existing run report without executing verification" }),
679
+ report: Flags.string({ description: "Output file, or - for stdout; defaults to statelyai-verify-report.json/xml" }),
680
+ "report-format": Flags.string({
681
+ options: ["json", "junit"],
682
+ default: "json"
683
+ }),
684
+ "fail-on": Flags.string({
685
+ options: ["divergent", "inconclusive"],
686
+ default: "divergent",
687
+ description: "inconclusive also fails on divergences"
688
+ })
689
+ };
690
+ async run() {
691
+ const { flags } = await this.parse(VerifyCommand);
692
+ let result;
693
+ try {
694
+ result = await verifyProject({
695
+ model: flags.model,
696
+ bindings: flags.bindings,
697
+ baseline: flags.baseline,
698
+ configPath: flags.config,
699
+ fromReport: flags["from-report"],
700
+ report: flags.report,
701
+ reportFormat: flags["report-format"],
702
+ failOn: flags["fail-on"]
703
+ });
704
+ } catch (error) {
705
+ this.error(error instanceof Error ? error.message : String(error), { exit: 2 });
706
+ }
707
+ this.log(result.destination === "-" ? result.output.trimEnd() : result.summary);
708
+ if (result.exitCode) this.exit(result.exitCode);
709
+ }
710
+ };
711
+
712
+ //#endregion
713
+ //#region src/version.ts
714
+ const CLI_VERSION = "0.11.0";
715
+
716
+ //#endregion
717
+ //#region src/cliHost.ts
718
+ const DEFAULT_SYNC_MAX_FILES = 150;
719
+ const DEFAULT_SYNC_MAX_BYTES = 2e6;
720
+ const supportedDocumentPattern = /\.(?:c|m)?(?:jsx?|tsx?)$|\.jsonc?$|\.ya?ml$|\.mmd$|\.mermaid$/i;
721
+ var WebSocketClient = class {
722
+ listeners = /* @__PURE__ */ new Set();
723
+ constructor(socket) {
724
+ this.socket = socket;
725
+ socket.on("message", (data, isBinary) => {
726
+ if (isBinary) return;
727
+ let message;
728
+ try {
729
+ message = JSON.parse(rawDataToBuffer(data).toString("utf8"));
730
+ } catch {
731
+ socket.close(1007, "Invalid JSON payload");
732
+ return;
733
+ }
734
+ for (const listener of this.listeners) listener(message);
735
+ });
736
+ }
737
+ onMessage(listener) {
738
+ this.listeners.add(listener);
739
+ }
740
+ send(message) {
741
+ if (this.socket.readyState === WebSocket.OPEN) this.socket.send(JSON.stringify(message));
742
+ }
743
+ close() {
744
+ if (this.socket.readyState === WebSocket.OPEN || this.socket.readyState === WebSocket.CONNECTING) this.socket.close();
745
+ }
746
+ };
747
+ function rawDataToBuffer(data) {
748
+ if (Array.isArray(data)) return Buffer.concat(data);
749
+ if (data instanceof ArrayBuffer) return Buffer.from(data);
750
+ return Buffer.from(data);
751
+ }
752
+ var RemoteEditorSession = class {
753
+ session;
754
+ workspaceWatcher;
755
+ constructor(options, sendMessage) {
756
+ this.options = options;
757
+ const send = (message) => {
758
+ this.log("send", message.type, message);
759
+ sendMessage(message);
760
+ };
761
+ const machines = options.machines ?? [{
762
+ id: `${options.rootUri}#0`,
763
+ fileName: options.fileName,
764
+ machineIndex: 0
765
+ }];
766
+ this.workspaceWatcher = createSharedWorkspaceWatcher(options.rootDir);
767
+ this.session = new EditorHostWorkspaceSession({
768
+ machines: machines.map((machine) => ({
769
+ id: machine.id,
770
+ name: machine.name,
771
+ source: {
772
+ rootUri: fileNameToUri(machine.fileName),
773
+ rootFileName: machine.fileName,
774
+ rootDir: options.rootDir,
775
+ machineIndex: machine.machineIndex,
776
+ collectDocuments: (extraUris) => collectSourceDocuments({
777
+ rootFileName: machine.fileName,
778
+ extraUris: [...extraUris ?? []]
779
+ }),
780
+ applyReplacements: async (replacements, rootUri) => {
781
+ await applyFileReplacements(replacements, rootUri);
782
+ return [...new Set(replacements.map((replacement) => replacement.uri ?? rootUri))];
783
+ },
784
+ watchSavedDocuments: (listener) => this.workspaceWatcher.subscribe(listener)
785
+ }
786
+ })),
787
+ currentMachineId: machines[0]?.id,
788
+ workspaceName: path$1.basename(options.rootDir),
789
+ projectWorkspaceOpen: false,
790
+ sourceUpdateStrategy: options.sourceUpdateStrategy ?? "preserve",
791
+ sync: createRemoteEditorSyncBackend({
792
+ editorUrl: options.editorUrl,
793
+ apiKey: options.apiKey,
794
+ fetch: (input, init) => fetchEditorHost(input instanceof URL ? input : new URL(String(input)), init)
795
+ }),
796
+ send,
797
+ apiKey: options.apiKey,
798
+ client: {
799
+ name: "statelyai",
800
+ version: CLI_VERSION
801
+ },
802
+ showError: (message) => console.error(`Stately Editor: ${message}`)
803
+ });
804
+ }
805
+ receiveMessage(message) {
806
+ this.log("recv", message.type, message);
807
+ return this.session.receive(message);
808
+ }
809
+ dispose() {
810
+ this.session.dispose();
811
+ this.workspaceWatcher.dispose();
812
+ }
813
+ log(direction, type, payload) {
814
+ if (this.options.debug) console.error(`[${direction}] ${type}`, redactDebugPayload(payload) ?? "");
815
+ }
816
+ };
817
+ function redactDebugPayload(payload) {
818
+ if (Array.isArray(payload)) return payload.map(redactDebugPayload);
819
+ if (!payload || typeof payload !== "object") return payload;
820
+ return Object.fromEntries(Object.entries(payload).map(([key, value]) => [key, /^(?:apiKey|accessToken|refreshToken|token|authorization)$/i.test(key) ? "[REDACTED]" : redactDebugPayload(value)]));
821
+ }
822
+ async function openEditor(options) {
823
+ const fileName = path$1.resolve(options.fileName);
824
+ const machines = (options.machines ?? [{
825
+ id: `${fileNameToUri(fileName)}#0`,
826
+ fileName,
827
+ machineIndex: 0
134
828
  }]).map((machine) => ({
135
829
  ...machine,
136
830
  fileName: path$1.resolve(machine.fileName)
@@ -624,99 +1318,32 @@ async function writeFileStoredCredential(credential) {
624
1318
  location: credentialsPath
625
1319
  };
626
1320
  }
627
- async function deleteFileCredentials() {
628
- try {
629
- await promises.unlink(getCredentialsFilePath());
630
- return true;
631
- } catch {
632
- return false;
633
- }
634
- }
635
- async function readMacOSStoredCredential() {
636
- if (process.platform !== "darwin") return;
637
- try {
638
- const { stdout } = await execFile$1("security", [
639
- "find-generic-password",
640
- "-s",
641
- SERVICE_NAME,
642
- "-a",
643
- ACCOUNT_NAME,
644
- "-w"
645
- ]);
646
- return parseStoredCredential(stdout, "macos-keychain", "macOS Keychain");
647
- } catch {
648
- return;
649
- }
650
- }
651
- async function writeMacOSKeychain(apiKey) {
652
- const stored = await writeMacOSStoredCredential({
653
- type: "api_key",
654
- token: apiKey
655
- });
656
- return stored ? {
657
- apiKey,
658
- backend: stored.backend,
659
- location: stored.location
660
- } : void 0;
661
- }
662
- async function writeMacOSStoredCredential(credential) {
663
- if (process.platform !== "darwin") return;
664
- const payload = {
665
- version: 1,
666
- credential
667
- };
668
- try {
669
- await execFile$1("security", [
670
- "add-generic-password",
671
- "-U",
672
- "-s",
673
- SERVICE_NAME,
674
- "-a",
675
- ACCOUNT_NAME,
676
- "-w",
677
- JSON.stringify(payload)
678
- ]);
679
- return {
680
- ...credential ? { credential } : {},
681
- backend: "macos-keychain",
682
- location: "macOS Keychain"
683
- };
684
- } catch {
685
- return;
686
- }
687
- }
688
- async function deleteMacOSKeychain() {
689
- if (process.platform !== "darwin") return false;
1321
+ async function deleteFileCredentials() {
690
1322
  try {
691
- await execFile$1("security", [
692
- "delete-generic-password",
693
- "-s",
694
- SERVICE_NAME,
695
- "-a",
696
- ACCOUNT_NAME
697
- ]);
1323
+ await promises.unlink(getCredentialsFilePath());
698
1324
  return true;
699
1325
  } catch {
700
1326
  return false;
701
1327
  }
702
1328
  }
703
- async function readLinuxStoredCredential() {
704
- if (process.platform !== "linux") return;
1329
+ async function readMacOSStoredCredential() {
1330
+ if (process.platform !== "darwin") return;
705
1331
  try {
706
- const { stdout } = await execFile$1("secret-tool", [
707
- "lookup",
708
- "service",
1332
+ const { stdout } = await execFile$1("security", [
1333
+ "find-generic-password",
1334
+ "-s",
709
1335
  SERVICE_NAME,
710
- "account",
711
- ACCOUNT_NAME
1336
+ "-a",
1337
+ ACCOUNT_NAME,
1338
+ "-w"
712
1339
  ]);
713
- return parseStoredCredential(stdout, "linux-secret-tool", "Secret Service (secret-tool)");
1340
+ return parseStoredCredential(stdout, "macos-keychain", "macOS Keychain");
714
1341
  } catch {
715
1342
  return;
716
1343
  }
717
1344
  }
718
- async function writeLinuxSecretTool(apiKey) {
719
- const stored = await writeLinuxStoredCredential({
1345
+ async function writeMacOSKeychain(apiKey) {
1346
+ const stored = await writeMacOSStoredCredential({
720
1347
  type: "api_key",
721
1348
  token: apiKey
722
1349
  });
@@ -726,394 +1353,148 @@ async function writeLinuxSecretTool(apiKey) {
726
1353
  location: stored.location
727
1354
  } : void 0;
728
1355
  }
729
- async function writeLinuxStoredCredential(credential) {
730
- if (process.platform !== "linux") return;
1356
+ async function writeMacOSStoredCredential(credential) {
1357
+ if (process.platform !== "darwin") return;
731
1358
  const payload = {
732
1359
  version: 1,
733
1360
  credential
734
1361
  };
735
1362
  try {
736
- execFileSync("secret-tool", [
737
- "store",
738
- "--label",
739
- "statelyai API key",
740
- "service",
741
- SERVICE_NAME,
742
- "account",
743
- ACCOUNT_NAME
744
- ], { input: JSON.stringify(payload) });
745
- return {
746
- ...credential ? { credential } : {},
747
- backend: "linux-secret-tool",
748
- location: "Secret Service (secret-tool)"
749
- };
750
- } catch {
751
- return;
752
- }
753
- }
754
- async function deleteLinuxSecretTool() {
755
- if (process.platform !== "linux") return false;
756
- try {
757
- await execFile$1("secret-tool", [
758
- "clear",
759
- "service",
1363
+ await execFile$1("security", [
1364
+ "add-generic-password",
1365
+ "-U",
1366
+ "-s",
760
1367
  SERVICE_NAME,
761
- "account",
762
- ACCOUNT_NAME
763
- ]);
764
- return true;
765
- } catch {
766
- return false;
767
- }
768
- }
769
- async function getStoredCredential() {
770
- if (shouldUseFileBackendOnly()) return readFileStoredCredential();
771
- return await readMacOSStoredCredential() ?? await readLinuxStoredCredential() ?? await readFileStoredCredential();
772
- }
773
- async function setStoredApiKey(apiKey) {
774
- if (shouldUseFileBackendOnly()) return writeFileCredentials(apiKey);
775
- return await writeMacOSKeychain(apiKey) ?? await writeLinuxSecretTool(apiKey) ?? await writeFileCredentials(apiKey);
776
- }
777
- async function setStoredCredential(credential) {
778
- if (shouldUseFileBackendOnly()) return writeFileStoredCredential(credential);
779
- return await writeMacOSStoredCredential(credential) ?? await writeLinuxStoredCredential(credential) ?? await writeFileStoredCredential(credential);
780
- }
781
- async function deleteStoredApiKey() {
782
- if (shouldUseFileBackendOnly()) {
783
- const deleted = await deleteFileCredentials();
784
- return {
785
- deleted,
786
- locations: deleted ? [getCredentialsFilePath()] : []
787
- };
788
- }
789
- const locations = [];
790
- if (await deleteMacOSKeychain()) locations.push("macOS Keychain");
791
- if (await deleteLinuxSecretTool()) locations.push("Secret Service (secret-tool)");
792
- if (await deleteFileCredentials()) locations.push(getCredentialsFilePath());
793
- return {
794
- deleted: locations.length > 0,
795
- locations
796
- };
797
- }
798
- function describeCredentialBackend(backend, location) {
799
- switch (backend) {
800
- case "macos-keychain": return location;
801
- case "linux-secret-tool": return location;
802
- case "file": return `file (${location})`;
803
- }
804
- }
805
-
806
- //#endregion
807
- //#region src/projectConfig.ts
808
- const STATELY_CONFIG_FILE = "statelyai.json";
809
- const STATELY_CONFIG_SCHEMA_URL = "https://stately.ai/schemas/statelyai.json";
810
- const STATELY_CONFIG_VERSION = "1.0.0";
811
- /**
812
- * Base URL of the deployment that hosts the owned resource contract
813
- * (`/api/v1/*`). Matches the SDK default.
814
- */
815
- const DEFAULT_STATELY_API_BASE_URL = "https://editor.stately.ai";
816
- /**
817
- * Hosts of the legacy hosted Studio. `statelyai.json#studioUrl` values that
818
- * point at them predate the editor-hosted API and must be redirected.
819
- */
820
- const HOSTED_STUDIO_HOSTNAMES = new Set(["stately.ai", "www.stately.ai"]);
821
- /**
822
- * Maps a configured `statelyai.json#studioUrl` (or a `--base-url` flag) to the
823
- * base URL of the editor API that serves our owned contract.
824
- *
825
- * Hosted legacy values (`https://stately.ai`, `http://www.stately.ai/`, ...)
826
- * resolve to {@link DEFAULT_STATELY_API_BASE_URL}. Anything else is used as-is
827
- * (minus trailing slashes): self-hosted editor deployments already point at the
828
- * server that hosts `/api/v1`.
829
- *
830
- * This is a read-only compatibility shim: the CLI never rewrites
831
- * `statelyai.json` because of it.
832
- */
833
- function resolveApiBaseUrl(studioUrl) {
834
- const trimmed = studioUrl?.trim();
835
- if (!trimmed) return DEFAULT_STATELY_API_BASE_URL;
836
- const parsed = tryParseUrl(trimmed) ?? tryParseUrl(`https://${trimmed}`);
837
- if (parsed && HOSTED_STUDIO_HOSTNAMES.has(parsed.hostname.toLowerCase())) return DEFAULT_STATELY_API_BASE_URL;
838
- return trimmed.replace(/\/+$/, "");
839
- }
840
- function tryParseUrl(value) {
841
- try {
842
- return new URL(value);
843
- } catch {
844
- return;
845
- }
846
- }
847
- const DEFAULT_SOURCE_EXCLUDES = ["**/*.test.*", "**/*.spec.*"];
848
- const CODE_SOURCE_EXTENSIONS = new Set([
849
- ".ts",
850
- ".tsx",
851
- ".js",
852
- ".jsx",
853
- ".mts",
854
- ".cts",
855
- ".mjs",
856
- ".cjs"
857
- ]);
858
- const execFileAsync$1 = promisify(execFile);
859
- const ALWAYS_IGNORED_DIRECTORIES = new Set([
860
- ".git",
861
- "node_modules",
862
- "dist",
863
- "build",
864
- "out",
865
- "coverage",
866
- ".next"
867
- ]);
868
- function createStatelyProjectConfig(options) {
869
- const defaultXStateVersion = options.defaultXStateVersion ?? 5;
870
- return {
871
- $schema: STATELY_CONFIG_SCHEMA_URL,
872
- version: STATELY_CONFIG_VERSION,
873
- projectId: options.projectId ?? "",
874
- studioUrl: options.studioUrl ?? "https://stately.ai",
875
- defaultXStateVersion,
876
- include: [],
877
- exclude: [...DEFAULT_SOURCE_EXCLUDES]
878
- };
879
- }
880
- async function writeStatelyProjectConfig(configPath, config) {
881
- const serialized = { ...config };
882
- if (!config.projectId) {
883
- delete serialized.projectId;
884
- delete serialized.studioUrl;
885
- }
886
- await fsPromises.writeFile(configPath, `${JSON.stringify(serialized, null, 2)}\n`, "utf8");
887
- }
888
- function normalizeProjectConfig(config) {
889
- const firstSource = config.sources?.[0];
890
- return {
891
- $schema: config.$schema ?? STATELY_CONFIG_SCHEMA_URL,
892
- version: config.version ?? STATELY_CONFIG_VERSION,
893
- projectId: config.projectId ?? "",
894
- studioUrl: config.studioUrl ?? "https://stately.ai",
895
- defaultXStateVersion: Math.max(5, config.defaultXStateVersion ?? 5),
896
- ...config.sourceUpdateStrategy === "preserve" || config.sourceUpdateStrategy === "rewrite" ? { sourceUpdateStrategy: config.sourceUpdateStrategy } : {},
897
- include: [...config.include ?? firstSource?.include ?? []],
898
- exclude: config.exclude == null ? [...firstSource?.exclude ?? DEFAULT_SOURCE_EXCLUDES] : [...config.exclude],
899
- ...config.newMachinesDir ? { newMachinesDir: config.newMachinesDir } : {}
900
- };
901
- }
902
- function getConfigRewriteStatus(config) {
903
- if (!config.sources) return { needsRewrite: false };
904
- if (config.sources.length > 1) return {
905
- needsRewrite: true,
906
- reason: "multiple-legacy-sources"
907
- };
908
- return {
909
- needsRewrite: true,
910
- reason: "legacy-sources"
911
- };
912
- }
913
- async function readStatelyProjectConfig(options = {}) {
914
- const rootDir = path.resolve(options.cwd ?? process.cwd());
915
- const configPath = path.resolve(rootDir, options.configPath ?? STATELY_CONFIG_FILE);
916
- const raw = await fsPromises.readFile(configPath, "utf8");
917
- const parsed = JSON.parse(raw);
918
- const config = normalizeProjectConfig(parsed);
919
- const rewriteStatus = getConfigRewriteStatus(parsed);
920
- if (options.rewriteIfNeeded && rewriteStatus.reason === "legacy-sources") {
921
- await writeStatelyProjectConfig(configPath, config);
922
- return {
923
- config,
924
- configPath,
925
- rootDir,
926
- rewriteStatus: {
927
- needsRewrite: false,
928
- rewritten: true
929
- }
930
- };
931
- }
932
- return {
933
- config,
934
- configPath,
935
- rootDir,
936
- rewriteStatus
937
- };
938
- }
939
- async function walkFiles(rootDir, currentDir = rootDir) {
940
- const entries = await fsPromises.readdir(currentDir, { withFileTypes: true });
941
- const files = [];
942
- for (const entry of entries) {
943
- if (ALWAYS_IGNORED_DIRECTORIES.has(entry.name)) continue;
944
- const absolutePath = path.join(currentDir, entry.name);
945
- if (entry.isDirectory()) {
946
- files.push(...await walkFiles(rootDir, absolutePath));
947
- continue;
948
- }
949
- if (!entry.isFile()) continue;
950
- files.push(path.relative(rootDir, absolutePath).replace(/\\/g, "/"));
1368
+ "-a",
1369
+ ACCOUNT_NAME,
1370
+ "-w",
1371
+ JSON.stringify(payload)
1372
+ ]);
1373
+ return {
1374
+ ...credential ? { credential } : {},
1375
+ backend: "macos-keychain",
1376
+ location: "macOS Keychain"
1377
+ };
1378
+ } catch {
1379
+ return;
951
1380
  }
952
- return files;
953
1381
  }
954
- async function filterGitIgnoredPaths(rootDir, relativeFiles) {
955
- if (relativeFiles.length === 0) return relativeFiles;
1382
+ async function deleteMacOSKeychain() {
1383
+ if (process.platform !== "darwin") return false;
956
1384
  try {
957
- const { stdout } = await execFileAsync$1("git", ["check-ignore", ...relativeFiles], { cwd: rootDir });
958
- const ignoredEntries = stdout.split(/\r?\n/).map((entry) => entry.trim()).filter(Boolean);
959
- if (ignoredEntries.length === 0) return relativeFiles;
960
- const ignoredPaths = new Set(ignoredEntries);
961
- return relativeFiles.filter((relativePath) => {
962
- return !ignoredPaths.has(relativePath);
963
- });
1385
+ await execFile$1("security", [
1386
+ "delete-generic-password",
1387
+ "-s",
1388
+ SERVICE_NAME,
1389
+ "-a",
1390
+ ACCOUNT_NAME
1391
+ ]);
1392
+ return true;
964
1393
  } catch {
965
- return relativeFiles;
1394
+ return false;
966
1395
  }
967
1396
  }
968
- function isCodeSourceFile(relativePath) {
969
- return CODE_SOURCE_EXTENSIONS.has(path.extname(relativePath).toLowerCase());
970
- }
971
- function expandBraces(pattern) {
972
- const match = pattern.match(/\{([^{}]+)\}/);
973
- if (!match || match.index == null) return [pattern];
974
- const [token, inner] = match;
975
- return inner.split(",").flatMap((variant) => expandBraces(`${pattern.slice(0, match.index)}${variant}${pattern.slice(match.index + token.length)}`));
976
- }
977
- function globToRegExp(pattern) {
978
- let regex = "^";
979
- for (let index = 0; index < pattern.length; index += 1) {
980
- const char = pattern[index];
981
- const next = pattern[index + 1];
982
- if (char === "*") {
983
- if (next === "*") {
984
- const slashAfterGlobstar = pattern[index + 2] === "/";
985
- regex += slashAfterGlobstar ? "(?:.*/)?" : ".*";
986
- index += slashAfterGlobstar ? 2 : 1;
987
- continue;
988
- }
989
- regex += "[^/]*";
990
- continue;
991
- }
992
- if (char === "?") {
993
- regex += "[^/]";
994
- continue;
995
- }
996
- if ("\\.[]{}()+-^$|".includes(char)) {
997
- regex += `\\${char}`;
998
- continue;
999
- }
1000
- regex += char;
1397
+ async function readLinuxStoredCredential() {
1398
+ if (process.platform !== "linux") return;
1399
+ try {
1400
+ const { stdout } = await execFile$1("secret-tool", [
1401
+ "lookup",
1402
+ "service",
1403
+ SERVICE_NAME,
1404
+ "account",
1405
+ ACCOUNT_NAME
1406
+ ]);
1407
+ return parseStoredCredential(stdout, "linux-secret-tool", "Secret Service (secret-tool)");
1408
+ } catch {
1409
+ return;
1001
1410
  }
1002
- regex += "$";
1003
- return new RegExp(regex);
1004
- }
1005
- function matchesGlob(relativePath, pattern) {
1006
- return expandBraces(pattern).some((expanded) => globToRegExp(expanded).test(relativePath));
1007
- }
1008
- function matchesAny(patterns, relativePath) {
1009
- return (patterns ?? []).some((pattern) => matchesGlob(relativePath, pattern));
1010
1411
  }
1011
- function isTrackedByStatelyProjectConfig(config, relativePath) {
1012
- return matchesAny(config.include, relativePath) && !matchesAny(config.exclude ?? DEFAULT_SOURCE_EXCLUDES, relativePath);
1013
- }
1014
- async function discoverCodeSourceFiles(options = {}) {
1015
- const rootDir = path.resolve(options.cwd ?? process.cwd());
1016
- return (await filterGitIgnoredPaths(rootDir, await walkFiles(rootDir))).filter((relativePath) => isCodeSourceFile(relativePath)).filter((relativePath) => !matchesAny(DEFAULT_SOURCE_EXCLUDES, relativePath)).sort((left, right) => left.localeCompare(right));
1412
+ async function writeLinuxSecretTool(apiKey) {
1413
+ const stored = await writeLinuxStoredCredential({
1414
+ type: "api_key",
1415
+ token: apiKey
1416
+ });
1417
+ return stored ? {
1418
+ apiKey,
1419
+ backend: stored.backend,
1420
+ location: stored.location
1421
+ } : void 0;
1017
1422
  }
1018
- function createSuggestedConfig(include) {
1019
- return {
1020
- include: [include],
1021
- exclude: [...DEFAULT_SOURCE_EXCLUDES]
1423
+ async function writeLinuxStoredCredential(credential) {
1424
+ if (process.platform !== "linux") return;
1425
+ const payload = {
1426
+ version: 1,
1427
+ credential
1022
1428
  };
1429
+ try {
1430
+ execFileSync("secret-tool", [
1431
+ "store",
1432
+ "--label",
1433
+ "statelyai API key",
1434
+ "service",
1435
+ SERVICE_NAME,
1436
+ "account",
1437
+ ACCOUNT_NAME
1438
+ ], { input: JSON.stringify(payload) });
1439
+ return {
1440
+ ...credential ? { credential } : {},
1441
+ backend: "linux-secret-tool",
1442
+ location: "Secret Service (secret-tool)"
1443
+ };
1444
+ } catch {
1445
+ return;
1446
+ }
1023
1447
  }
1024
- function toExtensionToken(extname) {
1025
- return extname.startsWith(".") ? extname.slice(1) : extname;
1026
- }
1027
- function formatExtensionPattern(extnames) {
1028
- const unique = [...new Set(extnames.map(toExtensionToken))].sort();
1029
- if (unique.length === 1) return `.${unique[0]}`;
1030
- return `.{${unique.join(",")}}`;
1448
+ async function deleteLinuxSecretTool() {
1449
+ if (process.platform !== "linux") return false;
1450
+ try {
1451
+ await execFile$1("secret-tool", [
1452
+ "clear",
1453
+ "service",
1454
+ SERVICE_NAME,
1455
+ "account",
1456
+ ACCOUNT_NAME
1457
+ ]);
1458
+ return true;
1459
+ } catch {
1460
+ return false;
1461
+ }
1031
1462
  }
1032
- function getSuggestionRoot(relativePath) {
1033
- if (relativePath.startsWith("src/")) return "src/";
1034
- const packageMatch = relativePath.match(/^packages\/([^/]+)\/src\//);
1035
- if (packageMatch) return `packages/${packageMatch[1]}/src/`;
1036
- const appMatch = relativePath.match(/^apps\/([^/]+)\/src\//);
1037
- if (appMatch) return `apps/${appMatch[1]}/src/`;
1038
- return null;
1463
+ async function getStoredCredential() {
1464
+ if (shouldUseFileBackendOnly()) return readFileStoredCredential();
1465
+ return await readMacOSStoredCredential() ?? await readLinuxStoredCredential() ?? await readFileStoredCredential();
1039
1466
  }
1040
- function getCommonDirectory(relativePaths) {
1041
- const [firstDirectory, ...remainingDirectories] = relativePaths.map((relativePath) => path.posix.dirname(relativePath).split("/"));
1042
- if (!firstDirectory) return;
1043
- const commonSegments = [];
1044
- for (const [index, segment] of firstDirectory.entries()) {
1045
- if (!remainingDirectories.every((directory) => directory[index] === segment)) break;
1046
- commonSegments.push(segment);
1047
- }
1048
- return commonSegments.length > 0 ? commonSegments.join("/") : void 0;
1467
+ async function setStoredApiKey(apiKey) {
1468
+ if (shouldUseFileBackendOnly()) return writeFileCredentials(apiKey);
1469
+ return await writeMacOSKeychain(apiKey) ?? await writeLinuxSecretTool(apiKey) ?? await writeFileCredentials(apiKey);
1049
1470
  }
1050
- function suggestGroupedInclude(relativePaths, root) {
1051
- const extPattern = formatExtensionPattern(relativePaths.map((relativePath) => path.posix.extname(relativePath)));
1052
- if (relativePaths.map((relativePath) => path.posix.basename(relativePath, path.posix.extname(relativePath))).every((basename) => basename.toLowerCase().endsWith("machine"))) return `${root}**/*machine${extPattern}`;
1053
- const commonDirectory = getCommonDirectory(relativePaths);
1054
- if (commonDirectory) return `${commonDirectory}/${relativePaths.every((relativePath) => path.posix.dirname(relativePath) === commonDirectory) ? "*" : "**/*"}${extPattern}`;
1055
- return `${root}**/*${extPattern}`;
1471
+ async function setStoredCredential(credential) {
1472
+ if (shouldUseFileBackendOnly()) return writeFileStoredCredential(credential);
1473
+ return await writeMacOSStoredCredential(credential) ?? await writeLinuxStoredCredential(credential) ?? await writeFileStoredCredential(credential);
1056
1474
  }
1057
- function suggestStatelySourceConfigs(relativePaths) {
1058
- const pending = new Set(relativePaths.map((relativePath) => relativePath.replace(/\\/g, "/")).filter(Boolean));
1059
- const suggestions = [];
1060
- const addSuggestion = (include, predicate) => {
1061
- const matched = [...pending].filter(predicate);
1062
- if (matched.length === 0) return;
1063
- suggestions.push(createSuggestedConfig(include));
1064
- for (const matchedPath of matched) pending.delete(matchedPath);
1065
- };
1066
- const groupedRoots = /* @__PURE__ */ new Map();
1067
- for (const relativePath of pending) {
1068
- const root = getSuggestionRoot(relativePath);
1069
- if (!root) continue;
1070
- const bucket = groupedRoots.get(root) ?? [];
1071
- bucket.push(relativePath);
1072
- groupedRoots.set(root, bucket);
1073
- }
1074
- for (const [root, groupedPaths] of [...groupedRoots.entries()].sort((left, right) => left[0].localeCompare(right[0]))) addSuggestion(suggestGroupedInclude(groupedPaths, root), (relativePath) => groupedPaths.includes(relativePath));
1075
- const byDirectory = /* @__PURE__ */ new Map();
1076
- for (const relativePath of pending) {
1077
- const directory = path.posix.dirname(relativePath);
1078
- const key = directory === "." ? relativePath : directory;
1079
- const bucket = byDirectory.get(key) ?? [];
1080
- bucket.push(relativePath);
1081
- byDirectory.set(key, bucket);
1082
- }
1083
- for (const [key, matchedPaths] of [...byDirectory.entries()].sort((left, right) => left[0].localeCompare(right[0]))) {
1084
- if (matchedPaths.length > 1 && key !== "." && key !== matchedPaths[0]) {
1085
- addSuggestion(`${key}/**/*.{ts,tsx,js,jsx,mts,cts,mjs,cjs}`, (relativePath) => relativePath.startsWith(`${key}/`));
1086
- continue;
1087
- }
1088
- const exactPath = matchedPaths[0];
1089
- addSuggestion(exactPath, (relativePath) => relativePath === exactPath);
1475
+ async function deleteStoredApiKey() {
1476
+ if (shouldUseFileBackendOnly()) {
1477
+ const deleted = await deleteFileCredentials();
1478
+ return {
1479
+ deleted,
1480
+ locations: deleted ? [getCredentialsFilePath()] : []
1481
+ };
1090
1482
  }
1091
- return suggestions;
1092
- }
1093
- async function discoverStatelySourceFiles(options = {}) {
1094
- const { config, rootDir } = options.config ? {
1095
- config: options.config,
1096
- rootDir: path.resolve(options.cwd ?? process.cwd())
1097
- } : await readStatelyProjectConfig(options);
1098
- const relativeFiles = await filterGitIgnoredPaths(rootDir, await walkFiles(rootDir));
1099
- const discovered = /* @__PURE__ */ new Map();
1100
- const source = {
1101
- include: config.include,
1102
- exclude: config.exclude ?? DEFAULT_SOURCE_EXCLUDES,
1103
- format: "xstate",
1104
- xstateVersion: config.defaultXStateVersion
1483
+ const locations = [];
1484
+ if (await deleteMacOSKeychain()) locations.push("macOS Keychain");
1485
+ if (await deleteLinuxSecretTool()) locations.push("Secret Service (secret-tool)");
1486
+ if (await deleteFileCredentials()) locations.push(getCredentialsFilePath());
1487
+ return {
1488
+ deleted: locations.length > 0,
1489
+ locations
1105
1490
  };
1106
- for (const relativePath of relativeFiles) {
1107
- if (!matchesAny(source.include, relativePath)) continue;
1108
- if (matchesAny(source.exclude, relativePath)) continue;
1109
- const filePath = path.join(rootDir, relativePath);
1110
- if (!discovered.has(filePath)) discovered.set(filePath, {
1111
- filePath,
1112
- relativePath,
1113
- source
1114
- });
1491
+ }
1492
+ function describeCredentialBackend(backend, location) {
1493
+ switch (backend) {
1494
+ case "macos-keychain": return location;
1495
+ case "linux-secret-tool": return location;
1496
+ case "file": return `file (${location})`;
1115
1497
  }
1116
- return [...discovered.values()].sort((left, right) => left.relativePath.localeCompare(right.relativePath));
1117
1498
  }
1118
1499
 
1119
1500
  //#endregion
@@ -2654,6 +3035,7 @@ var WhoamiCommand = class extends Command {
2654
3035
  }
2655
3036
  };
2656
3037
  const COMMANDS = {
3038
+ verify: VerifyCommand,
2657
3039
  plan: PlanCommand,
2658
3040
  diff: DiffCommand,
2659
3041
  pull: PullCommand,
@@ -2688,4 +3070,4 @@ function isDirectExecution() {
2688
3070
  if (isDirectExecution()) run$1();
2689
3071
 
2690
3072
  //#endregion
2691
- 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 };
3073
+ export { scanProjectMachines as C, formatVerificationJUnit as D, verifyProject as E, createStatelyProjectConfig as O, run$1 as S, resolveVerificationRequest 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 };