artidrop 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.js +444 -0
- package/package.json +28 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
3
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
4
|
+
}) : x)(function(x) {
|
|
5
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
6
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
// src/index.ts
|
|
10
|
+
import { Command } from "commander";
|
|
11
|
+
|
|
12
|
+
// src/commands/publish.ts
|
|
13
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
14
|
+
import { basename, extname } from "path";
|
|
15
|
+
|
|
16
|
+
// src/lib/config.ts
|
|
17
|
+
import { readFileSync, writeFileSync, mkdirSync, unlinkSync, existsSync } from "fs";
|
|
18
|
+
import { join } from "path";
|
|
19
|
+
import { homedir } from "os";
|
|
20
|
+
var CONFIG_DIR = join(homedir(), ".config", "artidrop");
|
|
21
|
+
var CONFIG_FILE = join(CONFIG_DIR, "config.json");
|
|
22
|
+
function getConfig() {
|
|
23
|
+
try {
|
|
24
|
+
const data = readFileSync(CONFIG_FILE, "utf-8");
|
|
25
|
+
return JSON.parse(data);
|
|
26
|
+
} catch {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function saveConfig(config) {
|
|
31
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
32
|
+
writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
33
|
+
}
|
|
34
|
+
function deleteConfig() {
|
|
35
|
+
try {
|
|
36
|
+
unlinkSync(CONFIG_FILE);
|
|
37
|
+
} catch {
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function configExists() {
|
|
41
|
+
return existsSync(CONFIG_FILE);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// src/lib/auth.ts
|
|
45
|
+
var DEFAULT_API_URL = "https://api.artidrop.app";
|
|
46
|
+
function getApiKey() {
|
|
47
|
+
const envKey = process.env.ARTIDROP_API_KEY;
|
|
48
|
+
if (envKey) return envKey;
|
|
49
|
+
const config = getConfig();
|
|
50
|
+
return config?.api_key ?? null;
|
|
51
|
+
}
|
|
52
|
+
function getApiUrl() {
|
|
53
|
+
const envUrl = process.env.ARTIDROP_API_URL;
|
|
54
|
+
if (envUrl) return envUrl;
|
|
55
|
+
const config = getConfig();
|
|
56
|
+
return config?.api_url ?? DEFAULT_API_URL;
|
|
57
|
+
}
|
|
58
|
+
function requireApiKey() {
|
|
59
|
+
const key = getApiKey();
|
|
60
|
+
if (!key) {
|
|
61
|
+
process.stderr.write(
|
|
62
|
+
"Error: Not authenticated. Run `artidrop login` or set ARTIDROP_API_KEY.\n"
|
|
63
|
+
);
|
|
64
|
+
process.exit(3);
|
|
65
|
+
}
|
|
66
|
+
return key;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// src/lib/api.ts
|
|
70
|
+
async function apiRequest(path, options = {}) {
|
|
71
|
+
const { method = "GET", body, apiKey } = options;
|
|
72
|
+
const url = `${getApiUrl()}/v1${path}`;
|
|
73
|
+
const headers = {
|
|
74
|
+
"Content-Type": "application/json"
|
|
75
|
+
};
|
|
76
|
+
if (apiKey) {
|
|
77
|
+
headers["Authorization"] = `Bearer ${apiKey}`;
|
|
78
|
+
}
|
|
79
|
+
const res = await fetch(url, {
|
|
80
|
+
method,
|
|
81
|
+
headers,
|
|
82
|
+
body: body ? JSON.stringify(body) : void 0
|
|
83
|
+
});
|
|
84
|
+
if (res.status === 204) {
|
|
85
|
+
return void 0;
|
|
86
|
+
}
|
|
87
|
+
const data = await res.json();
|
|
88
|
+
if (!res.ok) {
|
|
89
|
+
const error = data.error;
|
|
90
|
+
const code = error?.code ?? "UNKNOWN";
|
|
91
|
+
const message = error?.message ?? `HTTP ${res.status}`;
|
|
92
|
+
process.stderr.write(`Error: ${message}
|
|
93
|
+
`);
|
|
94
|
+
if (res.status === 401) process.exit(3);
|
|
95
|
+
if (res.status === 429) process.exit(4);
|
|
96
|
+
if (res.status === 400) process.exit(2);
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
|
99
|
+
return data;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// src/commands/publish.ts
|
|
103
|
+
var FORMAT_MAP = {
|
|
104
|
+
".html": "html",
|
|
105
|
+
".htm": "html",
|
|
106
|
+
".md": "markdown",
|
|
107
|
+
".markdown": "markdown"
|
|
108
|
+
};
|
|
109
|
+
async function publish(pathOrStdin, options) {
|
|
110
|
+
const apiKey = requireApiKey();
|
|
111
|
+
let content;
|
|
112
|
+
let inferredFormat;
|
|
113
|
+
if (pathOrStdin === "-" || !process.stdin.isTTY) {
|
|
114
|
+
content = await readStdin();
|
|
115
|
+
if (!options.format) {
|
|
116
|
+
process.stderr.write("Error: --format is required when reading from stdin.\n");
|
|
117
|
+
process.exit(2);
|
|
118
|
+
}
|
|
119
|
+
} else {
|
|
120
|
+
try {
|
|
121
|
+
content = readFileSync2(pathOrStdin, "utf-8");
|
|
122
|
+
} catch (err) {
|
|
123
|
+
process.stderr.write(`Error: Cannot read file "${pathOrStdin}".
|
|
124
|
+
`);
|
|
125
|
+
process.exit(2);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
const ext = extname(pathOrStdin).toLowerCase();
|
|
129
|
+
inferredFormat = FORMAT_MAP[ext];
|
|
130
|
+
if (!inferredFormat && !options.format) {
|
|
131
|
+
process.stderr.write(
|
|
132
|
+
"Error: Cannot infer format. Use --format html or --format markdown.\n"
|
|
133
|
+
);
|
|
134
|
+
process.exit(2);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
const format = options.format ?? inferredFormat;
|
|
138
|
+
if (format !== "html" && format !== "markdown") {
|
|
139
|
+
process.stderr.write('Error: Format must be "html" or "markdown".\n');
|
|
140
|
+
process.exit(2);
|
|
141
|
+
}
|
|
142
|
+
const title = options.title ?? (pathOrStdin !== "-" ? basename(pathOrStdin, extname(pathOrStdin)) : "Untitled");
|
|
143
|
+
if (options.update) {
|
|
144
|
+
const result = await apiRequest(
|
|
145
|
+
`/artifacts/${options.update}`,
|
|
146
|
+
{
|
|
147
|
+
method: "PUT",
|
|
148
|
+
body: { content, title },
|
|
149
|
+
apiKey
|
|
150
|
+
}
|
|
151
|
+
);
|
|
152
|
+
outputResult(result, options);
|
|
153
|
+
} else {
|
|
154
|
+
const result = await apiRequest("/artifacts", {
|
|
155
|
+
method: "POST",
|
|
156
|
+
body: {
|
|
157
|
+
content,
|
|
158
|
+
format,
|
|
159
|
+
title,
|
|
160
|
+
visibility: options.visibility ?? "public"
|
|
161
|
+
},
|
|
162
|
+
apiKey
|
|
163
|
+
});
|
|
164
|
+
outputResult(result, options);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
function outputResult(result, options) {
|
|
168
|
+
if (options.json) {
|
|
169
|
+
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
170
|
+
} else {
|
|
171
|
+
process.stdout.write(result.url + "\n");
|
|
172
|
+
}
|
|
173
|
+
if (options.copy) {
|
|
174
|
+
const { execSync } = __require("child_process");
|
|
175
|
+
try {
|
|
176
|
+
execSync(`echo -n "${result.url}" | pbcopy`, { stdio: "ignore" });
|
|
177
|
+
process.stderr.write("URL copied to clipboard.\n");
|
|
178
|
+
} catch {
|
|
179
|
+
try {
|
|
180
|
+
execSync(`echo -n "${result.url}" | xclip -selection clipboard`, { stdio: "ignore" });
|
|
181
|
+
process.stderr.write("URL copied to clipboard.\n");
|
|
182
|
+
} catch {
|
|
183
|
+
process.stderr.write("Could not copy to clipboard.\n");
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
if (options.open) {
|
|
188
|
+
const { execSync } = __require("child_process");
|
|
189
|
+
try {
|
|
190
|
+
execSync(`open "${result.url}"`, { stdio: "ignore" });
|
|
191
|
+
} catch {
|
|
192
|
+
try {
|
|
193
|
+
execSync(`xdg-open "${result.url}"`, { stdio: "ignore" });
|
|
194
|
+
} catch {
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
function readStdin() {
|
|
200
|
+
return new Promise((resolve, reject) => {
|
|
201
|
+
let data = "";
|
|
202
|
+
process.stdin.setEncoding("utf-8");
|
|
203
|
+
process.stdin.on("data", (chunk) => data += chunk);
|
|
204
|
+
process.stdin.on("end", () => resolve(data));
|
|
205
|
+
process.stdin.on("error", reject);
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// src/commands/list.ts
|
|
210
|
+
async function list(options) {
|
|
211
|
+
const apiKey = requireApiKey();
|
|
212
|
+
const params = new URLSearchParams();
|
|
213
|
+
if (options.limit) params.set("limit", options.limit);
|
|
214
|
+
if (options.offset) params.set("offset", options.offset);
|
|
215
|
+
if (options.format) params.set("format", options.format);
|
|
216
|
+
const query = params.toString();
|
|
217
|
+
const result = await apiRequest(
|
|
218
|
+
`/artifacts${query ? `?${query}` : ""}`,
|
|
219
|
+
{ apiKey }
|
|
220
|
+
);
|
|
221
|
+
if (options.json) {
|
|
222
|
+
process.stdout.write(JSON.stringify(result.items, null, 2) + "\n");
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
if (result.items.length === 0) {
|
|
226
|
+
process.stderr.write("No artifacts found.\n");
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
const header = "ID TITLE FORMAT VERSION CREATED";
|
|
230
|
+
process.stdout.write(header + "\n");
|
|
231
|
+
for (const item of result.items) {
|
|
232
|
+
const id = item.id.padEnd(16);
|
|
233
|
+
const title = (item.title ?? "Untitled").slice(0, 27).padEnd(28);
|
|
234
|
+
const format = item.format.padEnd(10);
|
|
235
|
+
const version = String(item.version).padEnd(9);
|
|
236
|
+
const created = item.created_at.slice(0, 10);
|
|
237
|
+
process.stdout.write(`${id}${title}${format}${version}${created}
|
|
238
|
+
`);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// src/commands/get.ts
|
|
243
|
+
async function get(artifactId, options) {
|
|
244
|
+
const apiKey = requireApiKey();
|
|
245
|
+
const result = await apiRequest(`/artifacts/${artifactId}`, {
|
|
246
|
+
apiKey
|
|
247
|
+
});
|
|
248
|
+
if (options.json) {
|
|
249
|
+
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
const size = formatBytes(result.size_bytes);
|
|
253
|
+
process.stdout.write(
|
|
254
|
+
`Title: ${result.title}
|
|
255
|
+
URL: ${result.url}
|
|
256
|
+
Format: ${result.format}
|
|
257
|
+
Version: ${result.version}
|
|
258
|
+
Visibility: ${result.visibility}
|
|
259
|
+
Size: ${size}
|
|
260
|
+
Created: ${result.created_at}
|
|
261
|
+
Updated: ${result.updated_at}
|
|
262
|
+
`
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
function formatBytes(bytes) {
|
|
266
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
267
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
268
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// src/commands/delete.ts
|
|
272
|
+
import { createInterface } from "readline";
|
|
273
|
+
async function del(artifactId, options) {
|
|
274
|
+
const apiKey = requireApiKey();
|
|
275
|
+
if (!options.yes) {
|
|
276
|
+
const confirmed = await confirm(`Are you sure you want to delete "${artifactId}"? (y/N) `);
|
|
277
|
+
if (!confirmed) {
|
|
278
|
+
process.stderr.write("Cancelled.\n");
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
await apiRequest(`/artifacts/${artifactId}`, {
|
|
283
|
+
method: "DELETE",
|
|
284
|
+
apiKey
|
|
285
|
+
});
|
|
286
|
+
process.stderr.write(`Deleted ${artifactId}
|
|
287
|
+
`);
|
|
288
|
+
}
|
|
289
|
+
function confirm(prompt) {
|
|
290
|
+
return new Promise((resolve) => {
|
|
291
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
292
|
+
rl.question(prompt, (answer) => {
|
|
293
|
+
rl.close();
|
|
294
|
+
resolve(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
|
|
295
|
+
});
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// src/commands/versions.ts
|
|
300
|
+
async function versions(artifactId) {
|
|
301
|
+
const apiKey = requireApiKey();
|
|
302
|
+
const result = await apiRequest(
|
|
303
|
+
`/artifacts/${artifactId}/versions`,
|
|
304
|
+
{ apiKey }
|
|
305
|
+
);
|
|
306
|
+
if (result.items.length === 0) {
|
|
307
|
+
process.stderr.write("No versions found.\n");
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
const header = "VERSION SIZE CREATED";
|
|
311
|
+
process.stdout.write(header + "\n");
|
|
312
|
+
for (const v of result.items) {
|
|
313
|
+
const version = String(v.version).padEnd(9);
|
|
314
|
+
const size = formatBytes2(v.size_bytes).padEnd(9);
|
|
315
|
+
process.stdout.write(`${version}${size}${v.created_at}
|
|
316
|
+
`);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
function formatBytes2(bytes) {
|
|
320
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
321
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
322
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// src/commands/login.ts
|
|
326
|
+
import { createServer } from "http";
|
|
327
|
+
import { createInterface as createInterface2 } from "readline";
|
|
328
|
+
async function login() {
|
|
329
|
+
const apiUrl = getApiUrl();
|
|
330
|
+
const appUrl = apiUrl.replace("api.", "").replace("/v1", "");
|
|
331
|
+
if (process.stdin.isTTY) {
|
|
332
|
+
await browserLogin(appUrl);
|
|
333
|
+
} else {
|
|
334
|
+
await manualLogin(appUrl);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
async function browserLogin(appUrl) {
|
|
338
|
+
return new Promise((resolve, reject) => {
|
|
339
|
+
const server = createServer((req, res) => {
|
|
340
|
+
const url = new URL(req.url, `http://localhost`);
|
|
341
|
+
if (url.pathname === "/callback") {
|
|
342
|
+
const key = url.searchParams.get("key");
|
|
343
|
+
if (key) {
|
|
344
|
+
saveConfig({ api_key: key });
|
|
345
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
346
|
+
res.end("<html><body><h2>Success! You can close this tab.</h2></body></html>");
|
|
347
|
+
process.stderr.write("Authenticated successfully.\n");
|
|
348
|
+
server.close();
|
|
349
|
+
resolve();
|
|
350
|
+
} else {
|
|
351
|
+
res.writeHead(400, { "Content-Type": "text/plain" });
|
|
352
|
+
res.end("Missing key parameter.");
|
|
353
|
+
server.close();
|
|
354
|
+
reject(new Error("No key received."));
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
});
|
|
358
|
+
server.listen(0, "127.0.0.1", () => {
|
|
359
|
+
const address = server.address();
|
|
360
|
+
if (!address || typeof address === "string") {
|
|
361
|
+
server.close();
|
|
362
|
+
reject(new Error("Could not start local server."));
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
const port = address.port;
|
|
366
|
+
const loginUrl = `${appUrl}/v1/auth/cli-auth?port=${port}`;
|
|
367
|
+
process.stderr.write(`Opening browser to: ${loginUrl}
|
|
368
|
+
`);
|
|
369
|
+
process.stderr.write("Waiting for authentication...\n");
|
|
370
|
+
import("child_process").then(({ execSync }) => {
|
|
371
|
+
try {
|
|
372
|
+
execSync(`open "${loginUrl}"`, { stdio: "ignore" });
|
|
373
|
+
} catch {
|
|
374
|
+
try {
|
|
375
|
+
execSync(`xdg-open "${loginUrl}"`, { stdio: "ignore" });
|
|
376
|
+
} catch {
|
|
377
|
+
process.stderr.write(`Could not open browser. Visit this URL manually:
|
|
378
|
+
${loginUrl}
|
|
379
|
+
`);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
});
|
|
383
|
+
});
|
|
384
|
+
setTimeout(() => {
|
|
385
|
+
server.close();
|
|
386
|
+
reject(new Error("Login timed out. Try `artidrop login` again."));
|
|
387
|
+
}, 5 * 60 * 1e3);
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
async function manualLogin(appUrl) {
|
|
391
|
+
const loginUrl = `${appUrl}/v1/auth/cli-auth?manual=true`;
|
|
392
|
+
process.stderr.write(`Open this URL in your browser: ${loginUrl}
|
|
393
|
+
`);
|
|
394
|
+
process.stderr.write("Then paste the API key here:\n");
|
|
395
|
+
const rl = createInterface2({ input: process.stdin, output: process.stderr });
|
|
396
|
+
const key = await new Promise((resolve) => {
|
|
397
|
+
rl.question("> ", (answer) => {
|
|
398
|
+
rl.close();
|
|
399
|
+
resolve(answer.trim());
|
|
400
|
+
});
|
|
401
|
+
});
|
|
402
|
+
if (!key.startsWith("sk-")) {
|
|
403
|
+
process.stderr.write("Error: Invalid API key format. Keys start with 'sk-'.\n");
|
|
404
|
+
process.exit(2);
|
|
405
|
+
}
|
|
406
|
+
saveConfig({ api_key: key });
|
|
407
|
+
process.stderr.write("Authenticated successfully.\n");
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// src/commands/logout.ts
|
|
411
|
+
function logout() {
|
|
412
|
+
if (!configExists()) {
|
|
413
|
+
process.stderr.write("Not logged in.\n");
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
deleteConfig();
|
|
417
|
+
process.stderr.write("Logged out.\n");
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// src/commands/whoami.ts
|
|
421
|
+
async function whoami() {
|
|
422
|
+
const apiKey = requireApiKey();
|
|
423
|
+
const result = await apiRequest("/auth/me", { apiKey });
|
|
424
|
+
if (!result.user) {
|
|
425
|
+
process.stderr.write("Not authenticated.\n");
|
|
426
|
+
process.exit(3);
|
|
427
|
+
}
|
|
428
|
+
const name = result.user.displayName ?? result.user.username;
|
|
429
|
+
process.stdout.write(`Authenticated as ${name} (${result.user.email})
|
|
430
|
+
`);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// src/index.ts
|
|
434
|
+
var program = new Command();
|
|
435
|
+
program.name("artidrop").description("Publish and share AI artifacts instantly").version("0.1.0");
|
|
436
|
+
program.command("publish").description("Publish a file or stdin content").argument("<path>", 'File path to publish, or "-" for stdin').option("-t, --title <title>", "Artifact title").option("-f, --format <format>", "Content format: html or markdown").option("-v, --visibility <visibility>", "Visibility: public or unlisted", "public").option("-u, --update <id>", "Update existing artifact (creates new version)").option("-o, --open", "Open URL in browser after publishing").option("--json", "Output full JSON response").option("-c, --copy", "Copy URL to clipboard").action(publish);
|
|
437
|
+
program.command("list").description("List your artifacts").option("-l, --limit <n>", "Number of items", "20").option("--offset <n>", "Pagination offset", "0").option("-f, --format <format>", "Filter by format: html or markdown").option("--json", "Output as JSON").action(list);
|
|
438
|
+
program.command("get").description("Show artifact details").argument("<id>", "Artifact ID").option("--json", "Output as JSON").action(get);
|
|
439
|
+
program.command("delete").description("Delete an artifact").argument("<id>", "Artifact ID").option("-y, --yes", "Skip confirmation").action(del);
|
|
440
|
+
program.command("versions").description("Show version history").argument("<id>", "Artifact ID").action(versions);
|
|
441
|
+
program.command("login").description("Sign in to artidrop").action(login);
|
|
442
|
+
program.command("logout").description("Sign out of artidrop").action(logout);
|
|
443
|
+
program.command("whoami").description("Show current user").action(whoami);
|
|
444
|
+
program.parse();
|
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "artidrop",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"bin": {
|
|
6
|
+
"artidrop": "./dist/index.js"
|
|
7
|
+
},
|
|
8
|
+
"files": [
|
|
9
|
+
"dist"
|
|
10
|
+
],
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "tsup",
|
|
13
|
+
"dev": "tsx src/index.ts",
|
|
14
|
+
"lint": "eslint src/",
|
|
15
|
+
"typecheck": "tsc --noEmit"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"chalk": "^5.6.2",
|
|
19
|
+
"commander": "^14.0.3",
|
|
20
|
+
"open-cli": "^8.0.0"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@types/node": "^25.5.0",
|
|
24
|
+
"tsup": "^8.5.1",
|
|
25
|
+
"tsx": "^4.21.0",
|
|
26
|
+
"typescript": "^5.9.3"
|
|
27
|
+
}
|
|
28
|
+
}
|