@floehq/cli 0.1.0

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 ADDED
@@ -0,0 +1,40 @@
1
+ # Floe CLI
2
+
3
+ `@floehq/cli` is the official command-line interface for the Floe API.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install -g @floehq/cli
9
+ ```
10
+
11
+ Or run without a global install:
12
+
13
+ ```bash
14
+ npx @floehq/cli --help
15
+ ```
16
+
17
+ ## Commands
18
+
19
+ ```bash
20
+ floe upload ./video.mp4
21
+ floe upload status <uploadId>
22
+ floe upload cancel <uploadId>
23
+ floe upload complete <uploadId>
24
+ floe upload wait <uploadId>
25
+ floe file metadata <fileId>
26
+ floe file manifest <fileId>
27
+ floe file stream-url <fileId>
28
+ floe ops health
29
+ floe config show
30
+ ```
31
+
32
+ Shortcuts are also supported for the most common lookups:
33
+
34
+ ```bash
35
+ floe status <uploadId>
36
+ floe cancel <uploadId>
37
+ floe metadata <fileId>
38
+ floe manifest <fileId>
39
+ floe stream-url <fileId>
40
+ ```
package/dist/cli.js ADDED
@@ -0,0 +1,476 @@
1
+ #!/usr/bin/env node
2
+ import { FloeApiError, FloeClient, createNodeFileResumeStore } from "@floehq/sdk";
3
+ import fs from "node:fs/promises";
4
+ import path from "node:path";
5
+ function printHelp(topic) {
6
+ const normalized = (topic ?? "").toLowerCase();
7
+ if (normalized === "upload") {
8
+ process.stdout.write(`Floe CLI: upload
9
+
10
+ Usage:
11
+ floe upload <file> [options]
12
+ floe upload status <uploadId> [options]
13
+ floe upload cancel <uploadId> [options]
14
+ floe upload complete <uploadId> [options]
15
+ floe upload wait <uploadId> [options]
16
+
17
+ Notes:
18
+ upload resume is automatic by default through the local resume store
19
+ use --no-resume to disable that behavior
20
+ `);
21
+ return;
22
+ }
23
+ if (normalized === "file") {
24
+ process.stdout.write(`Floe CLI: file
25
+
26
+ Usage:
27
+ floe file metadata <fileId> [options]
28
+ floe file manifest <fileId> [options]
29
+ floe file stream-url <fileId> [options]
30
+ `);
31
+ return;
32
+ }
33
+ if (normalized === "ops") {
34
+ process.stdout.write(`Floe CLI: ops
35
+
36
+ Usage:
37
+ floe ops health [options]
38
+ `);
39
+ return;
40
+ }
41
+ process.stdout.write(`Floe CLI
42
+
43
+ Usage:
44
+ floe <group> <command> [args] [options]
45
+
46
+ Groups:
47
+ upload upload, status, cancel, complete, and wait flows
48
+ file metadata, manifest, and stream URL lookups
49
+ ops health and operator-friendly checks
50
+ config show the effective local CLI configuration
51
+ help show top-level or group help
52
+
53
+ Primary Commands:
54
+ floe upload <file>
55
+ floe upload status <uploadId>
56
+ floe upload cancel <uploadId>
57
+ floe upload complete <uploadId>
58
+ floe upload wait <uploadId>
59
+ floe file metadata <fileId>
60
+ floe file manifest <fileId>
61
+ floe file stream-url <fileId>
62
+ floe ops health
63
+ floe config show
64
+
65
+ Shortcuts:
66
+ floe status <uploadId>
67
+ floe cancel <uploadId>
68
+ floe metadata <fileId>
69
+ floe manifest <fileId>
70
+ floe stream-url <fileId>
71
+
72
+ Global Options:
73
+ --base-url <url> Floe API base URL
74
+ --api-key <key> x-api-key auth
75
+ --bearer <token> Authorization bearer token
76
+ --owner-address <addr> x-owner-address auth hint
77
+ --wallet-address <addr> x-wallet-address auth hint
78
+ --auth-user <id> x-auth-user auth hint
79
+ --json Print JSON only
80
+ --include-blob-id Ask Floe to include blobId when supported
81
+
82
+ Upload Options:
83
+ --chunk-size <bytes> Upload chunk size in bytes
84
+ --epochs <n> Walrus epochs for upload create
85
+ --parallel <n> Parallel chunk uploads (default: 3)
86
+ --no-resume Disable resume-store lookup for uploads
87
+ --poll-interval-ms <n> Finalize wait poll interval
88
+ --max-wait-ms <n> Finalize max wait time
89
+
90
+ Examples:
91
+ floe upload ./movie.mp4 --base-url http://127.0.0.1:3001/v1
92
+ floe upload wait 123e4567-e89b-12d3-a456-426614174000
93
+ floe file metadata 0xabc...
94
+ floe ops health
95
+ floe config show
96
+ `);
97
+ }
98
+ function inferContentType(filePath) {
99
+ const ext = path.extname(filePath).toLowerCase();
100
+ if (ext === ".mp4")
101
+ return "video/mp4";
102
+ if (ext === ".webm")
103
+ return "video/webm";
104
+ if (ext === ".mov")
105
+ return "video/quicktime";
106
+ if (ext === ".json")
107
+ return "application/json";
108
+ if (ext === ".txt")
109
+ return "text/plain";
110
+ if (ext === ".mkv")
111
+ return "video/x-matroska";
112
+ return "application/octet-stream";
113
+ }
114
+ function parseIntFlag(value) {
115
+ const n = Number(value);
116
+ if (!Number.isFinite(n) || n <= 0)
117
+ return undefined;
118
+ return Math.floor(n);
119
+ }
120
+ function parseArgs(argv) {
121
+ const tokens = [];
122
+ const options = {
123
+ baseUrl: process.env.FLOE_BASE_URL || "http://127.0.0.1:3001/v1",
124
+ apiKey: process.env.FLOE_API_KEY,
125
+ bearerToken: process.env.FLOE_BEARER_TOKEN,
126
+ ownerAddress: process.env.FLOE_OWNER_ADDRESS,
127
+ authUser: process.env.FLOE_AUTH_USER,
128
+ walletAddress: process.env.FLOE_WALLET_ADDRESS,
129
+ json: false,
130
+ parallel: 3,
131
+ };
132
+ for (let i = 0; i < argv.length; i += 1) {
133
+ const arg = argv[i];
134
+ if (!arg.startsWith("--")) {
135
+ tokens.push(arg);
136
+ continue;
137
+ }
138
+ const readValue = () => {
139
+ const value = argv[i + 1];
140
+ i += 1;
141
+ return value;
142
+ };
143
+ switch (arg) {
144
+ case "--base-url":
145
+ options.baseUrl = readValue() || options.baseUrl;
146
+ break;
147
+ case "--api-key":
148
+ options.apiKey = readValue() || "";
149
+ break;
150
+ case "--bearer":
151
+ options.bearerToken = readValue() || "";
152
+ break;
153
+ case "--owner-address":
154
+ options.ownerAddress = readValue() || "";
155
+ break;
156
+ case "--wallet-address":
157
+ options.walletAddress = readValue() || "";
158
+ break;
159
+ case "--auth-user":
160
+ options.authUser = readValue() || "";
161
+ break;
162
+ case "--chunk-size":
163
+ options.chunkSize = parseIntFlag(readValue());
164
+ break;
165
+ case "--epochs":
166
+ options.epochs = parseIntFlag(readValue());
167
+ break;
168
+ case "--parallel":
169
+ options.parallel = parseIntFlag(readValue());
170
+ break;
171
+ case "--poll-interval-ms":
172
+ options.pollIntervalMs = parseIntFlag(readValue());
173
+ break;
174
+ case "--max-wait-ms":
175
+ options.maxWaitMs = parseIntFlag(readValue());
176
+ break;
177
+ case "--include-blob-id":
178
+ options.includeBlobId = true;
179
+ break;
180
+ case "--no-resume":
181
+ options.noResume = true;
182
+ break;
183
+ case "--json":
184
+ options.json = true;
185
+ break;
186
+ default:
187
+ break;
188
+ }
189
+ }
190
+ const [first, second, third] = tokens.map((v) => v.toLowerCase());
191
+ let command;
192
+ switch (first ?? "help") {
193
+ case "upload":
194
+ switch (second) {
195
+ case "status":
196
+ command = { kind: "upload.status", uploadId: tokens[2] };
197
+ break;
198
+ case "cancel":
199
+ command = { kind: "upload.cancel", uploadId: tokens[2] };
200
+ break;
201
+ case "complete":
202
+ command = { kind: "upload.complete", uploadId: tokens[2] };
203
+ break;
204
+ case "wait":
205
+ command = { kind: "upload.wait", uploadId: tokens[2] };
206
+ break;
207
+ case "help":
208
+ command = { kind: "help", topic: "upload" };
209
+ break;
210
+ default:
211
+ command = { kind: "upload.upload", filePath: tokens[1] ?? tokens[0] };
212
+ }
213
+ break;
214
+ case "file":
215
+ switch (second) {
216
+ case "metadata":
217
+ command = { kind: "file.metadata", fileId: tokens[2] };
218
+ break;
219
+ case "manifest":
220
+ command = { kind: "file.manifest", fileId: tokens[2] };
221
+ break;
222
+ case "stream-url":
223
+ command = { kind: "file.stream-url", fileId: tokens[2] };
224
+ break;
225
+ case "help":
226
+ command = { kind: "help", topic: "file" };
227
+ break;
228
+ default:
229
+ command = { kind: "help", topic: "file" };
230
+ }
231
+ break;
232
+ case "ops":
233
+ command = second === "health" ? { kind: "ops.health" } : { kind: "help", topic: "ops" };
234
+ break;
235
+ case "config":
236
+ command = second === "show" ? { kind: "config.show" } : { kind: "help" };
237
+ break;
238
+ case "status":
239
+ command = { kind: "upload.status", uploadId: tokens[1] };
240
+ break;
241
+ case "cancel":
242
+ command = { kind: "upload.cancel", uploadId: tokens[1] };
243
+ break;
244
+ case "metadata":
245
+ command = { kind: "file.metadata", fileId: tokens[1] };
246
+ break;
247
+ case "manifest":
248
+ command = { kind: "file.manifest", fileId: tokens[1] };
249
+ break;
250
+ case "stream-url":
251
+ command = { kind: "file.stream-url", fileId: tokens[1] };
252
+ break;
253
+ case "help":
254
+ command = { kind: "help", topic: tokens[1] };
255
+ break;
256
+ default:
257
+ command = { kind: "help" };
258
+ break;
259
+ }
260
+ return { command, options };
261
+ }
262
+ function printResult(value, json) {
263
+ const text = JSON.stringify(value, null, 2);
264
+ process.stdout.write(`${text}\n`);
265
+ }
266
+ async function readFileAsBlob(filePath, contentType) {
267
+ const openAsBlob = fs.openAsBlob;
268
+ if (typeof openAsBlob === "function") {
269
+ return await openAsBlob(filePath, { type: contentType });
270
+ }
271
+ const bytes = await fs.readFile(filePath);
272
+ return new Blob([bytes], { type: contentType });
273
+ }
274
+ async function buildClient(options) {
275
+ const resumeStore = options.noResume ? undefined : await createNodeFileResumeStore();
276
+ return new FloeClient({
277
+ baseUrl: options.baseUrl,
278
+ auth: {
279
+ ...(options.apiKey ? { apiKey: options.apiKey } : {}),
280
+ ...(options.bearerToken ? { bearerToken: options.bearerToken } : {}),
281
+ ...(options.ownerAddress ? { ownerAddress: options.ownerAddress } : {}),
282
+ ...(options.authUser ? { authUser: options.authUser } : {}),
283
+ ...(options.walletAddress ? { walletAddress: options.walletAddress } : {}),
284
+ },
285
+ resumeStore,
286
+ userAgent: "@floehq/cli",
287
+ });
288
+ }
289
+ function requireValue(value, label) {
290
+ if (!value)
291
+ throw new Error(`${label} is required`);
292
+ return value;
293
+ }
294
+ function rootApiUrl(baseUrl) {
295
+ return baseUrl.replace(/\/v1\/?$/, "");
296
+ }
297
+ async function fetchJson(url, options) {
298
+ const headers = new Headers();
299
+ if (options.apiKey)
300
+ headers.set("x-api-key", options.apiKey);
301
+ if (options.bearerToken)
302
+ headers.set("authorization", `Bearer ${options.bearerToken}`);
303
+ if (options.authUser)
304
+ headers.set("x-auth-user", options.authUser);
305
+ if (options.ownerAddress)
306
+ headers.set("x-owner-address", options.ownerAddress);
307
+ if (options.walletAddress)
308
+ headers.set("x-wallet-address", options.walletAddress);
309
+ headers.set("x-floe-sdk", "@floehq/cli");
310
+ const response = await fetch(url, { headers });
311
+ const text = await response.text();
312
+ const data = text ? JSON.parse(text) : null;
313
+ if (!response.ok) {
314
+ throw new Error(`Request failed (${response.status}): ${text}`);
315
+ }
316
+ return data;
317
+ }
318
+ async function runUpload(filePathRaw, options) {
319
+ const rawFile = requireValue(filePathRaw, "file path");
320
+ const filePath = path.resolve(rawFile);
321
+ const stat = await fs.stat(filePath);
322
+ if (!stat.isFile())
323
+ throw new Error(`Not a file: ${filePath}`);
324
+ const contentType = inferContentType(filePath);
325
+ const blob = await readFileAsBlob(filePath, contentType);
326
+ const client = await buildClient(options);
327
+ const result = await client.uploadBlob(blob, {
328
+ filename: path.basename(filePath),
329
+ contentType,
330
+ ...(options.chunkSize ? { chunkSize: options.chunkSize } : {}),
331
+ ...(options.epochs ? { epochs: options.epochs } : {}),
332
+ ...(options.parallel ? { parallel: options.parallel } : {}),
333
+ ...(options.includeBlobId ? { includeBlobId: true } : {}),
334
+ ...(options.pollIntervalMs ? { finalizePollIntervalMs: options.pollIntervalMs } : {}),
335
+ ...(options.maxWaitMs ? { finalizeMaxWaitMs: options.maxWaitMs } : {}),
336
+ onProgress(progress) {
337
+ if (options.json)
338
+ return;
339
+ process.stderr.write(`uploaded ${progress.uploadedChunks}/${progress.totalChunks} chunks (${progress.uploadedBytes}/${progress.totalBytes} bytes)\n`);
340
+ },
341
+ });
342
+ printResult(result, options.json);
343
+ }
344
+ async function runUploadStatus(uploadIdRaw, options) {
345
+ const uploadId = requireValue(uploadIdRaw, "uploadId");
346
+ const client = await buildClient(options);
347
+ const result = await client.getUploadStatus(uploadId, {
348
+ ...(options.includeBlobId ? { query: { includeBlobId: 1 } } : {}),
349
+ });
350
+ printResult(result, options.json);
351
+ }
352
+ async function runUploadCancel(uploadIdRaw, options) {
353
+ const uploadId = requireValue(uploadIdRaw, "uploadId");
354
+ const client = await buildClient(options);
355
+ const result = await client.cancelUpload(uploadId);
356
+ printResult(result, options.json);
357
+ }
358
+ async function runUploadComplete(uploadIdRaw, options) {
359
+ const uploadId = requireValue(uploadIdRaw, "uploadId");
360
+ const client = await buildClient(options);
361
+ const result = await client.completeUpload(uploadId, {
362
+ ...(options.includeBlobId ? { includeBlobId: true } : {}),
363
+ });
364
+ printResult(result, options.json);
365
+ }
366
+ async function runUploadWait(uploadIdRaw, options) {
367
+ const uploadId = requireValue(uploadIdRaw, "uploadId");
368
+ const client = await buildClient(options);
369
+ const result = await client.waitForUploadReady(uploadId, {
370
+ ...(options.includeBlobId ? { includeBlobId: true } : {}),
371
+ ...(options.pollIntervalMs ? { pollIntervalMs: options.pollIntervalMs } : {}),
372
+ ...(options.maxWaitMs ? { maxWaitMs: options.maxWaitMs } : {}),
373
+ });
374
+ printResult(result, options.json);
375
+ }
376
+ async function runFileMetadata(fileIdRaw, options) {
377
+ const fileId = requireValue(fileIdRaw, "fileId");
378
+ const client = await buildClient(options);
379
+ const result = await client.getFileMetadata(fileId, {
380
+ ...(options.includeBlobId ? { includeBlobId: true } : {}),
381
+ });
382
+ printResult(result, options.json);
383
+ }
384
+ async function runFileManifest(fileIdRaw, options) {
385
+ const fileId = requireValue(fileIdRaw, "fileId");
386
+ const client = await buildClient(options);
387
+ const result = await client.getFileManifest(fileId);
388
+ printResult(result, options.json);
389
+ }
390
+ async function runFileStreamUrl(fileIdRaw, options) {
391
+ const fileId = requireValue(fileIdRaw, "fileId");
392
+ const client = await buildClient(options);
393
+ printResult({ fileId, streamUrl: client.getFileStreamUrl(fileId) }, options.json);
394
+ }
395
+ async function runOpsHealth(options) {
396
+ const result = await fetchJson(`${rootApiUrl(options.baseUrl)}/health`, options);
397
+ printResult(result, options.json);
398
+ }
399
+ async function runConfigShow(options) {
400
+ printResult({
401
+ baseUrl: options.baseUrl,
402
+ auth: {
403
+ apiKey: options.apiKey ? "[configured]" : null,
404
+ bearerToken: options.bearerToken ? "[configured]" : null,
405
+ ownerAddress: options.ownerAddress ?? null,
406
+ authUser: options.authUser ?? null,
407
+ walletAddress: options.walletAddress ?? null,
408
+ },
409
+ upload: {
410
+ chunkSize: options.chunkSize ?? null,
411
+ epochs: options.epochs ?? null,
412
+ parallel: options.parallel ?? null,
413
+ includeBlobId: options.includeBlobId ?? false,
414
+ noResume: options.noResume ?? false,
415
+ pollIntervalMs: options.pollIntervalMs ?? null,
416
+ maxWaitMs: options.maxWaitMs ?? null,
417
+ },
418
+ }, options.json);
419
+ }
420
+ async function main() {
421
+ const { command, options } = parseArgs(process.argv.slice(2));
422
+ switch (command.kind) {
423
+ case "help":
424
+ printHelp(command.topic);
425
+ return;
426
+ case "upload.upload":
427
+ await runUpload(command.filePath, options);
428
+ return;
429
+ case "upload.status":
430
+ await runUploadStatus(command.uploadId, options);
431
+ return;
432
+ case "upload.cancel":
433
+ await runUploadCancel(command.uploadId, options);
434
+ return;
435
+ case "upload.complete":
436
+ await runUploadComplete(command.uploadId, options);
437
+ return;
438
+ case "upload.wait":
439
+ await runUploadWait(command.uploadId, options);
440
+ return;
441
+ case "file.metadata":
442
+ await runFileMetadata(command.fileId, options);
443
+ return;
444
+ case "file.manifest":
445
+ await runFileManifest(command.fileId, options);
446
+ return;
447
+ case "file.stream-url":
448
+ await runFileStreamUrl(command.fileId, options);
449
+ return;
450
+ case "ops.health":
451
+ await runOpsHealth(options);
452
+ return;
453
+ case "config.show":
454
+ await runConfigShow(options);
455
+ return;
456
+ }
457
+ }
458
+ main().catch((err) => {
459
+ if (err instanceof FloeApiError) {
460
+ process.stderr.write(`${JSON.stringify({
461
+ error: {
462
+ message: err.message,
463
+ status: err.status,
464
+ code: err.code,
465
+ retryable: err.retryable,
466
+ requestId: err.requestId,
467
+ details: err.details,
468
+ },
469
+ }, null, 2)}\n`);
470
+ process.exitCode = 1;
471
+ return;
472
+ }
473
+ process.stderr.write(`${String(err instanceof Error ? err.message : err)}\n`);
474
+ process.exitCode = 1;
475
+ });
476
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import { ApiClient } from "../core/api/client.js";
2
+ import { cancelUpload } from "../core/api/uploads.js";
3
+ import { readEnv } from "../core/config/env.js";
4
+ import { CliError } from "../util/errors.js";
5
+ export async function runCancel(args) {
6
+ const uploadId = args[0];
7
+ if (!uploadId) {
8
+ throw new CliError("Usage: floe cancel <uploadId>");
9
+ }
10
+ const client = new ApiClient(readEnv());
11
+ const result = await cancelUpload(client, uploadId);
12
+ process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
13
+ }
@@ -0,0 +1,24 @@
1
+ import { access } from "node:fs/promises";
2
+ import { constants } from "node:fs";
3
+ import { readEnv } from "../core/config/env.js";
4
+ import { getLegacyUploadScriptPath } from "../core/config/paths.js";
5
+ export async function runDoctor() {
6
+ const env = readEnv();
7
+ const checks = {
8
+ apiBase: env.apiBase,
9
+ authConfigured: Boolean(env.apiKey || env.bearerToken || env.authUser || env.walletAddress || env.ownerAddress),
10
+ legacyUploadScript: await isExecutable(getLegacyUploadScriptPath()),
11
+ ffmpeg: Boolean(process.env.PATH),
12
+ ffprobe: Boolean(process.env.PATH),
13
+ };
14
+ process.stdout.write(`${JSON.stringify(checks, null, 2)}\n`);
15
+ }
16
+ async function isExecutable(filePath) {
17
+ try {
18
+ await access(filePath, constants.X_OK);
19
+ return true;
20
+ }
21
+ catch {
22
+ return false;
23
+ }
24
+ }
@@ -0,0 +1,13 @@
1
+ import { ApiClient } from "../core/api/client.js";
2
+ import { getUploadStatus } from "../core/api/uploads.js";
3
+ import { readEnv } from "../core/config/env.js";
4
+ import { CliError } from "../util/errors.js";
5
+ export async function runStatus(args) {
6
+ const uploadId = args[0];
7
+ if (!uploadId) {
8
+ throw new CliError("Usage: floe status <uploadId>");
9
+ }
10
+ const client = new ApiClient(readEnv());
11
+ const status = await getUploadStatus(client, uploadId);
12
+ process.stdout.write(`${JSON.stringify(status, null, 2)}\n`);
13
+ }
@@ -0,0 +1,28 @@
1
+ import { spawn } from "node:child_process";
2
+ import { access } from "node:fs/promises";
3
+ import { constants } from "node:fs";
4
+ import { CliError } from "../util/errors.js";
5
+ import { getLegacyUploadScriptPath, getRepoRoot } from "../core/config/paths.js";
6
+ export async function runUpload(args) {
7
+ const scriptPath = getLegacyUploadScriptPath();
8
+ await access(scriptPath, constants.X_OK);
9
+ await new Promise((resolve, reject) => {
10
+ const child = spawn(scriptPath, args, {
11
+ cwd: getRepoRoot(),
12
+ stdio: "inherit",
13
+ env: process.env,
14
+ });
15
+ child.on("error", (error) => reject(error));
16
+ child.on("exit", (code, signal) => {
17
+ if (signal) {
18
+ reject(new CliError(`upload interrupted by ${signal}`, 1));
19
+ return;
20
+ }
21
+ if ((code ?? 1) !== 0) {
22
+ reject(new CliError(`upload command exited with code ${code ?? 1}`, code ?? 1));
23
+ return;
24
+ }
25
+ resolve();
26
+ });
27
+ });
28
+ }
@@ -0,0 +1,38 @@
1
+ import { CliError } from "../../util/errors.js";
2
+ import { buildAuthHeaders } from "../auth/headers.js";
3
+ export class ApiClient {
4
+ env;
5
+ constructor(env) {
6
+ this.env = env;
7
+ }
8
+ async request(pathname, init) {
9
+ const url = new URL(pathname, withTrailingSlash(this.env.apiBase));
10
+ const headers = buildAuthHeaders(this.env);
11
+ if (init?.headers) {
12
+ const extra = new Headers(init.headers);
13
+ extra.forEach((value, key) => headers.set(key, value));
14
+ }
15
+ const response = await fetch(url, {
16
+ ...init,
17
+ headers,
18
+ });
19
+ if (!response.ok) {
20
+ const payload = (await safeJson(response));
21
+ const code = payload?.error?.code ? ` ${payload.error.code}` : "";
22
+ const message = payload?.error?.message ?? response.statusText;
23
+ throw new CliError(`HTTP ${response.status}${code} ${message}`, 1);
24
+ }
25
+ return (await response.json());
26
+ }
27
+ }
28
+ function withTrailingSlash(value) {
29
+ return value.endsWith("/") ? value : `${value}/`;
30
+ }
31
+ async function safeJson(response) {
32
+ try {
33
+ return await response.json();
34
+ }
35
+ catch {
36
+ return null;
37
+ }
38
+ }
@@ -0,0 +1,10 @@
1
+ export async function getUploadStatus(client, uploadId) {
2
+ return client.request(`${uploadId}/status`, {
3
+ method: "GET",
4
+ });
5
+ }
6
+ export async function cancelUpload(client, uploadId) {
7
+ return client.request(`${uploadId}`, {
8
+ method: "DELETE",
9
+ });
10
+ }
@@ -0,0 +1,14 @@
1
+ export function buildAuthHeaders(env) {
2
+ const headers = new Headers();
3
+ if (env.apiKey)
4
+ headers.set("x-api-key", env.apiKey);
5
+ if (env.bearerToken)
6
+ headers.set("authorization", `Bearer ${env.bearerToken}`);
7
+ if (env.authUser)
8
+ headers.set("x-auth-user", env.authUser);
9
+ if (env.walletAddress)
10
+ headers.set("x-wallet-address", env.walletAddress);
11
+ if (env.ownerAddress)
12
+ headers.set("x-owner-address", env.ownerAddress);
13
+ return headers;
14
+ }
@@ -0,0 +1,10 @@
1
+ export function readEnv() {
2
+ return {
3
+ apiBase: process.env.FLOE_API_BASE ?? "http://localhost:3001/v1/uploads",
4
+ apiKey: process.env.FLOE_API_KEY ?? "",
5
+ bearerToken: process.env.FLOE_BEARER_TOKEN ?? "",
6
+ authUser: process.env.FLOE_AUTH_USER ?? "",
7
+ walletAddress: process.env.FLOE_WALLET_ADDRESS ?? "",
8
+ ownerAddress: process.env.FLOE_OWNER_ADDRESS ?? "",
9
+ };
10
+ }
@@ -0,0 +1,11 @@
1
+ import path from "node:path";
2
+ import { fileURLToPath } from "node:url";
3
+ const __filename = fileURLToPath(import.meta.url);
4
+ const __dirname = path.dirname(__filename);
5
+ const repoRoot = path.resolve(__dirname, "../../../../../");
6
+ export function getRepoRoot() {
7
+ return repoRoot;
8
+ }
9
+ export function getLegacyUploadScriptPath() {
10
+ return path.join(repoRoot, "scripts", "floe.sh");
11
+ }
@@ -0,0 +1,8 @@
1
+ export class Logger {
2
+ info(message) {
3
+ process.stderr.write(`${message}\n`);
4
+ }
5
+ error(message) {
6
+ process.stderr.write(`${message}\n`);
7
+ }
8
+ }
package/dist/index.js ADDED
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env node
2
+ import { runUpload } from "./commands/upload.js";
3
+ import { runStatus } from "./commands/status.js";
4
+ import { runCancel } from "./commands/cancel.js";
5
+ import { runDoctor } from "./commands/doctor.js";
6
+ import { CliError } from "./util/errors.js";
7
+ const COMMANDS = new Set(["upload", "status", "cancel", "doctor", "help", "--help", "-h"]);
8
+ async function main() {
9
+ const argv = process.argv.slice(2);
10
+ const first = argv[0];
11
+ if (!first || first === "help" || first === "--help" || first === "-h") {
12
+ printHelp();
13
+ return;
14
+ }
15
+ if (!COMMANDS.has(first)) {
16
+ await runUpload(argv);
17
+ return;
18
+ }
19
+ switch (first) {
20
+ case "upload":
21
+ await runUpload(argv.slice(1));
22
+ return;
23
+ case "status":
24
+ await runStatus(argv.slice(1));
25
+ return;
26
+ case "cancel":
27
+ await runCancel(argv.slice(1));
28
+ return;
29
+ case "doctor":
30
+ await runDoctor();
31
+ return;
32
+ default:
33
+ printHelp();
34
+ }
35
+ }
36
+ function printHelp() {
37
+ process.stdout.write(`Floe CLI\n\nCommands:\n floe upload <file> [options]\n floe status <uploadId>\n floe cancel <uploadId>\n floe doctor\n\nWhen no explicit command is provided, arguments are treated as upload arguments for compatibility.\n`);
38
+ }
39
+ main().catch((error) => {
40
+ if (error instanceof CliError) {
41
+ process.stderr.write(`${error.message}\n`);
42
+ process.exit(error.exitCode);
43
+ }
44
+ const message = error instanceof Error ? error.stack ?? error.message : String(error);
45
+ process.stderr.write(`${message}\n`);
46
+ process.exit(1);
47
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,8 @@
1
+ export class CliError extends Error {
2
+ exitCode;
3
+ constructor(message, exitCode = 1) {
4
+ super(message);
5
+ this.name = "CliError";
6
+ this.exitCode = exitCode;
7
+ }
8
+ }
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "@floehq/cli",
3
+ "version": "0.1.0",
4
+ "description": "Command-line interface for the Floe API",
5
+ "type": "module",
6
+ "bin": {
7
+ "floe": "./dist/cli.js"
8
+ },
9
+ "files": [
10
+ "dist"
11
+ ],
12
+ "engines": {
13
+ "node": ">=20.0.0"
14
+ },
15
+ "scripts": {
16
+ "build": "tsc"
17
+ },
18
+ "dependencies": {
19
+ "@floehq/sdk": "^0.1.0"
20
+ },
21
+ "publishConfig": {
22
+ "access": "public"
23
+ }
24
+ }