@flatkey-ai/cli 0.1.4 → 0.1.6
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/LICENSE +21 -0
- package/README.md +215 -13
- package/package.json +5 -1
- package/src/api.js +89 -6
- package/src/cli.js +78 -4
- package/src/config.js +3 -3
- package/src/help.js +153 -5
- package/src/models.js +5 -2
- package/.github/workflows/npm-publish.yml +0 -36
- package/docs/superpowers/plans/2026-07-17-flatkey-cli.md +0 -90
- package/docs/superpowers/specs/2026-07-17-flatkey-cli-design.md +0 -209
- package/release/deb/README.md +0 -16
- package/release/homebrew/flatkey.rb +0 -16
- package/release/msi/README.md +0 -15
- package/test/animation.test.js +0 -30
- package/test/api.test.js +0 -232
- package/test/artifacts.test.js +0 -108
- package/test/cli.test.js +0 -105
- package/test/config.test.js +0 -62
- package/test/help.test.js +0 -29
- package/test/integration.test.js +0 -204
- package/test/live-flatkey.test.js +0 -91
- package/test/models.test.js +0 -56
- package/test/package.test.js +0 -29
package/test/api.test.js
DELETED
|
@@ -1,232 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import { test } from "node:test";
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
FlatkeyError,
|
|
6
|
-
generateAudio,
|
|
7
|
-
generateImage,
|
|
8
|
-
generateText,
|
|
9
|
-
generateVideo,
|
|
10
|
-
getCredits,
|
|
11
|
-
getModels,
|
|
12
|
-
getStatus,
|
|
13
|
-
planImageRequest,
|
|
14
|
-
planTextRequest,
|
|
15
|
-
planVideoRequest,
|
|
16
|
-
} from "../src/api.js";
|
|
17
|
-
|
|
18
|
-
function fetchRecorder(responseBody = { ok: true }) {
|
|
19
|
-
const calls = [];
|
|
20
|
-
const fetch = async (url, init) => {
|
|
21
|
-
calls.push({ url, init });
|
|
22
|
-
return {
|
|
23
|
-
ok: true,
|
|
24
|
-
status: 200,
|
|
25
|
-
async json() {
|
|
26
|
-
return responseBody;
|
|
27
|
-
},
|
|
28
|
-
};
|
|
29
|
-
};
|
|
30
|
-
return { fetch, calls };
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
test("builds nano image request using gemini-style route", async () => {
|
|
34
|
-
const { fetch, calls } = fetchRecorder({ candidates: [] });
|
|
35
|
-
|
|
36
|
-
await generateImage({
|
|
37
|
-
apiKey: "key",
|
|
38
|
-
baseUrl: "https://router.test",
|
|
39
|
-
prompt: "poster",
|
|
40
|
-
fetch,
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
assert.equal(
|
|
44
|
-
calls[0].url,
|
|
45
|
-
"https://router.test/v1beta/models/nano-banana-pro-preview:generateContent?key=key",
|
|
46
|
-
);
|
|
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");
|
|
50
|
-
assert.deepEqual(JSON.parse(calls[0].init.body), {
|
|
51
|
-
contents: [{ parts: [{ text: "poster" }] }],
|
|
52
|
-
});
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
test("builds OpenAI image request for gpt image models", async () => {
|
|
56
|
-
const { fetch, calls } = fetchRecorder({ data: [] });
|
|
57
|
-
|
|
58
|
-
await generateImage({
|
|
59
|
-
apiKey: "key",
|
|
60
|
-
baseUrl: "https://router.test",
|
|
61
|
-
model: "gpt-image-2",
|
|
62
|
-
prompt: "cover",
|
|
63
|
-
size: "1024x1024",
|
|
64
|
-
n: "2",
|
|
65
|
-
fetch,
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
assert.equal(calls[0].url, "https://router.test/v1/images/generations");
|
|
69
|
-
assert.equal(calls[0].init.headers.Authorization, "Bearer key");
|
|
70
|
-
assert.deepEqual(JSON.parse(calls[0].init.body), {
|
|
71
|
-
model: "gpt-image-2",
|
|
72
|
-
prompt: "cover",
|
|
73
|
-
size: "1024x1024",
|
|
74
|
-
n: 2,
|
|
75
|
-
});
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
test("builds video generation request", async () => {
|
|
79
|
-
const { fetch, calls } = fetchRecorder({ data: [] });
|
|
80
|
-
|
|
81
|
-
await generateVideo({
|
|
82
|
-
apiKey: "key",
|
|
83
|
-
baseUrl: "https://router.test",
|
|
84
|
-
model: "veo-3",
|
|
85
|
-
prompt: "walkthrough",
|
|
86
|
-
duration: "8",
|
|
87
|
-
aspect: "16:9",
|
|
88
|
-
fps: "24",
|
|
89
|
-
fetch,
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
assert.equal(calls[0].url, "https://router.test/v1/video/generations");
|
|
93
|
-
assert.deepEqual(JSON.parse(calls[0].init.body), {
|
|
94
|
-
model: "veo-3",
|
|
95
|
-
prompt: "walkthrough",
|
|
96
|
-
duration: 8,
|
|
97
|
-
aspect: "16:9",
|
|
98
|
-
fps: 24,
|
|
99
|
-
});
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
test("builds seedance2 video generation request", async () => {
|
|
103
|
-
const { fetch, calls } = fetchRecorder({ data: [] });
|
|
104
|
-
|
|
105
|
-
await generateVideo({
|
|
106
|
-
apiKey: "key",
|
|
107
|
-
baseUrl: "https://router.test",
|
|
108
|
-
model: "seedance2",
|
|
109
|
-
prompt: "newsroom b-roll",
|
|
110
|
-
fetch,
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
assert.equal(calls[0].url, "https://router.test/v1/video/generations");
|
|
114
|
-
assert.equal(JSON.parse(calls[0].init.body).model, "seedance2");
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
test("builds text generation request for gpt-5.5", async () => {
|
|
118
|
-
const { fetch, calls } = fetchRecorder({ choices: [{ message: { content: "copy" } }] });
|
|
119
|
-
|
|
120
|
-
await generateText({
|
|
121
|
-
apiKey: "key",
|
|
122
|
-
baseUrl: "https://router.test",
|
|
123
|
-
model: "gpt-5.5",
|
|
124
|
-
prompt: "write headline",
|
|
125
|
-
fetch,
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
assert.equal(calls[0].url, "https://router.test/v1/chat/completions");
|
|
129
|
-
assert.deepEqual(JSON.parse(calls[0].init.body), {
|
|
130
|
-
model: "gpt-5.5",
|
|
131
|
-
messages: [{ role: "user", content: "write headline" }],
|
|
132
|
-
});
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
test("builds audio generation request", async () => {
|
|
136
|
-
const { fetch, calls } = fetchRecorder({ data: [] });
|
|
137
|
-
|
|
138
|
-
await generateAudio({
|
|
139
|
-
apiKey: "key",
|
|
140
|
-
baseUrl: "https://router.test",
|
|
141
|
-
model: "tts-1",
|
|
142
|
-
prompt: "voiceover",
|
|
143
|
-
voice: "alloy",
|
|
144
|
-
format: "mp3",
|
|
145
|
-
fetch,
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
assert.equal(calls[0].url, "https://router.test/v1/audio/generations");
|
|
149
|
-
assert.deepEqual(JSON.parse(calls[0].init.body), {
|
|
150
|
-
model: "tts-1",
|
|
151
|
-
prompt: "voiceover",
|
|
152
|
-
voice: "alloy",
|
|
153
|
-
format: "mp3",
|
|
154
|
-
});
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
test("builds credits, status, and models requests", async () => {
|
|
158
|
-
const { fetch, calls } = fetchRecorder({});
|
|
159
|
-
|
|
160
|
-
await getCredits({ apiKey: "key", baseUrl: "https://router.test", fetch });
|
|
161
|
-
await getStatus({ apiKey: "key", baseUrl: "https://router.test", fetch });
|
|
162
|
-
await getModels({ apiKey: "key", baseUrl: "https://router.test", fetch });
|
|
163
|
-
|
|
164
|
-
assert.equal(calls[0].url, "https://router.test/v1/credits");
|
|
165
|
-
assert.equal(calls[1].url, "https://router.test/v1/status");
|
|
166
|
-
assert.equal(calls[2].url, "https://router.test/v1/available_models");
|
|
167
|
-
assert.equal(calls[0].init.headers.Authorization, "Bearer key");
|
|
168
|
-
assert.equal(calls[2].init.headers.Authorization, "Bearer key");
|
|
169
|
-
});
|
|
170
|
-
|
|
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
|
-
});
|
|
177
|
-
|
|
178
|
-
await getModels({ apiKey: "env-key", fetch });
|
|
179
|
-
|
|
180
|
-
assert.equal(calls[0].url, "https://console.flatkey.ai/v1/available_models");
|
|
181
|
-
assert.equal(calls[0].init.method, "GET");
|
|
182
|
-
assert.equal(calls[0].init.headers.Authorization, "Bearer env-key");
|
|
183
|
-
});
|
|
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
|
-
|
|
193
|
-
test("plans dry-run requests for target media models", () => {
|
|
194
|
-
assert.equal(planImageRequest({
|
|
195
|
-
apiKey: "key",
|
|
196
|
-
model: "gpt-image-2",
|
|
197
|
-
prompt: "poster",
|
|
198
|
-
}).url, "https://router.flatkey.ai/v1/images/generations");
|
|
199
|
-
|
|
200
|
-
assert.match(planImageRequest({
|
|
201
|
-
apiKey: "key",
|
|
202
|
-
model: "nano-banana-pro-preview",
|
|
203
|
-
prompt: "poster",
|
|
204
|
-
}).url, /\/v1beta\/models\/nano-banana-pro-preview:generateContent\?key=key$/);
|
|
205
|
-
|
|
206
|
-
assert.equal(planVideoRequest({
|
|
207
|
-
apiKey: "key",
|
|
208
|
-
model: "seedance2",
|
|
209
|
-
prompt: "clip",
|
|
210
|
-
}).body.model, "seedance2");
|
|
211
|
-
|
|
212
|
-
assert.equal(planTextRequest({
|
|
213
|
-
apiKey: "key",
|
|
214
|
-
model: "gpt-5.5",
|
|
215
|
-
prompt: "headline",
|
|
216
|
-
}).body.model, "gpt-5.5");
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
test("throws FlatkeyError with API message on HTTP failure", async () => {
|
|
220
|
-
const fetch = async () => ({
|
|
221
|
-
ok: false,
|
|
222
|
-
status: 402,
|
|
223
|
-
async json() {
|
|
224
|
-
return { error: { message: "credits exhausted" } };
|
|
225
|
-
},
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
await assert.rejects(
|
|
229
|
-
() => getCredits({ apiKey: "key", baseUrl: "https://router.test", fetch }),
|
|
230
|
-
(error) => error instanceof FlatkeyError && /credits exhausted/.test(error.message),
|
|
231
|
-
);
|
|
232
|
-
});
|
package/test/artifacts.test.js
DELETED
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import { mkdtemp, readFile } from "node:fs/promises";
|
|
3
|
-
import { tmpdir } from "node:os";
|
|
4
|
-
import { join } from "node:path";
|
|
5
|
-
import { test } from "node:test";
|
|
6
|
-
|
|
7
|
-
import { persistArtifacts } from "../src/artifacts.js";
|
|
8
|
-
|
|
9
|
-
test("saves OpenAI base64 image artifacts", async () => {
|
|
10
|
-
const outDir = await mkdtemp(join(tmpdir(), "flatkey-artifacts-"));
|
|
11
|
-
|
|
12
|
-
const artifacts = await persistArtifacts({
|
|
13
|
-
kind: "image",
|
|
14
|
-
response: { data: [{ b64_json: Buffer.from("png-bytes").toString("base64") }] },
|
|
15
|
-
outDir,
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
assert.equal(artifacts.length, 1);
|
|
19
|
-
assert.match(artifacts[0].path, /image-01\.png$/);
|
|
20
|
-
assert.equal(await readFile(artifacts[0].path, "utf8"), "png-bytes");
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
test("saves data url video artifacts with mime extension", async () => {
|
|
24
|
-
const outDir = await mkdtemp(join(tmpdir(), "flatkey-artifacts-"));
|
|
25
|
-
|
|
26
|
-
const artifacts = await persistArtifacts({
|
|
27
|
-
kind: "video",
|
|
28
|
-
response: {
|
|
29
|
-
data: [{ url: `data:video/mp4;base64,${Buffer.from("mp4-bytes").toString("base64")}` }],
|
|
30
|
-
},
|
|
31
|
-
outDir,
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
assert.match(artifacts[0].path, /video-01\.mp4$/);
|
|
35
|
-
assert.equal(await readFile(artifacts[0].path, "utf8"), "mp4-bytes");
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
test("preserves remote url artifacts without downloading", async () => {
|
|
39
|
-
const outDir = await mkdtemp(join(tmpdir(), "flatkey-artifacts-"));
|
|
40
|
-
|
|
41
|
-
const artifacts = await persistArtifacts({
|
|
42
|
-
kind: "audio",
|
|
43
|
-
response: { data: [{ url: "https://cdn.test/audio.mp3" }] },
|
|
44
|
-
outDir,
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
assert.deepEqual(artifacts, [{ url: "https://cdn.test/audio.mp3" }]);
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
test("saves first artifact to explicit output path", async () => {
|
|
51
|
-
const outDir = await mkdtemp(join(tmpdir(), "flatkey-artifacts-"));
|
|
52
|
-
const output = join(outDir, "custom.png");
|
|
53
|
-
|
|
54
|
-
const artifacts = await persistArtifacts({
|
|
55
|
-
kind: "image",
|
|
56
|
-
response: { data: [{ b64_json: Buffer.from("custom-bytes").toString("base64") }] },
|
|
57
|
-
output,
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
assert.deepEqual(artifacts, [{ path: output }]);
|
|
61
|
-
assert.equal(await readFile(output, "utf8"), "custom-bytes");
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
test("adds numeric suffix for multiple artifacts with explicit output path", async () => {
|
|
65
|
-
const outDir = await mkdtemp(join(tmpdir(), "flatkey-artifacts-"));
|
|
66
|
-
const output = join(outDir, "custom.png");
|
|
67
|
-
|
|
68
|
-
const artifacts = await persistArtifacts({
|
|
69
|
-
kind: "image",
|
|
70
|
-
response: {
|
|
71
|
-
data: [
|
|
72
|
-
{ b64_json: Buffer.from("one").toString("base64") },
|
|
73
|
-
{ b64_json: Buffer.from("two").toString("base64") },
|
|
74
|
-
],
|
|
75
|
-
},
|
|
76
|
-
output,
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
assert.match(artifacts[0].path, /custom\.png$/);
|
|
80
|
-
assert.match(artifacts[1].path, /custom-02\.png$/);
|
|
81
|
-
assert.equal(await readFile(artifacts[1].path, "utf8"), "two");
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
test("downloads remote url artifact when explicit output path is set", async () => {
|
|
85
|
-
const outDir = await mkdtemp(join(tmpdir(), "flatkey-artifacts-"));
|
|
86
|
-
const output = join(outDir, "clip.mp4");
|
|
87
|
-
const calls = [];
|
|
88
|
-
|
|
89
|
-
const artifacts = await persistArtifacts({
|
|
90
|
-
kind: "video",
|
|
91
|
-
response: { data: [{ url: "https://cdn.test/clip.mp4" }] },
|
|
92
|
-
output,
|
|
93
|
-
fetch: async (url) => {
|
|
94
|
-
calls.push(url);
|
|
95
|
-
return {
|
|
96
|
-
ok: true,
|
|
97
|
-
status: 200,
|
|
98
|
-
async arrayBuffer() {
|
|
99
|
-
return Buffer.from("video-bytes");
|
|
100
|
-
},
|
|
101
|
-
};
|
|
102
|
-
},
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
assert.deepEqual(calls, ["https://cdn.test/clip.mp4"]);
|
|
106
|
-
assert.deepEqual(artifacts, [{ path: output }]);
|
|
107
|
-
assert.equal(await readFile(output, "utf8"), "video-bytes");
|
|
108
|
-
});
|
package/test/cli.test.js
DELETED
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import { test } from "node:test";
|
|
3
|
-
|
|
4
|
-
import { parseArgv, runCommand } from "../src/cli.js";
|
|
5
|
-
|
|
6
|
-
test("parses image generation with prompt and json mode", () => {
|
|
7
|
-
const command = parseArgv(["image", "generate", "--prompt", "city at dawn", "--json"]);
|
|
8
|
-
|
|
9
|
-
assert.deepEqual(command, {
|
|
10
|
-
group: "image",
|
|
11
|
-
action: "generate",
|
|
12
|
-
options: {
|
|
13
|
-
prompt: "city at dawn",
|
|
14
|
-
json: true,
|
|
15
|
-
},
|
|
16
|
-
});
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
test("parses credits with json mode", () => {
|
|
20
|
-
const command = parseArgv(["credits", "--json"]);
|
|
21
|
-
|
|
22
|
-
assert.deepEqual(command, {
|
|
23
|
-
group: "credits",
|
|
24
|
-
action: undefined,
|
|
25
|
-
options: {
|
|
26
|
-
json: true,
|
|
27
|
-
},
|
|
28
|
-
});
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
test("parses text generation with dry-run", () => {
|
|
32
|
-
const command = parseArgv([
|
|
33
|
-
"text",
|
|
34
|
-
"generate",
|
|
35
|
-
"--prompt",
|
|
36
|
-
"write lead",
|
|
37
|
-
"--model",
|
|
38
|
-
"gpt-5.5",
|
|
39
|
-
"--dry-run",
|
|
40
|
-
"--json",
|
|
41
|
-
]);
|
|
42
|
-
|
|
43
|
-
assert.deepEqual(command, {
|
|
44
|
-
group: "text",
|
|
45
|
-
action: "generate",
|
|
46
|
-
options: {
|
|
47
|
-
prompt: "write lead",
|
|
48
|
-
model: "gpt-5.5",
|
|
49
|
-
dry_run: true,
|
|
50
|
-
json: true,
|
|
51
|
-
},
|
|
52
|
-
});
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
test("parses output aliases", () => {
|
|
56
|
-
assert.equal(
|
|
57
|
-
parseArgv(["image", "generate", "--prompt", "x", "--output", "out.png"]).options.output,
|
|
58
|
-
"out.png",
|
|
59
|
-
);
|
|
60
|
-
assert.equal(
|
|
61
|
-
parseArgv(["video", "generate", "--prompt", "x", "-o", "clip.mp4"]).options.output,
|
|
62
|
-
"clip.mp4",
|
|
63
|
-
);
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
test("parses ai help", () => {
|
|
67
|
-
const command = parseArgv(["help", "--ai"]);
|
|
68
|
-
|
|
69
|
-
assert.deepEqual(command, {
|
|
70
|
-
group: "help",
|
|
71
|
-
action: undefined,
|
|
72
|
-
options: {
|
|
73
|
-
ai: true,
|
|
74
|
-
},
|
|
75
|
-
});
|
|
76
|
-
});
|
|
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
|
-
|
|
91
|
-
test("rejects unknown commands", () => {
|
|
92
|
-
assert.throws(
|
|
93
|
-
() => parseArgv(["wat"]),
|
|
94
|
-
/Unknown command: wat/,
|
|
95
|
-
);
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
test("version command matches package version", async () => {
|
|
99
|
-
const pkg = JSON.parse(await (await import("node:fs/promises")).readFile("package.json", "utf8"));
|
|
100
|
-
|
|
101
|
-
assert.equal(await runCommand({ group: "version", options: {} }), pkg.version);
|
|
102
|
-
assert.deepEqual(await runCommand({ group: "version", options: { json: true } }), {
|
|
103
|
-
version: pkg.version,
|
|
104
|
-
});
|
|
105
|
-
});
|
package/test/config.test.js
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import { mkdtemp, readFile, stat } from "node:fs/promises";
|
|
3
|
-
import { tmpdir } from "node:os";
|
|
4
|
-
import { join } from "node:path";
|
|
5
|
-
import { test } from "node:test";
|
|
6
|
-
|
|
7
|
-
import { getConfigPath, resolveApiKey, writeConfig } from "../src/config.js";
|
|
8
|
-
|
|
9
|
-
test("resolves api key from explicit option first", async () => {
|
|
10
|
-
const configDir = await mkdtemp(join(tmpdir(), "flatkey-config-"));
|
|
11
|
-
await writeConfig({ apiKey: "saved-key", configDir });
|
|
12
|
-
|
|
13
|
-
const key = await resolveApiKey({
|
|
14
|
-
apiKey: "option-key",
|
|
15
|
-
env: { FLATKEY_API_KEY: "env-key" },
|
|
16
|
-
configDir,
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
assert.equal(key, "option-key");
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
test("resolves api key from FLATKEY_API_KEY before saved config", async () => {
|
|
23
|
-
const configDir = await mkdtemp(join(tmpdir(), "flatkey-config-"));
|
|
24
|
-
await writeConfig({ apiKey: "saved-key", configDir });
|
|
25
|
-
|
|
26
|
-
const key = await resolveApiKey({
|
|
27
|
-
env: { FLATKEY_API_KEY: "env-key" },
|
|
28
|
-
configDir,
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
assert.equal(key, "env-key");
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
test("resolves api key from saved config", async () => {
|
|
35
|
-
const configDir = await mkdtemp(join(tmpdir(), "flatkey-config-"));
|
|
36
|
-
await writeConfig({ apiKey: "saved-key", configDir });
|
|
37
|
-
|
|
38
|
-
const key = await resolveApiKey({ env: {}, configDir });
|
|
39
|
-
|
|
40
|
-
assert.equal(key, "saved-key");
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
test("throws actionable error when api key is missing", async () => {
|
|
44
|
-
const configDir = await mkdtemp(join(tmpdir(), "flatkey-config-"));
|
|
45
|
-
|
|
46
|
-
await assert.rejects(
|
|
47
|
-
() => resolveApiKey({ env: {}, configDir }),
|
|
48
|
-
/Missing Flatkey API key.*flatkey onboard.*FLATKEY_API_KEY/s,
|
|
49
|
-
);
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
test("writes config json with api key", async () => {
|
|
53
|
-
const configDir = await mkdtemp(join(tmpdir(), "flatkey-config-"));
|
|
54
|
-
|
|
55
|
-
const configPath = await writeConfig({ apiKey: "written-key", configDir });
|
|
56
|
-
const saved = JSON.parse(await readFile(configPath, "utf8"));
|
|
57
|
-
const fileStat = await stat(configPath);
|
|
58
|
-
|
|
59
|
-
assert.equal(configPath, getConfigPath(configDir));
|
|
60
|
-
assert.equal(saved.apiKey, "written-key");
|
|
61
|
-
assert.ok((fileStat.mode & 0o777) === 0o600 || process.platform === "win32");
|
|
62
|
-
});
|
package/test/help.test.js
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import { test } from "node:test";
|
|
3
|
-
|
|
4
|
-
import { getAiHelp, getHumanHelp } from "../src/help.js";
|
|
5
|
-
|
|
6
|
-
test("ai help teaches agents setup and command usage", () => {
|
|
7
|
-
const help = getAiHelp();
|
|
8
|
-
|
|
9
|
-
assert.match(help, /FLATKEY_API_KEY/);
|
|
10
|
-
assert.match(help, /flatkey onboard --api-key/);
|
|
11
|
-
assert.match(help, /flatkey image generate/);
|
|
12
|
-
assert.match(help, /flatkey video generate/);
|
|
13
|
-
assert.match(help, /flatkey audio generate/);
|
|
14
|
-
assert.match(help, /flatkey text generate/);
|
|
15
|
-
assert.match(help, /flatkey credits --json/);
|
|
16
|
-
assert.match(help, /flatkey status --json/);
|
|
17
|
-
assert.match(help, /flatkey models --json/);
|
|
18
|
-
assert.match(help, /JSON mode/);
|
|
19
|
-
assert.match(help, /Missing key/);
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
test("human help lists core commands", () => {
|
|
23
|
-
const help = getHumanHelp();
|
|
24
|
-
|
|
25
|
-
assert.match(help, /Usage: flatkey/);
|
|
26
|
-
assert.match(help, /image generate/);
|
|
27
|
-
assert.match(help, /models/);
|
|
28
|
-
assert.match(help, /text generate/);
|
|
29
|
-
});
|