statelyai 0.6.0 → 0.7.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.
package/README.md CHANGED
@@ -13,18 +13,20 @@ This package contains the `statelyai` command implementation.
13
13
  statelyai login
14
14
  ```
15
15
 
16
- Automation can still pass `--api-key`, set `STATELY_API_KEY`, or set
17
- `STATELY_ACCESS_TOKEN`. For no-auth self-hosted deployments, run
18
- `statelyai login --auth none`.
16
+ Automation can set `STATELY_ACCESS_TOKEN` for OAuth or `STATELY_API_KEY` for
17
+ API-key auth.
19
18
 
20
- For a self-hosted or local MCP/API resource, discover OAuth from that resource:
19
+ For a custom OAuth protected resource, discover OAuth from that resource:
21
20
 
22
21
  ```bash
23
22
  statelyai login --base-url http://localhost:3000/registry/api/mcp
24
23
  ```
25
24
 
26
25
  `statelyai login --auth bearer` uses the same browser OAuth flow explicitly.
27
- `statelyai login --api-key` keeps the legacy manual API-key path.
26
+ `statelyai login --api-key` prompts securely for an API key, and
27
+ `statelyai login --stdin` reads an API key from standard input. All commands use
28
+ stored OAuth credentials or stored API keys. Free accounts open read-only
29
+ root-level views.
28
30
 
29
31
  2. Initialize the current repo:
30
32
 
package/dist/bin.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { _ as run } from "./cli-Buz_BC3I.mjs";
2
+ import { _ as run } from "./cli-DR7_Qkzm.mjs";
3
3
 
4
4
  //#region src/bin.ts
5
5
  run();
@@ -11,7 +11,7 @@ import { fileURLToPath, pathToFileURL } from "node:url";
11
11
  import { promisify } from "node:util";
12
12
  import { Args, Command, Flags, flush, handle, run } from "@oclif/core";
13
13
  import os from "node:os";
14
- import { StudioApiError, buildAuthorizeUrl, createPkcePair, createStatelyClient, discoverAuthorizationServer, discoverProtectedResource, exchangeCodeForToken, getStatelyPragma } from "@statelyai/sdk";
14
+ import { StudioApiError, buildAuthorizeUrl, createPkcePair, createStatelyClient, discoverAuthorizationServer, discoverProtectedResource, exchangeCodeForToken, getStatelyPragma, registerOAuthClient } from "@statelyai/sdk";
15
15
  import { planSync, pullSync, pushLocalMachineLinks } from "@statelyai/sdk/sync";
16
16
 
17
17
  //#region src/cliHost.ts
@@ -86,6 +86,7 @@ var RemoteEditorSession = class {
86
86
  lastPersistedGraph;
87
87
  currentSourceLocations;
88
88
  currentFormat;
89
+ editorSyncAccess;
89
90
  ready = false;
90
91
  pendingMessages = [];
91
92
  pendingRetrievals = /* @__PURE__ */ new Map();
@@ -146,13 +147,20 @@ var RemoteEditorSession = class {
146
147
  const update = await this.parseRootDocument();
147
148
  this.currentFormat = update.format;
148
149
  this.currentSourceLocations = update.sourceLocations;
150
+ this.editorSyncAccess = update.access;
149
151
  this.updateSourceLocations(update.sourceLocations);
152
+ if (this.options.apiKey) this.sendMessage({
153
+ type: "@statelyai.setApiKey",
154
+ apiKey: this.options.apiKey
155
+ });
150
156
  this.postMessage({
151
157
  type: "@statelyai.init",
152
158
  machine: update.machine,
153
159
  format: update.format,
154
160
  sourceLocations: update.sourceLocations,
155
- mode: "editing",
161
+ mode: update.access?.readOnly ? "viewing" : "editing",
162
+ readOnly: update.access?.readOnly,
163
+ capabilities: update.access?.capabilities,
156
164
  theme: "light",
157
165
  unsavedIndicator: { enabled: true },
158
166
  leftPanels: [],
@@ -173,6 +181,7 @@ var RemoteEditorSession = class {
173
181
  const update = await this.parseRootDocument();
174
182
  this.currentFormat = update.format;
175
183
  this.currentSourceLocations = update.sourceLocations;
184
+ this.editorSyncAccess = update.access ?? this.editorSyncAccess;
176
185
  this.updateSourceLocations(update.sourceLocations);
177
186
  this.postMessage({
178
187
  type: "@statelyai.update",
@@ -185,6 +194,16 @@ var RemoteEditorSession = class {
185
194
  }
186
195
  }
187
196
  async saveFromEditor(msg) {
197
+ if (this.editorSyncAccess?.readOnly) {
198
+ const message = "Your Stately plan allows read-only editor sync.";
199
+ this.postMessage({
200
+ type: "@statelyai.toast",
201
+ message,
202
+ toastType: "error"
203
+ });
204
+ this.showError(message);
205
+ return;
206
+ }
188
207
  const validationErrors = getValidationErrors(msg.validations);
189
208
  if (validationErrors.length > 0) {
190
209
  const message = formatValidationErrorMessage(validationErrors);
@@ -355,10 +374,7 @@ async function openEditor(options) {
355
374
  return;
356
375
  }
357
376
  response.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
358
- response.end(getCliWebviewContent({
359
- editorUrl: options.editorUrl,
360
- apiKey: options.apiKey
361
- }));
377
+ response.end(getCliWebviewContent({ editorUrl: options.editorUrl }));
362
378
  });
363
379
  server.on("upgrade", (request, socket) => {
364
380
  if (request.url !== "/ws") {
@@ -514,8 +530,6 @@ function watchWorkspace(rootDir, listener) {
514
530
  }
515
531
  function getCliWebviewContent(options) {
516
532
  const baseUrl = normalizedBaseUrl(options.editorUrl);
517
- const url = new URL(`${baseUrl}/embed`);
518
- if (options.apiKey) url.searchParams.set("api_key", options.apiKey);
519
533
  return `<!DOCTYPE html>
520
534
  <html lang="en" style="height:100%;margin:0">
521
535
  <head>
@@ -529,7 +543,7 @@ function getCliWebviewContent(options) {
529
543
  <body>
530
544
  <iframe
531
545
  id="stately-editor"
532
- src="${escapeAttribute(url.toString())}"
546
+ src="${escapeAttribute(new URL(`${baseUrl}/embed`).toString())}"
533
547
  allow="clipboard-read; clipboard-write"
534
548
  ></iframe>
535
549
  <script>
@@ -703,15 +717,6 @@ function getCredentialsFilePath() {
703
717
  function shouldUseFileBackendOnly() {
704
718
  return process.env.STATELYAI_CREDENTIALS_BACKEND === "file" || process.env.STATELYAI_CONFIG_DIR !== void 0;
705
719
  }
706
- async function readFileCredentials() {
707
- const storedCredential = await readFileStoredCredential();
708
- if (storedCredential?.credential?.type !== "api_key") return;
709
- return {
710
- apiKey: storedCredential.credential.token,
711
- backend: storedCredential.backend,
712
- location: storedCredential.location
713
- };
714
- }
715
720
  function parseStoredCredential(raw, backend, location) {
716
721
  try {
717
722
  const parsed = JSON.parse(raw);
@@ -816,15 +821,6 @@ async function deleteFileCredentials() {
816
821
  return false;
817
822
  }
818
823
  }
819
- async function readMacOSKeychain() {
820
- const storedCredential = await readMacOSStoredCredential();
821
- if (storedCredential?.credential?.type !== "api_key") return;
822
- return {
823
- apiKey: storedCredential.credential.token,
824
- backend: storedCredential.backend,
825
- location: storedCredential.location
826
- };
827
- }
828
824
  async function readMacOSStoredCredential() {
829
825
  if (process.platform !== "darwin") return;
830
826
  try {
@@ -897,15 +893,6 @@ async function deleteMacOSKeychain() {
897
893
  return false;
898
894
  }
899
895
  }
900
- async function readLinuxSecretTool() {
901
- const storedCredential = await readLinuxStoredCredential();
902
- if (storedCredential?.credential?.type !== "api_key") return;
903
- return {
904
- apiKey: storedCredential.credential.token,
905
- backend: storedCredential.backend,
906
- location: storedCredential.location
907
- };
908
- }
909
896
  async function readLinuxStoredCredential() {
910
897
  if (process.platform !== "linux") return;
911
898
  try {
@@ -976,10 +963,6 @@ async function deleteLinuxSecretTool() {
976
963
  return false;
977
964
  }
978
965
  }
979
- async function getStoredApiKey() {
980
- if (shouldUseFileBackendOnly()) return readFileCredentials();
981
- return await readMacOSKeychain() ?? await readLinuxSecretTool() ?? await readFileCredentials();
982
- }
983
966
  async function getStoredCredential() {
984
967
  if (shouldUseFileBackendOnly()) return readFileStoredCredential();
985
968
  return await readMacOSStoredCredential() ?? await readLinuxStoredCredential() ?? await readFileStoredCredential();
@@ -1339,23 +1322,18 @@ function getEnvCredential() {
1339
1322
  variable: "NEXT_PUBLIC_STATELY_API_KEY"
1340
1323
  };
1341
1324
  }
1342
- async function resolveApiKey(explicitApiKey) {
1343
- if (explicitApiKey) return {
1344
- apiKey: explicitApiKey,
1345
- source: "flag",
1346
- detail: "--api-key"
1347
- };
1325
+ async function resolveApiKey() {
1348
1326
  const envApiKey = getEnvApiKey();
1349
1327
  if (envApiKey) return {
1350
1328
  apiKey: envApiKey.apiKey,
1351
1329
  source: "env",
1352
1330
  detail: envApiKey.variable
1353
1331
  };
1354
- const storedApiKey = await getStoredApiKey();
1355
- if (storedApiKey) return {
1356
- apiKey: storedApiKey.apiKey,
1332
+ const storedCredential = await getStoredCredential();
1333
+ if (storedCredential?.credential) return {
1334
+ apiKey: storedCredential.credential.type === "oauth" ? storedCredential.credential.accessToken : storedCredential.credential.token,
1357
1335
  source: "stored",
1358
- detail: describeCredentialBackend(storedApiKey.backend, storedApiKey.location)
1336
+ detail: describeCredentialBackend(storedCredential.backend, storedCredential.location)
1359
1337
  };
1360
1338
  return { source: "missing" };
1361
1339
  }
@@ -1368,34 +1346,48 @@ function getDefaultEditorUrl() {
1368
1346
  function getResolvedStudioUrl(baseUrl) {
1369
1347
  return baseUrl ?? getDefaultBaseUrl() ?? "https://stately.ai";
1370
1348
  }
1371
- function getDefaultMcpResourceUrl() {
1372
- return process.env.STATELY_MCP_RESOURCE_URL ?? process.env.NEXT_PUBLIC_MCP_RESOURCE_URL ?? process.env.STATELY_MCP_API_BASE_URL ?? "https://editor.stately.ai/api/mcp";
1349
+ function getDefaultOAuthIssuer() {
1350
+ return process.env.STATELY_OAUTH_ISSUER ?? process.env.NEXT_PUBLIC_STATELY_OAUTH_ISSUER ?? "https://stately.ai";
1373
1351
  }
1374
1352
  function normalizeLoginResourceUrl(baseUrl) {
1375
- const raw = baseUrl ?? getDefaultMcpResourceUrl();
1353
+ const raw = baseUrl;
1376
1354
  const url = new URL(raw);
1377
1355
  if (url.pathname === "/" || url.pathname === "") url.pathname = "/api/mcp";
1378
1356
  return url.toString();
1379
1357
  }
1380
- function getOauthClientId() {
1381
- return process.env.STATELY_OAUTH_CLIENT_ID ?? process.env.NEXT_PUBLIC_STATELY_OAUTH_CLIENT_ID ?? "statelyai-cli";
1358
+ function getConfiguredOauthClientId() {
1359
+ return process.env.STATELY_OAUTH_CLIENT_ID ?? process.env.NEXT_PUBLIC_STATELY_OAUTH_CLIENT_ID;
1382
1360
  }
1383
1361
  async function discoverOAuthLogin(options = {}) {
1384
- const resourceUrl = normalizeLoginResourceUrl(options.baseUrl);
1385
- const protectedResource = await discoverProtectedResource(resourceUrl, { fetch: options.fetch });
1386
- const [issuer] = protectedResource.authorization_servers ?? [];
1387
- if (!issuer) throw new Error(`No OAuth authorization server advertised by ${resourceUrl}. Use \`statelyai login --auth none\` for no-auth self-hosted deployments.`);
1362
+ const resourceUrl = options.baseUrl ? normalizeLoginResourceUrl(options.baseUrl) : getDefaultOAuthIssuer();
1363
+ const protectedResource = options.baseUrl ? await discoverProtectedResource(resourceUrl, { fetch: options.fetch }) : void 0;
1364
+ const issuer = protectedResource ? protectedResource.authorization_servers?.[0] : resourceUrl;
1365
+ if (!issuer) throw new Error(`No OAuth authorization server advertised by ${resourceUrl}. Pass --base-url for a Stately OAuth-enabled resource or set STATELY_OAUTH_ISSUER.`);
1388
1366
  const authorizationServer = await discoverAuthorizationServer(issuer, { fetch: options.fetch });
1389
1367
  const authorizationEndpoint = authorizationServer.authorization_endpoint;
1390
1368
  const tokenEndpoint = authorizationServer.token_endpoint;
1391
1369
  if (!authorizationEndpoint || !tokenEndpoint) throw new Error(`Authorization server ${issuer} did not advertise authorization and token endpoints.`);
1370
+ const configuredClientId = options.clientId ?? getConfiguredOauthClientId();
1371
+ const scopes = protectedResource?.scopes_supported ?? [];
1372
+ let clientId = configuredClientId;
1373
+ if (!clientId && authorizationServer.registration_endpoint && options.redirectUri) try {
1374
+ clientId = (await registerOAuthClient({
1375
+ registrationEndpoint: authorizationServer.registration_endpoint,
1376
+ redirectUri: options.redirectUri,
1377
+ scopes,
1378
+ fetch: options.fetch
1379
+ })).client_id;
1380
+ } catch (error) {
1381
+ throw new Error(`OAuth dynamic client registration failed for ${authorizationServer.registration_endpoint}. Enable dynamic client registration on the OAuth authorization server, or set STATELY_OAUTH_CLIENT_ID to a registered OAuth client id.`, { cause: error });
1382
+ }
1383
+ if (!clientId) throw new Error(`Authorization server ${issuer} did not advertise dynamic client registration. Set STATELY_OAUTH_CLIENT_ID to a registered OAuth client id.`);
1392
1384
  return {
1393
1385
  authorizationServer,
1394
1386
  authorizationEndpoint,
1395
- clientId: options.clientId ?? getOauthClientId(),
1396
- protectedResource,
1387
+ clientId,
1388
+ ...protectedResource ? { protectedResource } : {},
1397
1389
  resourceUrl,
1398
- scopes: protectedResource.scopes_supported ?? [],
1390
+ scopes,
1399
1391
  tokenEndpoint
1400
1392
  };
1401
1393
  }
@@ -1458,7 +1450,7 @@ async function readApiKeyFromStdin() {
1458
1450
  return Buffer.concat(chunks).toString("utf8").trim();
1459
1451
  }
1460
1452
  async function promptForApiKey() {
1461
- if (!process.stdin.isTTY || !process.stdout.isTTY) throw new Error("No interactive terminal available. Pass --api-key or pipe the key on stdin.");
1453
+ if (!process.stdin.isTTY || !process.stdout.isTTY) throw new Error("No interactive terminal available. Pipe the API key to `statelyai login --stdin`.");
1462
1454
  const maskedOutput = new Writable({ write(chunk, _encoding, callback) {
1463
1455
  if (!maskedOutput.muted) process.stdout.write(chunk);
1464
1456
  callback();
@@ -1564,7 +1556,8 @@ async function buildOAuthLoginStart(options) {
1564
1556
  const [discovery, pkce] = await Promise.all([discoverOAuthLogin({
1565
1557
  baseUrl: options.baseUrl,
1566
1558
  clientId: options.clientId,
1567
- fetch: options.fetch
1559
+ fetch: options.fetch,
1560
+ redirectUri: options.redirectUri
1568
1561
  }), createPkcePair()]);
1569
1562
  return {
1570
1563
  authorizeUrl: buildAuthorizeUrl({
@@ -1681,8 +1674,8 @@ function formatMissingNewMachineDirMessage(options) {
1681
1674
  function toMachineFileStem(machineName) {
1682
1675
  return (typeof machineName === "string" ? machineName : "").trim().replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/[^A-Za-z0-9]+/g, "-").replace(/^-+|-+$/g, "").toLowerCase() || "machine";
1683
1676
  }
1684
- function getMissingApiKeyMessage() {
1685
- return `No API key configured. Use \`statelyai login\`, set \`STATELY_API_KEY\`, or pass \`--api-key\`.\nGet or create an API key at ${STATELY_API_KEY_SETTINGS_URL}`;
1677
+ function getMissingCredentialMessage() {
1678
+ return `No Stately credential configured. Run \`statelyai login\`, set \`STATELY_ACCESS_TOKEN\`, or set \`STATELY_API_KEY\`.\nGet or create an API key at ${STATELY_API_KEY_SETTINGS_URL}`;
1686
1679
  }
1687
1680
  function buildProjectEditorUrl(studioUrl, projectId) {
1688
1681
  return `${studioUrl.replace(/\/+$/, "")}/registry/editor/${encodeURIComponent(projectId)}`;
@@ -1951,7 +1944,6 @@ const sharedFlags = {
1951
1944
  default: false,
1952
1945
  description: "Exit with a nonzero code when differences are found"
1953
1946
  }),
1954
- "api-key": Flags.string({ description: "Stately API key used for remote source or target resolution" }),
1955
1947
  "base-url": Flags.string({ description: "Base URL for Stately Studio or a self-hosted deployment" })
1956
1948
  };
1957
1949
  function formatChangeList(label, items) {
@@ -2006,7 +1998,7 @@ var PlanCommand = class PlanCommand extends ParsedSyncCommand {
2006
1998
  static args = sharedArgs;
2007
1999
  async run() {
2008
2000
  const { args, flags } = await this.parseSync(PlanCommand);
2009
- const apiKey = (await resolveApiKey(flags["api-key"])).apiKey;
2001
+ const apiKey = (await resolveApiKey()).apiKey;
2010
2002
  const plan = await planSync({
2011
2003
  source: args.source,
2012
2004
  target: args.target,
@@ -2023,7 +2015,7 @@ var DiffCommand = class DiffCommand extends ParsedSyncCommand {
2023
2015
  static args = sharedArgs;
2024
2016
  async run() {
2025
2017
  const { args, flags } = await this.parseSync(DiffCommand);
2026
- const apiKey = (await resolveApiKey(flags["api-key"])).apiKey;
2018
+ const apiKey = (await resolveApiKey()).apiKey;
2027
2019
  const plan = await planSync({
2028
2020
  source: args.source,
2029
2021
  target: args.target,
@@ -2057,9 +2049,9 @@ var PullCommand = class PullCommand extends ParsedSyncCommand {
2057
2049
  };
2058
2050
  async run() {
2059
2051
  const { args, flags } = await this.parseSync(PullCommand);
2060
- const apiKey = (await resolveApiKey(flags["api-key"])).apiKey;
2052
+ const apiKey = (await resolveApiKey()).apiKey;
2061
2053
  if (!args.source) {
2062
- if (!apiKey) this.error(getMissingApiKeyMessage());
2054
+ if (!apiKey) this.error(getMissingCredentialMessage());
2063
2055
  this.log(colorize("Resolving configured project and linked source files...", ANSI_CYAN));
2064
2056
  const { config, linkedTargets, project, remoteOnlyTargets, skipped, rewriteStatus } = await resolveConfiguredPullTargets({
2065
2057
  apiKey,
@@ -2138,7 +2130,7 @@ var PullCommand = class PullCommand extends ParsedSyncCommand {
2138
2130
  target = localCandidate;
2139
2131
  }
2140
2132
  if (!target) this.error("Missing target path. Pass `statelyai pull <machine-id|url> <file>` or `statelyai pull <linked-file>`.");
2141
- if (!apiKey) this.error(getMissingApiKeyMessage());
2133
+ if (!apiKey) this.error(getMissingCredentialMessage());
2142
2134
  if (!flags.force && await fileExists(target) && await isFileGitDirty(target)) this.error(`Refusing to overwrite locally modified file ${target}. Pass --force to overwrite it.`);
2143
2135
  this.log(colorize(`Pulling ${source} into ${target}...`, ANSI_CYAN));
2144
2136
  const result = await pullSync({
@@ -2160,7 +2152,6 @@ var PushCommand = class PushCommand extends Command {
2160
2152
  }) };
2161
2153
  static flags = {
2162
2154
  help: Flags.help({ char: "h" }),
2163
- "api-key": Flags.string({ description: "Stately API key used to create or update remote machines" }),
2164
2155
  "base-url": Flags.string({ description: "Base URL for Stately Studio or a self-hosted deployment" }),
2165
2156
  config: Flags.string({ description: "Path to statelyai.json" }),
2166
2157
  "dry-run": Flags.boolean({
@@ -2170,8 +2161,8 @@ var PushCommand = class PushCommand extends Command {
2170
2161
  };
2171
2162
  async run() {
2172
2163
  const { args, flags } = await this.parse(PushCommand);
2173
- const resolvedApiKey = flags["dry-run"] ? { source: "missing" } : await resolveApiKey(flags["api-key"]);
2174
- if (!flags["dry-run"] && !resolvedApiKey.apiKey) this.error(getMissingApiKeyMessage());
2164
+ const resolvedApiKey = flags["dry-run"] ? { source: "missing" } : await resolveApiKey();
2165
+ if (!flags["dry-run"] && !resolvedApiKey.apiKey) this.error(getMissingCredentialMessage());
2175
2166
  this.log(colorize("Resolving configured project and source files...", ANSI_CYAN));
2176
2167
  const { client, config, files, rewriteStatus } = flags["dry-run"] ? await (async () => {
2177
2168
  const { config, rootDir, rewriteStatus } = await readStatelyProjectConfig({
@@ -2286,7 +2277,6 @@ var OpenCommand = class OpenCommand extends Command {
2286
2277
  }) };
2287
2278
  static flags = {
2288
2279
  help: Flags.help({ char: "h" }),
2289
- "api-key": Flags.string({ description: "Stately API key used when the editor server requires auth" }),
2290
2280
  "editor-url": Flags.string({ description: "Base URL for the Stately editor embed" }),
2291
2281
  host: Flags.string({
2292
2282
  description: "Local server host",
@@ -2309,7 +2299,8 @@ var OpenCommand = class OpenCommand extends Command {
2309
2299
  };
2310
2300
  async run() {
2311
2301
  const { args, flags } = await this.parse(OpenCommand);
2312
- const apiKey = (await resolveApiKey(flags["api-key"])).apiKey;
2302
+ const apiKey = (await resolveApiKey()).apiKey;
2303
+ if (!apiKey) throw new Error("No Stately credential configured. Run `statelyai login` and retry.");
2313
2304
  await openEditor({
2314
2305
  fileName: path.resolve(args.file),
2315
2306
  editorUrl: flags["editor-url"] ?? getDefaultEditorUrl(),
@@ -2327,7 +2318,6 @@ var InitCommand = class InitCommand extends Command {
2327
2318
  static description = "Creates or reuses a remote Studio project for the current working directory and writes a local statelyai.json configuration file.";
2328
2319
  static flags = {
2329
2320
  help: Flags.help({ char: "h" }),
2330
- "api-key": Flags.string({ description: "Stately API key used to create the remote project" }),
2331
2321
  "base-url": Flags.string({ description: "Base URL for Stately Studio or a self-hosted deployment" }),
2332
2322
  name: Flags.string({ description: "Project name to create remotely" }),
2333
2323
  visibility: Flags.string({
@@ -2350,8 +2340,8 @@ var InitCommand = class InitCommand extends Command {
2350
2340
  };
2351
2341
  async run() {
2352
2342
  const { flags } = await this.parse(InitCommand);
2353
- const resolvedApiKey = await resolveApiKey(flags["api-key"]);
2354
- if (!resolvedApiKey.apiKey) this.error(getMissingApiKeyMessage());
2343
+ const resolvedApiKey = await resolveApiKey();
2344
+ if (!resolvedApiKey.apiKey) this.error(getMissingCredentialMessage());
2355
2345
  this.log("Creating or reusing remote project...");
2356
2346
  const result = await initProject({
2357
2347
  apiKey: resolvedApiKey.apiKey,
@@ -2398,8 +2388,8 @@ var LoginCommand = class LoginCommand extends Command {
2398
2388
  static description = "Stores Stately credentials in the OS credential store when available, with a private file fallback.";
2399
2389
  static flags = {
2400
2390
  help: Flags.help({ char: "h" }),
2401
- "api-key": Flags.string({ description: "API key to store without an interactive prompt" }),
2402
- "base-url": Flags.string({ description: "MCP/API resource URL used for OAuth discovery" }),
2391
+ "api-key": Flags.boolean({ description: "Prompt securely for an API key to store" }),
2392
+ "base-url": Flags.string({ description: "OAuth protected-resource URL for custom deployments" }),
2403
2393
  stdin: Flags.boolean({
2404
2394
  description: "Read the API key from standard input",
2405
2395
  default: false
@@ -2411,7 +2401,7 @@ var LoginCommand = class LoginCommand extends Command {
2411
2401
  };
2412
2402
  async run() {
2413
2403
  const { flags } = await this.parse(LoginCommand);
2414
- if (flags["api-key"] && flags.auth === "bearer") this.error("Pass either --api-key or --auth bearer, not both.");
2404
+ if ((flags["api-key"] || flags.stdin) && flags.auth === "bearer") this.error("Pass either --auth bearer or API-key login flags, not both.");
2415
2405
  if (flags.auth === "none") {
2416
2406
  if (flags.stdin || flags["api-key"]) this.error("Pass either --auth none or an API key, not both.");
2417
2407
  const stored = await setStoredNoAuth();
@@ -2427,34 +2417,30 @@ var LoginCommand = class LoginCommand extends Command {
2427
2417
  return;
2428
2418
  }
2429
2419
  if (flags.stdin && flags["api-key"]) this.error("Pass either --api-key or --stdin, not both.");
2430
- if (!flags["api-key"] && !flags.stdin && process.stdin.isTTY && process.stdout.isTTY) this.log(`Get or create an API key at ${STATELY_API_KEY_SETTINGS_URL}`);
2431
- const apiKey = normalizeApiKey(flags["api-key"] ?? (!process.stdin.isTTY || flags.stdin ? await readApiKeyFromStdin() : await promptForApiKey()));
2420
+ const apiKey = normalizeApiKey(flags.stdin ? await readApiKeyFromStdin() : await promptForApiKey());
2432
2421
  if (!apiKey) this.error(`API key cannot be empty.\nGet or create an API key at ${STATELY_API_KEY_SETTINGS_URL}`);
2433
- const stored = flags["api-key"] || flags.stdin ? await setStoredApiKey(apiKey) : await setStoredCredential({
2434
- type: "api_key",
2435
- token: apiKey
2436
- });
2422
+ const stored = await setStoredApiKey(apiKey);
2437
2423
  this.log(`Stored API key in ${describeCredentialBackend(stored.backend, stored.location)}.`);
2438
2424
  }
2439
2425
  };
2440
2426
  var LogoutCommand = class extends Command {
2441
2427
  static enableJsonFlag = false;
2442
- static summary = "Remove any API key stored by the CLI.";
2443
- static description = "Deletes the locally stored API key. Environment variables are not changed.";
2428
+ static summary = "Remove credentials stored by the CLI.";
2429
+ static description = "Deletes locally stored credentials. Environment variables are not changed.";
2444
2430
  static flags = { help: Flags.help({ char: "h" }) };
2445
2431
  async run() {
2446
2432
  const result = await deleteStoredApiKey();
2447
2433
  if (!result.deleted) {
2448
- this.log("No stored API key found.");
2434
+ this.log("No stored credential found.");
2449
2435
  return;
2450
2436
  }
2451
- this.log(`Removed stored API key from ${result.locations.join(", ")}.`);
2437
+ this.log(`Removed stored credential from ${result.locations.join(", ")}.`);
2452
2438
  }
2453
2439
  };
2454
2440
  var WhoamiCommand = class extends Command {
2455
2441
  static enableJsonFlag = false;
2456
- static summary = "Show how the CLI would resolve its API key.";
2457
- static description = "Reports whether the CLI would use a flag, environment variable, or stored credential.";
2442
+ static summary = "Show how the CLI would resolve credentials.";
2443
+ static description = "Reports whether the CLI would use an environment variable or stored credential.";
2458
2444
  static flags = { help: Flags.help({ char: "h" }) };
2459
2445
  async run() {
2460
2446
  const envCredential = getEnvCredential();
@@ -2473,7 +2459,7 @@ var WhoamiCommand = class extends Command {
2473
2459
  this.log(formatStoredCredentialSource(storedCredential));
2474
2460
  return;
2475
2461
  }
2476
- this.log(getMissingApiKeyMessage());
2462
+ this.log(getMissingCredentialMessage());
2477
2463
  }
2478
2464
  };
2479
2465
  const COMMANDS = {
package/dist/index.d.mts CHANGED
@@ -100,7 +100,7 @@ interface ResolvedConfiguredPullTargets {
100
100
  type ApiKeyResolution = {
101
101
  apiKey: string;
102
102
  detail: string;
103
- source: 'env' | 'flag' | 'stored';
103
+ source: 'env' | 'stored';
104
104
  } | {
105
105
  apiKey?: undefined;
106
106
  detail?: undefined;
@@ -114,12 +114,12 @@ declare function getEnvCredential(): {
114
114
  credential: StatelyCredential;
115
115
  variable: 'NEXT_PUBLIC_STATELY_API_KEY' | 'STATELY_API_KEY' | 'STATELY_ACCESS_TOKEN';
116
116
  } | undefined;
117
- declare function resolveApiKey(explicitApiKey?: string): Promise<ApiKeyResolution>;
117
+ declare function resolveApiKey(): Promise<ApiKeyResolution>;
118
118
  interface OAuthLoginDiscovery {
119
119
  authorizationServer: OAuthAuthorizationServerMetadata;
120
120
  authorizationEndpoint: string;
121
121
  clientId: string;
122
- protectedResource: OAuthProtectedResourceMetadata;
122
+ protectedResource?: OAuthProtectedResourceMetadata;
123
123
  resourceUrl: string;
124
124
  scopes: string[];
125
125
  tokenEndpoint: string;
@@ -128,6 +128,7 @@ declare function discoverOAuthLogin(options?: {
128
128
  baseUrl?: string;
129
129
  clientId?: string;
130
130
  fetch?: typeof fetch;
131
+ redirectUri?: string;
131
132
  }): Promise<OAuthLoginDiscovery>;
132
133
  declare function inferInitProjectName(cwd: string, repo?: ConnectedRepo): string;
133
134
  declare function buildOAuthLoginStart(options: {
@@ -171,7 +172,6 @@ declare abstract class BaseSyncCommand extends Command {
171
172
  static flags: {
172
173
  help: _oclif_core_interfaces0.BooleanFlag<void>;
173
174
  'fail-on-changes': _oclif_core_interfaces0.BooleanFlag<boolean>;
174
- 'api-key': _oclif_core_interfaces0.OptionFlag<string | undefined, _oclif_core_interfaces0.CustomOptions>;
175
175
  'base-url': _oclif_core_interfaces0.OptionFlag<string | undefined, _oclif_core_interfaces0.CustomOptions>;
176
176
  };
177
177
  }
@@ -179,7 +179,6 @@ declare abstract class ParsedSyncCommand extends BaseSyncCommand {
179
179
  protected parseSync<T extends typeof BaseSyncCommand>(command: T): Promise<_oclif_core_interfaces0.ParserOutput<{
180
180
  help: void;
181
181
  'fail-on-changes': boolean;
182
- 'api-key': string | undefined;
183
182
  'base-url': string | undefined;
184
183
  }, {
185
184
  [flag: string]: any;
@@ -213,7 +212,6 @@ declare class PullCommand extends ParsedSyncCommand {
213
212
  force: _oclif_core_interfaces0.BooleanFlag<boolean>;
214
213
  help: _oclif_core_interfaces0.BooleanFlag<void>;
215
214
  'fail-on-changes': _oclif_core_interfaces0.BooleanFlag<boolean>;
216
- 'api-key': _oclif_core_interfaces0.OptionFlag<string | undefined, _oclif_core_interfaces0.CustomOptions>;
217
215
  'base-url': _oclif_core_interfaces0.OptionFlag<string | undefined, _oclif_core_interfaces0.CustomOptions>;
218
216
  };
219
217
  static args: {
@@ -231,7 +229,6 @@ declare class PushCommand extends Command {
231
229
  };
232
230
  static flags: {
233
231
  help: _oclif_core_interfaces0.BooleanFlag<void>;
234
- 'api-key': _oclif_core_interfaces0.OptionFlag<string | undefined, _oclif_core_interfaces0.CustomOptions>;
235
232
  'base-url': _oclif_core_interfaces0.OptionFlag<string | undefined, _oclif_core_interfaces0.CustomOptions>;
236
233
  config: _oclif_core_interfaces0.OptionFlag<string | undefined, _oclif_core_interfaces0.CustomOptions>;
237
234
  'dry-run': _oclif_core_interfaces0.BooleanFlag<boolean>;
@@ -247,7 +244,6 @@ declare class OpenCommand extends Command {
247
244
  };
248
245
  static flags: {
249
246
  help: _oclif_core_interfaces0.BooleanFlag<void>;
250
- 'api-key': _oclif_core_interfaces0.OptionFlag<string | undefined, _oclif_core_interfaces0.CustomOptions>;
251
247
  'editor-url': _oclif_core_interfaces0.OptionFlag<string | undefined, _oclif_core_interfaces0.CustomOptions>;
252
248
  host: _oclif_core_interfaces0.OptionFlag<string, _oclif_core_interfaces0.CustomOptions>;
253
249
  port: _oclif_core_interfaces0.OptionFlag<number, _oclif_core_interfaces0.CustomOptions>;
@@ -262,7 +258,6 @@ declare class InitCommand extends Command {
262
258
  static description: string;
263
259
  static flags: {
264
260
  help: _oclif_core_interfaces0.BooleanFlag<void>;
265
- 'api-key': _oclif_core_interfaces0.OptionFlag<string | undefined, _oclif_core_interfaces0.CustomOptions>;
266
261
  'base-url': _oclif_core_interfaces0.OptionFlag<string | undefined, _oclif_core_interfaces0.CustomOptions>;
267
262
  name: _oclif_core_interfaces0.OptionFlag<string | undefined, _oclif_core_interfaces0.CustomOptions>;
268
263
  visibility: _oclif_core_interfaces0.OptionFlag<string, _oclif_core_interfaces0.CustomOptions>;
@@ -277,7 +272,7 @@ declare class LoginCommand extends Command {
277
272
  static description: string;
278
273
  static flags: {
279
274
  help: _oclif_core_interfaces0.BooleanFlag<void>;
280
- 'api-key': _oclif_core_interfaces0.OptionFlag<string | undefined, _oclif_core_interfaces0.CustomOptions>;
275
+ 'api-key': _oclif_core_interfaces0.BooleanFlag<boolean>;
281
276
  'base-url': _oclif_core_interfaces0.OptionFlag<string | undefined, _oclif_core_interfaces0.CustomOptions>;
282
277
  stdin: _oclif_core_interfaces0.BooleanFlag<boolean>;
283
278
  auth: _oclif_core_interfaces0.OptionFlag<string | undefined, _oclif_core_interfaces0.CustomOptions>;
package/dist/index.mjs CHANGED
@@ -1,3 +1,3 @@
1
- import { _ as run, a as discoverOAuthLogin, c as formatPlanSummary, d as getEnvCredential, f as inferInitProjectName, g as resolveConfiguredPullTargets, h as resolveApiKey, i as discoverLinkedPullTargets, l as formatStoredCredentialSource, m as isFileGitDirty, n as buildOAuthLoginStart, o as discoverRemoteProjectMachineTargets, p as initProject, r as classifyPushCandidates, s as formatMissingNewMachineDirMessage, t as COMMANDS, u as getEnvApiKey, v as scanProjectSources, y as createStatelyProjectConfig } from "./cli-Buz_BC3I.mjs";
1
+ import { _ as run, a as discoverOAuthLogin, c as formatPlanSummary, d as getEnvCredential, f as inferInitProjectName, g as resolveConfiguredPullTargets, h as resolveApiKey, i as discoverLinkedPullTargets, l as formatStoredCredentialSource, m as isFileGitDirty, n as buildOAuthLoginStart, o as discoverRemoteProjectMachineTargets, p as initProject, r as classifyPushCandidates, s as formatMissingNewMachineDirMessage, t as COMMANDS, u as getEnvApiKey, v as scanProjectSources, y as createStatelyProjectConfig } from "./cli-DR7_Qkzm.mjs";
2
2
 
3
3
  export { COMMANDS, buildOAuthLoginStart, classifyPushCandidates, createStatelyProjectConfig, discoverLinkedPullTargets, discoverOAuthLogin, discoverRemoteProjectMachineTargets, formatMissingNewMachineDirMessage, formatPlanSummary, formatStoredCredentialSource, getEnvApiKey, getEnvCredential, inferInitProjectName, initProject, isFileGitDirty, resolveApiKey, resolveConfiguredPullTargets, run, scanProjectSources };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "statelyai",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "Command-line tools for Stately",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -21,7 +21,7 @@
21
21
  },
22
22
  "dependencies": {
23
23
  "@oclif/core": "^4.10.3",
24
- "@statelyai/sdk": "0.12.0"
24
+ "@statelyai/sdk": "0.12.2"
25
25
  },
26
26
  "devDependencies": {
27
27
  "tsdown": "0.21.0-beta.2",