konbini 0.1.1 → 0.1.2
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/api.d.ts +5 -0
- package/dist/api.js +2 -0
- package/dist/commands/list.js +86 -2
- package/package.json +1 -1
- package/src/api.ts +8 -0
- package/src/commands/list.ts +96 -2
package/dist/api.d.ts
CHANGED
|
@@ -60,6 +60,7 @@ export declare const api: {
|
|
|
60
60
|
name: string;
|
|
61
61
|
description: string;
|
|
62
62
|
price: number;
|
|
63
|
+
quantity?: number;
|
|
63
64
|
thumbnailKey?: string;
|
|
64
65
|
fileKey?: string;
|
|
65
66
|
category?: string;
|
|
@@ -116,5 +117,9 @@ export declare const api: {
|
|
|
116
117
|
};
|
|
117
118
|
isOpen: boolean;
|
|
118
119
|
}[]>>;
|
|
120
|
+
getUploadUrl: (filename: string, contentType: string, folder?: "files" | "thumbnails") => Promise<ApiResponse<{
|
|
121
|
+
uploadUrl: string;
|
|
122
|
+
fileKey: string;
|
|
123
|
+
}>>;
|
|
119
124
|
};
|
|
120
125
|
export {};
|
package/dist/api.js
CHANGED
|
@@ -101,4 +101,6 @@ export const api = {
|
|
|
101
101
|
},
|
|
102
102
|
// Map
|
|
103
103
|
getAllStores: () => apiRequest("/api/map/all"),
|
|
104
|
+
// Storage
|
|
105
|
+
getUploadUrl: (filename, contentType, folder = "files") => apiRequest(`/api/storage/upload-url?filename=${encodeURIComponent(filename)}&contentType=${encodeURIComponent(contentType)}&folder=${folder}`, { authenticated: true }),
|
|
104
106
|
};
|
package/dist/commands/list.js
CHANGED
|
@@ -4,17 +4,51 @@
|
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
import chalk from "chalk";
|
|
6
6
|
import ora from "ora";
|
|
7
|
+
import { readFileSync, existsSync } from "fs";
|
|
8
|
+
import { basename } from "path";
|
|
7
9
|
import { api } from "../api.js";
|
|
8
10
|
import { isAuthenticated, getConfig } from "../config.js";
|
|
11
|
+
// Get content type from file extension
|
|
12
|
+
function getContentType(filename) {
|
|
13
|
+
const ext = filename.toLowerCase().split('.').pop();
|
|
14
|
+
const types = {
|
|
15
|
+
// Code
|
|
16
|
+
'js': 'application/javascript',
|
|
17
|
+
'ts': 'application/typescript',
|
|
18
|
+
'py': 'text/x-python',
|
|
19
|
+
'json': 'application/json',
|
|
20
|
+
'md': 'text/markdown',
|
|
21
|
+
// Archives
|
|
22
|
+
'zip': 'application/zip',
|
|
23
|
+
'tar': 'application/x-tar',
|
|
24
|
+
'gz': 'application/gzip',
|
|
25
|
+
// Images
|
|
26
|
+
'png': 'image/png',
|
|
27
|
+
'jpg': 'image/jpeg',
|
|
28
|
+
'jpeg': 'image/jpeg',
|
|
29
|
+
'gif': 'image/gif',
|
|
30
|
+
'webp': 'image/webp',
|
|
31
|
+
// Audio
|
|
32
|
+
'mp3': 'audio/mpeg',
|
|
33
|
+
'wav': 'audio/wav',
|
|
34
|
+
'ogg': 'audio/ogg',
|
|
35
|
+
// Text
|
|
36
|
+
'txt': 'text/plain',
|
|
37
|
+
'csv': 'text/csv',
|
|
38
|
+
};
|
|
39
|
+
return types[ext || ''] || 'application/octet-stream';
|
|
40
|
+
}
|
|
9
41
|
export const listCommand = new Command("list")
|
|
10
42
|
.description("List an item for sale in your storefront")
|
|
11
43
|
.argument("<name>", "Item name")
|
|
12
44
|
.requiredOption("-p, --price <price>", "Price in $KONBINI")
|
|
13
45
|
.option("-d, --description <desc>", "Item description")
|
|
14
46
|
.option("-c, --category <category>", "Category (scripts, art, audio, data, prompts, tools, other)")
|
|
47
|
+
.option("-q, --quantity <qty>", "Number of copies to sell (default: 1)")
|
|
48
|
+
.option("-f, --file <path>", "Path to the file to upload")
|
|
15
49
|
.action(async (name, options) => {
|
|
16
50
|
if (!isAuthenticated()) {
|
|
17
|
-
console.log(chalk.red("❌ Not logged in. Run '
|
|
51
|
+
console.log(chalk.red("❌ Not logged in. Run 'konbini join' first."));
|
|
18
52
|
return;
|
|
19
53
|
}
|
|
20
54
|
const price = parseInt(options.price);
|
|
@@ -22,13 +56,60 @@ export const listCommand = new Command("list")
|
|
|
22
56
|
console.log(chalk.red("Invalid price. Must be a positive number."));
|
|
23
57
|
return;
|
|
24
58
|
}
|
|
59
|
+
const quantity = options.quantity ? parseInt(options.quantity) : 1;
|
|
60
|
+
if (isNaN(quantity) || quantity < 1) {
|
|
61
|
+
console.log(chalk.red("Invalid quantity. Must be at least 1."));
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
25
64
|
const description = options.description || `${name} - listed by ${getConfig().agentName}`;
|
|
65
|
+
let fileKey;
|
|
66
|
+
// Handle file upload if provided
|
|
67
|
+
if (options.file) {
|
|
68
|
+
const filePath = options.file;
|
|
69
|
+
if (!existsSync(filePath)) {
|
|
70
|
+
console.log(chalk.red(`File not found: ${filePath}`));
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const filename = basename(filePath);
|
|
74
|
+
const contentType = getContentType(filename);
|
|
75
|
+
const uploadSpinner = ora("Getting upload URL...").start();
|
|
76
|
+
// Get presigned upload URL
|
|
77
|
+
const urlResult = await api.getUploadUrl(filename, contentType, "files");
|
|
78
|
+
if (urlResult.error) {
|
|
79
|
+
uploadSpinner.fail(chalk.red(`Failed to get upload URL: ${urlResult.error}`));
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
uploadSpinner.text = "Uploading file to R2...";
|
|
83
|
+
try {
|
|
84
|
+
// Read file and upload to R2
|
|
85
|
+
const fileData = readFileSync(filePath);
|
|
86
|
+
const uploadResponse = await fetch(urlResult.data.uploadUrl, {
|
|
87
|
+
method: "PUT",
|
|
88
|
+
body: fileData,
|
|
89
|
+
headers: {
|
|
90
|
+
"Content-Type": contentType,
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
if (!uploadResponse.ok) {
|
|
94
|
+
uploadSpinner.fail(chalk.red(`Upload failed: ${uploadResponse.status}`));
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
fileKey = urlResult.data.fileKey;
|
|
98
|
+
uploadSpinner.succeed(chalk.green(`File uploaded: ${filename}`));
|
|
99
|
+
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
uploadSpinner.fail(chalk.red(`Upload error: ${err.message}`));
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
26
105
|
const spinner = ora("Listing item...").start();
|
|
27
106
|
const result = await api.listItem({
|
|
28
107
|
name,
|
|
29
108
|
description,
|
|
30
109
|
price,
|
|
110
|
+
quantity,
|
|
31
111
|
category: options.category,
|
|
112
|
+
fileKey,
|
|
32
113
|
});
|
|
33
114
|
if (result.error) {
|
|
34
115
|
spinner.fail(chalk.red(`Failed: ${result.error}`));
|
|
@@ -37,8 +118,11 @@ export const listCommand = new Command("list")
|
|
|
37
118
|
const data = result.data;
|
|
38
119
|
spinner.succeed(chalk.green("Item listed! 📦"));
|
|
39
120
|
console.log("");
|
|
40
|
-
console.log(` ${chalk.cyan(name)} — ${chalk.yellow(`◆${price}`)}`);
|
|
121
|
+
console.log(` ${chalk.cyan(name)} — ${chalk.yellow(`◆${price}`)}${quantity > 1 ? chalk.dim(` ×${quantity}`) : ""}`);
|
|
41
122
|
console.log(chalk.dim(` ID: ${data.itemId}`));
|
|
123
|
+
if (fileKey) {
|
|
124
|
+
console.log(chalk.green(` 📎 File attached`));
|
|
125
|
+
}
|
|
42
126
|
console.log("");
|
|
43
127
|
console.log(chalk.dim("Your item is now visible on the feed!"));
|
|
44
128
|
});
|
package/package.json
CHANGED
package/src/api.ts
CHANGED
|
@@ -98,6 +98,7 @@ export const api = {
|
|
|
98
98
|
name: string;
|
|
99
99
|
description: string;
|
|
100
100
|
price: number;
|
|
101
|
+
quantity?: number;
|
|
101
102
|
thumbnailKey?: string;
|
|
102
103
|
fileKey?: string;
|
|
103
104
|
category?: string;
|
|
@@ -212,4 +213,11 @@ export const api = {
|
|
|
212
213
|
isOpen: boolean;
|
|
213
214
|
}>
|
|
214
215
|
>("/api/map/all"),
|
|
216
|
+
|
|
217
|
+
// Storage
|
|
218
|
+
getUploadUrl: (filename: string, contentType: string, folder: "files" | "thumbnails" = "files") =>
|
|
219
|
+
apiRequest<{ uploadUrl: string; fileKey: string }>(
|
|
220
|
+
`/api/storage/upload-url?filename=${encodeURIComponent(filename)}&contentType=${encodeURIComponent(contentType)}&folder=${folder}`,
|
|
221
|
+
{ authenticated: true }
|
|
222
|
+
),
|
|
215
223
|
};
|
package/src/commands/list.ts
CHANGED
|
@@ -5,18 +5,53 @@
|
|
|
5
5
|
import { Command } from "commander";
|
|
6
6
|
import chalk from "chalk";
|
|
7
7
|
import ora from "ora";
|
|
8
|
+
import { readFileSync, existsSync } from "fs";
|
|
9
|
+
import { basename } from "path";
|
|
8
10
|
import { api } from "../api.js";
|
|
9
11
|
import { isAuthenticated, getConfig } from "../config.js";
|
|
10
12
|
|
|
13
|
+
// Get content type from file extension
|
|
14
|
+
function getContentType(filename: string): string {
|
|
15
|
+
const ext = filename.toLowerCase().split('.').pop();
|
|
16
|
+
const types: Record<string, string> = {
|
|
17
|
+
// Code
|
|
18
|
+
'js': 'application/javascript',
|
|
19
|
+
'ts': 'application/typescript',
|
|
20
|
+
'py': 'text/x-python',
|
|
21
|
+
'json': 'application/json',
|
|
22
|
+
'md': 'text/markdown',
|
|
23
|
+
// Archives
|
|
24
|
+
'zip': 'application/zip',
|
|
25
|
+
'tar': 'application/x-tar',
|
|
26
|
+
'gz': 'application/gzip',
|
|
27
|
+
// Images
|
|
28
|
+
'png': 'image/png',
|
|
29
|
+
'jpg': 'image/jpeg',
|
|
30
|
+
'jpeg': 'image/jpeg',
|
|
31
|
+
'gif': 'image/gif',
|
|
32
|
+
'webp': 'image/webp',
|
|
33
|
+
// Audio
|
|
34
|
+
'mp3': 'audio/mpeg',
|
|
35
|
+
'wav': 'audio/wav',
|
|
36
|
+
'ogg': 'audio/ogg',
|
|
37
|
+
// Text
|
|
38
|
+
'txt': 'text/plain',
|
|
39
|
+
'csv': 'text/csv',
|
|
40
|
+
};
|
|
41
|
+
return types[ext || ''] || 'application/octet-stream';
|
|
42
|
+
}
|
|
43
|
+
|
|
11
44
|
export const listCommand = new Command("list")
|
|
12
45
|
.description("List an item for sale in your storefront")
|
|
13
46
|
.argument("<name>", "Item name")
|
|
14
47
|
.requiredOption("-p, --price <price>", "Price in $KONBINI")
|
|
15
48
|
.option("-d, --description <desc>", "Item description")
|
|
16
49
|
.option("-c, --category <category>", "Category (scripts, art, audio, data, prompts, tools, other)")
|
|
50
|
+
.option("-q, --quantity <qty>", "Number of copies to sell (default: 1)")
|
|
51
|
+
.option("-f, --file <path>", "Path to the file to upload")
|
|
17
52
|
.action(async (name, options) => {
|
|
18
53
|
if (!isAuthenticated()) {
|
|
19
|
-
console.log(chalk.red("❌ Not logged in. Run '
|
|
54
|
+
console.log(chalk.red("❌ Not logged in. Run 'konbini join' first."));
|
|
20
55
|
return;
|
|
21
56
|
}
|
|
22
57
|
|
|
@@ -26,7 +61,61 @@ export const listCommand = new Command("list")
|
|
|
26
61
|
return;
|
|
27
62
|
}
|
|
28
63
|
|
|
64
|
+
const quantity = options.quantity ? parseInt(options.quantity) : 1;
|
|
65
|
+
if (isNaN(quantity) || quantity < 1) {
|
|
66
|
+
console.log(chalk.red("Invalid quantity. Must be at least 1."));
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
29
70
|
const description = options.description || `${name} - listed by ${getConfig().agentName}`;
|
|
71
|
+
let fileKey: string | undefined;
|
|
72
|
+
|
|
73
|
+
// Handle file upload if provided
|
|
74
|
+
if (options.file) {
|
|
75
|
+
const filePath = options.file;
|
|
76
|
+
|
|
77
|
+
if (!existsSync(filePath)) {
|
|
78
|
+
console.log(chalk.red(`File not found: ${filePath}`));
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const filename = basename(filePath);
|
|
83
|
+
const contentType = getContentType(filename);
|
|
84
|
+
|
|
85
|
+
const uploadSpinner = ora("Getting upload URL...").start();
|
|
86
|
+
|
|
87
|
+
// Get presigned upload URL
|
|
88
|
+
const urlResult = await api.getUploadUrl(filename, contentType, "files");
|
|
89
|
+
if (urlResult.error) {
|
|
90
|
+
uploadSpinner.fail(chalk.red(`Failed to get upload URL: ${urlResult.error}`));
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
uploadSpinner.text = "Uploading file to R2...";
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
// Read file and upload to R2
|
|
98
|
+
const fileData = readFileSync(filePath);
|
|
99
|
+
const uploadResponse = await fetch(urlResult.data!.uploadUrl, {
|
|
100
|
+
method: "PUT",
|
|
101
|
+
body: fileData,
|
|
102
|
+
headers: {
|
|
103
|
+
"Content-Type": contentType,
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
if (!uploadResponse.ok) {
|
|
108
|
+
uploadSpinner.fail(chalk.red(`Upload failed: ${uploadResponse.status}`));
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
fileKey = urlResult.data!.fileKey;
|
|
113
|
+
uploadSpinner.succeed(chalk.green(`File uploaded: ${filename}`));
|
|
114
|
+
} catch (err) {
|
|
115
|
+
uploadSpinner.fail(chalk.red(`Upload error: ${(err as Error).message}`));
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
30
119
|
|
|
31
120
|
const spinner = ora("Listing item...").start();
|
|
32
121
|
|
|
@@ -34,7 +123,9 @@ export const listCommand = new Command("list")
|
|
|
34
123
|
name,
|
|
35
124
|
description,
|
|
36
125
|
price,
|
|
126
|
+
quantity,
|
|
37
127
|
category: options.category,
|
|
128
|
+
fileKey,
|
|
38
129
|
});
|
|
39
130
|
|
|
40
131
|
if (result.error) {
|
|
@@ -46,8 +137,11 @@ export const listCommand = new Command("list")
|
|
|
46
137
|
spinner.succeed(chalk.green("Item listed! 📦"));
|
|
47
138
|
|
|
48
139
|
console.log("");
|
|
49
|
-
console.log(` ${chalk.cyan(name)} — ${chalk.yellow(`◆${price}`)}`);
|
|
140
|
+
console.log(` ${chalk.cyan(name)} — ${chalk.yellow(`◆${price}`)}${quantity > 1 ? chalk.dim(` ×${quantity}`) : ""}`);
|
|
50
141
|
console.log(chalk.dim(` ID: ${data.itemId}`));
|
|
142
|
+
if (fileKey) {
|
|
143
|
+
console.log(chalk.green(` 📎 File attached`));
|
|
144
|
+
}
|
|
51
145
|
console.log("");
|
|
52
146
|
console.log(chalk.dim("Your item is now visible on the feed!"));
|
|
53
147
|
});
|