@flatkey-ai/cli 0.1.2 → 0.1.4
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 +1 -1
- package/src/api.js +10 -3
- package/src/cli.js +5 -1
- package/test/api.test.js +21 -7
- package/test/cli.test.js +15 -1
- package/test/integration.test.js +8 -4
package/package.json
CHANGED
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: {
|
|
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/
|
|
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(
|
|
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/src/cli.js
CHANGED
|
@@ -18,6 +18,9 @@ export function parseArgv(argv) {
|
|
|
18
18
|
if (!group) {
|
|
19
19
|
return { group: "help", action: undefined, options: {} };
|
|
20
20
|
}
|
|
21
|
+
if (group === "--version" || group === "-v") {
|
|
22
|
+
return { group: "version", action: undefined, options: {} };
|
|
23
|
+
}
|
|
21
24
|
if (!COMMANDS.has(group)) {
|
|
22
25
|
throw new Error(`Unknown command: ${group}`);
|
|
23
26
|
}
|
|
@@ -87,7 +90,8 @@ export async function runCommand(command, deps = {}) {
|
|
|
87
90
|
return command.options.ai ? getAiHelp() : getHumanHelp();
|
|
88
91
|
}
|
|
89
92
|
if (command.group === "version") {
|
|
90
|
-
|
|
93
|
+
const version = await readPackageVersion();
|
|
94
|
+
return command.options.json ? { version } : version;
|
|
91
95
|
}
|
|
92
96
|
|
|
93
97
|
if (command.group === "models") {
|
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/
|
|
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/
|
|
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/
|
|
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({
|
|
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",
|
|
178
|
+
await getModels({ apiKey: "env-key", fetch });
|
|
173
179
|
|
|
174
|
-
assert.equal(calls[0].url, "https://
|
|
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",
|
package/test/cli.test.js
CHANGED
|
@@ -75,6 +75,19 @@ test("parses ai help", () => {
|
|
|
75
75
|
});
|
|
76
76
|
});
|
|
77
77
|
|
|
78
|
+
test("parses global version aliases", () => {
|
|
79
|
+
assert.deepEqual(parseArgv(["--version"]), {
|
|
80
|
+
group: "version",
|
|
81
|
+
action: undefined,
|
|
82
|
+
options: {},
|
|
83
|
+
});
|
|
84
|
+
assert.deepEqual(parseArgv(["-v"]), {
|
|
85
|
+
group: "version",
|
|
86
|
+
action: undefined,
|
|
87
|
+
options: {},
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
78
91
|
test("rejects unknown commands", () => {
|
|
79
92
|
assert.throws(
|
|
80
93
|
() => parseArgv(["wat"]),
|
|
@@ -85,7 +98,8 @@ test("rejects unknown commands", () => {
|
|
|
85
98
|
test("version command matches package version", async () => {
|
|
86
99
|
const pkg = JSON.parse(await (await import("node:fs/promises")).readFile("package.json", "utf8"));
|
|
87
100
|
|
|
88
|
-
assert.
|
|
101
|
+
assert.equal(await runCommand({ group: "version", options: {} }), pkg.version);
|
|
102
|
+
assert.deepEqual(await runCommand({ group: "version", options: { json: true } }), {
|
|
89
103
|
version: pkg.version,
|
|
90
104
|
});
|
|
91
105
|
});
|
package/test/integration.test.js
CHANGED
|
@@ -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/
|
|
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/
|
|
34
|
-
response.end(JSON.stringify({
|
|
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/
|
|
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
|
});
|