@shiori-sh/cli 0.1.0 → 0.3.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 +33 -1
- package/dist/index.js +317 -40
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -29,9 +29,16 @@ You can also set `SHIORI_API_KEY` as an environment variable.
|
|
|
29
29
|
```bash
|
|
30
30
|
shiori list # List recent links
|
|
31
31
|
shiori list --read unread # List unread links
|
|
32
|
-
shiori list --
|
|
32
|
+
shiori list --since 7d # Links saved in the last 7 days
|
|
33
|
+
shiori list --content --json # Include full markdown content
|
|
34
|
+
|
|
35
|
+
shiori search "react hooks" # Full-text search
|
|
36
|
+
shiori search "AI" --since 30d # Search within a time window
|
|
37
|
+
shiori search "go" --content --json # Search with full content
|
|
33
38
|
|
|
34
39
|
shiori get <id> # Get a link with full content
|
|
40
|
+
shiori content <id> # Print raw markdown (for piping)
|
|
41
|
+
|
|
35
42
|
shiori save <url> # Save a new link
|
|
36
43
|
shiori save <url> --title "..." # Save with custom title
|
|
37
44
|
|
|
@@ -44,6 +51,13 @@ shiori delete <id> # Move to trash
|
|
|
44
51
|
shiori trash # List trashed links
|
|
45
52
|
shiori trash --empty # Permanently delete all trash
|
|
46
53
|
|
|
54
|
+
shiori subscriptions list # List RSS subscriptions
|
|
55
|
+
shiori subscriptions add <url> # Subscribe to an RSS feed
|
|
56
|
+
shiori subscriptions add <url> --sync # Subscribe and sync recent items
|
|
57
|
+
shiori subscriptions remove <id> # Remove a subscription
|
|
58
|
+
shiori subscriptions sync <id> # Sync a subscription now
|
|
59
|
+
shiori subscriptions sync <id> --limit 5 # Sync with item limit
|
|
60
|
+
|
|
47
61
|
shiori me # Show account info
|
|
48
62
|
shiori auth --status # Check auth status
|
|
49
63
|
shiori auth --logout # Remove stored credentials
|
|
@@ -51,6 +65,24 @@ shiori auth --logout # Remove stored credentials
|
|
|
51
65
|
|
|
52
66
|
Add `--json` to any command for machine-readable output.
|
|
53
67
|
|
|
68
|
+
## AI and Scripting
|
|
69
|
+
|
|
70
|
+
The CLI is designed to make your links accessible to AI tools and scripts:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
# Pipe link content to an LLM
|
|
74
|
+
shiori content <id> | llm "summarize this"
|
|
75
|
+
|
|
76
|
+
# Get recent links with full content as JSON
|
|
77
|
+
shiori list --since 7d --content --json
|
|
78
|
+
|
|
79
|
+
# Search and pipe results
|
|
80
|
+
shiori search "react" --content --json | jq '.links[].content'
|
|
81
|
+
|
|
82
|
+
# Duration shortcuts: 1h, 7d, 2w, 1m, 1y
|
|
83
|
+
shiori list --since 2w --json
|
|
84
|
+
```
|
|
85
|
+
|
|
54
86
|
## Environment Variables
|
|
55
87
|
|
|
56
88
|
| Variable | Description |
|
package/dist/index.js
CHANGED
|
@@ -17,7 +17,7 @@ function readConfig() {
|
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
19
|
function writeConfig(config) {
|
|
20
|
-
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
20
|
+
mkdirSync(CONFIG_DIR, { recursive: true, mode: 448 });
|
|
21
21
|
writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2) + "\n", {
|
|
22
22
|
mode: 384
|
|
23
23
|
});
|
|
@@ -27,9 +27,7 @@ function getApiKey() {
|
|
|
27
27
|
if (envKey) return envKey;
|
|
28
28
|
const config = readConfig();
|
|
29
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
|
-
);
|
|
30
|
+
console.error("Not authenticated. Run `shiori auth` to set up your API key.");
|
|
33
31
|
console.error("Or set the SHIORI_API_KEY environment variable.");
|
|
34
32
|
process.exit(1);
|
|
35
33
|
}
|
|
@@ -198,7 +196,16 @@ function hasFlag(args, ...names) {
|
|
|
198
196
|
function getPositional(args) {
|
|
199
197
|
for (let i = 0; i < args.length; i++) {
|
|
200
198
|
if (args[i].startsWith("--")) {
|
|
201
|
-
if (i + 1 < args.length && !args[i + 1].startsWith("--") && [
|
|
199
|
+
if (i + 1 < args.length && !args[i + 1].startsWith("--") && [
|
|
200
|
+
"--limit",
|
|
201
|
+
"--offset",
|
|
202
|
+
"--sort",
|
|
203
|
+
"--read",
|
|
204
|
+
"--title",
|
|
205
|
+
"--summary",
|
|
206
|
+
"--since",
|
|
207
|
+
"--format"
|
|
208
|
+
].includes(args[i])) {
|
|
202
209
|
i++;
|
|
203
210
|
}
|
|
204
211
|
continue;
|
|
@@ -207,9 +214,66 @@ function getPositional(args) {
|
|
|
207
214
|
}
|
|
208
215
|
return void 0;
|
|
209
216
|
}
|
|
217
|
+
var DURATION_UNITS = {
|
|
218
|
+
h: 36e5,
|
|
219
|
+
d: 864e5,
|
|
220
|
+
w: 6048e5,
|
|
221
|
+
m: 2592e6,
|
|
222
|
+
y: 31536e6
|
|
223
|
+
};
|
|
224
|
+
function parseDuration(str) {
|
|
225
|
+
const match = str.match(/^(\d+)([hdwmy])$/);
|
|
226
|
+
if (!match) {
|
|
227
|
+
throw new Error(`Invalid duration "${str}". Use a number + unit: 1h, 7d, 2w, 1m, 1y`);
|
|
228
|
+
}
|
|
229
|
+
const amount = Number(match[1]);
|
|
230
|
+
const ms = amount * DURATION_UNITS[match[2]];
|
|
231
|
+
return new Date(Date.now() - ms).toISOString();
|
|
232
|
+
}
|
|
233
|
+
function formatLinkList(links, options) {
|
|
234
|
+
for (const [i, link] of links.entries()) {
|
|
235
|
+
const readStatus = link.read_at ? "read" : "unread";
|
|
236
|
+
const title = truncate(link.title || "(untitled)", 60);
|
|
237
|
+
console.log(
|
|
238
|
+
` ${String(options.offset + i + 1).padStart(3)}. ${title} ${link.domain || ""} ${formatDate(link.created_at)} ${readStatus}`
|
|
239
|
+
);
|
|
240
|
+
if (options.showContent && link.content) {
|
|
241
|
+
console.log(` ${truncate(link.content.replace(/\n/g, " "), 500)}`);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
210
245
|
|
|
211
|
-
// src/commands/
|
|
246
|
+
// src/commands/content.ts
|
|
212
247
|
async function run2(args) {
|
|
248
|
+
if (hasFlag(args, "--help", "-h")) {
|
|
249
|
+
console.log(`shiori content - Print link content as markdown
|
|
250
|
+
|
|
251
|
+
Usage: shiori content <id>
|
|
252
|
+
|
|
253
|
+
Prints the extracted markdown content to stdout with no decoration.
|
|
254
|
+
Useful for piping into other tools:
|
|
255
|
+
|
|
256
|
+
shiori content <id> | llm "summarize this"
|
|
257
|
+
shiori content <id> | pbcopy
|
|
258
|
+
|
|
259
|
+
Options:
|
|
260
|
+
--help, -h Show this help`);
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
const id = getPositional(args);
|
|
264
|
+
if (!id) {
|
|
265
|
+
console.error("Usage: shiori content <link-id>");
|
|
266
|
+
process.exit(1);
|
|
267
|
+
}
|
|
268
|
+
const { data } = await api("GET", `/api/links/${id}`);
|
|
269
|
+
const content = data.link?.content;
|
|
270
|
+
if (content) {
|
|
271
|
+
process.stdout.write(content);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// src/commands/delete.ts
|
|
276
|
+
async function run3(args) {
|
|
213
277
|
if (hasFlag(args, "--help", "-h")) {
|
|
214
278
|
console.log(`shiori delete - Delete a link (move to trash)
|
|
215
279
|
|
|
@@ -234,7 +298,7 @@ Options:
|
|
|
234
298
|
}
|
|
235
299
|
|
|
236
300
|
// src/commands/get.ts
|
|
237
|
-
async function
|
|
301
|
+
async function run4(args) {
|
|
238
302
|
if (hasFlag(args, "--help", "-h")) {
|
|
239
303
|
console.log(`shiori get - Get a link by ID (includes content)
|
|
240
304
|
|
|
@@ -266,9 +330,7 @@ Options:
|
|
|
266
330
|
console.log(` Author: ${link.author || "--"}`);
|
|
267
331
|
console.log(` Published: ${formatDate(link.publication_date)}`);
|
|
268
332
|
console.log(` Saved: ${formatDate(link.created_at)}`);
|
|
269
|
-
console.log(
|
|
270
|
-
` Read: ${link.read_at ? formatDate(link.read_at) : "unread"}`
|
|
271
|
-
);
|
|
333
|
+
console.log(` Read: ${link.read_at ? formatDate(link.read_at) : "unread"}`);
|
|
272
334
|
console.log(` Summary: ${link.summary || "--"}`);
|
|
273
335
|
if (link.content) {
|
|
274
336
|
console.log(`
|
|
@@ -281,7 +343,7 @@ Options:
|
|
|
281
343
|
}
|
|
282
344
|
|
|
283
345
|
// src/commands/list.ts
|
|
284
|
-
async function
|
|
346
|
+
async function run5(args) {
|
|
285
347
|
if (hasFlag(args, "--help", "-h")) {
|
|
286
348
|
console.log(`shiori list - List saved links
|
|
287
349
|
|
|
@@ -292,6 +354,8 @@ Options:
|
|
|
292
354
|
--offset <n> Pagination offset (default: 0)
|
|
293
355
|
--sort <newest|oldest> Sort order (default: newest)
|
|
294
356
|
--read <all|read|unread> Filter by read status (default: all)
|
|
357
|
+
--since <duration> Only links saved within this period (e.g. 1h, 7d, 2w, 1m, 1y)
|
|
358
|
+
--content Include extracted markdown content
|
|
295
359
|
--json Output raw JSON
|
|
296
360
|
--help, -h Show this help`);
|
|
297
361
|
return;
|
|
@@ -300,12 +364,16 @@ Options:
|
|
|
300
364
|
const offset = getFlag(args, "--offset", "0");
|
|
301
365
|
const sort = getFlag(args, "--sort", "newest");
|
|
302
366
|
const read = getFlag(args, "--read", "all");
|
|
367
|
+
const sinceFlag = getFlag(args, "--since");
|
|
368
|
+
const includeContent = hasFlag(args, "--content");
|
|
303
369
|
const params = new URLSearchParams({
|
|
304
370
|
limit,
|
|
305
371
|
offset,
|
|
306
372
|
sort,
|
|
307
373
|
read
|
|
308
374
|
});
|
|
375
|
+
if (includeContent) params.set("include_content", "true");
|
|
376
|
+
if (sinceFlag) params.set("since", parseDuration(sinceFlag));
|
|
309
377
|
const { data } = await api("GET", `/api/links?${params}`);
|
|
310
378
|
if (hasFlag(args, "--json")) {
|
|
311
379
|
console.log(JSON.stringify(data, null, 2));
|
|
@@ -314,13 +382,7 @@ Options:
|
|
|
314
382
|
console.log(`
|
|
315
383
|
Links: ${data.links.length} of ${data.total} total
|
|
316
384
|
`);
|
|
317
|
-
|
|
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
|
-
}
|
|
385
|
+
formatLinkList(data.links, { offset: Number(offset), showContent: includeContent });
|
|
324
386
|
if (data.links.length === 0) {
|
|
325
387
|
console.log(" No links found.");
|
|
326
388
|
}
|
|
@@ -328,7 +390,7 @@ Links: ${data.links.length} of ${data.total} total
|
|
|
328
390
|
}
|
|
329
391
|
|
|
330
392
|
// src/commands/me.ts
|
|
331
|
-
async function
|
|
393
|
+
async function run6(args) {
|
|
332
394
|
if (hasFlag(args, "--help", "-h")) {
|
|
333
395
|
console.log(`shiori me - Show current user info
|
|
334
396
|
|
|
@@ -349,12 +411,14 @@ Options:
|
|
|
349
411
|
Name: ${user.full_name || "--"}`);
|
|
350
412
|
const plan = user.subscription?.plan === "subscription" ? "Pro" : user.subscription?.plan || "Free";
|
|
351
413
|
console.log(` Plan: ${plan}`);
|
|
352
|
-
console.log(
|
|
414
|
+
console.log(
|
|
415
|
+
` Member since: ${new Date(user.created_at).toLocaleDateString("en-US", { month: "long", year: "numeric" })}`
|
|
416
|
+
);
|
|
353
417
|
console.log();
|
|
354
418
|
}
|
|
355
419
|
|
|
356
420
|
// src/commands/save.ts
|
|
357
|
-
async function
|
|
421
|
+
async function run7(args) {
|
|
358
422
|
if (hasFlag(args, "--help", "-h")) {
|
|
359
423
|
console.log(`shiori save - Save a new link
|
|
360
424
|
|
|
@@ -388,8 +452,214 @@ Options:
|
|
|
388
452
|
}
|
|
389
453
|
}
|
|
390
454
|
|
|
455
|
+
// src/commands/search.ts
|
|
456
|
+
async function run8(args) {
|
|
457
|
+
if (hasFlag(args, "--help", "-h")) {
|
|
458
|
+
console.log(`shiori search - Search saved links
|
|
459
|
+
|
|
460
|
+
Usage: shiori search <query> [options]
|
|
461
|
+
|
|
462
|
+
Options:
|
|
463
|
+
--limit <n> Number of links (1-100, default: 25)
|
|
464
|
+
--offset <n> Pagination offset (default: 0)
|
|
465
|
+
--since <duration> Only links saved within this period (e.g. 1h, 7d, 2w, 1m, 1y)
|
|
466
|
+
--content Include extracted markdown content
|
|
467
|
+
--json Output raw JSON
|
|
468
|
+
--help, -h Show this help`);
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
const query = getPositional(args);
|
|
472
|
+
if (!query) {
|
|
473
|
+
console.error("Usage: shiori search <query>");
|
|
474
|
+
process.exit(1);
|
|
475
|
+
}
|
|
476
|
+
const limit = getFlag(args, "--limit", "25");
|
|
477
|
+
const offset = getFlag(args, "--offset", "0");
|
|
478
|
+
const sinceFlag = getFlag(args, "--since");
|
|
479
|
+
const includeContent = hasFlag(args, "--content");
|
|
480
|
+
const params = new URLSearchParams({
|
|
481
|
+
search: query,
|
|
482
|
+
limit,
|
|
483
|
+
offset
|
|
484
|
+
});
|
|
485
|
+
if (includeContent) params.set("include_content", "true");
|
|
486
|
+
if (sinceFlag) params.set("since", parseDuration(sinceFlag));
|
|
487
|
+
const { data } = await api("GET", `/api/links?${params}`);
|
|
488
|
+
if (hasFlag(args, "--json")) {
|
|
489
|
+
console.log(JSON.stringify(data, null, 2));
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
console.log(`
|
|
493
|
+
Results: ${data.links.length} of ${data.total} matches
|
|
494
|
+
`);
|
|
495
|
+
formatLinkList(data.links, { offset: Number(offset), showContent: includeContent });
|
|
496
|
+
if (data.links.length === 0) {
|
|
497
|
+
console.log(" No links found.");
|
|
498
|
+
}
|
|
499
|
+
console.log();
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// src/commands/subscriptions.ts
|
|
503
|
+
var HELP = `shiori subscriptions - Manage RSS subscriptions
|
|
504
|
+
|
|
505
|
+
Usage: shiori subscriptions <subcommand> [options]
|
|
506
|
+
|
|
507
|
+
Subcommands:
|
|
508
|
+
list List your subscriptions
|
|
509
|
+
add <url> Subscribe to an RSS feed
|
|
510
|
+
remove <id> Remove a subscription
|
|
511
|
+
sync <id> Sync a subscription now
|
|
512
|
+
|
|
513
|
+
Options:
|
|
514
|
+
--json Output raw JSON
|
|
515
|
+
--help, -h Show this help
|
|
516
|
+
|
|
517
|
+
Examples:
|
|
518
|
+
shiori subscriptions list
|
|
519
|
+
shiori subscriptions add https://example.com/feed.xml
|
|
520
|
+
shiori subscriptions add https://example.com --sync
|
|
521
|
+
shiori subscriptions remove <id>
|
|
522
|
+
shiori subscriptions sync <id> --limit 5`;
|
|
523
|
+
async function run9(args) {
|
|
524
|
+
if (hasFlag(args, "--help", "-h") && !args[0]) {
|
|
525
|
+
console.log(HELP);
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
const subcommand = args[0];
|
|
529
|
+
const subArgs = args.slice(1);
|
|
530
|
+
switch (subcommand) {
|
|
531
|
+
case "list":
|
|
532
|
+
case "ls":
|
|
533
|
+
return listSubscriptions(subArgs);
|
|
534
|
+
case "add":
|
|
535
|
+
return addSubscription(subArgs);
|
|
536
|
+
case "remove":
|
|
537
|
+
case "rm":
|
|
538
|
+
return removeSubscription(subArgs);
|
|
539
|
+
case "sync":
|
|
540
|
+
return syncSubscription(subArgs);
|
|
541
|
+
default:
|
|
542
|
+
if (subcommand) {
|
|
543
|
+
console.error(`Unknown subcommand: ${subcommand}
|
|
544
|
+
`);
|
|
545
|
+
}
|
|
546
|
+
console.log(HELP);
|
|
547
|
+
if (subcommand) process.exit(1);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
async function listSubscriptions(args) {
|
|
551
|
+
if (hasFlag(args, "--help", "-h")) {
|
|
552
|
+
console.log(`shiori subscriptions list - List your RSS subscriptions
|
|
553
|
+
|
|
554
|
+
Usage: shiori subscriptions list [options]
|
|
555
|
+
|
|
556
|
+
Options:
|
|
557
|
+
--json Output raw JSON
|
|
558
|
+
--help, -h Show this help`);
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
const { data } = await api("GET", "/api/subscriptions/");
|
|
562
|
+
if (hasFlag(args, "--json")) {
|
|
563
|
+
console.log(JSON.stringify(data, null, 2));
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
const subs = data.subscriptions;
|
|
567
|
+
if (subs.length === 0) {
|
|
568
|
+
console.log("\n No subscriptions. Add one with: shiori subscriptions add <url>\n");
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
console.log(`
|
|
572
|
+
Subscriptions: ${subs.length}
|
|
573
|
+
`);
|
|
574
|
+
for (const sub of subs) {
|
|
575
|
+
const title = truncate(sub.title || sub.feed_url, 50);
|
|
576
|
+
const synced = sub.last_synced_at ? `synced ${formatDate(sub.last_synced_at)}` : "never synced";
|
|
577
|
+
console.log(` ${sub.id} ${title} ${synced}`);
|
|
578
|
+
}
|
|
579
|
+
console.log();
|
|
580
|
+
}
|
|
581
|
+
async function addSubscription(args) {
|
|
582
|
+
if (hasFlag(args, "--help", "-h")) {
|
|
583
|
+
console.log(`shiori subscriptions add - Subscribe to an RSS feed
|
|
584
|
+
|
|
585
|
+
Usage: shiori subscriptions add <url> [options]
|
|
586
|
+
|
|
587
|
+
Options:
|
|
588
|
+
--sync Sync the 3 most recent items immediately
|
|
589
|
+
--json Output raw JSON
|
|
590
|
+
--help, -h Show this help`);
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
const url = getPositional(args);
|
|
594
|
+
if (!url) {
|
|
595
|
+
console.error("Usage: shiori subscriptions add <url>");
|
|
596
|
+
process.exit(1);
|
|
597
|
+
}
|
|
598
|
+
const body = { feedUrl: url };
|
|
599
|
+
if (hasFlag(args, "--sync")) {
|
|
600
|
+
body.initialSync = true;
|
|
601
|
+
}
|
|
602
|
+
const { data } = await api("POST", "/api/subscriptions/", body);
|
|
603
|
+
if (hasFlag(args, "--json")) {
|
|
604
|
+
console.log(JSON.stringify(data, null, 2));
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
const sub = data.subscription;
|
|
608
|
+
console.log(`Subscribed: ${sub.title || sub.feed_url} (${sub.id})`);
|
|
609
|
+
}
|
|
610
|
+
async function removeSubscription(args) {
|
|
611
|
+
if (hasFlag(args, "--help", "-h")) {
|
|
612
|
+
console.log(`shiori subscriptions remove - Remove a subscription
|
|
613
|
+
|
|
614
|
+
Usage: shiori subscriptions remove <id> [options]
|
|
615
|
+
|
|
616
|
+
Options:
|
|
617
|
+
--json Output raw JSON
|
|
618
|
+
--help, -h Show this help`);
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
const id = getPositional(args);
|
|
622
|
+
if (!id) {
|
|
623
|
+
console.error("Usage: shiori subscriptions remove <id>");
|
|
624
|
+
process.exit(1);
|
|
625
|
+
}
|
|
626
|
+
const { data } = await api("DELETE", `/api/subscriptions/${id}`);
|
|
627
|
+
if (hasFlag(args, "--json")) {
|
|
628
|
+
console.log(JSON.stringify(data, null, 2));
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
console.log("Subscription removed.");
|
|
632
|
+
}
|
|
633
|
+
async function syncSubscription(args) {
|
|
634
|
+
if (hasFlag(args, "--help", "-h")) {
|
|
635
|
+
console.log(`shiori subscriptions sync - Sync a subscription now
|
|
636
|
+
|
|
637
|
+
Usage: shiori subscriptions sync <id> [options]
|
|
638
|
+
|
|
639
|
+
Options:
|
|
640
|
+
--limit <n> Max items to sync (1-100)
|
|
641
|
+
--json Output raw JSON
|
|
642
|
+
--help, -h Show this help`);
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
645
|
+
const id = getPositional(args);
|
|
646
|
+
if (!id) {
|
|
647
|
+
console.error("Usage: shiori subscriptions sync <id>");
|
|
648
|
+
process.exit(1);
|
|
649
|
+
}
|
|
650
|
+
const body = {};
|
|
651
|
+
const limit = getFlag(args, "--limit");
|
|
652
|
+
if (limit) body.limit = Number(limit);
|
|
653
|
+
const { data } = await api("POST", `/api/subscriptions/sync/${id}`, body);
|
|
654
|
+
if (hasFlag(args, "--json")) {
|
|
655
|
+
console.log(JSON.stringify(data, null, 2));
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
console.log(`Synced: ${data.newItems} new, ${data.skipped} skipped, ${data.errors} errors`);
|
|
659
|
+
}
|
|
660
|
+
|
|
391
661
|
// src/commands/trash.ts
|
|
392
|
-
async function
|
|
662
|
+
async function run10(args) {
|
|
393
663
|
if (hasFlag(args, "--help", "-h")) {
|
|
394
664
|
console.log(`shiori trash - List or empty the trash
|
|
395
665
|
|
|
@@ -440,7 +710,7 @@ Trashed links: ${data.links.length} of ${data.total} total
|
|
|
440
710
|
}
|
|
441
711
|
|
|
442
712
|
// src/commands/update.ts
|
|
443
|
-
async function
|
|
713
|
+
async function run11(args) {
|
|
444
714
|
if (hasFlag(args, "--help", "-h")) {
|
|
445
715
|
console.log(`shiori update - Update a link
|
|
446
716
|
|
|
@@ -472,7 +742,9 @@ Options:
|
|
|
472
742
|
const title = getFlag(args, "--title");
|
|
473
743
|
const summary = getFlag(args, "--summary");
|
|
474
744
|
if (!title && !summary) {
|
|
475
|
-
console.error(
|
|
745
|
+
console.error(
|
|
746
|
+
"Provide at least one update flag: --read, --unread, --title, --summary, --restore"
|
|
747
|
+
);
|
|
476
748
|
process.exit(1);
|
|
477
749
|
}
|
|
478
750
|
body = {};
|
|
@@ -489,13 +761,14 @@ Options:
|
|
|
489
761
|
|
|
490
762
|
// src/error-report.ts
|
|
491
763
|
function reportError(command, error) {
|
|
492
|
-
const
|
|
764
|
+
const raw = error instanceof Error ? error.message : String(error);
|
|
765
|
+
const message = raw.replace(/shk_[a-zA-Z0-9]+/g, "shk_[REDACTED]");
|
|
493
766
|
const baseUrl = getBaseUrl();
|
|
494
767
|
fetch(`${baseUrl}/api/cli/report`, {
|
|
495
768
|
method: "POST",
|
|
496
769
|
headers: { "Content-Type": "application/json" },
|
|
497
770
|
body: JSON.stringify({
|
|
498
|
-
version: "0.
|
|
771
|
+
version: "0.3.0",
|
|
499
772
|
command,
|
|
500
773
|
error: message,
|
|
501
774
|
platform: process.platform
|
|
@@ -512,8 +785,7 @@ async function checkForUpdate(currentVersion, isJson) {
|
|
|
512
785
|
if (process.env.CI || process.env.NO_UPDATE_NOTIFIER || isJson) return;
|
|
513
786
|
const config = readConfig();
|
|
514
787
|
const now = Date.now();
|
|
515
|
-
if (config.last_update_check && now - config.last_update_check < CHECK_INTERVAL_MS)
|
|
516
|
-
return;
|
|
788
|
+
if (config.last_update_check && now - config.last_update_check < CHECK_INTERVAL_MS) return;
|
|
517
789
|
try {
|
|
518
790
|
const res = await fetch(REGISTRY_URL, {
|
|
519
791
|
signal: AbortSignal.timeout(3e3)
|
|
@@ -537,13 +809,16 @@ async function checkForUpdate(currentVersion, isJson) {
|
|
|
537
809
|
// src/index.ts
|
|
538
810
|
var COMMANDS = {
|
|
539
811
|
auth: { run, desc: "Authenticate with your Shiori API key" },
|
|
540
|
-
list: { run:
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
812
|
+
list: { run: run5, desc: "List saved links" },
|
|
813
|
+
search: { run: run8, desc: "Search saved links" },
|
|
814
|
+
get: { run: run4, desc: "Get a link by ID (includes content)" },
|
|
815
|
+
content: { run: run2, desc: "Print link content as markdown" },
|
|
816
|
+
save: { run: run7, desc: "Save a new link" },
|
|
817
|
+
update: { run: run11, desc: "Update a link" },
|
|
818
|
+
delete: { run: run3, desc: "Delete a link (move to trash)" },
|
|
819
|
+
trash: { run: run10, desc: "List or empty the trash" },
|
|
820
|
+
subscriptions: { run: run9, desc: "Manage RSS subscriptions" },
|
|
821
|
+
me: { run: run6, desc: "Show current user info" }
|
|
547
822
|
};
|
|
548
823
|
function printHelp() {
|
|
549
824
|
console.log(`shiori - Manage your Shiori link library from the terminal
|
|
@@ -558,9 +833,11 @@ Options:
|
|
|
558
833
|
--version, -v Show version
|
|
559
834
|
|
|
560
835
|
Get started:
|
|
561
|
-
shiori auth
|
|
562
|
-
shiori list
|
|
563
|
-
shiori save <url>
|
|
836
|
+
shiori auth Authenticate with your API key
|
|
837
|
+
shiori list List your recent links
|
|
838
|
+
shiori save <url> Save a new link
|
|
839
|
+
shiori search <query> Search your links
|
|
840
|
+
shiori content <id> Print markdown content (pipe to other tools)`);
|
|
564
841
|
}
|
|
565
842
|
async function main() {
|
|
566
843
|
const args = process.argv.slice(2);
|
|
@@ -570,7 +847,7 @@ async function main() {
|
|
|
570
847
|
return;
|
|
571
848
|
}
|
|
572
849
|
if (command === "--version" || command === "-v") {
|
|
573
|
-
console.log("0.
|
|
850
|
+
console.log("0.3.0");
|
|
574
851
|
return;
|
|
575
852
|
}
|
|
576
853
|
const cmd = COMMANDS[command];
|
|
@@ -583,7 +860,7 @@ async function main() {
|
|
|
583
860
|
const cmdArgs = args.slice(1);
|
|
584
861
|
const isJson = cmdArgs.includes("--json");
|
|
585
862
|
await cmd.run(cmdArgs);
|
|
586
|
-
checkForUpdate("0.
|
|
863
|
+
checkForUpdate("0.3.0", isJson).catch(() => {
|
|
587
864
|
});
|
|
588
865
|
}
|
|
589
866
|
main().catch((err) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shiori-sh/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "CLI for managing your Shiori link library",
|
|
5
5
|
"author": "Brian Lovin",
|
|
6
6
|
"license": "MIT",
|
|
@@ -21,11 +21,13 @@
|
|
|
21
21
|
"build": "tsup",
|
|
22
22
|
"dev": "tsup --watch",
|
|
23
23
|
"typecheck": "tsc --noEmit",
|
|
24
|
+
"test": "vitest run",
|
|
24
25
|
"prepublishOnly": "bun run build"
|
|
25
26
|
},
|
|
26
27
|
"devDependencies": {
|
|
27
28
|
"tsup": "^8.0.0",
|
|
28
|
-
"typescript": "^5.8.3"
|
|
29
|
+
"typescript": "^5.8.3",
|
|
30
|
+
"vitest": "^3.0.0"
|
|
29
31
|
},
|
|
30
32
|
"engines": {
|
|
31
33
|
"node": ">=18"
|