@shiori-sh/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.
Files changed (3) hide show
  1. package/README.md +63 -0
  2. package/dist/index.js +594 -0
  3. package/package.json +40 -0
package/README.md ADDED
@@ -0,0 +1,63 @@
1
+ # @shiori-sh/cli
2
+
3
+ Command-line interface for [Shiori](https://www.shiori.sh) — save, organize, and manage your links from the terminal.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install -g @shiori-sh/cli
9
+ ```
10
+
11
+ Or use without installing:
12
+
13
+ ```bash
14
+ npx @shiori-sh/cli list
15
+ ```
16
+
17
+ ## Authenticate
18
+
19
+ ```bash
20
+ shiori auth
21
+ ```
22
+
23
+ This saves your API key to `~/.shiori/config.json`. Generate an API key at [shiori.sh/home](https://www.shiori.sh/home) → Settings.
24
+
25
+ You can also set `SHIORI_API_KEY` as an environment variable.
26
+
27
+ ## Commands
28
+
29
+ ```bash
30
+ shiori list # List recent links
31
+ shiori list --read unread # List unread links
32
+ shiori list --limit 5 --sort oldest # Paginate and sort
33
+
34
+ shiori get <id> # Get a link with full content
35
+ shiori save <url> # Save a new link
36
+ shiori save <url> --title "..." # Save with custom title
37
+
38
+ shiori update <id> --read # Mark as read
39
+ shiori update <id> --unread # Mark as unread
40
+ shiori update <id> --title "..." # Update title
41
+ shiori update <id> --restore # Restore from trash
42
+
43
+ shiori delete <id> # Move to trash
44
+ shiori trash # List trashed links
45
+ shiori trash --empty # Permanently delete all trash
46
+
47
+ shiori me # Show account info
48
+ shiori auth --status # Check auth status
49
+ shiori auth --logout # Remove stored credentials
50
+ ```
51
+
52
+ Add `--json` to any command for machine-readable output.
53
+
54
+ ## Environment Variables
55
+
56
+ | Variable | Description |
57
+ | ----------------- | -------------------------------------------------- |
58
+ | `SHIORI_API_KEY` | API key (overrides config file) |
59
+ | `SHIORI_BASE_URL` | Custom base URL (default: `https://www.shiori.sh`) |
60
+
61
+ ## License
62
+
63
+ MIT
package/dist/index.js ADDED
@@ -0,0 +1,594 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/commands/auth.ts
4
+ import { createInterface } from "readline";
5
+
6
+ // src/config.ts
7
+ import { mkdirSync, readFileSync, writeFileSync } from "fs";
8
+ import { homedir } from "os";
9
+ import { join } from "path";
10
+ var CONFIG_DIR = join(homedir(), ".shiori");
11
+ var CONFIG_FILE = join(CONFIG_DIR, "config.json");
12
+ function readConfig() {
13
+ try {
14
+ return JSON.parse(readFileSync(CONFIG_FILE, "utf-8"));
15
+ } catch {
16
+ return {};
17
+ }
18
+ }
19
+ function writeConfig(config) {
20
+ mkdirSync(CONFIG_DIR, { recursive: true });
21
+ writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2) + "\n", {
22
+ mode: 384
23
+ });
24
+ }
25
+ function getApiKey() {
26
+ const envKey = process.env.SHIORI_API_KEY;
27
+ if (envKey) return envKey;
28
+ const config = readConfig();
29
+ if (config.api_key) return config.api_key;
30
+ console.error(
31
+ "Not authenticated. Run `shiori auth` to set up your API key."
32
+ );
33
+ console.error("Or set the SHIORI_API_KEY environment variable.");
34
+ process.exit(1);
35
+ }
36
+ function getBaseUrl() {
37
+ return process.env.SHIORI_BASE_URL || readConfig().base_url || "https://www.shiori.sh";
38
+ }
39
+
40
+ // src/commands/auth.ts
41
+ function prompt(question) {
42
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
43
+ return new Promise((resolve) => {
44
+ rl.question(question, (answer) => {
45
+ rl.close();
46
+ resolve(answer);
47
+ });
48
+ });
49
+ }
50
+ function promptSecret(question) {
51
+ return new Promise((resolve) => {
52
+ process.stdout.write(question);
53
+ const { stdin } = process;
54
+ stdin.setRawMode(true);
55
+ stdin.resume();
56
+ stdin.setEncoding("utf8");
57
+ let input = "";
58
+ let displayed = 0;
59
+ const MAX_STARS = 10;
60
+ const onData = (char) => {
61
+ if (char === "\r" || char === "\n") {
62
+ stdin.setRawMode(false);
63
+ stdin.pause();
64
+ stdin.removeListener("data", onData);
65
+ process.stdout.write("\n");
66
+ resolve(input);
67
+ } else if (char === "") {
68
+ stdin.setRawMode(false);
69
+ stdin.pause();
70
+ process.stdout.write("\n");
71
+ process.exit(1);
72
+ } else if (char === "\x7F" || char === "\b") {
73
+ if (input.length > 0) {
74
+ input = input.slice(0, -1);
75
+ if (displayed > 0) {
76
+ displayed--;
77
+ process.stdout.write("\b \b");
78
+ }
79
+ }
80
+ } else {
81
+ input += char;
82
+ const toShow = Math.min(char.length, MAX_STARS - displayed);
83
+ if (toShow > 0) {
84
+ process.stdout.write("*".repeat(toShow));
85
+ displayed += toShow;
86
+ }
87
+ }
88
+ };
89
+ stdin.on("data", onData);
90
+ });
91
+ }
92
+ async function run(args) {
93
+ if (args.includes("--help") || args.includes("-h")) {
94
+ console.log(`shiori auth - Authenticate with your Shiori API key
95
+
96
+ Usage: shiori auth [options]
97
+
98
+ Options:
99
+ --status Show current authentication status
100
+ --logout Remove stored credentials
101
+ --help, -h Show this help`);
102
+ return;
103
+ }
104
+ if (args.includes("--status")) {
105
+ const config2 = readConfig();
106
+ if (config2.api_key) {
107
+ const prefix = config2.api_key.slice(0, 8) + "...";
108
+ console.log(`Authenticated with key: ${prefix}`);
109
+ if (config2.base_url) console.log(`Base URL: ${config2.base_url}`);
110
+ } else if (process.env.SHIORI_API_KEY) {
111
+ console.log("Authenticated via SHIORI_API_KEY environment variable.");
112
+ } else {
113
+ console.log("Not authenticated. Run `shiori auth` to set up.");
114
+ }
115
+ return;
116
+ }
117
+ if (args.includes("--logout")) {
118
+ writeConfig({});
119
+ console.log("Credentials removed from ~/.shiori/config.json");
120
+ return;
121
+ }
122
+ const config = readConfig();
123
+ if (config.api_key) {
124
+ const answer = await prompt("You are already authenticated. Replace existing key? (y/N) ");
125
+ if (answer.trim().toLowerCase() !== "y") return;
126
+ }
127
+ console.log("\nTo authenticate, you need an API key from Shiori.\n");
128
+ console.log(" 1. Open https://www.shiori.sh/home -> Settings");
129
+ console.log(" 2. Create and copy your API key");
130
+ console.log(" 3. Paste it below\n");
131
+ const key = await promptSecret("API key: ");
132
+ const trimmed = key.trim();
133
+ if (!trimmed.startsWith("shk_")) {
134
+ console.error("\nInvalid API key format. Keys start with 'shk_'.");
135
+ process.exit(1);
136
+ }
137
+ process.stdout.write("\nVerifying... ");
138
+ const baseUrl = getBaseUrl();
139
+ const res = await fetch(`${baseUrl}/api/user/me`, {
140
+ headers: { Authorization: `Bearer ${trimmed}` }
141
+ });
142
+ if (!res.ok) {
143
+ console.error("failed.\nAuthentication failed. Check your API key and try again.");
144
+ process.exit(1);
145
+ }
146
+ const data = await res.json();
147
+ writeConfig({ ...readConfig(), api_key: trimmed });
148
+ console.log(`done.
149
+
150
+ Authenticated as ${data.user?.full_name || data.user?.id}`);
151
+ console.log("API key saved to ~/.shiori/config.json");
152
+ }
153
+
154
+ // src/api.ts
155
+ async function api(method, path, body) {
156
+ const baseUrl = getBaseUrl();
157
+ const apiKey = getApiKey();
158
+ const res = await fetch(`${baseUrl}${path}`, {
159
+ method,
160
+ headers: {
161
+ Authorization: `Bearer ${apiKey}`,
162
+ "Content-Type": "application/json"
163
+ },
164
+ body: body ? JSON.stringify(body) : void 0
165
+ });
166
+ const data = await res.json();
167
+ if (!res.ok || data.success === false) {
168
+ const message = data.error || `Request failed (HTTP ${res.status})`;
169
+ console.error(`Error: ${message}`);
170
+ process.exit(1);
171
+ }
172
+ return { status: res.status, data };
173
+ }
174
+
175
+ // src/format.ts
176
+ function formatDate(iso) {
177
+ if (!iso) return "--";
178
+ return new Date(iso).toLocaleDateString("en-US", {
179
+ month: "short",
180
+ day: "numeric",
181
+ year: "numeric",
182
+ hour: "numeric",
183
+ minute: "2-digit"
184
+ });
185
+ }
186
+ function truncate(str, max) {
187
+ if (str.length <= max) return str;
188
+ return str.slice(0, max - 1) + "\u2026";
189
+ }
190
+ function getFlag(args, name, fallback) {
191
+ const idx = args.indexOf(name);
192
+ if (idx === -1 || idx + 1 >= args.length) return fallback;
193
+ return args[idx + 1];
194
+ }
195
+ function hasFlag(args, ...names) {
196
+ return names.some((n) => args.includes(n));
197
+ }
198
+ function getPositional(args) {
199
+ for (let i = 0; i < args.length; i++) {
200
+ if (args[i].startsWith("--")) {
201
+ if (i + 1 < args.length && !args[i + 1].startsWith("--") && ["--limit", "--offset", "--sort", "--read", "--title", "--summary"].includes(args[i])) {
202
+ i++;
203
+ }
204
+ continue;
205
+ }
206
+ return args[i];
207
+ }
208
+ return void 0;
209
+ }
210
+
211
+ // src/commands/delete.ts
212
+ async function run2(args) {
213
+ if (hasFlag(args, "--help", "-h")) {
214
+ console.log(`shiori delete - Delete a link (move to trash)
215
+
216
+ Usage: shiori delete <id> [options]
217
+
218
+ Options:
219
+ --json Output raw JSON
220
+ --help, -h Show this help`);
221
+ return;
222
+ }
223
+ const id = getPositional(args);
224
+ if (!id) {
225
+ console.error("Usage: shiori delete <id>");
226
+ process.exit(1);
227
+ }
228
+ const { data } = await api("DELETE", `/api/links/${id}`);
229
+ if (hasFlag(args, "--json")) {
230
+ console.log(JSON.stringify(data, null, 2));
231
+ return;
232
+ }
233
+ console.log(data.message || "Deleted.");
234
+ }
235
+
236
+ // src/commands/get.ts
237
+ async function run3(args) {
238
+ if (hasFlag(args, "--help", "-h")) {
239
+ console.log(`shiori get - Get a link by ID (includes content)
240
+
241
+ Usage: shiori get <id> [options]
242
+
243
+ Options:
244
+ --json Output raw JSON
245
+ --help, -h Show this help`);
246
+ return;
247
+ }
248
+ const id = getPositional(args);
249
+ if (!id) {
250
+ console.error("Usage: shiori get <link-id>");
251
+ process.exit(1);
252
+ }
253
+ const { data } = await api("GET", `/api/links/${id}`);
254
+ if (hasFlag(args, "--json")) {
255
+ console.log(JSON.stringify(data, null, 2));
256
+ return;
257
+ }
258
+ const link = data.link;
259
+ console.log();
260
+ console.log(` ID: ${link.id}`);
261
+ console.log(` Title: ${link.title || "(untitled)"}`);
262
+ console.log(` URL: ${link.url}`);
263
+ console.log(` Domain: ${link.domain || "--"}`);
264
+ console.log(` Status: ${link.status}`);
265
+ console.log(` Source: ${link.source || "--"}`);
266
+ console.log(` Author: ${link.author || "--"}`);
267
+ console.log(` Published: ${formatDate(link.publication_date)}`);
268
+ console.log(` Saved: ${formatDate(link.created_at)}`);
269
+ console.log(
270
+ ` Read: ${link.read_at ? formatDate(link.read_at) : "unread"}`
271
+ );
272
+ console.log(` Summary: ${link.summary || "--"}`);
273
+ if (link.content) {
274
+ console.log(`
275
+ --- Content (${link.content.length} chars) ---`);
276
+ console.log(truncate(link.content, 3e3));
277
+ } else {
278
+ console.log("\n (no content)");
279
+ }
280
+ console.log();
281
+ }
282
+
283
+ // src/commands/list.ts
284
+ async function run4(args) {
285
+ if (hasFlag(args, "--help", "-h")) {
286
+ console.log(`shiori list - List saved links
287
+
288
+ Usage: shiori list [options]
289
+
290
+ Options:
291
+ --limit <n> Number of links (1-100, default: 25)
292
+ --offset <n> Pagination offset (default: 0)
293
+ --sort <newest|oldest> Sort order (default: newest)
294
+ --read <all|read|unread> Filter by read status (default: all)
295
+ --json Output raw JSON
296
+ --help, -h Show this help`);
297
+ return;
298
+ }
299
+ const limit = getFlag(args, "--limit", "25");
300
+ const offset = getFlag(args, "--offset", "0");
301
+ const sort = getFlag(args, "--sort", "newest");
302
+ const read = getFlag(args, "--read", "all");
303
+ const params = new URLSearchParams({
304
+ limit,
305
+ offset,
306
+ sort,
307
+ read
308
+ });
309
+ const { data } = await api("GET", `/api/links?${params}`);
310
+ if (hasFlag(args, "--json")) {
311
+ console.log(JSON.stringify(data, null, 2));
312
+ return;
313
+ }
314
+ console.log(`
315
+ Links: ${data.links.length} of ${data.total} total
316
+ `);
317
+ for (const [i, link] of data.links.entries()) {
318
+ const readStatus = link.read_at ? "read" : "unread";
319
+ const title = truncate(link.title || "(untitled)", 60);
320
+ console.log(
321
+ ` ${String(Number(offset) + i + 1).padStart(3)}. ${title} ${link.domain || ""} ${formatDate(link.created_at)} ${readStatus}`
322
+ );
323
+ }
324
+ if (data.links.length === 0) {
325
+ console.log(" No links found.");
326
+ }
327
+ console.log();
328
+ }
329
+
330
+ // src/commands/me.ts
331
+ async function run5(args) {
332
+ if (hasFlag(args, "--help", "-h")) {
333
+ console.log(`shiori me - Show current user info
334
+
335
+ Usage: shiori me [options]
336
+
337
+ Options:
338
+ --json Output raw JSON
339
+ --help, -h Show this help`);
340
+ return;
341
+ }
342
+ const { data } = await api("GET", "/api/user/me");
343
+ if (hasFlag(args, "--json")) {
344
+ console.log(JSON.stringify(data, null, 2));
345
+ return;
346
+ }
347
+ const user = data.user;
348
+ console.log(`
349
+ Name: ${user.full_name || "--"}`);
350
+ const plan = user.subscription?.plan === "subscription" ? "Pro" : user.subscription?.plan || "Free";
351
+ console.log(` Plan: ${plan}`);
352
+ console.log(` Member since: ${new Date(user.created_at).toLocaleDateString("en-US", { month: "long", year: "numeric" })}`);
353
+ console.log();
354
+ }
355
+
356
+ // src/commands/save.ts
357
+ async function run6(args) {
358
+ if (hasFlag(args, "--help", "-h")) {
359
+ console.log(`shiori save - Save a new link
360
+
361
+ Usage: shiori save <url> [options]
362
+
363
+ Options:
364
+ --title <title> Set a custom title
365
+ --read Mark as read immediately
366
+ --json Output raw JSON
367
+ --help, -h Show this help`);
368
+ return;
369
+ }
370
+ const url = getPositional(args);
371
+ if (!url) {
372
+ console.error("Usage: shiori save <url> [--title <title>] [--read]");
373
+ process.exit(1);
374
+ }
375
+ const body = { url };
376
+ const title = getFlag(args, "--title");
377
+ if (title) body.title = title;
378
+ if (hasFlag(args, "--read")) body.read = true;
379
+ const { data } = await api("POST", "/api/links", body);
380
+ if (hasFlag(args, "--json")) {
381
+ console.log(JSON.stringify(data, null, 2));
382
+ return;
383
+ }
384
+ if (data.duplicate) {
385
+ console.log(`Link already saved (bumped to top): ${data.linkId}`);
386
+ } else {
387
+ console.log(`Saved: ${data.linkId}`);
388
+ }
389
+ }
390
+
391
+ // src/commands/trash.ts
392
+ async function run7(args) {
393
+ if (hasFlag(args, "--help", "-h")) {
394
+ console.log(`shiori trash - List or empty the trash
395
+
396
+ Usage: shiori trash [options]
397
+
398
+ Options:
399
+ --limit <n> Number of links (1-100, default: 25)
400
+ --offset <n> Pagination offset (default: 0)
401
+ --empty Permanently delete all trashed links
402
+ --json Output raw JSON
403
+ --help, -h Show this help`);
404
+ return;
405
+ }
406
+ if (hasFlag(args, "--empty")) {
407
+ const { data: data2 } = await api("DELETE", "/api/links");
408
+ if (hasFlag(args, "--json")) {
409
+ console.log(JSON.stringify(data2, null, 2));
410
+ return;
411
+ }
412
+ console.log(`Permanently deleted ${data2.deleted || 0} link(s).`);
413
+ return;
414
+ }
415
+ const limit = getFlag(args, "--limit", "25");
416
+ const offset = getFlag(args, "--offset", "0");
417
+ const params = new URLSearchParams({
418
+ trash: "true",
419
+ limit,
420
+ offset
421
+ });
422
+ const { data } = await api("GET", `/api/links?${params}`);
423
+ if (hasFlag(args, "--json")) {
424
+ console.log(JSON.stringify(data, null, 2));
425
+ return;
426
+ }
427
+ console.log(`
428
+ Trashed links: ${data.links.length} of ${data.total} total
429
+ `);
430
+ for (const [i, link] of data.links.entries()) {
431
+ const title = truncate(link.title || "(untitled)", 60);
432
+ console.log(
433
+ ` ${String(i + 1).padStart(3)}. ${title} ${link.domain || ""} deleted ${formatDate(link.deleted_at)}`
434
+ );
435
+ }
436
+ if (data.links.length === 0) {
437
+ console.log(" Trash is empty.");
438
+ }
439
+ console.log();
440
+ }
441
+
442
+ // src/commands/update.ts
443
+ async function run8(args) {
444
+ if (hasFlag(args, "--help", "-h")) {
445
+ console.log(`shiori update - Update a link
446
+
447
+ Usage: shiori update <id> [options]
448
+
449
+ Options:
450
+ --read Mark as read
451
+ --unread Mark as unread
452
+ --title <title> Update title
453
+ --summary <text> Update summary
454
+ --restore Restore from trash
455
+ --json Output raw JSON
456
+ --help, -h Show this help`);
457
+ return;
458
+ }
459
+ const id = getPositional(args);
460
+ if (!id) {
461
+ console.error("Usage: shiori update <id> [--read | --unread | --title <title> | --restore]");
462
+ process.exit(1);
463
+ }
464
+ let body;
465
+ if (hasFlag(args, "--restore")) {
466
+ body = { restore: true };
467
+ } else if (hasFlag(args, "--read")) {
468
+ body = { read: true };
469
+ } else if (hasFlag(args, "--unread")) {
470
+ body = { read: false };
471
+ } else {
472
+ const title = getFlag(args, "--title");
473
+ const summary = getFlag(args, "--summary");
474
+ if (!title && !summary) {
475
+ console.error("Provide at least one update flag: --read, --unread, --title, --summary, --restore");
476
+ process.exit(1);
477
+ }
478
+ body = {};
479
+ if (title) body.title = title;
480
+ if (summary !== void 0) body.summary = summary;
481
+ }
482
+ const { data } = await api("PATCH", `/api/links/${id}`, body);
483
+ if (hasFlag(args, "--json")) {
484
+ console.log(JSON.stringify(data, null, 2));
485
+ return;
486
+ }
487
+ console.log(data.message || "Updated.");
488
+ }
489
+
490
+ // src/error-report.ts
491
+ function reportError(command, error) {
492
+ const message = error instanceof Error ? error.message : String(error);
493
+ const baseUrl = getBaseUrl();
494
+ fetch(`${baseUrl}/api/cli/report`, {
495
+ method: "POST",
496
+ headers: { "Content-Type": "application/json" },
497
+ body: JSON.stringify({
498
+ version: "0.1.0",
499
+ command,
500
+ error: message,
501
+ platform: process.platform
502
+ }),
503
+ signal: AbortSignal.timeout(3e3)
504
+ }).catch(() => {
505
+ });
506
+ }
507
+
508
+ // src/update-check.ts
509
+ var REGISTRY_URL = "https://registry.npmjs.org/@shiori-sh/cli/latest";
510
+ var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
511
+ async function checkForUpdate(currentVersion, isJson) {
512
+ if (process.env.CI || process.env.NO_UPDATE_NOTIFIER || isJson) return;
513
+ const config = readConfig();
514
+ const now = Date.now();
515
+ if (config.last_update_check && now - config.last_update_check < CHECK_INTERVAL_MS)
516
+ return;
517
+ try {
518
+ const res = await fetch(REGISTRY_URL, {
519
+ signal: AbortSignal.timeout(3e3)
520
+ });
521
+ if (!res.ok) return;
522
+ const data = await res.json();
523
+ writeConfig({ ...config, last_update_check: now });
524
+ if (data.version && data.version !== currentVersion) {
525
+ process.stderr.write(
526
+ `
527
+ Update available: ${currentVersion} \u2192 ${data.version}
528
+ Run: npm install -g @shiori-sh/cli
529
+
530
+ `
531
+ );
532
+ }
533
+ } catch {
534
+ }
535
+ }
536
+
537
+ // src/index.ts
538
+ var COMMANDS = {
539
+ auth: { run, desc: "Authenticate with your Shiori API key" },
540
+ list: { run: run4, desc: "List saved links" },
541
+ get: { run: run3, desc: "Get a link by ID (includes content)" },
542
+ save: { run: run6, desc: "Save a new link" },
543
+ update: { run: run8, desc: "Update a link" },
544
+ delete: { run: run2, desc: "Delete a link (move to trash)" },
545
+ trash: { run: run7, desc: "List or empty the trash" },
546
+ me: { run: run5, desc: "Show current user info" }
547
+ };
548
+ function printHelp() {
549
+ console.log(`shiori - Manage your Shiori link library from the terminal
550
+
551
+ Usage: shiori <command> [options]
552
+
553
+ Commands:
554
+ ${Object.entries(COMMANDS).map(([name, { desc }]) => ` ${name.padEnd(10)} ${desc}`).join("\n")}
555
+
556
+ Options:
557
+ --help, -h Show help
558
+ --version, -v Show version
559
+
560
+ Get started:
561
+ shiori auth Authenticate with your API key
562
+ shiori list List your recent links
563
+ shiori save <url> Save a new link`);
564
+ }
565
+ async function main() {
566
+ const args = process.argv.slice(2);
567
+ const command = args[0];
568
+ if (!command || command === "--help" || command === "-h") {
569
+ printHelp();
570
+ return;
571
+ }
572
+ if (command === "--version" || command === "-v") {
573
+ console.log("0.1.0");
574
+ return;
575
+ }
576
+ const cmd = COMMANDS[command];
577
+ if (!cmd) {
578
+ console.error(`Unknown command: ${command}
579
+ `);
580
+ printHelp();
581
+ process.exit(1);
582
+ }
583
+ const cmdArgs = args.slice(1);
584
+ const isJson = cmdArgs.includes("--json");
585
+ await cmd.run(cmdArgs);
586
+ checkForUpdate("0.1.0", isJson).catch(() => {
587
+ });
588
+ }
589
+ main().catch((err) => {
590
+ const command = process.argv[2] ?? "unknown";
591
+ reportError(command, err);
592
+ console.error(err.message || err);
593
+ process.exit(1);
594
+ });
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@shiori-sh/cli",
3
+ "version": "0.1.0",
4
+ "description": "CLI for managing your Shiori link library",
5
+ "author": "Brian Lovin",
6
+ "license": "MIT",
7
+ "homepage": "https://www.shiori.sh",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/brianlovin/shiori",
11
+ "directory": "cli"
12
+ },
13
+ "type": "module",
14
+ "bin": {
15
+ "shiori": "dist/index.js"
16
+ },
17
+ "files": [
18
+ "dist"
19
+ ],
20
+ "scripts": {
21
+ "build": "tsup",
22
+ "dev": "tsup --watch",
23
+ "typecheck": "tsc --noEmit",
24
+ "prepublishOnly": "bun run build"
25
+ },
26
+ "devDependencies": {
27
+ "tsup": "^8.0.0",
28
+ "typescript": "^5.8.3"
29
+ },
30
+ "engines": {
31
+ "node": ">=18"
32
+ },
33
+ "keywords": [
34
+ "shiori",
35
+ "bookmarks",
36
+ "links",
37
+ "read-it-later",
38
+ "cli"
39
+ ]
40
+ }