@jackwener/opencli 0.6.3 → 0.7.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.
Files changed (81) hide show
  1. package/LICENSE +190 -28
  2. package/README.md +4 -4
  3. package/README.zh-CN.md +4 -4
  4. package/SKILL.md +22 -6
  5. package/dist/browser.js +2 -3
  6. package/dist/build-manifest.js +2 -0
  7. package/dist/cli-manifest.json +604 -24
  8. package/dist/clis/reddit/comment.d.ts +1 -0
  9. package/dist/clis/reddit/comment.js +57 -0
  10. package/dist/clis/reddit/popular.yaml +40 -0
  11. package/dist/clis/reddit/read.yaml +76 -0
  12. package/dist/clis/reddit/save.d.ts +1 -0
  13. package/dist/clis/reddit/save.js +51 -0
  14. package/dist/clis/reddit/saved.d.ts +1 -0
  15. package/dist/clis/reddit/saved.js +46 -0
  16. package/dist/clis/reddit/search.yaml +37 -11
  17. package/dist/clis/reddit/subreddit.yaml +14 -4
  18. package/dist/clis/reddit/subscribe.d.ts +1 -0
  19. package/dist/clis/reddit/subscribe.js +50 -0
  20. package/dist/clis/reddit/upvote.d.ts +1 -0
  21. package/dist/clis/reddit/upvote.js +64 -0
  22. package/dist/clis/reddit/upvoted.d.ts +1 -0
  23. package/dist/clis/reddit/upvoted.js +46 -0
  24. package/dist/clis/reddit/user-comments.yaml +45 -0
  25. package/dist/clis/reddit/user-posts.yaml +43 -0
  26. package/dist/clis/reddit/user.yaml +39 -0
  27. package/dist/clis/twitter/article.d.ts +1 -0
  28. package/dist/clis/twitter/article.js +157 -0
  29. package/dist/clis/twitter/bookmark.d.ts +1 -0
  30. package/dist/clis/twitter/bookmark.js +63 -0
  31. package/dist/clis/twitter/follow.d.ts +1 -0
  32. package/dist/clis/twitter/follow.js +65 -0
  33. package/dist/clis/twitter/profile.js +110 -42
  34. package/dist/clis/twitter/thread.d.ts +1 -0
  35. package/dist/clis/twitter/thread.js +150 -0
  36. package/dist/clis/twitter/unbookmark.d.ts +1 -0
  37. package/dist/clis/twitter/unbookmark.js +62 -0
  38. package/dist/clis/twitter/unfollow.d.ts +1 -0
  39. package/dist/clis/twitter/unfollow.js +71 -0
  40. package/dist/engine.js +2 -1
  41. package/dist/main.js +41 -10
  42. package/dist/output.js +2 -1
  43. package/dist/registry.d.ts +2 -8
  44. package/dist/snapshotFormatter.d.ts +9 -0
  45. package/dist/snapshotFormatter.js +352 -15
  46. package/dist/snapshotFormatter.test.d.ts +7 -0
  47. package/dist/snapshotFormatter.test.js +521 -0
  48. package/dist/validate.d.ts +14 -2
  49. package/dist/verify.d.ts +14 -2
  50. package/package.json +2 -2
  51. package/src/browser.ts +2 -4
  52. package/src/build-manifest.ts +3 -0
  53. package/src/clis/reddit/comment.ts +60 -0
  54. package/src/clis/reddit/popular.yaml +40 -0
  55. package/src/clis/reddit/read.yaml +76 -0
  56. package/src/clis/reddit/save.ts +54 -0
  57. package/src/clis/reddit/saved.ts +48 -0
  58. package/src/clis/reddit/search.yaml +37 -11
  59. package/src/clis/reddit/subreddit.yaml +14 -4
  60. package/src/clis/reddit/subscribe.ts +53 -0
  61. package/src/clis/reddit/upvote.ts +67 -0
  62. package/src/clis/reddit/upvoted.ts +48 -0
  63. package/src/clis/reddit/user-comments.yaml +45 -0
  64. package/src/clis/reddit/user-posts.yaml +43 -0
  65. package/src/clis/reddit/user.yaml +39 -0
  66. package/src/clis/twitter/article.ts +161 -0
  67. package/src/clis/twitter/bookmark.ts +67 -0
  68. package/src/clis/twitter/follow.ts +69 -0
  69. package/src/clis/twitter/profile.ts +113 -45
  70. package/src/clis/twitter/thread.ts +181 -0
  71. package/src/clis/twitter/unbookmark.ts +66 -0
  72. package/src/clis/twitter/unfollow.ts +75 -0
  73. package/src/engine.ts +4 -1
  74. package/src/main.ts +34 -7
  75. package/src/output.ts +2 -1
  76. package/src/registry.ts +2 -8
  77. package/src/snapshotFormatter.test.ts +579 -0
  78. package/src/snapshotFormatter.ts +399 -13
  79. package/src/validate.ts +19 -4
  80. package/src/verify.ts +17 -3
  81. package/vitest.config.ts +15 -1
@@ -680,6 +680,34 @@
680
680
  ],
681
681
  "type": "yaml"
682
682
  },
683
+ {
684
+ "site": "reddit",
685
+ "name": "comment",
686
+ "description": "Post a comment on a Reddit post",
687
+ "strategy": "cookie",
688
+ "browser": true,
689
+ "args": [
690
+ {
691
+ "name": "post_id",
692
+ "type": "string",
693
+ "required": true,
694
+ "help": "Post ID (e.g. 1abc123) or fullname (t3_xxx)"
695
+ },
696
+ {
697
+ "name": "text",
698
+ "type": "string",
699
+ "required": true,
700
+ "help": "Comment text"
701
+ }
702
+ ],
703
+ "type": "ts",
704
+ "modulePath": "reddit/comment.js",
705
+ "domain": "reddit.com",
706
+ "columns": [
707
+ "status",
708
+ "message"
709
+ ]
710
+ },
683
711
  {
684
712
  "site": "reddit",
685
713
  "name": "frontpage",
@@ -779,6 +807,161 @@
779
807
  ],
780
808
  "type": "yaml"
781
809
  },
810
+ {
811
+ "site": "reddit",
812
+ "name": "popular",
813
+ "description": "Reddit Popular posts (/r/popular)",
814
+ "domain": "reddit.com",
815
+ "strategy": "cookie",
816
+ "browser": true,
817
+ "args": [
818
+ {
819
+ "name": "limit",
820
+ "type": "int",
821
+ "default": 20,
822
+ "required": false,
823
+ "help": ""
824
+ }
825
+ ],
826
+ "columns": [
827
+ "rank",
828
+ "title",
829
+ "subreddit",
830
+ "score",
831
+ "comments",
832
+ "url"
833
+ ],
834
+ "pipeline": [
835
+ {
836
+ "navigate": "https://www.reddit.com"
837
+ },
838
+ {
839
+ "evaluate": "(async () => {\n const limit = ${{ args.limit }};\n const res = await fetch('/r/popular.json?limit=' + limit + '&raw_json=1', {\n credentials: 'include'\n });\n const d = await res.json();\n return (d?.data?.children || []).map(c => ({\n title: c.data.title,\n subreddit: c.data.subreddit_name_prefixed,\n score: c.data.score,\n comments: c.data.num_comments,\n author: c.data.author,\n url: 'https://www.reddit.com' + c.data.permalink,\n }));\n})()\n"
840
+ },
841
+ {
842
+ "map": {
843
+ "rank": "${{ index + 1 }}",
844
+ "title": "${{ item.title }}",
845
+ "subreddit": "${{ item.subreddit }}",
846
+ "score": "${{ item.score }}",
847
+ "comments": "${{ item.comments }}",
848
+ "url": "${{ item.url }}"
849
+ }
850
+ },
851
+ {
852
+ "limit": "${{ args.limit }}"
853
+ }
854
+ ],
855
+ "type": "yaml"
856
+ },
857
+ {
858
+ "site": "reddit",
859
+ "name": "read",
860
+ "description": "Read a Reddit post and its comments",
861
+ "domain": "reddit.com",
862
+ "strategy": "cookie",
863
+ "browser": true,
864
+ "args": [
865
+ {
866
+ "name": "post_id",
867
+ "type": "string",
868
+ "required": true,
869
+ "help": "Post ID (e.g. 1abc123) or full URL"
870
+ },
871
+ {
872
+ "name": "sort",
873
+ "type": "string",
874
+ "default": "best",
875
+ "required": false,
876
+ "help": "Comment sort: best, top, new, controversial, old, qa"
877
+ },
878
+ {
879
+ "name": "limit",
880
+ "type": "int",
881
+ "default": 25,
882
+ "required": false,
883
+ "help": "Number of top-level comments to fetch"
884
+ }
885
+ ],
886
+ "columns": [
887
+ "type",
888
+ "author",
889
+ "score",
890
+ "text"
891
+ ],
892
+ "pipeline": [
893
+ {
894
+ "navigate": "https://www.reddit.com"
895
+ },
896
+ {
897
+ "evaluate": "(async () => {\n let postId = ${{ args.post_id | json }};\n const urlMatch = postId.match(/comments\\/([a-z0-9]+)/);\n if (urlMatch) postId = urlMatch[1];\n\n const sort = ${{ args.sort | json }};\n const limit = ${{ args.limit }};\n const res = await fetch('/comments/' + postId + '.json?sort=' + sort + '&limit=' + limit + '&raw_json=1', {\n credentials: 'include'\n });\n const data = await res.json();\n if (!Array.isArray(data) || data.length < 1) return [];\n\n const results = [];\n\n // First element: post itself\n const post = data[0]?.data?.children?.[0]?.data;\n if (post) {\n let body = post.selftext || '';\n if (body.length > 2000) body = body.slice(0, 2000) + '\\n... [truncated]';\n results.push({\n type: '📰 POST',\n author: post.author,\n score: post.score,\n text: post.title + (body ? '\\n\\n' + body : '') + (post.url && !post.is_self ? '\\n🔗 ' + post.url : ''),\n });\n }\n\n // Second element: comments\n const comments = data[1]?.data?.children || [];\n for (const c of comments) {\n if (c.kind !== 't1') continue;\n const d = c.data;\n let body = d.body || '';\n if (body.length > 500) body = body.slice(0, 500) + '...';\n results.push({\n type: '💬 COMMENT',\n author: d.author || '[deleted]',\n score: d.score || 0,\n text: body,\n });\n }\n\n return results;\n})()\n"
898
+ },
899
+ {
900
+ "map": {
901
+ "type": "${{ item.type }}",
902
+ "author": "${{ item.author }}",
903
+ "score": "${{ item.score }}",
904
+ "text": "${{ item.text }}"
905
+ }
906
+ }
907
+ ],
908
+ "type": "yaml"
909
+ },
910
+ {
911
+ "site": "reddit",
912
+ "name": "save",
913
+ "description": "Save or unsave a Reddit post",
914
+ "strategy": "cookie",
915
+ "browser": true,
916
+ "args": [
917
+ {
918
+ "name": "post_id",
919
+ "type": "string",
920
+ "required": true,
921
+ "help": "Post ID (e.g. 1abc123) or fullname (t3_xxx)"
922
+ },
923
+ {
924
+ "name": "undo",
925
+ "type": "boolean",
926
+ "default": false,
927
+ "required": false,
928
+ "help": "Unsave instead of save"
929
+ }
930
+ ],
931
+ "type": "ts",
932
+ "modulePath": "reddit/save.js",
933
+ "domain": "reddit.com",
934
+ "columns": [
935
+ "status",
936
+ "message"
937
+ ]
938
+ },
939
+ {
940
+ "site": "reddit",
941
+ "name": "saved",
942
+ "description": "Browse your saved Reddit posts",
943
+ "strategy": "cookie",
944
+ "browser": true,
945
+ "args": [
946
+ {
947
+ "name": "limit",
948
+ "type": "int",
949
+ "default": 15,
950
+ "required": false,
951
+ "help": ""
952
+ }
953
+ ],
954
+ "type": "ts",
955
+ "modulePath": "reddit/saved.js",
956
+ "domain": "reddit.com",
957
+ "columns": [
958
+ "title",
959
+ "subreddit",
960
+ "score",
961
+ "comments",
962
+ "url"
963
+ ]
964
+ },
782
965
  {
783
966
  "site": "reddit",
784
967
  "name": "search",
@@ -793,6 +976,27 @@
793
976
  "required": true,
794
977
  "help": ""
795
978
  },
979
+ {
980
+ "name": "subreddit",
981
+ "type": "string",
982
+ "default": "",
983
+ "required": false,
984
+ "help": "Search within a specific subreddit"
985
+ },
986
+ {
987
+ "name": "sort",
988
+ "type": "string",
989
+ "default": "relevance",
990
+ "required": false,
991
+ "help": "Sort order: relevance, hot, top, new, comments"
992
+ },
993
+ {
994
+ "name": "time",
995
+ "type": "string",
996
+ "default": "all",
997
+ "required": false,
998
+ "help": "Time filter: hour, day, week, month, year, all"
999
+ },
796
1000
  {
797
1001
  "name": "limit",
798
1002
  "type": "int",
@@ -805,7 +1009,7 @@
805
1009
  "title",
806
1010
  "subreddit",
807
1011
  "author",
808
- "upvotes",
1012
+ "score",
809
1013
  "comments",
810
1014
  "url"
811
1015
  ],
@@ -814,16 +1018,16 @@
814
1018
  "navigate": "https://www.reddit.com"
815
1019
  },
816
1020
  {
817
- "evaluate": "(async () => {\n const q = encodeURIComponent('${{ args.query }}');\n const res = await fetch('/search.json?q=' + q + '&limit=${{ args.limit }}', { credentials: 'include' });\n const j = await res.json();\n return j?.data?.children || [];\n})()\n"
1021
+ "evaluate": "(async () => {\n const q = encodeURIComponent(${{ args.query | json }});\n const sub = ${{ args.subreddit | json }};\n const sort = ${{ args.sort | json }};\n const time = ${{ args.time | json }};\n const limit = ${{ args.limit }};\n const basePath = sub ? '/r/' + sub + '/search.json' : '/search.json';\n const params = 'q=' + q + '&sort=' + sort + '&t=' + time + '&limit=' + limit\n + '&restrict_sr=' + (sub ? 'on' : 'off') + '&raw_json=1';\n const res = await fetch(basePath + '?' + params, { credentials: 'include' });\n const d = await res.json();\n return (d?.data?.children || []).map(c => ({\n title: c.data.title,\n subreddit: c.data.subreddit_name_prefixed,\n author: c.data.author,\n score: c.data.score,\n comments: c.data.num_comments,\n url: 'https://www.reddit.com' + c.data.permalink,\n }));\n})()\n"
818
1022
  },
819
1023
  {
820
1024
  "map": {
821
- "title": "${{ item.data.title }}",
822
- "subreddit": "${{ item.data.subreddit_name_prefixed }}",
823
- "author": "${{ item.data.author }}",
824
- "upvotes": "${{ item.data.score }}",
825
- "comments": "${{ item.data.num_comments }}",
826
- "url": "https://www.reddit.com${{ item.data.permalink }}"
1025
+ "title": "${{ item.title }}",
1026
+ "subreddit": "${{ item.subreddit }}",
1027
+ "author": "${{ item.author }}",
1028
+ "score": "${{ item.score }}",
1029
+ "comments": "${{ item.comments }}",
1030
+ "url": "${{ item.url }}"
827
1031
  }
828
1032
  },
829
1033
  {
@@ -851,7 +1055,14 @@
851
1055
  "type": "string",
852
1056
  "default": "hot",
853
1057
  "required": false,
854
- "help": "Sorting method: hot, new, top, rising"
1058
+ "help": "Sorting method: hot, new, top, rising, controversial"
1059
+ },
1060
+ {
1061
+ "name": "time",
1062
+ "type": "string",
1063
+ "default": "all",
1064
+ "required": false,
1065
+ "help": "Time filter for top/controversial: hour, day, week, month, year, all"
855
1066
  },
856
1067
  {
857
1068
  "name": "limit",
@@ -873,7 +1084,7 @@
873
1084
  "navigate": "https://www.reddit.com"
874
1085
  },
875
1086
  {
876
- "evaluate": "(async () => {\n let sub = '${{ args.name }}';\n if (sub.startsWith('r/')) sub = sub.slice(2);\n const sort = '${{ args.sort }}';\n const res = await fetch('/r/' + sub + '/' + sort + '.json?limit=${{ args.limit }}', { credentials: 'include' });\n const j = await res.json();\n return j?.data?.children || [];\n})()\n"
1087
+ "evaluate": "(async () => {\n let sub = ${{ args.name | json }};\n if (sub.startsWith('r/')) sub = sub.slice(2);\n const sort = ${{ args.sort | json }};\n const time = ${{ args.time | json }};\n const limit = ${{ args.limit }};\n let url = '/r/' + sub + '/' + sort + '.json?limit=' + limit + '&raw_json=1';\n if ((sort === 'top' || sort === 'controversial') && time) {\n url += '&t=' + time;\n }\n const res = await fetch(url, { credentials: 'include' });\n const j = await res.json();\n return j?.data?.children || [];\n})()\n"
877
1088
  },
878
1089
  {
879
1090
  "map": {
@@ -890,6 +1101,225 @@
890
1101
  ],
891
1102
  "type": "yaml"
892
1103
  },
1104
+ {
1105
+ "site": "reddit",
1106
+ "name": "subscribe",
1107
+ "description": "Subscribe or unsubscribe to a subreddit",
1108
+ "strategy": "cookie",
1109
+ "browser": true,
1110
+ "args": [
1111
+ {
1112
+ "name": "subreddit",
1113
+ "type": "string",
1114
+ "required": true,
1115
+ "help": "Subreddit name (e.g. python)"
1116
+ },
1117
+ {
1118
+ "name": "undo",
1119
+ "type": "boolean",
1120
+ "default": false,
1121
+ "required": false,
1122
+ "help": "Unsubscribe instead of subscribe"
1123
+ }
1124
+ ],
1125
+ "type": "ts",
1126
+ "modulePath": "reddit/subscribe.js",
1127
+ "domain": "reddit.com",
1128
+ "columns": [
1129
+ "status",
1130
+ "message"
1131
+ ]
1132
+ },
1133
+ {
1134
+ "site": "reddit",
1135
+ "name": "upvote",
1136
+ "description": "Upvote or downvote a Reddit post",
1137
+ "strategy": "cookie",
1138
+ "browser": true,
1139
+ "args": [
1140
+ {
1141
+ "name": "post_id",
1142
+ "type": "string",
1143
+ "required": true,
1144
+ "help": "Post ID (e.g. 1abc123) or fullname (t3_xxx)"
1145
+ },
1146
+ {
1147
+ "name": "direction",
1148
+ "type": "string",
1149
+ "default": "up",
1150
+ "required": false,
1151
+ "help": "Vote direction: up, down, none"
1152
+ }
1153
+ ],
1154
+ "type": "ts",
1155
+ "modulePath": "reddit/upvote.js",
1156
+ "domain": "reddit.com",
1157
+ "columns": [
1158
+ "status",
1159
+ "message"
1160
+ ]
1161
+ },
1162
+ {
1163
+ "site": "reddit",
1164
+ "name": "upvoted",
1165
+ "description": "Browse your upvoted Reddit posts",
1166
+ "strategy": "cookie",
1167
+ "browser": true,
1168
+ "args": [
1169
+ {
1170
+ "name": "limit",
1171
+ "type": "int",
1172
+ "default": 15,
1173
+ "required": false,
1174
+ "help": ""
1175
+ }
1176
+ ],
1177
+ "type": "ts",
1178
+ "modulePath": "reddit/upvoted.js",
1179
+ "domain": "reddit.com",
1180
+ "columns": [
1181
+ "title",
1182
+ "subreddit",
1183
+ "score",
1184
+ "comments",
1185
+ "url"
1186
+ ]
1187
+ },
1188
+ {
1189
+ "site": "reddit",
1190
+ "name": "user-comments",
1191
+ "description": "View a Reddit user's comment history",
1192
+ "domain": "reddit.com",
1193
+ "strategy": "cookie",
1194
+ "browser": true,
1195
+ "args": [
1196
+ {
1197
+ "name": "username",
1198
+ "type": "string",
1199
+ "required": true,
1200
+ "help": ""
1201
+ },
1202
+ {
1203
+ "name": "limit",
1204
+ "type": "int",
1205
+ "default": 15,
1206
+ "required": false,
1207
+ "help": ""
1208
+ }
1209
+ ],
1210
+ "columns": [
1211
+ "subreddit",
1212
+ "score",
1213
+ "body",
1214
+ "url"
1215
+ ],
1216
+ "pipeline": [
1217
+ {
1218
+ "navigate": "https://www.reddit.com"
1219
+ },
1220
+ {
1221
+ "evaluate": "(async () => {\n const username = ${{ args.username | json }};\n const name = username.startsWith('u/') ? username.slice(2) : username;\n const limit = ${{ args.limit }};\n const res = await fetch('/user/' + name + '/comments.json?limit=' + limit + '&raw_json=1', {\n credentials: 'include'\n });\n const d = await res.json();\n return (d?.data?.children || []).map(c => {\n let body = c.data.body || '';\n if (body.length > 300) body = body.slice(0, 300) + '...';\n return {\n subreddit: c.data.subreddit_name_prefixed,\n score: c.data.score,\n body: body,\n url: 'https://www.reddit.com' + c.data.permalink,\n };\n });\n})()\n"
1222
+ },
1223
+ {
1224
+ "map": {
1225
+ "subreddit": "${{ item.subreddit }}",
1226
+ "score": "${{ item.score }}",
1227
+ "body": "${{ item.body }}",
1228
+ "url": "${{ item.url }}"
1229
+ }
1230
+ },
1231
+ {
1232
+ "limit": "${{ args.limit }}"
1233
+ }
1234
+ ],
1235
+ "type": "yaml"
1236
+ },
1237
+ {
1238
+ "site": "reddit",
1239
+ "name": "user-posts",
1240
+ "description": "View a Reddit user's submitted posts",
1241
+ "domain": "reddit.com",
1242
+ "strategy": "cookie",
1243
+ "browser": true,
1244
+ "args": [
1245
+ {
1246
+ "name": "username",
1247
+ "type": "string",
1248
+ "required": true,
1249
+ "help": ""
1250
+ },
1251
+ {
1252
+ "name": "limit",
1253
+ "type": "int",
1254
+ "default": 15,
1255
+ "required": false,
1256
+ "help": ""
1257
+ }
1258
+ ],
1259
+ "columns": [
1260
+ "title",
1261
+ "subreddit",
1262
+ "score",
1263
+ "comments",
1264
+ "url"
1265
+ ],
1266
+ "pipeline": [
1267
+ {
1268
+ "navigate": "https://www.reddit.com"
1269
+ },
1270
+ {
1271
+ "evaluate": "(async () => {\n const username = ${{ args.username | json }};\n const name = username.startsWith('u/') ? username.slice(2) : username;\n const limit = ${{ args.limit }};\n const res = await fetch('/user/' + name + '/submitted.json?limit=' + limit + '&raw_json=1', {\n credentials: 'include'\n });\n const d = await res.json();\n return (d?.data?.children || []).map(c => ({\n title: c.data.title,\n subreddit: c.data.subreddit_name_prefixed,\n score: c.data.score,\n comments: c.data.num_comments,\n url: 'https://www.reddit.com' + c.data.permalink,\n }));\n})()\n"
1272
+ },
1273
+ {
1274
+ "map": {
1275
+ "title": "${{ item.title }}",
1276
+ "subreddit": "${{ item.subreddit }}",
1277
+ "score": "${{ item.score }}",
1278
+ "comments": "${{ item.comments }}",
1279
+ "url": "${{ item.url }}"
1280
+ }
1281
+ },
1282
+ {
1283
+ "limit": "${{ args.limit }}"
1284
+ }
1285
+ ],
1286
+ "type": "yaml"
1287
+ },
1288
+ {
1289
+ "site": "reddit",
1290
+ "name": "user",
1291
+ "description": "View a Reddit user profile",
1292
+ "domain": "reddit.com",
1293
+ "strategy": "cookie",
1294
+ "browser": true,
1295
+ "args": [
1296
+ {
1297
+ "name": "username",
1298
+ "type": "string",
1299
+ "required": true,
1300
+ "help": ""
1301
+ }
1302
+ ],
1303
+ "columns": [
1304
+ "field",
1305
+ "value"
1306
+ ],
1307
+ "pipeline": [
1308
+ {
1309
+ "navigate": "https://www.reddit.com"
1310
+ },
1311
+ {
1312
+ "evaluate": "(async () => {\n const username = ${{ args.username | json }};\n const name = username.startsWith('u/') ? username.slice(2) : username;\n const res = await fetch('/user/' + name + '/about.json?raw_json=1', {\n credentials: 'include'\n });\n const d = await res.json();\n const u = d?.data || d || {};\n const created = u.created_utc ? new Date(u.created_utc * 1000).toISOString().split('T')[0] : '-';\n return [\n { field: 'Username', value: 'u/' + (u.name || name) },\n { field: 'Post Karma', value: String(u.link_karma || 0) },\n { field: 'Comment Karma', value: String(u.comment_karma || 0) },\n { field: 'Total Karma', value: String(u.total_karma || (u.link_karma||0) + (u.comment_karma||0)) },\n { field: 'Account Created', value: created },\n { field: 'Gold', value: u.is_gold ? '⭐ Yes' : 'No' },\n { field: 'Verified', value: u.verified ? '✅ Yes' : 'No' },\n ];\n})()\n"
1313
+ },
1314
+ {
1315
+ "map": {
1316
+ "field": "${{ item.field }}",
1317
+ "value": "${{ item.value }}"
1318
+ }
1319
+ }
1320
+ ],
1321
+ "type": "yaml"
1322
+ },
893
1323
  {
894
1324
  "site": "reuters",
895
1325
  "name": "search",
@@ -955,6 +1385,54 @@
955
1385
  "url"
956
1386
  ]
957
1387
  },
1388
+ {
1389
+ "site": "twitter",
1390
+ "name": "article",
1391
+ "description": "Fetch a Twitter Article (long-form content) and export as Markdown",
1392
+ "strategy": "cookie",
1393
+ "browser": true,
1394
+ "args": [
1395
+ {
1396
+ "name": "tweet_id",
1397
+ "type": "string",
1398
+ "required": true,
1399
+ "positional": true,
1400
+ "help": "Tweet ID or URL containing the article"
1401
+ }
1402
+ ],
1403
+ "type": "ts",
1404
+ "modulePath": "twitter/article.js",
1405
+ "domain": "x.com",
1406
+ "columns": [
1407
+ "title",
1408
+ "author",
1409
+ "content",
1410
+ "url"
1411
+ ]
1412
+ },
1413
+ {
1414
+ "site": "twitter",
1415
+ "name": "bookmark",
1416
+ "description": "Bookmark a tweet",
1417
+ "strategy": "ui",
1418
+ "browser": true,
1419
+ "args": [
1420
+ {
1421
+ "name": "url",
1422
+ "type": "string",
1423
+ "required": true,
1424
+ "positional": true,
1425
+ "help": "Tweet URL to bookmark"
1426
+ }
1427
+ ],
1428
+ "type": "ts",
1429
+ "modulePath": "twitter/bookmark.js",
1430
+ "domain": "x.com",
1431
+ "columns": [
1432
+ "status",
1433
+ "message"
1434
+ ]
1435
+ },
958
1436
  {
959
1437
  "site": "twitter",
960
1438
  "name": "bookmarks",
@@ -1023,6 +1501,29 @@
1023
1501
  "message"
1024
1502
  ]
1025
1503
  },
1504
+ {
1505
+ "site": "twitter",
1506
+ "name": "follow",
1507
+ "description": "Follow a Twitter user",
1508
+ "strategy": "ui",
1509
+ "browser": true,
1510
+ "args": [
1511
+ {
1512
+ "name": "username",
1513
+ "type": "string",
1514
+ "required": true,
1515
+ "positional": true,
1516
+ "help": "Twitter screen name (without @)"
1517
+ }
1518
+ ],
1519
+ "type": "ts",
1520
+ "modulePath": "twitter/follow.js",
1521
+ "domain": "x.com",
1522
+ "columns": [
1523
+ "status",
1524
+ "message"
1525
+ ]
1526
+ },
1026
1527
  {
1027
1528
  "site": "twitter",
1028
1529
  "name": "followers",
@@ -1159,33 +1660,33 @@
1159
1660
  {
1160
1661
  "site": "twitter",
1161
1662
  "name": "profile",
1162
- "description": "Fetch tweets from a user profile",
1163
- "strategy": "intercept",
1663
+ "description": "Fetch a Twitter user profile (bio, stats, etc.)",
1664
+ "strategy": "cookie",
1164
1665
  "browser": true,
1165
1666
  "args": [
1166
1667
  {
1167
1668
  "name": "username",
1168
1669
  "type": "string",
1169
- "required": true,
1170
- "help": ""
1171
- },
1172
- {
1173
- "name": "limit",
1174
- "type": "int",
1175
- "default": 15,
1176
1670
  "required": false,
1177
- "help": ""
1671
+ "positional": true,
1672
+ "help": "Twitter screen name (without @). Defaults to logged-in user."
1178
1673
  }
1179
1674
  ],
1180
1675
  "type": "ts",
1181
1676
  "modulePath": "twitter/profile.js",
1182
1677
  "domain": "x.com",
1183
1678
  "columns": [
1184
- "id",
1185
- "text",
1679
+ "screen_name",
1680
+ "name",
1681
+ "bio",
1682
+ "location",
1683
+ "url",
1684
+ "followers",
1685
+ "following",
1686
+ "tweets",
1186
1687
  "likes",
1187
- "views",
1188
- "url"
1688
+ "verified",
1689
+ "created_at"
1189
1690
  ]
1190
1691
  },
1191
1692
  {
@@ -1250,6 +1751,39 @@
1250
1751
  "url"
1251
1752
  ]
1252
1753
  },
1754
+ {
1755
+ "site": "twitter",
1756
+ "name": "thread",
1757
+ "description": "Get a tweet thread (original + all replies)",
1758
+ "strategy": "cookie",
1759
+ "browser": true,
1760
+ "args": [
1761
+ {
1762
+ "name": "tweet_id",
1763
+ "type": "string",
1764
+ "required": true,
1765
+ "help": ""
1766
+ },
1767
+ {
1768
+ "name": "limit",
1769
+ "type": "int",
1770
+ "default": 50,
1771
+ "required": false,
1772
+ "help": ""
1773
+ }
1774
+ ],
1775
+ "type": "ts",
1776
+ "modulePath": "twitter/thread.js",
1777
+ "domain": "x.com",
1778
+ "columns": [
1779
+ "id",
1780
+ "author",
1781
+ "text",
1782
+ "likes",
1783
+ "retweets",
1784
+ "url"
1785
+ ]
1786
+ },
1253
1787
  {
1254
1788
  "site": "twitter",
1255
1789
  "name": "timeline",
@@ -1314,6 +1848,52 @@
1314
1848
  ],
1315
1849
  "type": "yaml"
1316
1850
  },
1851
+ {
1852
+ "site": "twitter",
1853
+ "name": "unbookmark",
1854
+ "description": "Remove a tweet from bookmarks",
1855
+ "strategy": "ui",
1856
+ "browser": true,
1857
+ "args": [
1858
+ {
1859
+ "name": "url",
1860
+ "type": "string",
1861
+ "required": true,
1862
+ "positional": true,
1863
+ "help": "Tweet URL to unbookmark"
1864
+ }
1865
+ ],
1866
+ "type": "ts",
1867
+ "modulePath": "twitter/unbookmark.js",
1868
+ "domain": "x.com",
1869
+ "columns": [
1870
+ "status",
1871
+ "message"
1872
+ ]
1873
+ },
1874
+ {
1875
+ "site": "twitter",
1876
+ "name": "unfollow",
1877
+ "description": "Unfollow a Twitter user",
1878
+ "strategy": "ui",
1879
+ "browser": true,
1880
+ "args": [
1881
+ {
1882
+ "name": "username",
1883
+ "type": "string",
1884
+ "required": true,
1885
+ "positional": true,
1886
+ "help": "Twitter screen name (without @)"
1887
+ }
1888
+ ],
1889
+ "type": "ts",
1890
+ "modulePath": "twitter/unfollow.js",
1891
+ "domain": "x.com",
1892
+ "columns": [
1893
+ "status",
1894
+ "message"
1895
+ ]
1896
+ },
1317
1897
  {
1318
1898
  "site": "v2ex",
1319
1899
  "name": "daily",