@shiori-sh/cli 0.4.0 → 0.7.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 +11 -1
  2. package/dist/index.js +315 -50
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -29,6 +29,7 @@ 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 --tag "design" # Filter links by tag
32
33
  shiori list --since 7d # Links saved in the last 7 days
33
34
  shiori list --content --json # Include full markdown content
34
35
 
@@ -46,11 +47,20 @@ shiori update <id> --read # Mark as read
46
47
  shiori update <id> --unread # Mark as unread
47
48
  shiori update <id> --title "..." # Update title
48
49
  shiori update <id> --restore # Restore from trash
50
+ shiori update --ids <id1,id2,...> --read # Bulk mark as read
51
+ shiori update --ids <id1,id2,...> --unread # Bulk mark as unread
49
52
 
50
53
  shiori delete <id> # Move to trash
54
+ shiori delete --ids <id1,id2,...> # Bulk move to trash
51
55
  shiori trash # List trashed links
52
56
  shiori trash --empty # Permanently delete all trash
53
57
 
58
+ shiori tags list # List your tags
59
+ shiori tags create "design" # Create a tag
60
+ shiori tags update <id> --name "dev" # Rename a tag
61
+ shiori tags delete <id> # Delete a tag
62
+ shiori tags set <link-id> design,work # Set tags on a link
63
+
54
64
  shiori subscriptions list # List RSS subscriptions
55
65
  shiori subscriptions add <url> # Subscribe to an RSS feed
56
66
  shiori subscriptions add <url> --sync # Subscribe and sync recent items
@@ -58,7 +68,7 @@ shiori subscriptions remove <id> # Remove a subscription
58
68
  shiori subscriptions sync <id> # Sync a subscription now
59
69
  shiori subscriptions sync <id> --limit 5 # Sync with item limit
60
70
 
61
- shiori me # Show account info
71
+ shiori whoami # Show account info
62
72
  shiori auth --status # Check auth status
63
73
  shiori auth --logout # Remove stored credentials
64
74
  ```
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/commands/auth.ts
4
- import { exec } from "child_process";
4
+ import { execFile } from "child_process";
5
5
  import { hostname } from "os";
6
6
  import { createInterface } from "readline";
7
7
 
@@ -92,7 +92,7 @@ function promptSecret(question) {
92
92
  function openUrl(url) {
93
93
  const platform = process.platform;
94
94
  const cmd = platform === "darwin" ? "open" : platform === "win32" ? "start" : "xdg-open";
95
- exec(`${cmd} ${JSON.stringify(url)}`);
95
+ execFile(cmd, [url]);
96
96
  }
97
97
  function sleep(ms) {
98
98
  return new Promise((resolve) => setTimeout(resolve, ms));
@@ -290,7 +290,10 @@ function getPositional(args) {
290
290
  "--title",
291
291
  "--summary",
292
292
  "--since",
293
- "--format"
293
+ "--format",
294
+ "--ids",
295
+ "--tag",
296
+ "--name"
294
297
  ].includes(args[i])) {
295
298
  i++;
296
299
  }
@@ -361,18 +364,35 @@ Options:
361
364
  // src/commands/delete.ts
362
365
  async function run3(args) {
363
366
  if (hasFlag(args, "--help", "-h")) {
364
- console.log(`shiori delete - Delete a link (move to trash)
367
+ console.log(`shiori delete - Delete links (move to trash)
365
368
 
366
369
  Usage: shiori delete <id> [options]
370
+ shiori delete --ids <id1,id2,...> [options]
367
371
 
368
372
  Options:
369
- --json Output raw JSON
370
- --help, -h Show this help`);
373
+ --ids <ids> Comma-separated list of link IDs for bulk delete
374
+ --json Output raw JSON
375
+ --help, -h Show this help`);
376
+ return;
377
+ }
378
+ const idsFlag = getFlag(args, "--ids");
379
+ if (idsFlag) {
380
+ const ids = idsFlag.split(",").map((id2) => id2.trim()).filter(Boolean);
381
+ if (ids.length === 0) {
382
+ console.error("No valid IDs provided.");
383
+ process.exit(1);
384
+ }
385
+ const { data: data2 } = await api("PATCH", "/api/links", { ids, deleted: true });
386
+ if (hasFlag(args, "--json")) {
387
+ console.log(JSON.stringify(data2, null, 2));
388
+ return;
389
+ }
390
+ console.log(`Deleted ${data2.updated} link(s).`);
371
391
  return;
372
392
  }
373
393
  const id = getPositional(args);
374
394
  if (!id) {
375
- console.error("Usage: shiori delete <id>");
395
+ console.error("Usage: shiori delete <id> or shiori delete --ids <id1,id2,...>");
376
396
  process.exit(1);
377
397
  }
378
398
  const { data } = await api("DELETE", `/api/links/${id}`);
@@ -440,6 +460,7 @@ Options:
440
460
  --offset <n> Pagination offset (default: 0)
441
461
  --sort <newest|oldest> Sort order (default: newest)
442
462
  --read <all|read|unread> Filter by read status (default: all)
463
+ --tag <name> Filter by tag name
443
464
  --since <duration> Only links saved within this period (e.g. 1h, 7d, 2w, 1m, 1y)
444
465
  --content Include extracted markdown content
445
466
  --json Output raw JSON
@@ -451,6 +472,7 @@ Options:
451
472
  const sort = getFlag(args, "--sort", "newest");
452
473
  const read = getFlag(args, "--read", "all");
453
474
  const sinceFlag = getFlag(args, "--since");
475
+ const tag = getFlag(args, "--tag");
454
476
  const includeContent = hasFlag(args, "--content");
455
477
  const params = new URLSearchParams({
456
478
  limit,
@@ -460,6 +482,7 @@ Options:
460
482
  });
461
483
  if (includeContent) params.set("include_content", "true");
462
484
  if (sinceFlag) params.set("since", parseDuration(sinceFlag));
485
+ if (tag) params.set("tag", tag);
463
486
  const { data } = await api("GET", `/api/links?${params}`);
464
487
  if (hasFlag(args, "--json")) {
465
488
  console.log(JSON.stringify(data, null, 2));
@@ -475,36 +498,8 @@ Links: ${data.links.length} of ${data.total} total
475
498
  console.log();
476
499
  }
477
500
 
478
- // src/commands/me.ts
479
- async function run6(args) {
480
- if (hasFlag(args, "--help", "-h")) {
481
- console.log(`shiori me - Show current user info
482
-
483
- Usage: shiori me [options]
484
-
485
- Options:
486
- --json Output raw JSON
487
- --help, -h Show this help`);
488
- return;
489
- }
490
- const { data } = await api("GET", "/api/user/me");
491
- if (hasFlag(args, "--json")) {
492
- console.log(JSON.stringify(data, null, 2));
493
- return;
494
- }
495
- const user = data.user;
496
- console.log(`
497
- Name: ${user.full_name || "--"}`);
498
- const plan = user.subscription?.plan === "subscription" ? "Pro" : user.subscription?.plan || "Free";
499
- console.log(` Plan: ${plan}`);
500
- console.log(
501
- ` Member since: ${new Date(user.created_at).toLocaleDateString("en-US", { month: "long", year: "numeric" })}`
502
- );
503
- console.log();
504
- }
505
-
506
501
  // src/commands/save.ts
507
- async function run7(args) {
502
+ async function run6(args) {
508
503
  if (hasFlag(args, "--help", "-h")) {
509
504
  console.log(`shiori save - Save a new link
510
505
 
@@ -539,7 +534,7 @@ Options:
539
534
  }
540
535
 
541
536
  // src/commands/search.ts
542
- async function run8(args) {
537
+ async function run7(args) {
543
538
  if (hasFlag(args, "--help", "-h")) {
544
539
  console.log(`shiori search - Search saved links
545
540
 
@@ -606,7 +601,7 @@ Examples:
606
601
  shiori subscriptions add https://example.com --sync
607
602
  shiori subscriptions remove <id>
608
603
  shiori subscriptions sync <id> --limit 5`;
609
- async function run9(args) {
604
+ async function run8(args) {
610
605
  if (hasFlag(args, "--help", "-h") && !args[0]) {
611
606
  console.log(HELP);
612
607
  return;
@@ -744,6 +739,224 @@ Options:
744
739
  console.log(`Synced: ${data.newItems} new, ${data.skipped} skipped, ${data.errors} errors`);
745
740
  }
746
741
 
742
+ // src/commands/tags.ts
743
+ var HELP2 = `shiori tags - Manage tags
744
+
745
+ Usage: shiori tags <subcommand> [options]
746
+
747
+ Subcommands:
748
+ list List your tags
749
+ create <name> Create a tag
750
+ update <id> --name <name> Rename a tag
751
+ delete <id> Delete a tag
752
+ set <link-id> <tag1,tag2,...> Set tags on a link (by name)
753
+
754
+ Options:
755
+ --json Output raw JSON
756
+ --help, -h Show this help
757
+
758
+ Examples:
759
+ shiori tags list
760
+ shiori tags create "design"
761
+ shiori tags update <id> --name "dev"
762
+ shiori tags delete <id>
763
+ shiori tags set <link-id> design,work`;
764
+ async function run9(args) {
765
+ if (hasFlag(args, "--help", "-h") && !args[0]) {
766
+ console.log(HELP2);
767
+ return;
768
+ }
769
+ const subcommand = args[0];
770
+ const subArgs = args.slice(1);
771
+ switch (subcommand) {
772
+ case "list":
773
+ case "ls":
774
+ return listTags(subArgs);
775
+ case "create":
776
+ return createTag(subArgs);
777
+ case "update":
778
+ return updateTag(subArgs);
779
+ case "delete":
780
+ case "rm":
781
+ return deleteTag(subArgs);
782
+ case "set":
783
+ return setLinkTags(subArgs);
784
+ default:
785
+ if (subcommand) {
786
+ console.error(`Unknown subcommand: ${subcommand}
787
+ `);
788
+ }
789
+ console.log(HELP2);
790
+ if (subcommand) process.exit(1);
791
+ }
792
+ }
793
+ async function listTags(args) {
794
+ if (hasFlag(args, "--help", "-h")) {
795
+ console.log(`shiori tags list - List your tags
796
+
797
+ Usage: shiori tags list [options]
798
+
799
+ Options:
800
+ --json Output raw JSON
801
+ --help, -h Show this help`);
802
+ return;
803
+ }
804
+ const { data } = await api("GET", "/api/tags/");
805
+ if (hasFlag(args, "--json")) {
806
+ console.log(JSON.stringify(data, null, 2));
807
+ return;
808
+ }
809
+ const tags = data.tags;
810
+ if (tags.length === 0) {
811
+ console.log("\n No tags. Create one with: shiori tags create <name>\n");
812
+ return;
813
+ }
814
+ console.log(`
815
+ Tags: ${tags.length}
816
+ `);
817
+ for (const tag of tags) {
818
+ console.log(` ${tag.id} ${tag.name}`);
819
+ }
820
+ console.log();
821
+ }
822
+ async function createTag(args) {
823
+ if (hasFlag(args, "--help", "-h")) {
824
+ console.log(`shiori tags create - Create a tag
825
+
826
+ Usage: shiori tags create <name> [options]
827
+
828
+ Options:
829
+ --json Output raw JSON
830
+ --help, -h Show this help`);
831
+ return;
832
+ }
833
+ const name = getPositional(args);
834
+ if (!name) {
835
+ console.error("Usage: shiori tags create <name>");
836
+ process.exit(1);
837
+ }
838
+ const { data } = await api("POST", "/api/tags/", { name });
839
+ if (hasFlag(args, "--json")) {
840
+ console.log(JSON.stringify(data, null, 2));
841
+ return;
842
+ }
843
+ console.log(`Created: ${data.tag.name} (${data.tag.id})`);
844
+ }
845
+ async function updateTag(args) {
846
+ if (hasFlag(args, "--help", "-h")) {
847
+ console.log(`shiori tags update - Update a tag
848
+
849
+ Usage: shiori tags update <id> [options]
850
+
851
+ Options:
852
+ --name <name> New tag name
853
+ --json Output raw JSON
854
+ --help, -h Show this help`);
855
+ return;
856
+ }
857
+ const id = getPositional(args);
858
+ if (!id) {
859
+ console.error("Usage: shiori tags update <id> --name <name>");
860
+ process.exit(1);
861
+ }
862
+ const name = getFlag(args, "--name");
863
+ if (!name) {
864
+ console.error("Provide --name to update.");
865
+ process.exit(1);
866
+ }
867
+ const { data } = await api("PATCH", `/api/tags/${id}`, { name });
868
+ if (hasFlag(args, "--json")) {
869
+ console.log(JSON.stringify(data, null, 2));
870
+ return;
871
+ }
872
+ console.log(`Updated: ${data.tag.name} (${data.tag.id})`);
873
+ }
874
+ async function deleteTag(args) {
875
+ if (hasFlag(args, "--help", "-h")) {
876
+ console.log(`shiori tags delete - Delete a tag
877
+
878
+ Usage: shiori tags delete <id> [options]
879
+
880
+ Options:
881
+ --json Output raw JSON
882
+ --help, -h Show this help`);
883
+ return;
884
+ }
885
+ const id = getPositional(args);
886
+ if (!id) {
887
+ console.error("Usage: shiori tags delete <id>");
888
+ process.exit(1);
889
+ }
890
+ const { data } = await api("DELETE", `/api/tags/${id}`);
891
+ if (hasFlag(args, "--json")) {
892
+ console.log(JSON.stringify(data, null, 2));
893
+ return;
894
+ }
895
+ console.log("Tag deleted.");
896
+ }
897
+ async function setLinkTags(args) {
898
+ if (hasFlag(args, "--help", "-h")) {
899
+ console.log(`shiori tags set - Set tags on a link
900
+
901
+ Usage: shiori tags set <link-id> <tag1,tag2,...> [options]
902
+
903
+ Pass a comma-separated list of tag names. Tags are replaced, not appended.
904
+ Pass an empty string "" to remove all tags.
905
+
906
+ Options:
907
+ --json Output raw JSON
908
+ --help, -h Show this help`);
909
+ return;
910
+ }
911
+ const positionals = [];
912
+ for (let i = 0; i < args.length; i++) {
913
+ if (args[i].startsWith("--")) {
914
+ i++;
915
+ continue;
916
+ }
917
+ positionals.push(args[i]);
918
+ }
919
+ const linkId = positionals[0];
920
+ const tagNamesRaw = positionals[1];
921
+ if (!linkId) {
922
+ console.error("Usage: shiori tags set <link-id> <tag1,tag2,...>");
923
+ process.exit(1);
924
+ }
925
+ const tagNames = tagNamesRaw === void 0 || tagNamesRaw === "" ? [] : tagNamesRaw.split(",").map((t) => t.trim()).filter(Boolean);
926
+ const tagIds = [];
927
+ if (tagNames.length > 0) {
928
+ const { data: tagsData } = await api("GET", "/api/tags/");
929
+ const tagMap = /* @__PURE__ */ new Map();
930
+ for (const tag of tagsData.tags) {
931
+ tagMap.set(tag.name.toLowerCase(), tag.id);
932
+ }
933
+ const missing = [];
934
+ for (const name of tagNames) {
935
+ const id = tagMap.get(name.toLowerCase());
936
+ if (id) {
937
+ tagIds.push(id);
938
+ } else {
939
+ missing.push(name);
940
+ }
941
+ }
942
+ if (missing.length > 0) {
943
+ console.error(`Tags not found: ${missing.join(", ")}`);
944
+ console.error("Create them first with: shiori tags create <name>");
945
+ process.exit(1);
946
+ }
947
+ }
948
+ const { data } = await api("PUT", `/api/links/${linkId}/tags`, { tagIds });
949
+ if (hasFlag(args, "--json")) {
950
+ console.log(JSON.stringify(data, null, 2));
951
+ return;
952
+ }
953
+ if (tagIds.length === 0) {
954
+ console.log("All tags removed from link.");
955
+ } else {
956
+ console.log(`Set ${tagIds.length} tag${tagIds.length === 1 ? "" : "s"} on link.`);
957
+ }
958
+ }
959
+
747
960
  // src/commands/trash.ts
748
961
  async function run10(args) {
749
962
  if (hasFlag(args, "--help", "-h")) {
@@ -798,20 +1011,42 @@ Trashed links: ${data.links.length} of ${data.total} total
798
1011
  // src/commands/update.ts
799
1012
  async function run11(args) {
800
1013
  if (hasFlag(args, "--help", "-h")) {
801
- console.log(`shiori update - Update a link
1014
+ console.log(`shiori update - Update links
802
1015
 
803
1016
  Usage: shiori update <id> [options]
1017
+ shiori update --ids <id1,id2,...> --read|--unread [options]
804
1018
 
805
1019
  Options:
1020
+ --ids <ids> Comma-separated list of link IDs for bulk update
806
1021
  --read Mark as read
807
1022
  --unread Mark as unread
808
- --title <title> Update title
809
- --summary <text> Update summary
810
- --restore Restore from trash
1023
+ --title <title> Update title (single link only)
1024
+ --summary <text> Update summary (single link only)
1025
+ --restore Restore from trash (single link only)
811
1026
  --json Output raw JSON
812
1027
  --help, -h Show this help`);
813
1028
  return;
814
1029
  }
1030
+ const idsFlag = getFlag(args, "--ids");
1031
+ if (idsFlag) {
1032
+ const ids = idsFlag.split(",").map((id2) => id2.trim()).filter(Boolean);
1033
+ if (ids.length === 0) {
1034
+ console.error("No valid IDs provided.");
1035
+ process.exit(1);
1036
+ }
1037
+ if (!hasFlag(args, "--read") && !hasFlag(args, "--unread")) {
1038
+ console.error("Bulk update requires --read or --unread.");
1039
+ process.exit(1);
1040
+ }
1041
+ const read = hasFlag(args, "--read");
1042
+ const { data: data2 } = await api("PATCH", "/api/links", { ids, read });
1043
+ if (hasFlag(args, "--json")) {
1044
+ console.log(JSON.stringify(data2, null, 2));
1045
+ return;
1046
+ }
1047
+ console.log(`Updated ${data2.updated} link(s).`);
1048
+ return;
1049
+ }
815
1050
  const id = getPositional(args);
816
1051
  if (!id) {
817
1052
  console.error("Usage: shiori update <id> [--read | --unread | --title <title> | --restore]");
@@ -845,6 +1080,34 @@ Options:
845
1080
  console.log(data.message || "Updated.");
846
1081
  }
847
1082
 
1083
+ // src/commands/whoami.ts
1084
+ async function run12(args) {
1085
+ if (hasFlag(args, "--help", "-h")) {
1086
+ console.log(`shiori whoami - Show current user info
1087
+
1088
+ Usage: shiori whoami [options]
1089
+
1090
+ Options:
1091
+ --json Output raw JSON
1092
+ --help, -h Show this help`);
1093
+ return;
1094
+ }
1095
+ const { data } = await api("GET", "/api/user/me");
1096
+ if (hasFlag(args, "--json")) {
1097
+ console.log(JSON.stringify(data, null, 2));
1098
+ return;
1099
+ }
1100
+ const user = data.user;
1101
+ console.log(`
1102
+ Name: ${user.full_name || "--"}`);
1103
+ const plan = user.subscription?.plan === "subscription" ? "Pro" : user.subscription?.plan || "Free";
1104
+ console.log(` Plan: ${plan}`);
1105
+ console.log(
1106
+ ` Member since: ${new Date(user.created_at).toLocaleDateString("en-US", { month: "long", year: "numeric" })}`
1107
+ );
1108
+ console.log();
1109
+ }
1110
+
848
1111
  // src/error-report.ts
849
1112
  function reportError(command, error) {
850
1113
  const raw = error instanceof Error ? error.message : String(error);
@@ -854,7 +1117,7 @@ function reportError(command, error) {
854
1117
  method: "POST",
855
1118
  headers: { "Content-Type": "application/json" },
856
1119
  body: JSON.stringify({
857
- version: "0.4.0",
1120
+ version: "0.7.0",
858
1121
  command,
859
1122
  error: message,
860
1123
  platform: process.platform
@@ -896,15 +1159,16 @@ async function checkForUpdate(currentVersion, isJson) {
896
1159
  var COMMANDS = {
897
1160
  auth: { run, desc: "Authenticate with Shiori (browser or API key)" },
898
1161
  list: { run: run5, desc: "List saved links" },
899
- search: { run: run8, desc: "Search saved links" },
1162
+ search: { run: run7, desc: "Search saved links" },
900
1163
  get: { run: run4, desc: "Get a link by ID (includes content)" },
901
1164
  content: { run: run2, desc: "Print link content as markdown" },
902
- save: { run: run7, desc: "Save a new link" },
1165
+ save: { run: run6, desc: "Save a new link" },
903
1166
  update: { run: run11, desc: "Update a link" },
904
1167
  delete: { run: run3, desc: "Delete a link (move to trash)" },
905
1168
  trash: { run: run10, desc: "List or empty the trash" },
906
- subscriptions: { run: run9, desc: "Manage RSS subscriptions" },
907
- me: { run: run6, desc: "Show current user info" }
1169
+ subscriptions: { run: run8, desc: "Manage RSS subscriptions" },
1170
+ tags: { run: run9, desc: "Manage tags" },
1171
+ whoami: { run: run12, desc: "Show current user info" }
908
1172
  };
909
1173
  function printHelp() {
910
1174
  console.log(`shiori - Manage your Shiori link library from the terminal
@@ -923,7 +1187,8 @@ Get started:
923
1187
  shiori list List your recent links
924
1188
  shiori save <url> Save a new link
925
1189
  shiori search <query> Search your links
926
- shiori content <id> Print markdown content (pipe to other tools)`);
1190
+ shiori content <id> Print markdown content (pipe to other tools)
1191
+ shiori whoami Show your account info`);
927
1192
  }
928
1193
  async function main() {
929
1194
  const args = process.argv.slice(2);
@@ -933,7 +1198,7 @@ async function main() {
933
1198
  return;
934
1199
  }
935
1200
  if (command === "--version" || command === "-v") {
936
- console.log("0.4.0");
1201
+ console.log("0.7.0");
937
1202
  return;
938
1203
  }
939
1204
  const cmd = COMMANDS[command];
@@ -946,7 +1211,7 @@ async function main() {
946
1211
  const cmdArgs = args.slice(1);
947
1212
  const isJson = cmdArgs.includes("--json");
948
1213
  await cmd.run(cmdArgs);
949
- checkForUpdate("0.4.0", isJson).catch(() => {
1214
+ checkForUpdate("0.7.0", isJson).catch(() => {
950
1215
  });
951
1216
  }
952
1217
  main().catch((err) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shiori-sh/cli",
3
- "version": "0.4.0",
3
+ "version": "0.7.0",
4
4
  "description": "CLI for managing your Shiori link library",
5
5
  "author": "Brian Lovin",
6
6
  "license": "MIT",