statelyai 0.5.4 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -6
- package/dist/bin.mjs +1 -1
- package/dist/{cli-DB0A-O4Y.mjs → cli-jiby9qdf.mjs} +451 -42
- package/dist/index.d.mts +55 -3
- package/dist/index.mjs +2 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -7,20 +7,33 @@ This package contains the `statelyai` command implementation.
|
|
|
7
7
|
<!-- happy-path CLI usage and statelyai.json shape derived from packages/cli/src/cli.ts#COMMANDS and packages/cli/src/projectConfig.ts -->
|
|
8
8
|
## Happy Path
|
|
9
9
|
|
|
10
|
-
1.
|
|
11
|
-
2. Log in once:
|
|
10
|
+
1. Log in once:
|
|
12
11
|
|
|
13
12
|
```bash
|
|
14
13
|
statelyai login
|
|
15
14
|
```
|
|
16
15
|
|
|
17
|
-
|
|
16
|
+
Automation can set `STATELY_ACCESS_TOKEN` for OAuth, or pass `--api-key` /
|
|
17
|
+
`STATELY_API_KEY` for legacy API-key commands.
|
|
18
|
+
|
|
19
|
+
For a custom OAuth protected resource, discover OAuth from that resource:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
statelyai login --base-url http://localhost:3000/registry/api/mcp
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`statelyai login --auth bearer` uses the same browser OAuth flow explicitly.
|
|
26
|
+
`statelyai login --api-key` keeps the legacy manual API-key path.
|
|
27
|
+
`statelyai open` requires OAuth (`statelyai login` or `STATELY_ACCESS_TOKEN`);
|
|
28
|
+
free accounts open read-only root-level views.
|
|
29
|
+
|
|
30
|
+
2. Initialize the current repo:
|
|
18
31
|
|
|
19
32
|
```bash
|
|
20
33
|
statelyai init
|
|
21
34
|
```
|
|
22
35
|
|
|
23
|
-
|
|
36
|
+
3. Optionally scan the repo and save suggested source globs:
|
|
24
37
|
|
|
25
38
|
```bash
|
|
26
39
|
statelyai init --scan
|
|
@@ -43,7 +56,7 @@ That writes a `statelyai.json` like:
|
|
|
43
56
|
}
|
|
44
57
|
```
|
|
45
58
|
|
|
46
|
-
|
|
59
|
+
4. Push local machines:
|
|
47
60
|
|
|
48
61
|
```bash
|
|
49
62
|
statelyai push
|
|
@@ -57,7 +70,7 @@ statelyai push --dry-run
|
|
|
57
70
|
|
|
58
71
|
If a saved `// @statelyai id=...` points to a deleted or inaccessible remote machine, `push` will prompt to relink the file as a new remote machine and replace the local id.
|
|
59
72
|
|
|
60
|
-
|
|
73
|
+
5. Pull remote changes back into linked local files and create new local files for remote-only project machines when `newMachinesDir` is configured:
|
|
61
74
|
|
|
62
75
|
```bash
|
|
63
76
|
statelyai pull
|
package/dist/bin.mjs
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import * as crypto from "node:crypto";
|
|
1
2
|
import fs, { promises, watch } from "node:fs";
|
|
2
3
|
import fsPromises from "node:fs/promises";
|
|
4
|
+
import * as http from "node:http";
|
|
3
5
|
import { execFile, execFileSync, spawn } from "node:child_process";
|
|
4
6
|
import * as path$1 from "node:path";
|
|
5
7
|
import path from "node:path";
|
|
@@ -8,10 +10,8 @@ import { Writable } from "node:stream";
|
|
|
8
10
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
9
11
|
import { promisify } from "node:util";
|
|
10
12
|
import { Args, Command, Flags, flush, handle, run } from "@oclif/core";
|
|
11
|
-
import * as crypto from "node:crypto";
|
|
12
|
-
import * as http from "node:http";
|
|
13
13
|
import os from "node:os";
|
|
14
|
-
import { StudioApiError, createStatelyClient, 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,16 @@ 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);
|
|
150
152
|
this.postMessage({
|
|
151
153
|
type: "@statelyai.init",
|
|
152
154
|
machine: update.machine,
|
|
153
155
|
format: update.format,
|
|
154
156
|
sourceLocations: update.sourceLocations,
|
|
155
|
-
mode: "editing",
|
|
157
|
+
mode: update.access?.readOnly ? "viewing" : "editing",
|
|
158
|
+
readOnly: update.access?.readOnly,
|
|
159
|
+
capabilities: update.access?.capabilities,
|
|
156
160
|
theme: "light",
|
|
157
161
|
unsavedIndicator: { enabled: true },
|
|
158
162
|
leftPanels: [],
|
|
@@ -173,6 +177,7 @@ var RemoteEditorSession = class {
|
|
|
173
177
|
const update = await this.parseRootDocument();
|
|
174
178
|
this.currentFormat = update.format;
|
|
175
179
|
this.currentSourceLocations = update.sourceLocations;
|
|
180
|
+
this.editorSyncAccess = update.access ?? this.editorSyncAccess;
|
|
176
181
|
this.updateSourceLocations(update.sourceLocations);
|
|
177
182
|
this.postMessage({
|
|
178
183
|
type: "@statelyai.update",
|
|
@@ -185,6 +190,16 @@ var RemoteEditorSession = class {
|
|
|
185
190
|
}
|
|
186
191
|
}
|
|
187
192
|
async saveFromEditor(msg) {
|
|
193
|
+
if (this.editorSyncAccess?.readOnly) {
|
|
194
|
+
const message = "Your Stately plan allows read-only editor sync.";
|
|
195
|
+
this.postMessage({
|
|
196
|
+
type: "@statelyai.toast",
|
|
197
|
+
message,
|
|
198
|
+
toastType: "error"
|
|
199
|
+
});
|
|
200
|
+
this.showError(message);
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
188
203
|
const validationErrors = getValidationErrors(msg.validations);
|
|
189
204
|
if (validationErrors.length > 0) {
|
|
190
205
|
const message = formatValidationErrorMessage(validationErrors);
|
|
@@ -701,36 +716,109 @@ function getCredentialsFilePath() {
|
|
|
701
716
|
return path.join(getConfigDir(), CREDENTIALS_FILE_NAME);
|
|
702
717
|
}
|
|
703
718
|
function shouldUseFileBackendOnly() {
|
|
704
|
-
return process.env.STATELYAI_CREDENTIALS_BACKEND === "file";
|
|
719
|
+
return process.env.STATELYAI_CREDENTIALS_BACKEND === "file" || process.env.STATELYAI_CONFIG_DIR !== void 0;
|
|
705
720
|
}
|
|
706
721
|
async function readFileCredentials() {
|
|
722
|
+
const storedCredential = await readFileStoredCredential();
|
|
723
|
+
if (storedCredential?.credential?.type !== "api_key") return;
|
|
724
|
+
return {
|
|
725
|
+
apiKey: storedCredential.credential.token,
|
|
726
|
+
backend: storedCredential.backend,
|
|
727
|
+
location: storedCredential.location
|
|
728
|
+
};
|
|
729
|
+
}
|
|
730
|
+
function parseStoredCredential(raw, backend, location) {
|
|
707
731
|
try {
|
|
708
|
-
const raw = await promises.readFile(getCredentialsFilePath(), "utf8");
|
|
709
732
|
const parsed = JSON.parse(raw);
|
|
710
|
-
if (typeof parsed.apiKey
|
|
733
|
+
if (typeof parsed.apiKey === "string" && parsed.apiKey.length > 0) return {
|
|
734
|
+
credential: {
|
|
735
|
+
type: "api_key",
|
|
736
|
+
token: parsed.apiKey
|
|
737
|
+
},
|
|
738
|
+
backend,
|
|
739
|
+
location
|
|
740
|
+
};
|
|
741
|
+
if (parsed.authMode === "none") return {
|
|
742
|
+
authMode: "none",
|
|
743
|
+
backend,
|
|
744
|
+
location
|
|
745
|
+
};
|
|
746
|
+
if (typeof parsed.credential === "object" && parsed.credential !== null && "type" in parsed.credential) {
|
|
747
|
+
const credential = parsed.credential;
|
|
748
|
+
if (credential.type === "api_key" && typeof credential.token === "string" && credential.token.length > 0) return {
|
|
749
|
+
credential: {
|
|
750
|
+
type: "api_key",
|
|
751
|
+
token: credential.token
|
|
752
|
+
},
|
|
753
|
+
backend,
|
|
754
|
+
location
|
|
755
|
+
};
|
|
756
|
+
if (credential.type === "oauth" && typeof credential.accessToken === "string" && credential.accessToken.length > 0) return {
|
|
757
|
+
credential: {
|
|
758
|
+
type: "oauth",
|
|
759
|
+
accessToken: credential.accessToken,
|
|
760
|
+
...typeof credential.refreshToken === "string" ? { refreshToken: credential.refreshToken } : {},
|
|
761
|
+
...typeof credential.expiresAt === "number" ? { expiresAt: credential.expiresAt } : {}
|
|
762
|
+
},
|
|
763
|
+
backend,
|
|
764
|
+
location
|
|
765
|
+
};
|
|
766
|
+
}
|
|
767
|
+
return;
|
|
768
|
+
} catch {
|
|
769
|
+
const token = raw.trim();
|
|
770
|
+
if (!token) return;
|
|
711
771
|
return {
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
772
|
+
credential: {
|
|
773
|
+
type: "api_key",
|
|
774
|
+
token
|
|
775
|
+
},
|
|
776
|
+
backend,
|
|
777
|
+
location
|
|
715
778
|
};
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
async function readFileStoredCredential() {
|
|
782
|
+
try {
|
|
783
|
+
const credentialsPath = getCredentialsFilePath();
|
|
784
|
+
return parseStoredCredential(await promises.readFile(credentialsPath, "utf8"), "file", credentialsPath);
|
|
716
785
|
} catch {
|
|
717
786
|
return;
|
|
718
787
|
}
|
|
719
788
|
}
|
|
720
789
|
async function writeFileCredentials(apiKey) {
|
|
790
|
+
const stored = await writeFileStoredCredential({
|
|
791
|
+
type: "api_key",
|
|
792
|
+
token: apiKey
|
|
793
|
+
});
|
|
794
|
+
return {
|
|
795
|
+
apiKey,
|
|
796
|
+
backend: stored.backend,
|
|
797
|
+
location: stored.location
|
|
798
|
+
};
|
|
799
|
+
}
|
|
800
|
+
async function writeFileStoredCredential(credential, authMode) {
|
|
721
801
|
const configDir = getConfigDir();
|
|
722
802
|
const credentialsPath = getCredentialsFilePath();
|
|
803
|
+
const payload = authMode === "none" ? {
|
|
804
|
+
version: 1,
|
|
805
|
+
authMode: "none"
|
|
806
|
+
} : {
|
|
807
|
+
version: 1,
|
|
808
|
+
credential
|
|
809
|
+
};
|
|
723
810
|
await promises.mkdir(configDir, {
|
|
724
811
|
recursive: true,
|
|
725
812
|
mode: 448
|
|
726
813
|
});
|
|
727
|
-
await promises.writeFile(credentialsPath, `${JSON.stringify(
|
|
814
|
+
await promises.writeFile(credentialsPath, `${JSON.stringify(payload, null, 2)}\n`, {
|
|
728
815
|
encoding: "utf8",
|
|
729
816
|
mode: 384
|
|
730
817
|
});
|
|
731
818
|
await promises.chmod(credentialsPath, 384);
|
|
732
819
|
return {
|
|
733
|
-
|
|
820
|
+
...credential ? { credential } : {},
|
|
821
|
+
...authMode ? { authMode } : {},
|
|
734
822
|
backend: "file",
|
|
735
823
|
location: credentialsPath
|
|
736
824
|
};
|
|
@@ -744,6 +832,15 @@ async function deleteFileCredentials() {
|
|
|
744
832
|
}
|
|
745
833
|
}
|
|
746
834
|
async function readMacOSKeychain() {
|
|
835
|
+
const storedCredential = await readMacOSStoredCredential();
|
|
836
|
+
if (storedCredential?.credential?.type !== "api_key") return;
|
|
837
|
+
return {
|
|
838
|
+
apiKey: storedCredential.credential.token,
|
|
839
|
+
backend: storedCredential.backend,
|
|
840
|
+
location: storedCredential.location
|
|
841
|
+
};
|
|
842
|
+
}
|
|
843
|
+
async function readMacOSStoredCredential() {
|
|
747
844
|
if (process.platform !== "darwin") return;
|
|
748
845
|
try {
|
|
749
846
|
const { stdout } = await execFile$1("security", [
|
|
@@ -754,19 +851,31 @@ async function readMacOSKeychain() {
|
|
|
754
851
|
ACCOUNT_NAME,
|
|
755
852
|
"-w"
|
|
756
853
|
]);
|
|
757
|
-
|
|
758
|
-
if (!apiKey) return;
|
|
759
|
-
return {
|
|
760
|
-
apiKey,
|
|
761
|
-
backend: "macos-keychain",
|
|
762
|
-
location: "macOS Keychain"
|
|
763
|
-
};
|
|
854
|
+
return parseStoredCredential(stdout, "macos-keychain", "macOS Keychain");
|
|
764
855
|
} catch {
|
|
765
856
|
return;
|
|
766
857
|
}
|
|
767
858
|
}
|
|
768
859
|
async function writeMacOSKeychain(apiKey) {
|
|
860
|
+
const stored = await writeMacOSStoredCredential({
|
|
861
|
+
type: "api_key",
|
|
862
|
+
token: apiKey
|
|
863
|
+
});
|
|
864
|
+
return stored ? {
|
|
865
|
+
apiKey,
|
|
866
|
+
backend: stored.backend,
|
|
867
|
+
location: stored.location
|
|
868
|
+
} : void 0;
|
|
869
|
+
}
|
|
870
|
+
async function writeMacOSStoredCredential(credential, authMode) {
|
|
769
871
|
if (process.platform !== "darwin") return;
|
|
872
|
+
const payload = authMode === "none" ? {
|
|
873
|
+
version: 1,
|
|
874
|
+
authMode: "none"
|
|
875
|
+
} : {
|
|
876
|
+
version: 1,
|
|
877
|
+
credential
|
|
878
|
+
};
|
|
770
879
|
try {
|
|
771
880
|
await execFile$1("security", [
|
|
772
881
|
"add-generic-password",
|
|
@@ -776,10 +885,11 @@ async function writeMacOSKeychain(apiKey) {
|
|
|
776
885
|
"-a",
|
|
777
886
|
ACCOUNT_NAME,
|
|
778
887
|
"-w",
|
|
779
|
-
|
|
888
|
+
JSON.stringify(payload)
|
|
780
889
|
]);
|
|
781
890
|
return {
|
|
782
|
-
|
|
891
|
+
...credential ? { credential } : {},
|
|
892
|
+
...authMode ? { authMode } : {},
|
|
783
893
|
backend: "macos-keychain",
|
|
784
894
|
location: "macOS Keychain"
|
|
785
895
|
};
|
|
@@ -803,6 +913,15 @@ async function deleteMacOSKeychain() {
|
|
|
803
913
|
}
|
|
804
914
|
}
|
|
805
915
|
async function readLinuxSecretTool() {
|
|
916
|
+
const storedCredential = await readLinuxStoredCredential();
|
|
917
|
+
if (storedCredential?.credential?.type !== "api_key") return;
|
|
918
|
+
return {
|
|
919
|
+
apiKey: storedCredential.credential.token,
|
|
920
|
+
backend: storedCredential.backend,
|
|
921
|
+
location: storedCredential.location
|
|
922
|
+
};
|
|
923
|
+
}
|
|
924
|
+
async function readLinuxStoredCredential() {
|
|
806
925
|
if (process.platform !== "linux") return;
|
|
807
926
|
try {
|
|
808
927
|
const { stdout } = await execFile$1("secret-tool", [
|
|
@@ -812,19 +931,31 @@ async function readLinuxSecretTool() {
|
|
|
812
931
|
"account",
|
|
813
932
|
ACCOUNT_NAME
|
|
814
933
|
]);
|
|
815
|
-
|
|
816
|
-
if (!apiKey) return;
|
|
817
|
-
return {
|
|
818
|
-
apiKey,
|
|
819
|
-
backend: "linux-secret-tool",
|
|
820
|
-
location: "Secret Service (secret-tool)"
|
|
821
|
-
};
|
|
934
|
+
return parseStoredCredential(stdout, "linux-secret-tool", "Secret Service (secret-tool)");
|
|
822
935
|
} catch {
|
|
823
936
|
return;
|
|
824
937
|
}
|
|
825
938
|
}
|
|
826
939
|
async function writeLinuxSecretTool(apiKey) {
|
|
940
|
+
const stored = await writeLinuxStoredCredential({
|
|
941
|
+
type: "api_key",
|
|
942
|
+
token: apiKey
|
|
943
|
+
});
|
|
944
|
+
return stored ? {
|
|
945
|
+
apiKey,
|
|
946
|
+
backend: stored.backend,
|
|
947
|
+
location: stored.location
|
|
948
|
+
} : void 0;
|
|
949
|
+
}
|
|
950
|
+
async function writeLinuxStoredCredential(credential, authMode) {
|
|
827
951
|
if (process.platform !== "linux") return;
|
|
952
|
+
const payload = authMode === "none" ? {
|
|
953
|
+
version: 1,
|
|
954
|
+
authMode: "none"
|
|
955
|
+
} : {
|
|
956
|
+
version: 1,
|
|
957
|
+
credential
|
|
958
|
+
};
|
|
828
959
|
try {
|
|
829
960
|
execFileSync("secret-tool", [
|
|
830
961
|
"store",
|
|
@@ -834,9 +965,10 @@ async function writeLinuxSecretTool(apiKey) {
|
|
|
834
965
|
SERVICE_NAME,
|
|
835
966
|
"account",
|
|
836
967
|
ACCOUNT_NAME
|
|
837
|
-
], { input:
|
|
968
|
+
], { input: JSON.stringify(payload) });
|
|
838
969
|
return {
|
|
839
|
-
|
|
970
|
+
...credential ? { credential } : {},
|
|
971
|
+
...authMode ? { authMode } : {},
|
|
840
972
|
backend: "linux-secret-tool",
|
|
841
973
|
location: "Secret Service (secret-tool)"
|
|
842
974
|
};
|
|
@@ -863,10 +995,22 @@ async function getStoredApiKey() {
|
|
|
863
995
|
if (shouldUseFileBackendOnly()) return readFileCredentials();
|
|
864
996
|
return await readMacOSKeychain() ?? await readLinuxSecretTool() ?? await readFileCredentials();
|
|
865
997
|
}
|
|
998
|
+
async function getStoredCredential() {
|
|
999
|
+
if (shouldUseFileBackendOnly()) return readFileStoredCredential();
|
|
1000
|
+
return await readMacOSStoredCredential() ?? await readLinuxStoredCredential() ?? await readFileStoredCredential();
|
|
1001
|
+
}
|
|
866
1002
|
async function setStoredApiKey(apiKey) {
|
|
867
1003
|
if (shouldUseFileBackendOnly()) return writeFileCredentials(apiKey);
|
|
868
1004
|
return await writeMacOSKeychain(apiKey) ?? await writeLinuxSecretTool(apiKey) ?? await writeFileCredentials(apiKey);
|
|
869
1005
|
}
|
|
1006
|
+
async function setStoredCredential(credential) {
|
|
1007
|
+
if (shouldUseFileBackendOnly()) return writeFileStoredCredential(credential);
|
|
1008
|
+
return await writeMacOSStoredCredential(credential) ?? await writeLinuxStoredCredential(credential) ?? await writeFileStoredCredential(credential);
|
|
1009
|
+
}
|
|
1010
|
+
async function setStoredNoAuth() {
|
|
1011
|
+
if (shouldUseFileBackendOnly()) return writeFileStoredCredential(void 0, "none");
|
|
1012
|
+
return await writeMacOSStoredCredential(void 0, "none") ?? await writeLinuxStoredCredential(void 0, "none") ?? await writeFileStoredCredential(void 0, "none");
|
|
1013
|
+
}
|
|
870
1014
|
async function deleteStoredApiKey() {
|
|
871
1015
|
if (shouldUseFileBackendOnly()) {
|
|
872
1016
|
const deleted = await deleteFileCredentials();
|
|
@@ -1168,6 +1312,11 @@ function loadLocalEnv() {
|
|
|
1168
1312
|
}
|
|
1169
1313
|
loadLocalEnv();
|
|
1170
1314
|
function getEnvApiKey() {
|
|
1315
|
+
const accessToken = process.env.STATELY_ACCESS_TOKEN;
|
|
1316
|
+
if (accessToken) return {
|
|
1317
|
+
apiKey: accessToken,
|
|
1318
|
+
variable: "STATELY_ACCESS_TOKEN"
|
|
1319
|
+
};
|
|
1171
1320
|
const statelyApiKey = process.env.STATELY_API_KEY;
|
|
1172
1321
|
if (statelyApiKey) return {
|
|
1173
1322
|
apiKey: statelyApiKey,
|
|
@@ -1179,6 +1328,32 @@ function getEnvApiKey() {
|
|
|
1179
1328
|
variable: "NEXT_PUBLIC_STATELY_API_KEY"
|
|
1180
1329
|
};
|
|
1181
1330
|
}
|
|
1331
|
+
function getEnvCredential() {
|
|
1332
|
+
const accessToken = process.env.STATELY_ACCESS_TOKEN;
|
|
1333
|
+
if (accessToken) return {
|
|
1334
|
+
credential: {
|
|
1335
|
+
type: "oauth",
|
|
1336
|
+
accessToken
|
|
1337
|
+
},
|
|
1338
|
+
variable: "STATELY_ACCESS_TOKEN"
|
|
1339
|
+
};
|
|
1340
|
+
const statelyApiKey = process.env.STATELY_API_KEY;
|
|
1341
|
+
if (statelyApiKey) return {
|
|
1342
|
+
credential: {
|
|
1343
|
+
type: "api_key",
|
|
1344
|
+
token: statelyApiKey
|
|
1345
|
+
},
|
|
1346
|
+
variable: "STATELY_API_KEY"
|
|
1347
|
+
};
|
|
1348
|
+
const publicApiKey = process.env.NEXT_PUBLIC_STATELY_API_KEY;
|
|
1349
|
+
if (publicApiKey) return {
|
|
1350
|
+
credential: {
|
|
1351
|
+
type: "api_key",
|
|
1352
|
+
token: publicApiKey
|
|
1353
|
+
},
|
|
1354
|
+
variable: "NEXT_PUBLIC_STATELY_API_KEY"
|
|
1355
|
+
};
|
|
1356
|
+
}
|
|
1182
1357
|
async function resolveApiKey(explicitApiKey) {
|
|
1183
1358
|
if (explicitApiKey) return {
|
|
1184
1359
|
apiKey: explicitApiKey,
|
|
@@ -1199,6 +1374,23 @@ async function resolveApiKey(explicitApiKey) {
|
|
|
1199
1374
|
};
|
|
1200
1375
|
return { source: "missing" };
|
|
1201
1376
|
}
|
|
1377
|
+
async function resolveOAuthAccessToken() {
|
|
1378
|
+
const envCredential = getEnvCredential();
|
|
1379
|
+
if (envCredential?.credential.type === "oauth") return {
|
|
1380
|
+
apiKey: envCredential.credential.accessToken,
|
|
1381
|
+
source: "env",
|
|
1382
|
+
detail: envCredential.variable
|
|
1383
|
+
};
|
|
1384
|
+
if (envCredential?.credential.type === "api_key") throw new Error("`statelyai open` requires OAuth login. Run `statelyai login` instead of using an API key.");
|
|
1385
|
+
const storedCredential = await getStoredCredential();
|
|
1386
|
+
if (storedCredential?.credential?.type === "oauth") return {
|
|
1387
|
+
apiKey: storedCredential.credential.accessToken,
|
|
1388
|
+
source: "stored",
|
|
1389
|
+
detail: describeCredentialBackend(storedCredential.backend, storedCredential.location)
|
|
1390
|
+
};
|
|
1391
|
+
if (storedCredential?.credential?.type === "api_key") throw new Error("`statelyai open` requires OAuth login. Run `statelyai login` to replace the stored API key.");
|
|
1392
|
+
return { source: "missing" };
|
|
1393
|
+
}
|
|
1202
1394
|
function getDefaultBaseUrl() {
|
|
1203
1395
|
return process.env.STATELY_API_URL ?? process.env.NEXT_PUBLIC_BASE_URL ?? process.env.NEXT_PUBLIC_STATELY_API_URL;
|
|
1204
1396
|
}
|
|
@@ -1208,6 +1400,51 @@ function getDefaultEditorUrl() {
|
|
|
1208
1400
|
function getResolvedStudioUrl(baseUrl) {
|
|
1209
1401
|
return baseUrl ?? getDefaultBaseUrl() ?? "https://stately.ai";
|
|
1210
1402
|
}
|
|
1403
|
+
function getDefaultOAuthIssuer() {
|
|
1404
|
+
return process.env.STATELY_OAUTH_ISSUER ?? process.env.NEXT_PUBLIC_STATELY_OAUTH_ISSUER ?? "https://stately.ai";
|
|
1405
|
+
}
|
|
1406
|
+
function normalizeLoginResourceUrl(baseUrl) {
|
|
1407
|
+
const raw = baseUrl;
|
|
1408
|
+
const url = new URL(raw);
|
|
1409
|
+
if (url.pathname === "/" || url.pathname === "") url.pathname = "/api/mcp";
|
|
1410
|
+
return url.toString();
|
|
1411
|
+
}
|
|
1412
|
+
function getConfiguredOauthClientId() {
|
|
1413
|
+
return process.env.STATELY_OAUTH_CLIENT_ID ?? process.env.NEXT_PUBLIC_STATELY_OAUTH_CLIENT_ID;
|
|
1414
|
+
}
|
|
1415
|
+
async function discoverOAuthLogin(options = {}) {
|
|
1416
|
+
const resourceUrl = options.baseUrl ? normalizeLoginResourceUrl(options.baseUrl) : getDefaultOAuthIssuer();
|
|
1417
|
+
const protectedResource = options.baseUrl ? await discoverProtectedResource(resourceUrl, { fetch: options.fetch }) : void 0;
|
|
1418
|
+
const issuer = protectedResource ? protectedResource.authorization_servers?.[0] : resourceUrl;
|
|
1419
|
+
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.`);
|
|
1420
|
+
const authorizationServer = await discoverAuthorizationServer(issuer, { fetch: options.fetch });
|
|
1421
|
+
const authorizationEndpoint = authorizationServer.authorization_endpoint;
|
|
1422
|
+
const tokenEndpoint = authorizationServer.token_endpoint;
|
|
1423
|
+
if (!authorizationEndpoint || !tokenEndpoint) throw new Error(`Authorization server ${issuer} did not advertise authorization and token endpoints.`);
|
|
1424
|
+
const configuredClientId = options.clientId ?? getConfiguredOauthClientId();
|
|
1425
|
+
const scopes = protectedResource?.scopes_supported ?? [];
|
|
1426
|
+
let clientId = configuredClientId;
|
|
1427
|
+
if (!clientId && authorizationServer.registration_endpoint && options.redirectUri) try {
|
|
1428
|
+
clientId = (await registerOAuthClient({
|
|
1429
|
+
registrationEndpoint: authorizationServer.registration_endpoint,
|
|
1430
|
+
redirectUri: options.redirectUri,
|
|
1431
|
+
scopes,
|
|
1432
|
+
fetch: options.fetch
|
|
1433
|
+
})).client_id;
|
|
1434
|
+
} catch (error) {
|
|
1435
|
+
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 });
|
|
1436
|
+
}
|
|
1437
|
+
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.`);
|
|
1438
|
+
return {
|
|
1439
|
+
authorizationServer,
|
|
1440
|
+
authorizationEndpoint,
|
|
1441
|
+
clientId,
|
|
1442
|
+
...protectedResource ? { protectedResource } : {},
|
|
1443
|
+
resourceUrl,
|
|
1444
|
+
scopes,
|
|
1445
|
+
tokenEndpoint
|
|
1446
|
+
};
|
|
1447
|
+
}
|
|
1211
1448
|
function parseGitHubRemote(remoteUrl) {
|
|
1212
1449
|
const httpsMatch = remoteUrl.match(/^https:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?$/);
|
|
1213
1450
|
if (httpsMatch) {
|
|
@@ -1289,6 +1526,148 @@ async function promptForApiKey() {
|
|
|
1289
1526
|
rl.close();
|
|
1290
1527
|
}
|
|
1291
1528
|
}
|
|
1529
|
+
async function promptForOAuthCallback() {
|
|
1530
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) throw new Error("No interactive terminal available to paste the OAuth callback URL.");
|
|
1531
|
+
const rl = createInterface({
|
|
1532
|
+
input: process.stdin,
|
|
1533
|
+
output: process.stdout,
|
|
1534
|
+
terminal: true
|
|
1535
|
+
});
|
|
1536
|
+
try {
|
|
1537
|
+
return (await rl.question("Paste the callback URL or code: ")).trim();
|
|
1538
|
+
} finally {
|
|
1539
|
+
rl.close();
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
function parseOAuthCallback(value) {
|
|
1543
|
+
try {
|
|
1544
|
+
const url = new URL(value);
|
|
1545
|
+
const code = url.searchParams.get("code");
|
|
1546
|
+
const state = url.searchParams.get("state") ?? void 0;
|
|
1547
|
+
if (!code) throw new Error("Callback URL did not include a code parameter.");
|
|
1548
|
+
return {
|
|
1549
|
+
code,
|
|
1550
|
+
state
|
|
1551
|
+
};
|
|
1552
|
+
} catch (error) {
|
|
1553
|
+
if (error instanceof Error && error.message.includes("code parameter")) throw error;
|
|
1554
|
+
if (!value) throw new Error("OAuth code cannot be empty.");
|
|
1555
|
+
return { code: value };
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
function createOAuthCredential(tokenResponse, now = Date.now()) {
|
|
1559
|
+
if (!tokenResponse.access_token) throw new Error("Token response did not include an access_token.");
|
|
1560
|
+
const expiresAt = typeof tokenResponse.expires_at === "number" ? tokenResponse.expires_at * 1e3 : typeof tokenResponse.expires_in === "number" ? now + tokenResponse.expires_in * 1e3 : void 0;
|
|
1561
|
+
return {
|
|
1562
|
+
type: "oauth",
|
|
1563
|
+
accessToken: tokenResponse.access_token,
|
|
1564
|
+
...tokenResponse.refresh_token ? { refreshToken: tokenResponse.refresh_token } : {},
|
|
1565
|
+
...expiresAt ? { expiresAt } : {}
|
|
1566
|
+
};
|
|
1567
|
+
}
|
|
1568
|
+
async function createLocalOAuthCallbackServer(options) {
|
|
1569
|
+
let resolveCode;
|
|
1570
|
+
let rejectCode;
|
|
1571
|
+
const codePromise = new Promise((resolve, reject) => {
|
|
1572
|
+
resolveCode = resolve;
|
|
1573
|
+
rejectCode = reject;
|
|
1574
|
+
});
|
|
1575
|
+
const server = http.createServer((req, res) => {
|
|
1576
|
+
const requestUrl = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
1577
|
+
const code = requestUrl.searchParams.get("code");
|
|
1578
|
+
const state = requestUrl.searchParams.get("state");
|
|
1579
|
+
const error = requestUrl.searchParams.get("error");
|
|
1580
|
+
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
1581
|
+
if (error) {
|
|
1582
|
+
res.statusCode = 400;
|
|
1583
|
+
res.end("<!doctype html><title>Sign in failed</title><p>Sign in failed. Return to the terminal.</p>");
|
|
1584
|
+
rejectCode(new Error(error));
|
|
1585
|
+
return;
|
|
1586
|
+
}
|
|
1587
|
+
if (!code || state !== options.state) {
|
|
1588
|
+
res.statusCode = 400;
|
|
1589
|
+
res.end("<!doctype html><title>Sign in failed</title><p>Invalid sign-in callback. Return to the terminal.</p>");
|
|
1590
|
+
rejectCode(/* @__PURE__ */ new Error("Invalid OAuth callback."));
|
|
1591
|
+
return;
|
|
1592
|
+
}
|
|
1593
|
+
res.end("<!doctype html><title>Signed in</title><p>Signed in. You can close this window.</p>");
|
|
1594
|
+
resolveCode(code);
|
|
1595
|
+
});
|
|
1596
|
+
await new Promise((resolve) => {
|
|
1597
|
+
server.listen(0, "127.0.0.1", resolve);
|
|
1598
|
+
});
|
|
1599
|
+
const address = server.address();
|
|
1600
|
+
if (!address || typeof address === "string") throw new Error("Could not start OAuth callback server.");
|
|
1601
|
+
return {
|
|
1602
|
+
redirectUri: `http://127.0.0.1:${address.port}/callback`,
|
|
1603
|
+
waitForCode: () => codePromise,
|
|
1604
|
+
close: () => new Promise((resolve) => {
|
|
1605
|
+
server.close(() => resolve());
|
|
1606
|
+
})
|
|
1607
|
+
};
|
|
1608
|
+
}
|
|
1609
|
+
async function buildOAuthLoginStart(options) {
|
|
1610
|
+
const [discovery, pkce] = await Promise.all([discoverOAuthLogin({
|
|
1611
|
+
baseUrl: options.baseUrl,
|
|
1612
|
+
clientId: options.clientId,
|
|
1613
|
+
fetch: options.fetch,
|
|
1614
|
+
redirectUri: options.redirectUri
|
|
1615
|
+
}), createPkcePair()]);
|
|
1616
|
+
return {
|
|
1617
|
+
authorizeUrl: buildAuthorizeUrl({
|
|
1618
|
+
authorizationEndpoint: discovery.authorizationEndpoint,
|
|
1619
|
+
clientId: discovery.clientId,
|
|
1620
|
+
redirectUri: options.redirectUri,
|
|
1621
|
+
codeChallenge: pkce.codeChallenge,
|
|
1622
|
+
state: options.state,
|
|
1623
|
+
scopes: discovery.scopes
|
|
1624
|
+
}),
|
|
1625
|
+
codeVerifier: pkce.codeVerifier,
|
|
1626
|
+
discovery
|
|
1627
|
+
};
|
|
1628
|
+
}
|
|
1629
|
+
function formatStoredCredentialSource(storedCredential) {
|
|
1630
|
+
if (storedCredential.credential?.type === "api_key") return `Credential source: stored api_key (${describeCredentialBackend(storedCredential.backend, storedCredential.location)}).`;
|
|
1631
|
+
if (storedCredential.credential?.type === "oauth") return `Credential source: stored oauth (${describeCredentialBackend(storedCredential.backend, storedCredential.location)}).`;
|
|
1632
|
+
if (storedCredential.authMode === "none") return `Credential source: stored no-auth mode (${describeCredentialBackend(storedCredential.backend, storedCredential.location)}).`;
|
|
1633
|
+
return `Credential source: stored unknown (${describeCredentialBackend(storedCredential.backend, storedCredential.location)}).`;
|
|
1634
|
+
}
|
|
1635
|
+
async function runOAuthBrowserLogin(options) {
|
|
1636
|
+
const state = crypto.randomBytes(16).toString("base64url");
|
|
1637
|
+
const callbackServer = await createLocalOAuthCallbackServer({ state });
|
|
1638
|
+
try {
|
|
1639
|
+
const { authorizeUrl, codeVerifier, discovery } = await buildOAuthLoginStart({
|
|
1640
|
+
baseUrl: options.baseUrl,
|
|
1641
|
+
redirectUri: callbackServer.redirectUri,
|
|
1642
|
+
state
|
|
1643
|
+
});
|
|
1644
|
+
options.log(`Opening browser for ${discovery.resourceUrl}`);
|
|
1645
|
+
options.log(authorizeUrl);
|
|
1646
|
+
let openedBrowser = false;
|
|
1647
|
+
try {
|
|
1648
|
+
openBrowser(authorizeUrl);
|
|
1649
|
+
openedBrowser = true;
|
|
1650
|
+
} catch {
|
|
1651
|
+
options.log("Could not open a browser automatically.");
|
|
1652
|
+
}
|
|
1653
|
+
const codePromise = callbackServer.waitForCode();
|
|
1654
|
+
const pastedCodePromise = !openedBrowser && process.stdin.isTTY && process.stdout.isTTY ? promptForOAuthCallback().then((value) => {
|
|
1655
|
+
const callback = parseOAuthCallback(value);
|
|
1656
|
+
if (callback.state && callback.state !== state) throw new Error("OAuth state did not match.");
|
|
1657
|
+
return callback.code;
|
|
1658
|
+
}) : void 0;
|
|
1659
|
+
const code = await (pastedCodePromise ? Promise.race([codePromise, pastedCodePromise]) : codePromise);
|
|
1660
|
+
return setStoredCredential(createOAuthCredential(await exchangeCodeForToken({
|
|
1661
|
+
tokenEndpoint: discovery.tokenEndpoint,
|
|
1662
|
+
code,
|
|
1663
|
+
codeVerifier,
|
|
1664
|
+
redirectUri: callbackServer.redirectUri,
|
|
1665
|
+
clientId: discovery.clientId
|
|
1666
|
+
})));
|
|
1667
|
+
} finally {
|
|
1668
|
+
await callbackServer.close();
|
|
1669
|
+
}
|
|
1670
|
+
}
|
|
1292
1671
|
async function promptYesNo(question, defaultValue = true) {
|
|
1293
1672
|
if (!process.stdin.isTTY || !process.stdout.isTTY) throw new Error("No interactive terminal available.");
|
|
1294
1673
|
const rl = createInterface({
|
|
@@ -1954,7 +2333,7 @@ var OpenCommand = class OpenCommand extends Command {
|
|
|
1954
2333
|
}) };
|
|
1955
2334
|
static flags = {
|
|
1956
2335
|
help: Flags.help({ char: "h" }),
|
|
1957
|
-
"api-key": Flags.string({ description: "
|
|
2336
|
+
"api-key": Flags.string({ description: "Deprecated. `statelyai open` requires OAuth login." }),
|
|
1958
2337
|
"editor-url": Flags.string({ description: "Base URL for the Stately editor embed" }),
|
|
1959
2338
|
host: Flags.string({
|
|
1960
2339
|
description: "Local server host",
|
|
@@ -1977,7 +2356,9 @@ var OpenCommand = class OpenCommand extends Command {
|
|
|
1977
2356
|
};
|
|
1978
2357
|
async run() {
|
|
1979
2358
|
const { args, flags } = await this.parse(OpenCommand);
|
|
1980
|
-
|
|
2359
|
+
if (flags["api-key"]) throw new Error("`statelyai open` requires OAuth login. Run `statelyai login` and retry.");
|
|
2360
|
+
const apiKey = (await resolveOAuthAccessToken()).apiKey;
|
|
2361
|
+
if (!apiKey) throw new Error("`statelyai open` requires OAuth login. Run `statelyai login` and retry.");
|
|
1981
2362
|
await openEditor({
|
|
1982
2363
|
fileName: path.resolve(args.file),
|
|
1983
2364
|
editorUrl: flags["editor-url"] ?? getDefaultEditorUrl(),
|
|
@@ -2062,23 +2443,46 @@ var InitCommand = class InitCommand extends Command {
|
|
|
2062
2443
|
};
|
|
2063
2444
|
var LoginCommand = class LoginCommand extends Command {
|
|
2064
2445
|
static enableJsonFlag = false;
|
|
2065
|
-
static summary = "Store
|
|
2066
|
-
static description = "Stores
|
|
2446
|
+
static summary = "Store Stately credentials for future CLI use.";
|
|
2447
|
+
static description = "Stores Stately credentials in the OS credential store when available, with a private file fallback.";
|
|
2067
2448
|
static flags = {
|
|
2068
2449
|
help: Flags.help({ char: "h" }),
|
|
2069
2450
|
"api-key": Flags.string({ description: "API key to store without an interactive prompt" }),
|
|
2451
|
+
"base-url": Flags.string({ description: "OAuth protected-resource URL for custom deployments" }),
|
|
2070
2452
|
stdin: Flags.boolean({
|
|
2071
2453
|
description: "Read the API key from standard input",
|
|
2072
2454
|
default: false
|
|
2455
|
+
}),
|
|
2456
|
+
auth: Flags.string({
|
|
2457
|
+
description: "Authentication mode for the target server",
|
|
2458
|
+
options: ["none", "bearer"]
|
|
2073
2459
|
})
|
|
2074
2460
|
};
|
|
2075
2461
|
async run() {
|
|
2076
2462
|
const { flags } = await this.parse(LoginCommand);
|
|
2463
|
+
if (flags["api-key"] && flags.auth === "bearer") this.error("Pass either --api-key or --auth bearer, not both.");
|
|
2464
|
+
if (flags.auth === "none") {
|
|
2465
|
+
if (flags.stdin || flags["api-key"]) this.error("Pass either --auth none or an API key, not both.");
|
|
2466
|
+
const stored = await setStoredNoAuth();
|
|
2467
|
+
this.log(`Stored no-auth mode in ${describeCredentialBackend(stored.backend, stored.location)}.`);
|
|
2468
|
+
return;
|
|
2469
|
+
}
|
|
2470
|
+
if (!flags["api-key"] && !flags.stdin) {
|
|
2471
|
+
const stored = await runOAuthBrowserLogin({
|
|
2472
|
+
baseUrl: flags["base-url"],
|
|
2473
|
+
log: (message) => this.log(message)
|
|
2474
|
+
});
|
|
2475
|
+
this.log(`Stored OAuth credential in ${describeCredentialBackend(stored.backend, stored.location)}.`);
|
|
2476
|
+
return;
|
|
2477
|
+
}
|
|
2077
2478
|
if (flags.stdin && flags["api-key"]) this.error("Pass either --api-key or --stdin, not both.");
|
|
2078
2479
|
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}`);
|
|
2079
2480
|
const apiKey = normalizeApiKey(flags["api-key"] ?? (!process.stdin.isTTY || flags.stdin ? await readApiKeyFromStdin() : await promptForApiKey()));
|
|
2080
2481
|
if (!apiKey) this.error(`API key cannot be empty.\nGet or create an API key at ${STATELY_API_KEY_SETTINGS_URL}`);
|
|
2081
|
-
const stored = await setStoredApiKey(apiKey)
|
|
2482
|
+
const stored = flags["api-key"] || flags.stdin ? await setStoredApiKey(apiKey) : await setStoredCredential({
|
|
2483
|
+
type: "api_key",
|
|
2484
|
+
token: apiKey
|
|
2485
|
+
});
|
|
2082
2486
|
this.log(`Stored API key in ${describeCredentialBackend(stored.backend, stored.location)}.`);
|
|
2083
2487
|
}
|
|
2084
2488
|
};
|
|
@@ -2102,15 +2506,20 @@ var WhoamiCommand = class extends Command {
|
|
|
2102
2506
|
static description = "Reports whether the CLI would use a flag, environment variable, or stored credential.";
|
|
2103
2507
|
static flags = { help: Flags.help({ char: "h" }) };
|
|
2104
2508
|
async run() {
|
|
2105
|
-
const
|
|
2106
|
-
const
|
|
2107
|
-
|
|
2108
|
-
|
|
2509
|
+
const envCredential = getEnvCredential();
|
|
2510
|
+
const storedCredential = await getStoredCredential();
|
|
2511
|
+
const storedApiKey = storedCredential?.credential?.type === "api_key" ? {
|
|
2512
|
+
apiKey: storedCredential.credential.token,
|
|
2513
|
+
backend: storedCredential.backend,
|
|
2514
|
+
location: storedCredential.location
|
|
2515
|
+
} : void 0;
|
|
2516
|
+
if (envCredential) {
|
|
2517
|
+
this.log(`Credential source: environment ${envCredential.credential.type} (${envCredential.variable}).`);
|
|
2109
2518
|
if (storedApiKey) this.log(`Stored credential also available in ${describeCredentialBackend(storedApiKey.backend, storedApiKey.location)}.`);
|
|
2110
2519
|
return;
|
|
2111
2520
|
}
|
|
2112
|
-
if (
|
|
2113
|
-
this.log(
|
|
2521
|
+
if (storedCredential) {
|
|
2522
|
+
this.log(formatStoredCredentialSource(storedCredential));
|
|
2114
2523
|
return;
|
|
2115
2524
|
}
|
|
2116
2525
|
this.log(getMissingApiKeyMessage());
|
|
@@ -2149,4 +2558,4 @@ function isDirectExecution() {
|
|
|
2149
2558
|
if (isDirectExecution()) run$1();
|
|
2150
2559
|
|
|
2151
2560
|
//#endregion
|
|
2152
|
-
export {
|
|
2561
|
+
export { resolveOAuthAccessToken as _, discoverOAuthLogin as a, createStatelyProjectConfig as b, formatPlanSummary as c, getEnvCredential as d, inferInitProjectName as f, resolveConfiguredPullTargets as g, resolveApiKey as h, discoverLinkedPullTargets as i, formatStoredCredentialSource as l, isFileGitDirty as m, buildOAuthLoginStart as n, discoverRemoteProjectMachineTargets as o, initProject as p, classifyPushCandidates as r, formatMissingNewMachineDirMessage as s, COMMANDS as t, getEnvApiKey as u, run$1 as v, scanProjectSources as y };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,8 +1,26 @@
|
|
|
1
1
|
import { Command } from "@oclif/core";
|
|
2
|
-
import { ConnectedRepo, CreateProjectInput, ProjectData, StudioClient } from "@statelyai/sdk";
|
|
2
|
+
import { ConnectedRepo, CreateProjectInput, OAuthAuthorizationServerMetadata, OAuthProtectedResourceMetadata, ProjectData, StudioClient } from "@statelyai/sdk";
|
|
3
3
|
import { SyncPlan } from "@statelyai/sdk/sync";
|
|
4
4
|
import * as _oclif_core_interfaces0 from "@oclif/core/interfaces";
|
|
5
5
|
|
|
6
|
+
//#region src/credentials.d.ts
|
|
7
|
+
type StatelyCredential = {
|
|
8
|
+
type: 'api_key';
|
|
9
|
+
token: string;
|
|
10
|
+
} | {
|
|
11
|
+
type: 'oauth';
|
|
12
|
+
accessToken: string;
|
|
13
|
+
refreshToken?: string;
|
|
14
|
+
expiresAt?: number;
|
|
15
|
+
};
|
|
16
|
+
type CredentialBackend = 'file' | 'linux-secret-tool' | 'macos-keychain';
|
|
17
|
+
type StoredCredential = {
|
|
18
|
+
credential?: StatelyCredential;
|
|
19
|
+
authMode?: 'none';
|
|
20
|
+
backend: CredentialBackend;
|
|
21
|
+
location: string;
|
|
22
|
+
};
|
|
23
|
+
//#endregion
|
|
6
24
|
//#region src/projectConfig.d.ts
|
|
7
25
|
interface StatelySourceConfig {
|
|
8
26
|
include: string[];
|
|
@@ -90,10 +108,42 @@ type ApiKeyResolution = {
|
|
|
90
108
|
};
|
|
91
109
|
declare function getEnvApiKey(): {
|
|
92
110
|
apiKey: string;
|
|
93
|
-
variable: 'NEXT_PUBLIC_STATELY_API_KEY' | 'STATELY_API_KEY';
|
|
111
|
+
variable: 'NEXT_PUBLIC_STATELY_API_KEY' | 'STATELY_API_KEY' | 'STATELY_ACCESS_TOKEN';
|
|
112
|
+
} | undefined;
|
|
113
|
+
declare function getEnvCredential(): {
|
|
114
|
+
credential: StatelyCredential;
|
|
115
|
+
variable: 'NEXT_PUBLIC_STATELY_API_KEY' | 'STATELY_API_KEY' | 'STATELY_ACCESS_TOKEN';
|
|
94
116
|
} | undefined;
|
|
95
117
|
declare function resolveApiKey(explicitApiKey?: string): Promise<ApiKeyResolution>;
|
|
118
|
+
declare function resolveOAuthAccessToken(): Promise<ApiKeyResolution>;
|
|
119
|
+
interface OAuthLoginDiscovery {
|
|
120
|
+
authorizationServer: OAuthAuthorizationServerMetadata;
|
|
121
|
+
authorizationEndpoint: string;
|
|
122
|
+
clientId: string;
|
|
123
|
+
protectedResource?: OAuthProtectedResourceMetadata;
|
|
124
|
+
resourceUrl: string;
|
|
125
|
+
scopes: string[];
|
|
126
|
+
tokenEndpoint: string;
|
|
127
|
+
}
|
|
128
|
+
declare function discoverOAuthLogin(options?: {
|
|
129
|
+
baseUrl?: string;
|
|
130
|
+
clientId?: string;
|
|
131
|
+
fetch?: typeof fetch;
|
|
132
|
+
redirectUri?: string;
|
|
133
|
+
}): Promise<OAuthLoginDiscovery>;
|
|
96
134
|
declare function inferInitProjectName(cwd: string, repo?: ConnectedRepo): string;
|
|
135
|
+
declare function buildOAuthLoginStart(options: {
|
|
136
|
+
baseUrl?: string;
|
|
137
|
+
clientId?: string;
|
|
138
|
+
fetch?: typeof fetch;
|
|
139
|
+
redirectUri: string;
|
|
140
|
+
state: string;
|
|
141
|
+
}): Promise<{
|
|
142
|
+
authorizeUrl: string;
|
|
143
|
+
codeVerifier: string;
|
|
144
|
+
discovery: OAuthLoginDiscovery;
|
|
145
|
+
}>;
|
|
146
|
+
declare function formatStoredCredentialSource(storedCredential: StoredCredential): string;
|
|
97
147
|
declare function formatMissingNewMachineDirMessage(options: {
|
|
98
148
|
config: Pick<StatelyProjectConfig, 'include' | 'newMachinesDir'>;
|
|
99
149
|
remoteMachineCount: number;
|
|
@@ -230,7 +280,9 @@ declare class LoginCommand extends Command {
|
|
|
230
280
|
static flags: {
|
|
231
281
|
help: _oclif_core_interfaces0.BooleanFlag<void>;
|
|
232
282
|
'api-key': _oclif_core_interfaces0.OptionFlag<string | undefined, _oclif_core_interfaces0.CustomOptions>;
|
|
283
|
+
'base-url': _oclif_core_interfaces0.OptionFlag<string | undefined, _oclif_core_interfaces0.CustomOptions>;
|
|
233
284
|
stdin: _oclif_core_interfaces0.BooleanFlag<boolean>;
|
|
285
|
+
auth: _oclif_core_interfaces0.OptionFlag<string | undefined, _oclif_core_interfaces0.CustomOptions>;
|
|
234
286
|
};
|
|
235
287
|
run(): Promise<void>;
|
|
236
288
|
}
|
|
@@ -265,4 +317,4 @@ declare const COMMANDS: {
|
|
|
265
317
|
};
|
|
266
318
|
declare function run(argv?: any, entryUrl?: string): Promise<void>;
|
|
267
319
|
//#endregion
|
|
268
|
-
export { ApiKeyResolution, COMMANDS, InitProjectOptions, InitProjectResult, LinkedPullTarget, PushCandidateClassification, RemoteProjectMachineTarget, ResolvedConfiguredPullTargets, ScanProjectSourcesOptions, classifyPushCandidates, createStatelyProjectConfig, discoverLinkedPullTargets, discoverRemoteProjectMachineTargets, formatMissingNewMachineDirMessage, formatPlanSummary, getEnvApiKey, inferInitProjectName, initProject, isFileGitDirty, resolveApiKey, resolveConfiguredPullTargets, run, scanProjectSources };
|
|
320
|
+
export { ApiKeyResolution, COMMANDS, InitProjectOptions, InitProjectResult, LinkedPullTarget, OAuthLoginDiscovery, PushCandidateClassification, RemoteProjectMachineTarget, ResolvedConfiguredPullTargets, ScanProjectSourcesOptions, buildOAuthLoginStart, classifyPushCandidates, createStatelyProjectConfig, discoverLinkedPullTargets, discoverOAuthLogin, discoverRemoteProjectMachineTargets, formatMissingNewMachineDirMessage, formatPlanSummary, formatStoredCredentialSource, getEnvApiKey, getEnvCredential, inferInitProjectName, initProject, isFileGitDirty, resolveApiKey, resolveConfiguredPullTargets, resolveOAuthAccessToken, run, scanProjectSources };
|
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { a as
|
|
1
|
+
import { _ as resolveOAuthAccessToken, a as discoverOAuthLogin, b as createStatelyProjectConfig, 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 run, y as scanProjectSources } from "./cli-jiby9qdf.mjs";
|
|
2
2
|
|
|
3
|
-
export { COMMANDS, classifyPushCandidates, createStatelyProjectConfig, discoverLinkedPullTargets, discoverRemoteProjectMachineTargets, formatMissingNewMachineDirMessage, formatPlanSummary, getEnvApiKey, inferInitProjectName, initProject, isFileGitDirty, resolveApiKey, resolveConfiguredPullTargets, run, scanProjectSources };
|
|
3
|
+
export { COMMANDS, buildOAuthLoginStart, classifyPushCandidates, createStatelyProjectConfig, discoverLinkedPullTargets, discoverOAuthLogin, discoverRemoteProjectMachineTargets, formatMissingNewMachineDirMessage, formatPlanSummary, formatStoredCredentialSource, getEnvApiKey, getEnvCredential, inferInitProjectName, initProject, isFileGitDirty, resolveApiKey, resolveConfiguredPullTargets, resolveOAuthAccessToken, run, scanProjectSources };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "statelyai",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
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.
|
|
24
|
+
"@statelyai/sdk": "0.12.1"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"tsdown": "0.21.0-beta.2",
|