@flatkey-ai/cli 0.1.2 → 0.1.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flatkey-ai/cli",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Flatkey media generation CLI for image, video, audio, credits, status, and models.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/api.js CHANGED
@@ -1,4 +1,5 @@
1
1
  export const DEFAULT_BASE_URL = "https://router.flatkey.ai";
2
+ export const DEFAULT_MODELS_BASE_URL = "https://console.flatkey.ai";
2
3
 
3
4
  export class FlatkeyError extends Error {
4
5
  constructor(message, { status } = {}) {
@@ -27,7 +28,10 @@ export function planImageRequest(options) {
27
28
  const path = `/v1beta/models/${encodeURIComponent(model)}:generateContent?key=${encodeURIComponent(options.apiKey)}`;
28
29
  return planRequest(options, path, {
29
30
  method: "POST",
30
- headers: { "content-type": "application/json" },
31
+ headers: {
32
+ ...jsonHeaders(options.apiKey),
33
+ "x-goog-api-key": options.apiKey,
34
+ },
31
35
  body: {
32
36
  contents: [{ parts: [{ text: options.prompt }] }],
33
37
  },
@@ -39,7 +43,7 @@ export function generateVideo(options) {
39
43
  }
40
44
 
41
45
  export function planVideoRequest(options) {
42
- return planJsonPost(options, "/v1/videos/generations", cleanObject({
46
+ return planJsonPost(options, "/v1/video/generations", cleanObject({
43
47
  model: options.model ?? "veo-3",
44
48
  prompt: options.prompt,
45
49
  duration: parseOptionalInteger(options.duration),
@@ -81,7 +85,10 @@ export function getStatus(options) {
81
85
  }
82
86
 
83
87
  export function getModels(options) {
84
- return requestJson(options, "/v1/models");
88
+ return requestJson({
89
+ ...options,
90
+ baseUrl: options.baseUrl ?? DEFAULT_MODELS_BASE_URL,
91
+ }, "/v1/available_models");
85
92
  }
86
93
 
87
94
  async function postJson(options, path, payload) {
package/test/api.test.js CHANGED
@@ -45,6 +45,8 @@ test("builds nano image request using gemini-style route", async () => {
45
45
  "https://router.test/v1beta/models/nano-banana-pro-preview:generateContent?key=key",
46
46
  );
47
47
  assert.equal(calls[0].init.method, "POST");
48
+ assert.equal(calls[0].init.headers.Authorization, "Bearer key");
49
+ assert.equal(calls[0].init.headers["x-goog-api-key"], "key");
48
50
  assert.deepEqual(JSON.parse(calls[0].init.body), {
49
51
  contents: [{ parts: [{ text: "poster" }] }],
50
52
  });
@@ -87,7 +89,7 @@ test("builds video generation request", async () => {
87
89
  fetch,
88
90
  });
89
91
 
90
- assert.equal(calls[0].url, "https://router.test/v1/videos/generations");
92
+ assert.equal(calls[0].url, "https://router.test/v1/video/generations");
91
93
  assert.deepEqual(JSON.parse(calls[0].init.body), {
92
94
  model: "veo-3",
93
95
  prompt: "walkthrough",
@@ -108,7 +110,7 @@ test("builds seedance2 video generation request", async () => {
108
110
  fetch,
109
111
  });
110
112
 
111
- assert.equal(calls[0].url, "https://router.test/v1/videos/generations");
113
+ assert.equal(calls[0].url, "https://router.test/v1/video/generations");
112
114
  assert.equal(JSON.parse(calls[0].init.body).model, "seedance2");
113
115
  });
114
116
 
@@ -161,21 +163,33 @@ test("builds credits, status, and models requests", async () => {
161
163
 
162
164
  assert.equal(calls[0].url, "https://router.test/v1/credits");
163
165
  assert.equal(calls[1].url, "https://router.test/v1/status");
164
- assert.equal(calls[2].url, "https://router.test/v1/models");
166
+ assert.equal(calls[2].url, "https://router.test/v1/available_models");
165
167
  assert.equal(calls[0].init.headers.Authorization, "Bearer key");
166
168
  assert.equal(calls[2].init.headers.Authorization, "Bearer key");
167
169
  });
168
170
 
169
- test("plans model list request from new-api relay route", async () => {
170
- const { fetch, calls } = fetchRecorder({ object: "list", data: [{ id: "gpt-5.5" }] });
171
+ test("plans available model list request from new-api relay route", async () => {
172
+ const { fetch, calls } = fetchRecorder({
173
+ success: true,
174
+ object: "list",
175
+ data: [{ id: "gpt-5.5", object: "model", owned_by: "flatkey" }],
176
+ });
171
177
 
172
- await getModels({ apiKey: "env-key", baseUrl: "https://router.flatkey.ai", fetch });
178
+ await getModels({ apiKey: "env-key", fetch });
173
179
 
174
- assert.equal(calls[0].url, "https://router.flatkey.ai/v1/models");
180
+ assert.equal(calls[0].url, "https://console.flatkey.ai/v1/available_models");
175
181
  assert.equal(calls[0].init.method, "GET");
176
182
  assert.equal(calls[0].init.headers.Authorization, "Bearer env-key");
177
183
  });
178
184
 
185
+ test("honors explicit base url for available model list request", async () => {
186
+ const { fetch, calls } = fetchRecorder({ success: true, object: "list", data: [] });
187
+
188
+ await getModels({ apiKey: "env-key", baseUrl: "https://router.test", fetch });
189
+
190
+ assert.equal(calls[0].url, "https://router.test/v1/available_models");
191
+ });
192
+
179
193
  test("plans dry-run requests for target media models", () => {
180
194
  assert.equal(planImageRequest({
181
195
  apiKey: "key",
@@ -20,7 +20,7 @@ test("runs generation and utility commands in json mode", async (t) => {
20
20
  response.setHeader("content-type", "application/json");
21
21
  if (request.url.startsWith("/v1beta/models/")) {
22
22
  response.end(JSON.stringify({ data: [{ url: "https://cdn.test/image.png" }] }));
23
- } else if (request.url === "/v1/videos/generations") {
23
+ } else if (request.url === "/v1/video/generations") {
24
24
  response.end(JSON.stringify({ data: [{ url: "https://cdn.test/video.mp4" }] }));
25
25
  } else if (request.url === "/v1/audio/generations") {
26
26
  response.end(JSON.stringify({ data: [{ url: "https://cdn.test/audio.mp3" }] }));
@@ -30,8 +30,12 @@ test("runs generation and utility commands in json mode", async (t) => {
30
30
  response.end(JSON.stringify({ remaining: 42, used: 8 }));
31
31
  } else if (request.url === "/v1/status") {
32
32
  response.end(JSON.stringify({ status: "ok" }));
33
- } else if (request.url === "/v1/models") {
34
- response.end(JSON.stringify({ data: [{ id: "remote-image", type: "image" }] }));
33
+ } else if (request.url === "/v1/available_models") {
34
+ response.end(JSON.stringify({
35
+ success: true,
36
+ object: "list",
37
+ data: [{ id: "remote-image", object: "model", owned_by: "flatkey" }],
38
+ }));
35
39
  } else {
36
40
  response.statusCode = 404;
37
41
  response.end(JSON.stringify({ error: { message: "not found" } }));
@@ -62,7 +66,7 @@ test("runs generation and utility commands in json mode", async (t) => {
62
66
  ]);
63
67
  assert.equal(image.stderr, "");
64
68
  assert.ok(requests.some((request) => request.url.startsWith("/v1beta/models/")));
65
- assert.ok(requests.some((request) => request.url === "/v1/videos/generations"));
69
+ assert.ok(requests.some((request) => request.url === "/v1/video/generations"));
66
70
  assert.ok(requests.some((request) => request.url === "/v1/audio/generations"));
67
71
  assert.ok(requests.some((request) => request.url === "/v1/chat/completions"));
68
72
  });