f2u-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/dist/index.d.ts +1 -0
- package/dist/index.js +243 -0
- package/package.json +29 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
#!/usr/bin/env node
|
|
3
|
+
|
|
4
|
+
// src/index.ts
|
|
5
|
+
import { Command as Command7 } from "commander";
|
|
6
|
+
|
|
7
|
+
// src/commands/auth-command.ts
|
|
8
|
+
import { Command } from "commander";
|
|
9
|
+
|
|
10
|
+
// src/config.ts
|
|
11
|
+
import { readFileSync, writeFileSync, mkdirSync, chmodSync } from "fs";
|
|
12
|
+
import { join } from "path";
|
|
13
|
+
import { homedir } from "os";
|
|
14
|
+
var CONFIG_DIR = join(homedir(), ".config", "f2u");
|
|
15
|
+
var CONFIG_FILE = join(CONFIG_DIR, "config.json");
|
|
16
|
+
function loadConfig() {
|
|
17
|
+
try {
|
|
18
|
+
return JSON.parse(readFileSync(CONFIG_FILE, "utf-8"));
|
|
19
|
+
} catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function saveConfig(config) {
|
|
24
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
25
|
+
writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
26
|
+
chmodSync(CONFIG_FILE, 384);
|
|
27
|
+
}
|
|
28
|
+
function requireConfig() {
|
|
29
|
+
const envEndpoint = process.env["F2U_ENDPOINT"];
|
|
30
|
+
const envKey = process.env["F2U_API_KEY"];
|
|
31
|
+
if (envEndpoint && envKey) {
|
|
32
|
+
return { endpoint: envEndpoint.replace(/\/$/, ""), api_key: envKey };
|
|
33
|
+
}
|
|
34
|
+
const config = loadConfig();
|
|
35
|
+
if (!config) {
|
|
36
|
+
process.stderr.write(
|
|
37
|
+
JSON.stringify({ error: "Not configured. Run: f2u auth --endpoint <url> --key <key>" }) + "\n"
|
|
38
|
+
);
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
config.endpoint = config.endpoint.replace(/\/$/, "");
|
|
42
|
+
return config;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// src/commands/auth-command.ts
|
|
46
|
+
function makeAuthCommand() {
|
|
47
|
+
return new Command("auth").description("Configure f2u with your Worker endpoint and API key").requiredOption("--endpoint <url>", "Worker base URL (e.g. https://f2u.example.com)").requiredOption("--key <key>", "API key for authentication").action((opts) => {
|
|
48
|
+
const config = {
|
|
49
|
+
endpoint: opts.endpoint.replace(/\/$/, ""),
|
|
50
|
+
api_key: opts.key
|
|
51
|
+
};
|
|
52
|
+
saveConfig(config);
|
|
53
|
+
process.stdout.write(
|
|
54
|
+
JSON.stringify({ success: true, endpoint: config.endpoint, message: "Configuration saved." }) + "\n"
|
|
55
|
+
);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// src/commands/upload-command.ts
|
|
60
|
+
import { Command as Command2 } from "commander";
|
|
61
|
+
import { existsSync } from "fs";
|
|
62
|
+
|
|
63
|
+
// src/api-client.ts
|
|
64
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
65
|
+
import { basename } from "path";
|
|
66
|
+
var ApiClient = class {
|
|
67
|
+
baseUrl;
|
|
68
|
+
headers;
|
|
69
|
+
constructor(config) {
|
|
70
|
+
this.baseUrl = config.endpoint;
|
|
71
|
+
this.headers = {
|
|
72
|
+
Authorization: `Bearer ${config.api_key}`
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
// Parse response and exit with JSON error on non-ok status
|
|
76
|
+
async handleResponse(response) {
|
|
77
|
+
if (!response.ok) {
|
|
78
|
+
let errorBody;
|
|
79
|
+
try {
|
|
80
|
+
errorBody = await response.json();
|
|
81
|
+
} catch {
|
|
82
|
+
errorBody = { message: response.statusText };
|
|
83
|
+
}
|
|
84
|
+
process.stderr.write(
|
|
85
|
+
JSON.stringify({ error: "API request failed", status: response.status, detail: errorBody }) + "\n"
|
|
86
|
+
);
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
return response.json();
|
|
90
|
+
}
|
|
91
|
+
async upload(filePath, ttl) {
|
|
92
|
+
const fileBuffer = readFileSync2(filePath);
|
|
93
|
+
const filename = basename(filePath);
|
|
94
|
+
const blob = new Blob([fileBuffer]);
|
|
95
|
+
const form = new FormData();
|
|
96
|
+
form.append("file", blob, filename);
|
|
97
|
+
form.append("ttl", ttl);
|
|
98
|
+
const response = await fetch(`${this.baseUrl}/upload`, {
|
|
99
|
+
method: "POST",
|
|
100
|
+
headers: this.headers,
|
|
101
|
+
body: form
|
|
102
|
+
});
|
|
103
|
+
return this.handleResponse(response);
|
|
104
|
+
}
|
|
105
|
+
async listFiles() {
|
|
106
|
+
const response = await fetch(`${this.baseUrl}/files`, {
|
|
107
|
+
headers: this.headers
|
|
108
|
+
});
|
|
109
|
+
return this.handleResponse(response);
|
|
110
|
+
}
|
|
111
|
+
async deleteFile(id) {
|
|
112
|
+
const response = await fetch(`${this.baseUrl}/${encodeURIComponent(id)}`, {
|
|
113
|
+
method: "DELETE",
|
|
114
|
+
headers: this.headers
|
|
115
|
+
});
|
|
116
|
+
return this.handleResponse(response);
|
|
117
|
+
}
|
|
118
|
+
async fileInfo(id) {
|
|
119
|
+
const response = await fetch(`${this.baseUrl}/info/${encodeURIComponent(id)}`, {
|
|
120
|
+
headers: this.headers
|
|
121
|
+
});
|
|
122
|
+
return this.handleResponse(response);
|
|
123
|
+
}
|
|
124
|
+
async usage() {
|
|
125
|
+
const response = await fetch(`${this.baseUrl}/usage`, {
|
|
126
|
+
headers: this.headers
|
|
127
|
+
});
|
|
128
|
+
return this.handleResponse(response);
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
// src/commands/upload-command.ts
|
|
133
|
+
var VALID_TTLS = ["5m", "15m", "30m", "1h", "6h", "12h", "24h"];
|
|
134
|
+
function isValidTtl(value) {
|
|
135
|
+
return VALID_TTLS.includes(value);
|
|
136
|
+
}
|
|
137
|
+
function makeUploadCommand() {
|
|
138
|
+
return new Command2("up").description("Upload a file and get a temporary public URL").requiredOption("-f, --file <path>", "Path to the file to upload").option("-t, --ttl <duration>", `TTL for the file (${VALID_TTLS.join(", ")})`, "5m").action(async (opts) => {
|
|
139
|
+
if (!existsSync(opts.file)) {
|
|
140
|
+
process.stderr.write(JSON.stringify({ error: `File not found: ${opts.file}` }) + "\n");
|
|
141
|
+
process.exit(1);
|
|
142
|
+
}
|
|
143
|
+
if (!isValidTtl(opts.ttl)) {
|
|
144
|
+
process.stderr.write(
|
|
145
|
+
JSON.stringify({ error: `Invalid TTL "${opts.ttl}". Must be one of: ${VALID_TTLS.join(", ")}` }) + "\n"
|
|
146
|
+
);
|
|
147
|
+
process.exit(1);
|
|
148
|
+
}
|
|
149
|
+
const config = requireConfig();
|
|
150
|
+
const client = new ApiClient(config);
|
|
151
|
+
try {
|
|
152
|
+
const result = await client.upload(opts.file, opts.ttl);
|
|
153
|
+
process.stdout.write(JSON.stringify(result) + "\n");
|
|
154
|
+
} catch (err) {
|
|
155
|
+
process.stderr.write(
|
|
156
|
+
JSON.stringify({ error: "Upload failed", detail: err instanceof Error ? err.message : String(err) }) + "\n"
|
|
157
|
+
);
|
|
158
|
+
process.exit(1);
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// src/commands/list-command.ts
|
|
164
|
+
import { Command as Command3 } from "commander";
|
|
165
|
+
function makeListCommand() {
|
|
166
|
+
return new Command3("ls").description("List all active uploaded files").action(async () => {
|
|
167
|
+
const config = requireConfig();
|
|
168
|
+
const client = new ApiClient(config);
|
|
169
|
+
try {
|
|
170
|
+
const files = await client.listFiles();
|
|
171
|
+
process.stdout.write(JSON.stringify(files) + "\n");
|
|
172
|
+
} catch (err) {
|
|
173
|
+
process.stderr.write(
|
|
174
|
+
JSON.stringify({ error: "List failed", detail: err instanceof Error ? err.message : String(err) }) + "\n"
|
|
175
|
+
);
|
|
176
|
+
process.exit(1);
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// src/commands/delete-command.ts
|
|
182
|
+
import { Command as Command4 } from "commander";
|
|
183
|
+
function makeDeleteCommand() {
|
|
184
|
+
return new Command4("rm").description("Delete an uploaded file by ID").argument("<id>", "File ID to delete").action(async (id) => {
|
|
185
|
+
const config = requireConfig();
|
|
186
|
+
const client = new ApiClient(config);
|
|
187
|
+
try {
|
|
188
|
+
const result = await client.deleteFile(id);
|
|
189
|
+
process.stdout.write(JSON.stringify(result) + "\n");
|
|
190
|
+
} catch (err) {
|
|
191
|
+
process.stderr.write(
|
|
192
|
+
JSON.stringify({ error: "Delete failed", detail: err instanceof Error ? err.message : String(err) }) + "\n"
|
|
193
|
+
);
|
|
194
|
+
process.exit(1);
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// src/commands/info-command.ts
|
|
200
|
+
import { Command as Command5 } from "commander";
|
|
201
|
+
function makeInfoCommand() {
|
|
202
|
+
return new Command5("info").description("Get details for a file including TTL remaining").argument("<id>", "File ID to inspect").action(async (id) => {
|
|
203
|
+
const config = requireConfig();
|
|
204
|
+
const client = new ApiClient(config);
|
|
205
|
+
try {
|
|
206
|
+
const info = await client.fileInfo(id);
|
|
207
|
+
process.stdout.write(JSON.stringify(info) + "\n");
|
|
208
|
+
} catch (err) {
|
|
209
|
+
process.stderr.write(
|
|
210
|
+
JSON.stringify({ error: "Info fetch failed", detail: err instanceof Error ? err.message : String(err) }) + "\n"
|
|
211
|
+
);
|
|
212
|
+
process.exit(1);
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// src/commands/usage-command.ts
|
|
218
|
+
import { Command as Command6 } from "commander";
|
|
219
|
+
function makeUsageCommand() {
|
|
220
|
+
return new Command6("usage").description("Show storage usage statistics").action(async () => {
|
|
221
|
+
const config = requireConfig();
|
|
222
|
+
const client = new ApiClient(config);
|
|
223
|
+
try {
|
|
224
|
+
const stats = await client.usage();
|
|
225
|
+
process.stdout.write(JSON.stringify(stats) + "\n");
|
|
226
|
+
} catch (err) {
|
|
227
|
+
process.stderr.write(
|
|
228
|
+
JSON.stringify({ error: "Usage fetch failed", detail: err instanceof Error ? err.message : String(err) }) + "\n"
|
|
229
|
+
);
|
|
230
|
+
process.exit(1);
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// src/index.ts
|
|
236
|
+
var program = new Command7().name("f2u").description("Upload temporary files to Cloudflare R2 (for AI agents)").version("0.1.0");
|
|
237
|
+
program.addCommand(makeAuthCommand());
|
|
238
|
+
program.addCommand(makeUploadCommand());
|
|
239
|
+
program.addCommand(makeListCommand());
|
|
240
|
+
program.addCommand(makeDeleteCommand());
|
|
241
|
+
program.addCommand(makeInfoCommand());
|
|
242
|
+
program.addCommand(makeUsageCommand());
|
|
243
|
+
program.parse();
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "f2u-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CLI tool for AI agents to upload temporary files to Cloudflare R2",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"f2u": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"dev": "tsx src/index.ts",
|
|
14
|
+
"build": "tsup",
|
|
15
|
+
"prepublishOnly": "pnpm build"
|
|
16
|
+
},
|
|
17
|
+
"publishConfig": {
|
|
18
|
+
"access": "public"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"commander": "^12.0.0"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"tsup": "^8.0.0",
|
|
25
|
+
"tsx": "^4.0.0",
|
|
26
|
+
"typescript": "^5.5.0",
|
|
27
|
+
"@types/node": "^20.0.0"
|
|
28
|
+
}
|
|
29
|
+
}
|