@flatkey-ai/cli 0.1.22 → 0.1.25

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.
Files changed (3) hide show
  1. package/README.md +34 -3
  2. package/package.json +1 -1
  3. package/src/cli.js +75 -5
package/README.md CHANGED
@@ -13,7 +13,7 @@ Website: [flatkey.ai](https://flatkey.ai/?utm_source=github&utm_medium=readme&ut
13
13
 
14
14
  ```bash
15
15
  npm install -g @flatkey-ai/cli
16
- export FLATKEY_API_KEY=<your-flatkey-api-key>
16
+ flatkey login
17
17
 
18
18
  flatkey image generate --prompt "editorial cover, neon city at dawn" -o cover.png
19
19
  flatkey video generate --prompt "cinematic product launch clip" --model seedance2 -o launch.mp4
@@ -67,13 +67,44 @@ flatkey -v
67
67
 
68
68
  ## Auth
69
69
 
70
- Use an environment variable:
70
+ Recommended: sign in through the Flatkey console:
71
+
72
+ ```bash
73
+ flatkey login
74
+ ```
75
+
76
+ `flatkey login` opens a browser authorization page, waits for approval, then saves the CLI API key and account metadata in your local Flatkey config. After login, check the active auth state:
77
+
78
+ ```bash
79
+ flatkey auth status
80
+ flatkey status --json
81
+ ```
82
+
83
+ If your terminal cannot open a browser automatically, print the approval URL and open it yourself:
84
+
85
+ ```bash
86
+ flatkey login --no-open
87
+ ```
88
+
89
+ For staging or self-hosted console testing:
90
+
91
+ ```bash
92
+ flatkey login --console-url https://console.flatkey.ai
93
+ ```
94
+
95
+ To remove the saved API key:
96
+
97
+ ```bash
98
+ flatkey logout
99
+ ```
100
+
101
+ Alternative: use an environment variable:
71
102
 
72
103
  ```bash
73
104
  export FLATKEY_API_KEY=<key>
74
105
  ```
75
106
 
76
- Or save it locally:
107
+ Or save a manually created key locally:
77
108
 
78
109
  ```bash
79
110
  flatkey onboard --api-key <key>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flatkey-ai/cli",
3
- "version": "0.1.22",
3
+ "version": "0.1.25",
4
4
  "description": "Flatkey media generation CLI for image, video, audio, credits, status, and models.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli.js CHANGED
@@ -267,6 +267,7 @@ async function handleLogin(command, deps) {
267
267
  });
268
268
  const pollData = poll?.data ?? poll;
269
269
  if (pollData?.status === "approved") {
270
+ const account = accountFromAuthData(pollData);
270
271
  if (!pollData.api_key) {
271
272
  const saved = await readConfig(deps.configDir);
272
273
  if (typeof saved?.apiKey === "string" && saved.apiKey.trim() !== "") {
@@ -276,6 +277,7 @@ async function handleLogin(command, deps) {
276
277
  deviceId,
277
278
  userId: pollData.user_id ?? saved.auth?.userId,
278
279
  tokenId: pollData.token_id ?? saved.auth?.tokenId,
280
+ account: account ?? saved.auth?.account,
279
281
  loginAt: Math.floor(Date.now() / 1000),
280
282
  },
281
283
  configDir: deps.configDir,
@@ -284,8 +286,7 @@ async function handleLogin(command, deps) {
284
286
  ? {
285
287
  success: true,
286
288
  configPath,
287
- tokenId: pollData.token_id ?? saved.auth?.tokenId,
288
- userId: pollData.user_id ?? saved.auth?.userId,
289
+ account: stripDisplayOnlyIds(account ?? saved.auth?.account),
289
290
  reusedConfig: true,
290
291
  }
291
292
  : `Flatkey CLI already authorized. Saved config: ${configPath}`;
@@ -298,12 +299,17 @@ async function handleLogin(command, deps) {
298
299
  deviceId,
299
300
  userId: pollData.user_id,
300
301
  tokenId: pollData.token_id,
302
+ account,
301
303
  loginAt: Math.floor(Date.now() / 1000),
302
304
  },
303
305
  configDir: deps.configDir,
304
306
  });
305
307
  return command.options.json
306
- ? { success: true, configPath, tokenId: pollData.token_id, userId: pollData.user_id }
308
+ ? {
309
+ success: true,
310
+ configPath,
311
+ account: stripDisplayOnlyIds(account),
312
+ }
307
313
  : `Flatkey CLI authorized. Saved config: ${configPath}`;
308
314
  }
309
315
  if (pollData?.status === "denied") {
@@ -732,7 +738,8 @@ function redactRequest(request) {
732
738
  }
733
739
 
734
740
  async function handleUtility(command, deps) {
735
- const { resolveApiKey, resolveOrigins } = await import("./config.js");
741
+ const { readConfig, resolveApiKey, resolveOrigins } = await import("./config.js");
742
+ const saved = await readConfig(deps.configDir);
736
743
  const { getCredits, getStatus } = await import("./api.js");
737
744
  const apiKey = await resolveApiKey({
738
745
  apiKey: command.options.api_key,
@@ -749,7 +756,70 @@ async function handleUtility(command, deps) {
749
756
  baseUrl: consoleOrigin,
750
757
  fetch: deps.fetch,
751
758
  };
752
- return command.group === "credits" ? getCredits(options) : getStatus(options);
759
+ if (command.group === "credits") return getCredits(options);
760
+
761
+ const status = sanitizeStatusResponse(await getStatus(options));
762
+ const account = effectiveAccountForStatus({ command, saved, apiKey });
763
+ if (!account || !status || typeof status !== "object" || Array.isArray(status) || status.account !== undefined) {
764
+ return command.options.json ? status : extractStatusDisplayFields(status);
765
+ }
766
+ const mergedStatus = sanitizeStatusResponse({ ...status, account });
767
+ return command.options.json ? mergedStatus : extractStatusDisplayFields(mergedStatus);
768
+ }
769
+
770
+ function effectiveAccountForStatus({ command, saved, apiKey }) {
771
+ if (command.options.api_key || !saved?.apiKey || saved.apiKey !== apiKey) return null;
772
+ if (saved.auth?.account !== undefined) return saved.auth.account;
773
+ return accountFromAuthData(saved.auth);
774
+ }
775
+
776
+ function accountFromAuthData(data) {
777
+ if (!data || typeof data !== "object") return null;
778
+ const email = data.account?.email ?? data.email ?? data.userEmail ?? data.user_email;
779
+ const name = data.account?.name ?? data.name;
780
+ const userId = data.account?.userId ?? data.account?.user_id ?? data.userId ?? data.user_id;
781
+ if (email === undefined && name === undefined && userId === undefined) return null;
782
+ return Object.fromEntries(
783
+ Object.entries({
784
+ email,
785
+ name,
786
+ userId,
787
+ }).filter(([, value]) => value !== undefined && value !== null),
788
+ );
789
+ }
790
+
791
+ function sanitizeStatusResponse(status) {
792
+ if (!status || typeof status !== "object") return status;
793
+ if (Array.isArray(status)) {
794
+ return status.map((entry) => sanitizeStatusResponse(entry));
795
+ }
796
+ return Object.fromEntries(
797
+ Object.entries(status)
798
+ .filter(([key]) => !["userId", "user_id"].includes(key))
799
+ .map(([key, value]) => [key, sanitizeStatusResponse(value)]),
800
+ );
801
+ }
802
+
803
+ function stripDisplayOnlyIds(value) {
804
+ if (!value || typeof value !== "object") return value;
805
+ if (Array.isArray(value)) {
806
+ return value.map((entry) => stripDisplayOnlyIds(entry));
807
+ }
808
+ return Object.fromEntries(
809
+ Object.entries(value)
810
+ .filter(([key]) => !["userId", "user_id", "tokenId", "token_id"].includes(key))
811
+ .map(([key, nested]) => [key, stripDisplayOnlyIds(nested)]),
812
+ );
813
+ }
814
+
815
+ function extractStatusDisplayFields(status) {
816
+ if (!status || typeof status !== "object") return status;
817
+ const email = status.email ?? status.account?.email ?? status.user?.email;
818
+ const name = status.name ?? status.account?.name ?? status.user?.name;
819
+ const result = {};
820
+ if (email !== undefined) result.email = email;
821
+ if (name !== undefined) result.name = name;
822
+ return result;
753
823
  }
754
824
 
755
825
  async function handleModels(command, deps) {