@biggora/claude-plugins 1.0.0 → 1.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 (80) hide show
  1. package/.claude/settings.local.json +13 -0
  2. package/CLAUDE.md +55 -0
  3. package/LICENSE +1 -1
  4. package/README.md +208 -39
  5. package/bin/cli.js +39 -0
  6. package/package.json +30 -17
  7. package/registry/registry.json +166 -1
  8. package/registry/schema.json +10 -0
  9. package/src/commands/skills/add.js +194 -0
  10. package/src/commands/skills/list.js +52 -0
  11. package/src/commands/skills/remove.js +27 -0
  12. package/src/commands/skills/update.js +74 -0
  13. package/src/config.js +5 -0
  14. package/src/skills/codex-cli/SKILL.md +265 -0
  15. package/src/skills/commafeed-api/SKILL.md +1012 -0
  16. package/src/skills/gemini-cli/SKILL.md +379 -0
  17. package/src/skills/gemini-cli/references/commands.md +145 -0
  18. package/src/skills/gemini-cli/references/configuration.md +182 -0
  19. package/src/skills/gemini-cli/references/headless-and-scripting.md +181 -0
  20. package/src/skills/gemini-cli/references/mcp-and-extensions.md +254 -0
  21. package/src/skills/n8n-api/SKILL.md +623 -0
  22. package/src/skills/notebook-lm/SKILL.md +217 -0
  23. package/src/skills/notebook-lm/references/artifact-options.md +168 -0
  24. package/src/skills/notebook-lm/references/auth.md +58 -0
  25. package/src/skills/notebook-lm/references/workflows.md +144 -0
  26. package/src/skills/screen-recording/SKILL.md +309 -0
  27. package/src/skills/screen-recording/references/approach1-programmatic.md +311 -0
  28. package/src/skills/screen-recording/references/approach2-xvfb.md +232 -0
  29. package/src/skills/screen-recording/references/design-patterns.md +168 -0
  30. package/src/skills/test-mobile-app/SKILL.md +212 -0
  31. package/src/skills/test-mobile-app/references/report-template.md +95 -0
  32. package/src/skills/test-mobile-app/references/setup-appium.md +154 -0
  33. package/src/skills/test-mobile-app/scripts/analyze_apk.py +164 -0
  34. package/src/skills/test-mobile-app/scripts/check_environment.py +116 -0
  35. package/src/skills/test-mobile-app/scripts/generate_report.py +250 -0
  36. package/src/skills/test-mobile-app/scripts/run_tests.py +326 -0
  37. package/src/skills/test-web-ui/SKILL.md +232 -0
  38. package/src/skills/test-web-ui/references/test_case_schema.md +102 -0
  39. package/src/skills/test-web-ui/scripts/discover.py +176 -0
  40. package/src/skills/test-web-ui/scripts/generate_report.py +237 -0
  41. package/src/skills/test-web-ui/scripts/run_tests.py +296 -0
  42. package/src/skills/text-to-speech/SKILL.md +236 -0
  43. package/src/skills/text-to-speech/references/espeak-cli.md +277 -0
  44. package/src/skills/text-to-speech/references/kokoro-onnx.md +124 -0
  45. package/src/skills/text-to-speech/references/online-engines.md +128 -0
  46. package/src/skills/text-to-speech/references/pyttsx3-espeak.md +143 -0
  47. package/src/skills/tm-search/SKILL.md +240 -0
  48. package/src/skills/tm-search/references/field-guide.md +79 -0
  49. package/src/skills/tm-search/references/scraping-fallback.md +140 -0
  50. package/src/skills/tm-search/scripts/tm_search.py +375 -0
  51. package/src/skills/wp-rest-api/SKILL.md +114 -0
  52. package/src/skills/wp-rest-api/references/authentication.md +18 -0
  53. package/src/skills/wp-rest-api/references/custom-content-types.md +20 -0
  54. package/src/skills/wp-rest-api/references/discovery-and-params.md +20 -0
  55. package/src/skills/wp-rest-api/references/responses-and-fields.md +30 -0
  56. package/src/skills/wp-rest-api/references/routes-and-endpoints.md +36 -0
  57. package/src/skills/wp-rest-api/references/schema.md +22 -0
  58. package/src/skills/youtube-search/SKILL.md +412 -0
  59. package/src/skills/youtube-search/references/parsing-examples.md +159 -0
  60. package/src/skills/youtube-search/references/youtube-api-quota.md +85 -0
  61. package/src/skills/youtube-thumbnail/SKILL.md +1060 -0
  62. package/tests/commands/info.test.js +49 -0
  63. package/tests/commands/install.test.js +36 -0
  64. package/tests/commands/list.test.js +66 -0
  65. package/tests/commands/publish.test.js +182 -0
  66. package/tests/commands/search.test.js +45 -0
  67. package/tests/commands/uninstall.test.js +29 -0
  68. package/tests/commands/update.test.js +59 -0
  69. package/tests/functional/skills-lifecycle.test.js +293 -0
  70. package/tests/helpers/fixtures.js +63 -0
  71. package/tests/integration/cli.test.js +83 -0
  72. package/tests/skills/add.test.js +138 -0
  73. package/tests/skills/list.test.js +63 -0
  74. package/tests/skills/remove.test.js +38 -0
  75. package/tests/skills/update.test.js +60 -0
  76. package/tests/unit/config.test.js +31 -0
  77. package/tests/unit/registry.test.js +79 -0
  78. package/tests/unit/utils.test.js +150 -0
  79. package/tests/validation/registry-schema.test.js +112 -0
  80. package/tests/validation/skills-validation.test.js +96 -0
@@ -0,0 +1,1012 @@
1
+ ---
2
+ name: commafeed-api
3
+ description: >
4
+ Use this skill whenever the user wants to interact with a CommaFeed RSS reader
5
+ instance via its REST API. Triggers include: listing, subscribing, unsubscribing
6
+ from RSS feeds; reading, starring, or tagging feed entries; managing categories;
7
+ marking entries as read/unread; importing or exporting OPML; managing user settings
8
+ or profile; viewing unread counts; refreshing feeds; fetching feed info by URL;
9
+ administering users on a CommaFeed instance. Also use for requests like "show my
10
+ feeds", "subscribe to RSS feed", "list unread articles", "star this entry",
11
+ "export my feeds as OPML", "mark all as read", "get unread count", or "refresh
12
+ my feeds". Always use this skill for any CommaFeed API interaction.
13
+ ---
14
+
15
+ # CommaFeed REST API Skill
16
+
17
+ Interact with a self-hosted CommaFeed RSS reader instance via its REST API.
18
+
19
+ ## Environment Variables (REQUIRED)
20
+
21
+ Before making any API calls, ensure these environment variables are set:
22
+
23
+ | Variable | Description | Example |
24
+ |---|---|---|
25
+ | `COMMAFEED_HOST` | CommaFeed instance URL (with protocol, no trailing slash) | `https://commafeed.example.com` |
26
+ | `COMMAFEED_USER` | CommaFeed username | `admin` |
27
+ | `COMMAFEED_PASS` | CommaFeed password | `secretpass` |
28
+
29
+ ```bash
30
+ export COMMAFEED_HOST="https://commafeed.example.com"
31
+ export COMMAFEED_USER="admin"
32
+ export COMMAFEED_PASS="your-password"
33
+ ```
34
+
35
+ If these are not set, **ask the user** to provide them before proceeding.
36
+
37
+ ## Authentication
38
+
39
+ All requests use **HTTP Basic Auth**:
40
+
41
+ ```bash
42
+ curl -s -u "$COMMAFEED_USER:$COMMAFEED_PASS" \
43
+ "$COMMAFEED_HOST/rest/category/get" | jq .
44
+ ```
45
+
46
+ Every request pattern below assumes:
47
+ - Base URL: `$COMMAFEED_HOST/rest`
48
+ - Auth: `-u "$COMMAFEED_USER:$COMMAFEED_PASS"`
49
+ - Content-Type: `application/json` (for POST requests with JSON body)
50
+
51
+ Helper alias for examples:
52
+
53
+ ```bash
54
+ CF="curl -s -u $COMMAFEED_USER:$COMMAFEED_PASS"
55
+ ```
56
+
57
+ ---
58
+
59
+ ## API Endpoints
60
+
61
+ ### Categories
62
+
63
+ | Method | Path | Description |
64
+ |---|---|---|
65
+ | GET | `/rest/category/get` | Get root category tree (all categories + subscriptions) |
66
+ | POST | `/rest/category/add` | Add a new category |
67
+ | POST | `/rest/category/modify` | Rename or move a category |
68
+ | POST | `/rest/category/delete` | Delete a category |
69
+ | POST | `/rest/category/collapse` | Collapse/expand a category in the UI |
70
+ | GET | `/rest/category/entries` | Get entries for a category |
71
+ | GET | `/rest/category/entriesAsFeed` | Get category entries as RSS/Atom XML |
72
+ | POST | `/rest/category/mark` | Mark all entries in a category as read |
73
+ | GET | `/rest/category/unreadCount` | Get unread count per subscription |
74
+
75
+ #### Get category tree (all subscriptions)
76
+
77
+ Returns the full tree: root category with nested children and feed subscriptions.
78
+
79
+ ```bash
80
+ curl -s -u "$COMMAFEED_USER:$COMMAFEED_PASS" \
81
+ "$COMMAFEED_HOST/rest/category/get" | jq .
82
+ ```
83
+
84
+ Response — `Category` object:
85
+ ```json
86
+ {
87
+ "id": "all",
88
+ "name": "All",
89
+ "children": [
90
+ {
91
+ "id": "10",
92
+ "parentId": "all",
93
+ "name": "Tech",
94
+ "children": [],
95
+ "feeds": [
96
+ {
97
+ "id": 42,
98
+ "name": "Hacker News",
99
+ "feedUrl": "https://news.ycombinator.com/rss",
100
+ "feedLink": "https://news.ycombinator.com",
101
+ "iconUrl": "/rest/feed/favicon/42",
102
+ "unread": 15,
103
+ "categoryId": "10",
104
+ "position": 0,
105
+ "newestItemTime": "2026-03-07T10:30:00Z",
106
+ "errorCount": 0,
107
+ "lastRefresh": "2026-03-07T10:00:00Z",
108
+ "nextRefresh": "2026-03-07T10:15:00Z"
109
+ }
110
+ ],
111
+ "expanded": true,
112
+ "position": 0
113
+ }
114
+ ],
115
+ "feeds": [],
116
+ "expanded": true,
117
+ "position": 0
118
+ }
119
+ ```
120
+
121
+ #### Add a category
122
+
123
+ ```bash
124
+ curl -s -X POST \
125
+ -u "$COMMAFEED_USER:$COMMAFEED_PASS" \
126
+ -H "Content-Type: application/json" \
127
+ -d '{"name": "Technology", "parentId": "all"}' \
128
+ "$COMMAFEED_HOST/rest/category/add" | jq .
129
+ ```
130
+
131
+ Request body — `AddCategoryRequest`:
132
+ - `name` (string, required, max 128 chars) — category name
133
+ - `parentId` (string, optional) — parent category ID, omit for root level
134
+
135
+ Response: category ID (integer).
136
+
137
+ #### Modify a category
138
+
139
+ ```bash
140
+ curl -s -X POST \
141
+ -u "$COMMAFEED_USER:$COMMAFEED_PASS" \
142
+ -H "Content-Type: application/json" \
143
+ -d '{"id": 10, "name": "Tech News", "parentId": "all", "position": 0}' \
144
+ "$COMMAFEED_HOST/rest/category/modify"
145
+ ```
146
+
147
+ Request body — `CategoryModificationRequest`:
148
+ - `id` (integer, required) — category ID
149
+ - `name` (string) — new name
150
+ - `parentId` (string) — new parent category ID
151
+ - `position` (integer) — position in parent
152
+
153
+ #### Delete a category
154
+
155
+ ```bash
156
+ curl -s -X POST \
157
+ -u "$COMMAFEED_USER:$COMMAFEED_PASS" \
158
+ -H "Content-Type: application/json" \
159
+ -d '{"id": 10}' \
160
+ "$COMMAFEED_HOST/rest/category/delete"
161
+ ```
162
+
163
+ Request body — `IDRequest`:
164
+ - `id` (integer, required) — category ID
165
+
166
+ #### Get category entries
167
+
168
+ ```bash
169
+ curl -s -u "$COMMAFEED_USER:$COMMAFEED_PASS" \
170
+ "$COMMAFEED_HOST/rest/category/entries?id=all&readType=unread&limit=20&order=desc" | jq .
171
+ ```
172
+
173
+ Query parameters:
174
+ - `id` (required) — category ID, `"all"` for all feeds, or `"starred"` for starred entries
175
+ - `readType` (required) — `all` or `unread`
176
+ - `limit` (integer, default 20, max 1000) — entries per page
177
+ - `offset` (integer, default 0) — pagination offset
178
+ - `order` (string, default `desc`) — `desc` or `asc`
179
+ - `newerThan` (long) — Unix timestamp in ms, only entries newer than this
180
+ - `keywords` (string) — search filter
181
+ - `tag` (string) — filter by tag
182
+ - `excludedSubscriptionIds` (string) — comma-separated subscription IDs to exclude
183
+
184
+ Response — `Entries` object:
185
+ ```json
186
+ {
187
+ "name": "All",
188
+ "entries": [
189
+ {
190
+ "id": "feed/42:entry/abc123",
191
+ "guid": "https://example.com/article-1",
192
+ "title": "Article Title",
193
+ "content": "<p>Article HTML content...</p>",
194
+ "author": "John Doe",
195
+ "date": "2026-03-07T09:00:00Z",
196
+ "insertedDate": "2026-03-07T09:05:00Z",
197
+ "url": "https://example.com/article-1",
198
+ "feedId": "42",
199
+ "feedName": "Example Feed",
200
+ "feedUrl": "https://example.com/rss",
201
+ "feedLink": "https://example.com",
202
+ "iconUrl": "/rest/feed/favicon/42",
203
+ "read": false,
204
+ "starred": false,
205
+ "markable": true,
206
+ "tags": [],
207
+ "categories": "tech, news",
208
+ "rtl": false,
209
+ "enclosureUrl": null,
210
+ "enclosureType": null,
211
+ "mediaThumbnailUrl": null
212
+ }
213
+ ],
214
+ "timestamp": 1741339200000,
215
+ "hasMore": true,
216
+ "offset": 0,
217
+ "limit": 20,
218
+ "errorCount": 0,
219
+ "ignoredReadStatus": false
220
+ }
221
+ ```
222
+
223
+ #### Get unread counts
224
+
225
+ ```bash
226
+ curl -s -u "$COMMAFEED_USER:$COMMAFEED_PASS" \
227
+ "$COMMAFEED_HOST/rest/category/unreadCount" | jq .
228
+ ```
229
+
230
+ Response — array of `UnreadCount`:
231
+ ```json
232
+ [
233
+ {"feedId": 42, "unreadCount": 15},
234
+ {"feedId": 55, "unreadCount": 3}
235
+ ]
236
+ ```
237
+
238
+ #### Mark category as read
239
+
240
+ ```bash
241
+ curl -s -X POST \
242
+ -u "$COMMAFEED_USER:$COMMAFEED_PASS" \
243
+ -H "Content-Type: application/json" \
244
+ -d '{"id": "all", "olderThan": 1741339200000, "read": true}' \
245
+ "$COMMAFEED_HOST/rest/category/mark"
246
+ ```
247
+
248
+ Request body — `MarkRequest`:
249
+ - `id` (string, required) — category ID or `"all"`
250
+ - `read` (boolean) — true to mark read, false for unread
251
+ - `olderThan` (long) — timestamp, mark only entries older than this
252
+ - `insertedBefore` (long) — mark only entries inserted before this
253
+ - `keywords` (string) — filter by keywords
254
+
255
+ #### Collapse/expand category
256
+
257
+ ```bash
258
+ curl -s -X POST \
259
+ -u "$COMMAFEED_USER:$COMMAFEED_PASS" \
260
+ -H "Content-Type: application/json" \
261
+ -d '{"id": 10, "collapse": true}' \
262
+ "$COMMAFEED_HOST/rest/category/collapse"
263
+ ```
264
+
265
+ ---
266
+
267
+ ### Feed Subscriptions
268
+
269
+ | Method | Path | Description |
270
+ |---|---|---|
271
+ | POST | `/rest/feed/subscribe` | Subscribe to a feed |
272
+ | POST | `/rest/feed/unsubscribe` | Unsubscribe from a feed |
273
+ | POST | `/rest/feed/modify` | Modify subscription (rename, move, filter) |
274
+ | GET | `/rest/feed/get/{id}` | Get subscription details |
275
+ | POST | `/rest/feed/fetch` | Fetch feed info by URL (preview before subscribing) |
276
+ | GET | `/rest/feed/refreshAll` | Queue all feeds for refresh |
277
+ | GET | `/rest/feed/entries` | Get entries for a specific feed |
278
+ | GET | `/rest/feed/entriesAsFeed` | Get feed entries as RSS/Atom XML |
279
+ | POST | `/rest/feed/mark` | Mark feed entries as read |
280
+ | GET | `/rest/feed/favicon/{id}` | Get feed favicon |
281
+ | GET | `/rest/feed/export` | Export all subscriptions as OPML |
282
+ | POST | `/rest/feed/import` | Import subscriptions from OPML file |
283
+
284
+ #### Subscribe to a feed
285
+
286
+ ```bash
287
+ curl -s -X POST \
288
+ -u "$COMMAFEED_USER:$COMMAFEED_PASS" \
289
+ -H "Content-Type: application/json" \
290
+ -d '{
291
+ "url": "https://example.com/rss",
292
+ "title": "Example Blog",
293
+ "categoryId": 10
294
+ }' \
295
+ "$COMMAFEED_HOST/rest/feed/subscribe" | jq .
296
+ ```
297
+
298
+ Request body — `SubscribeRequest`:
299
+ - `url` (string, required) — feed URL
300
+ - `title` (string, required) — display name
301
+ - `categoryId` (integer) — target category ID
302
+
303
+ Response: subscription ID (integer).
304
+
305
+ #### Unsubscribe from a feed
306
+
307
+ ```bash
308
+ curl -s -X POST \
309
+ -u "$COMMAFEED_USER:$COMMAFEED_PASS" \
310
+ -H "Content-Type: application/json" \
311
+ -d '{"id": 42}' \
312
+ "$COMMAFEED_HOST/rest/feed/unsubscribe"
313
+ ```
314
+
315
+ #### Modify a subscription
316
+
317
+ ```bash
318
+ curl -s -X POST \
319
+ -u "$COMMAFEED_USER:$COMMAFEED_PASS" \
320
+ -H "Content-Type: application/json" \
321
+ -d '{
322
+ "id": 42,
323
+ "name": "New Feed Name",
324
+ "categoryId": 10,
325
+ "position": 0,
326
+ "filter": "title.contains(\"important\")",
327
+ "pushNotificationsEnabled": false,
328
+ "autoMarkAsReadAfterDays": 30
329
+ }' \
330
+ "$COMMAFEED_HOST/rest/feed/modify"
331
+ ```
332
+
333
+ Request body — `FeedModificationRequest`:
334
+ - `id` (integer, required) — subscription ID
335
+ - `name` (string) — new display name
336
+ - `categoryId` (integer) — move to a different category
337
+ - `position` (integer) — position in category
338
+ - `filter` (string) — CEL expression to auto-mark entries as read
339
+ - `pushNotificationsEnabled` (boolean) — enable push notifications
340
+ - `autoMarkAsReadAfterDays` (integer) — auto-mark read after N days (null to disable)
341
+
342
+ #### Get subscription details
343
+
344
+ ```bash
345
+ curl -s -u "$COMMAFEED_USER:$COMMAFEED_PASS" \
346
+ "$COMMAFEED_HOST/rest/feed/get/42" | jq .
347
+ ```
348
+
349
+ Response — `Subscription` object:
350
+ ```json
351
+ {
352
+ "id": 42,
353
+ "name": "Hacker News",
354
+ "message": null,
355
+ "errorCount": 0,
356
+ "lastRefresh": "2026-03-07T10:00:00Z",
357
+ "nextRefresh": "2026-03-07T10:15:00Z",
358
+ "feedUrl": "https://news.ycombinator.com/rss",
359
+ "feedLink": "https://news.ycombinator.com",
360
+ "iconUrl": "/rest/feed/favicon/42",
361
+ "unread": 15,
362
+ "categoryId": "10",
363
+ "position": 0,
364
+ "newestItemTime": "2026-03-07T10:30:00Z",
365
+ "filter": null,
366
+ "pushNotificationsEnabled": false,
367
+ "autoMarkAsReadAfterDays": null
368
+ }
369
+ ```
370
+
371
+ #### Fetch feed info (preview)
372
+
373
+ ```bash
374
+ curl -s -X POST \
375
+ -u "$COMMAFEED_USER:$COMMAFEED_PASS" \
376
+ -H "Content-Type: application/json" \
377
+ -d '{"url": "https://example.com/rss"}' \
378
+ "$COMMAFEED_HOST/rest/feed/fetch" | jq .
379
+ ```
380
+
381
+ Request body — `FeedInfoRequest`:
382
+ - `url` (string, required, 1–4096 chars) — feed URL to probe
383
+
384
+ Response — `FeedInfo`: title, url, link for the discovered feed.
385
+
386
+ #### Refresh all feeds
387
+
388
+ ```bash
389
+ curl -s -u "$COMMAFEED_USER:$COMMAFEED_PASS" \
390
+ "$COMMAFEED_HOST/rest/feed/refreshAll"
391
+ ```
392
+
393
+ > **Note:** Returns 429 Too Many Requests if called too frequently.
394
+
395
+ #### Get feed entries
396
+
397
+ ```bash
398
+ curl -s -u "$COMMAFEED_USER:$COMMAFEED_PASS" \
399
+ "$COMMAFEED_HOST/rest/feed/entries?id=42&readType=unread&limit=50&order=desc" | jq .
400
+ ```
401
+
402
+ Query parameters (same pattern as category entries):
403
+ - `id` (required) — subscription ID
404
+ - `readType` (required) — `all` or `unread`
405
+ - `limit` (integer, default 20, max 1000)
406
+ - `offset` (integer, default 0)
407
+ - `order` — `desc` or `asc`
408
+ - `newerThan` (long) — Unix timestamp in ms
409
+ - `keywords` (string) — search filter
410
+
411
+ Response: `Entries` object (same structure as category entries).
412
+
413
+ #### Mark feed as read
414
+
415
+ ```bash
416
+ curl -s -X POST \
417
+ -u "$COMMAFEED_USER:$COMMAFEED_PASS" \
418
+ -H "Content-Type: application/json" \
419
+ -d '{"id": "42", "read": true}' \
420
+ "$COMMAFEED_HOST/rest/feed/mark"
421
+ ```
422
+
423
+ #### OPML Export
424
+
425
+ ```bash
426
+ curl -s -u "$COMMAFEED_USER:$COMMAFEED_PASS" \
427
+ "$COMMAFEED_HOST/rest/feed/export" -o subscriptions.opml
428
+ ```
429
+
430
+ Response: OPML XML file.
431
+
432
+ #### OPML Import
433
+
434
+ ```bash
435
+ curl -s -X POST \
436
+ -u "$COMMAFEED_USER:$COMMAFEED_PASS" \
437
+ -F "file=@subscriptions.opml" \
438
+ "$COMMAFEED_HOST/rest/feed/import"
439
+ ```
440
+
441
+ > **Note:** Uses `multipart/form-data`, not JSON.
442
+
443
+ ---
444
+
445
+ ### Entries (individual entry operations)
446
+
447
+ | Method | Path | Description |
448
+ |---|---|---|
449
+ | POST | `/rest/entry/mark` | Mark a single entry as read/unread |
450
+ | POST | `/rest/entry/markMultiple` | Mark multiple entries at once |
451
+ | POST | `/rest/entry/star` | Star/unstar an entry |
452
+ | POST | `/rest/entry/tag` | Set tags on an entry |
453
+ | GET | `/rest/entry/tags` | Get all user tags |
454
+
455
+ #### Mark entry as read/unread
456
+
457
+ ```bash
458
+ curl -s -X POST \
459
+ -u "$COMMAFEED_USER:$COMMAFEED_PASS" \
460
+ -H "Content-Type: application/json" \
461
+ -d '{"id": "feed/42:entry/abc123", "read": true}' \
462
+ "$COMMAFEED_HOST/rest/entry/mark"
463
+ ```
464
+
465
+ Request body — `MarkRequest`:
466
+ - `id` (string, required) — entry ID
467
+ - `read` (boolean) — true = read, false = unread
468
+
469
+ #### Mark multiple entries
470
+
471
+ ```bash
472
+ curl -s -X POST \
473
+ -u "$COMMAFEED_USER:$COMMAFEED_PASS" \
474
+ -H "Content-Type: application/json" \
475
+ -d '{
476
+ "requests": [
477
+ {"id": "feed/42:entry/abc123", "read": true},
478
+ {"id": "feed/42:entry/def456", "read": true}
479
+ ]
480
+ }' \
481
+ "$COMMAFEED_HOST/rest/entry/markMultiple"
482
+ ```
483
+
484
+ #### Star/unstar an entry
485
+
486
+ ```bash
487
+ curl -s -X POST \
488
+ -u "$COMMAFEED_USER:$COMMAFEED_PASS" \
489
+ -H "Content-Type: application/json" \
490
+ -d '{
491
+ "id": "feed/42:entry/abc123",
492
+ "feedId": 42,
493
+ "starred": true
494
+ }' \
495
+ "$COMMAFEED_HOST/rest/entry/star"
496
+ ```
497
+
498
+ Request body — `StarRequest`:
499
+ - `id` (string, required) — entry ID
500
+ - `feedId` (integer, required) — feed subscription ID
501
+ - `starred` (boolean, required) — true to star, false to unstar
502
+
503
+ #### Tag an entry
504
+
505
+ ```bash
506
+ curl -s -X POST \
507
+ -u "$COMMAFEED_USER:$COMMAFEED_PASS" \
508
+ -H "Content-Type: application/json" \
509
+ -d '{
510
+ "entryId": 12345,
511
+ "tags": ["important", "read-later"]
512
+ }' \
513
+ "$COMMAFEED_HOST/rest/entry/tag"
514
+ ```
515
+
516
+ Request body — `TagRequest`:
517
+ - `entryId` (integer, required) — entry ID
518
+ - `tags` (array of strings, required) — tags to assign (replaces existing)
519
+
520
+ #### Get all user tags
521
+
522
+ ```bash
523
+ curl -s -u "$COMMAFEED_USER:$COMMAFEED_PASS" \
524
+ "$COMMAFEED_HOST/rest/entry/tags" | jq .
525
+ ```
526
+
527
+ Response: array of tag strings.
528
+
529
+ ---
530
+
531
+ ### User Profile & Settings
532
+
533
+ | Method | Path | Description |
534
+ |---|---|---|
535
+ | GET | `/rest/user/profile` | Get user profile |
536
+ | POST | `/rest/user/profile` | Update user profile |
537
+ | POST | `/rest/user/profile/deleteAccount` | Delete own account |
538
+ | GET | `/rest/user/settings` | Get user settings |
539
+ | POST | `/rest/user/settings` | Save user settings |
540
+
541
+ #### Get profile
542
+
543
+ ```bash
544
+ curl -s -u "$COMMAFEED_USER:$COMMAFEED_PASS" \
545
+ "$COMMAFEED_HOST/rest/user/profile" | jq .
546
+ ```
547
+
548
+ Response — `UserModel`:
549
+ ```json
550
+ {
551
+ "id": 1,
552
+ "name": "admin",
553
+ "email": "admin@example.com",
554
+ "apiKey": "abc-123-def-456",
555
+ "enabled": true,
556
+ "admin": true
557
+ }
558
+ ```
559
+
560
+ #### Update profile
561
+
562
+ ```bash
563
+ curl -s -X POST \
564
+ -u "$COMMAFEED_USER:$COMMAFEED_PASS" \
565
+ -H "Content-Type: application/json" \
566
+ -d '{
567
+ "currentPassword": "old-pass",
568
+ "newPassword": "new-pass",
569
+ "newEmail": "new@example.com"
570
+ }' \
571
+ "$COMMAFEED_HOST/rest/user/profile"
572
+ ```
573
+
574
+ Request body — `ProfileModificationRequest`:
575
+ - `currentPassword` (string, required) — current password for verification
576
+ - `newPassword` (string) — new password
577
+ - `newEmail` (string) — new email
578
+ - `newApiKey` (boolean) — true to regenerate API key
579
+
580
+ #### Get settings
581
+
582
+ ```bash
583
+ curl -s -u "$COMMAFEED_USER:$COMMAFEED_PASS" \
584
+ "$COMMAFEED_HOST/rest/user/settings" | jq .
585
+ ```
586
+
587
+ Response — `Settings`:
588
+ ```json
589
+ {
590
+ "language": "en",
591
+ "readingMode": "unread",
592
+ "readingOrder": "desc",
593
+ "showRead": true,
594
+ "scrollMarks": true,
595
+ "scrollSpeed": 400,
596
+ "scrollMode": "if_needed",
597
+ "customCss": "",
598
+ "customJs": "",
599
+ "entriesToKeepOnTopWhenScrolling": 1,
600
+ "starIconDisplayMode": "always",
601
+ "externalLinkIconDisplayMode": "always",
602
+ "markAllAsReadConfirmation": true,
603
+ "markAllAsReadNavigateToNextUnread": false,
604
+ "customContextMenu": true,
605
+ "mobileFooter": false,
606
+ "unreadCountTitle": true,
607
+ "unreadCountFavicon": true,
608
+ "disablePullToRefresh": false,
609
+ "primaryColor": null,
610
+ "sharingSettings": {
611
+ "email": false,
612
+ "gmail": false,
613
+ "facebook": false,
614
+ "twitter": false,
615
+ "tumblr": false,
616
+ "pocket": false,
617
+ "instapaper": false,
618
+ "buffer": false
619
+ }
620
+ }
621
+ ```
622
+
623
+ Settings enums:
624
+ - `readingMode`: `all`, `unread`
625
+ - `readingOrder`: `asc`, `desc`
626
+ - `scrollMode`: `always`, `never`, `if_needed`
627
+ - `starIconDisplayMode` / `externalLinkIconDisplayMode`: `always`, `never`, `on_desktop`, `on_mobile`
628
+
629
+ #### Save settings
630
+
631
+ ```bash
632
+ curl -s -X POST \
633
+ -u "$COMMAFEED_USER:$COMMAFEED_PASS" \
634
+ -H "Content-Type: application/json" \
635
+ -d '{
636
+ "language": "en",
637
+ "readingMode": "unread",
638
+ "readingOrder": "desc",
639
+ "showRead": false,
640
+ "scrollMarks": true
641
+ }' \
642
+ "$COMMAFEED_HOST/rest/user/settings"
643
+ ```
644
+
645
+ ---
646
+
647
+ ### Server Info
648
+
649
+ | Method | Path | Description |
650
+ |---|---|---|
651
+ | GET | `/rest/server/get` | Get server information (no auth required) |
652
+ | GET | `/rest/server/proxy` | Proxy an image through the server |
653
+
654
+ #### Get server info
655
+
656
+ ```bash
657
+ curl -s "$COMMAFEED_HOST/rest/server/get" | jq .
658
+ ```
659
+
660
+ Response — `ServerInfo`:
661
+ ```json
662
+ {
663
+ "announcement": "",
664
+ "version": "5.x.x",
665
+ "gitCommit": "abc1234",
666
+ "loginPageTitle": "CommaFeed",
667
+ "allowRegistrations": false,
668
+ "googleAnalyticsTrackingCode": null,
669
+ "smtpEnabled": false,
670
+ "demoAccountEnabled": false,
671
+ "websocketEnabled": true,
672
+ "websocketPingInterval": 15000,
673
+ "treeMode": "unread"
674
+ }
675
+ ```
676
+
677
+ #### Proxy image
678
+
679
+ ```bash
680
+ curl -s -u "$COMMAFEED_USER:$COMMAFEED_PASS" \
681
+ "$COMMAFEED_HOST/rest/server/proxy?u=https://example.com/image.png" \
682
+ -o proxied-image.png
683
+ ```
684
+
685
+ ---
686
+
687
+ ### Admin Endpoints
688
+
689
+ > Require `ADMIN` role.
690
+
691
+ | Method | Path | Description |
692
+ |---|---|---|
693
+ | GET | `/rest/admin/metrics` | Get server metrics |
694
+ | GET | `/rest/admin/user/getAll` | List all users |
695
+ | GET | `/rest/admin/user/get/{id}` | Get user by ID |
696
+ | POST | `/rest/admin/user/save` | Create or update a user |
697
+ | POST | `/rest/admin/user/delete` | Delete a user |
698
+
699
+ #### Get server metrics
700
+
701
+ ```bash
702
+ curl -s -u "$COMMAFEED_USER:$COMMAFEED_PASS" \
703
+ "$COMMAFEED_HOST/rest/admin/metrics" | jq .
704
+ ```
705
+
706
+ #### List all users
707
+
708
+ ```bash
709
+ curl -s -u "$COMMAFEED_USER:$COMMAFEED_PASS" \
710
+ "$COMMAFEED_HOST/rest/admin/user/getAll" | jq .
711
+ ```
712
+
713
+ Response: array of `UserModel`.
714
+
715
+ #### Get user by ID
716
+
717
+ ```bash
718
+ curl -s -u "$COMMAFEED_USER:$COMMAFEED_PASS" \
719
+ "$COMMAFEED_HOST/rest/admin/user/get/1" | jq .
720
+ ```
721
+
722
+ #### Create / update user
723
+
724
+ ```bash
725
+ curl -s -X POST \
726
+ -u "$COMMAFEED_USER:$COMMAFEED_PASS" \
727
+ -H "Content-Type: application/json" \
728
+ -d '{
729
+ "name": "newuser",
730
+ "password": "securepass123",
731
+ "email": "user@example.com",
732
+ "enabled": true,
733
+ "admin": false
734
+ }' \
735
+ "$COMMAFEED_HOST/rest/admin/user/save"
736
+ ```
737
+
738
+ Request body — `AdminSaveUserRequest`:
739
+ - `name` (string, required) — username
740
+ - `password` (string) — password (required for new users)
741
+ - `email` (string) — email address
742
+ - `enabled` (boolean, required) — account active
743
+ - `admin` (boolean, required) — admin role
744
+
745
+ #### Delete user
746
+
747
+ ```bash
748
+ curl -s -X POST \
749
+ -u "$COMMAFEED_USER:$COMMAFEED_PASS" \
750
+ -H "Content-Type: application/json" \
751
+ -d '{"id": 5}' \
752
+ "$COMMAFEED_HOST/rest/admin/user/delete"
753
+ ```
754
+
755
+ ---
756
+
757
+ ### Registration & Password Reset
758
+
759
+ | Method | Path | Description |
760
+ |---|---|---|
761
+ | POST | `/rest/user/register` | Register new account (if allowed) |
762
+ | POST | `/rest/user/initialSetup` | Create initial admin (first-run only) |
763
+ | POST | `/rest/user/passwordReset` | Request password reset email |
764
+ | POST | `/rest/user/passwordResetCallback` | Confirm password reset with token |
765
+
766
+ #### Register
767
+
768
+ ```bash
769
+ curl -s -X POST \
770
+ -H "Content-Type: application/json" \
771
+ -d '{
772
+ "name": "newuser",
773
+ "password": "mypassword",
774
+ "email": "user@example.com"
775
+ }' \
776
+ "$COMMAFEED_HOST/rest/user/register"
777
+ ```
778
+
779
+ Request: `name` (3–32 chars), `password`, `email` (all required).
780
+
781
+ ---
782
+
783
+ ## Common Patterns
784
+
785
+ ### List all feeds with unread counts
786
+
787
+ ```bash
788
+ curl -s -u "$COMMAFEED_USER:$COMMAFEED_PASS" \
789
+ "$COMMAFEED_HOST/rest/category/get" | \
790
+ jq '[.. | .feeds? // empty | .[] | {id, name, unread, feedUrl}]'
791
+ ```
792
+
793
+ ### Get all unread entries across all feeds
794
+
795
+ ```bash
796
+ curl -s -u "$COMMAFEED_USER:$COMMAFEED_PASS" \
797
+ "$COMMAFEED_HOST/rest/category/entries?id=all&readType=unread&limit=100" | \
798
+ jq '.entries[] | {title, url, feedName, date}'
799
+ ```
800
+
801
+ ### Get starred entries
802
+
803
+ ```bash
804
+ curl -s -u "$COMMAFEED_USER:$COMMAFEED_PASS" \
805
+ "$COMMAFEED_HOST/rest/category/entries?id=starred&readType=all&limit=50" | \
806
+ jq '.entries[] | {title, url, starred, date}'
807
+ ```
808
+
809
+ ### Search entries by keyword
810
+
811
+ ```bash
812
+ curl -s -u "$COMMAFEED_USER:$COMMAFEED_PASS" \
813
+ "$COMMAFEED_HOST/rest/category/entries?id=all&readType=all&keywords=kubernetes&limit=50" | \
814
+ jq '.entries[] | {title, url, feedName}'
815
+ ```
816
+
817
+ ### Mark all entries as read
818
+
819
+ ```bash
820
+ # Get current timestamp
821
+ TS=$(date +%s)000
822
+ curl -s -X POST \
823
+ -u "$COMMAFEED_USER:$COMMAFEED_PASS" \
824
+ -H "Content-Type: application/json" \
825
+ -d "{\"id\": \"all\", \"read\": true, \"olderThan\": $TS}" \
826
+ "$COMMAFEED_HOST/rest/category/mark"
827
+ ```
828
+
829
+ ### Subscribe to a feed and put it in a category
830
+
831
+ ```bash
832
+ # 1. Fetch feed info to verify URL
833
+ curl -s -X POST \
834
+ -u "$COMMAFEED_USER:$COMMAFEED_PASS" \
835
+ -H "Content-Type: application/json" \
836
+ -d '{"url": "https://blog.example.com/feed"}' \
837
+ "$COMMAFEED_HOST/rest/feed/fetch" | jq .
838
+
839
+ # 2. Subscribe
840
+ curl -s -X POST \
841
+ -u "$COMMAFEED_USER:$COMMAFEED_PASS" \
842
+ -H "Content-Type: application/json" \
843
+ -d '{
844
+ "url": "https://blog.example.com/feed",
845
+ "title": "Example Blog",
846
+ "categoryId": 10
847
+ }' \
848
+ "$COMMAFEED_HOST/rest/feed/subscribe" | jq .
849
+ ```
850
+
851
+ ### Export OPML backup
852
+
853
+ ```bash
854
+ curl -s -u "$COMMAFEED_USER:$COMMAFEED_PASS" \
855
+ "$COMMAFEED_HOST/rest/feed/export" -o "commafeed-backup-$(date +%Y%m%d).opml"
856
+ ```
857
+
858
+ ### Paginate through all entries
859
+
860
+ ```bash
861
+ OFFSET=0
862
+ LIMIT=100
863
+ while true; do
864
+ RESPONSE=$(curl -s -u "$COMMAFEED_USER:$COMMAFEED_PASS" \
865
+ "$COMMAFEED_HOST/rest/category/entries?id=all&readType=all&limit=$LIMIT&offset=$OFFSET")
866
+
867
+ COUNT=$(echo "$RESPONSE" | jq '.entries | length')
868
+ HAS_MORE=$(echo "$RESPONSE" | jq '.hasMore')
869
+
870
+ echo "$RESPONSE" | jq '.entries[] | {title, date, feedName}'
871
+
872
+ if [ "$HAS_MORE" = "false" ] || [ "$COUNT" -eq 0 ]; then
873
+ break
874
+ fi
875
+
876
+ OFFSET=$((OFFSET + LIMIT))
877
+ done
878
+ ```
879
+
880
+ ### Get entries by tag
881
+
882
+ ```bash
883
+ curl -s -u "$COMMAFEED_USER:$COMMAFEED_PASS" \
884
+ "$COMMAFEED_HOST/rest/category/entries?id=all&readType=all&tag=important&limit=50" | \
885
+ jq '.entries[] | {title, url, tags}'
886
+ ```
887
+
888
+ ---
889
+
890
+ ## Node.js Example
891
+
892
+ ```javascript
893
+ const COMMAFEED_HOST = process.env.COMMAFEED_HOST;
894
+ const COMMAFEED_USER = process.env.COMMAFEED_USER;
895
+ const COMMAFEED_PASS = process.env.COMMAFEED_PASS;
896
+
897
+ const AUTH = 'Basic ' + Buffer.from(`${COMMAFEED_USER}:${COMMAFEED_PASS}`).toString('base64');
898
+
899
+ async function cfApi(method, path, body = null) {
900
+ const url = `${COMMAFEED_HOST}/rest${path}`;
901
+ const options = {
902
+ method,
903
+ headers: {
904
+ 'Authorization': AUTH,
905
+ 'Content-Type': 'application/json',
906
+ },
907
+ };
908
+ if (body) options.body = JSON.stringify(body);
909
+ const res = await fetch(url, options);
910
+ if (!res.ok) throw new Error(`CommaFeed API ${res.status}: ${await res.text()}`);
911
+ const text = await res.text();
912
+ return text ? JSON.parse(text) : null;
913
+ }
914
+
915
+ // Get all subscriptions
916
+ const tree = await cfApi('GET', '/category/get');
917
+ console.log(tree);
918
+
919
+ // Get unread entries
920
+ const entries = await cfApi('GET', '/category/entries?id=all&readType=unread&limit=20');
921
+ for (const e of entries.entries) {
922
+ console.log(`[${e.feedName}] ${e.title} — ${e.url}`);
923
+ }
924
+
925
+ // Subscribe to a feed
926
+ const subId = await cfApi('POST', '/feed/subscribe', {
927
+ url: 'https://example.com/rss',
928
+ title: 'Example Feed',
929
+ });
930
+ console.log('Subscribed, ID:', subId);
931
+
932
+ // Star an entry
933
+ await cfApi('POST', '/entry/star', {
934
+ id: entries.entries[0].id,
935
+ feedId: parseInt(entries.entries[0].feedId),
936
+ starred: true,
937
+ });
938
+ ```
939
+
940
+ ## Python Example
941
+
942
+ ```python
943
+ import os
944
+ import requests
945
+
946
+ COMMAFEED_HOST = os.environ["COMMAFEED_HOST"]
947
+ COMMAFEED_USER = os.environ["COMMAFEED_USER"]
948
+ COMMAFEED_PASS = os.environ["COMMAFEED_PASS"]
949
+
950
+ AUTH = (COMMAFEED_USER, COMMAFEED_PASS)
951
+ HEADERS = {"Content-Type": "application/json"}
952
+
953
+ def cf_api(method, path, json=None):
954
+ url = f"{COMMAFEED_HOST}/rest{path}"
955
+ resp = requests.request(method, url, auth=AUTH, headers=HEADERS, json=json)
956
+ resp.raise_for_status()
957
+ return resp.json() if resp.text else None
958
+
959
+ # Get category tree
960
+ tree = cf_api("GET", "/category/get")
961
+ for cat in tree["children"]:
962
+ print(f"Category: {cat['name']}")
963
+ for feed in cat["feeds"]:
964
+ print(f" {feed['name']} — unread: {feed['unread']}")
965
+
966
+ # Get unread entries
967
+ entries = cf_api("GET", "/category/entries?id=all&readType=unread&limit=20")
968
+ for e in entries["entries"]:
969
+ print(f"[{e['feedName']}] {e['title']}")
970
+
971
+ # Subscribe to a feed
972
+ sub_id = cf_api("POST", "/feed/subscribe", {
973
+ "url": "https://example.com/rss",
974
+ "title": "Example Feed",
975
+ })
976
+ print(f"Subscribed, ID: {sub_id}")
977
+
978
+ # Export OPML
979
+ resp = requests.get(f"{COMMAFEED_HOST}/rest/feed/export", auth=AUTH)
980
+ with open("backup.opml", "w") as f:
981
+ f.write(resp.text)
982
+ ```
983
+
984
+ ---
985
+
986
+ ## Error Handling
987
+
988
+ | Code | Meaning |
989
+ |---|---|
990
+ | 200 | Success |
991
+ | 400 | Bad request (missing/invalid fields) |
992
+ | 401 | Not authorized (wrong credentials) |
993
+ | 403 | Forbidden (insufficient role — need ADMIN) |
994
+ | 404 | Resource not found (wrong feed/entry/category ID) |
995
+ | 429 | Too many requests (refreshAll rate limit) |
996
+ | 500 | Server error |
997
+
998
+ ---
999
+
1000
+ ## Implementation Notes
1001
+
1002
+ - Always read `COMMAFEED_HOST`, `COMMAFEED_USER`, `COMMAFEED_PASS` from environment variables — never hardcode
1003
+ - Authentication is **HTTP Basic Auth**, not API key headers
1004
+ - The `/rest/server/get` endpoint does **not** require authentication
1005
+ - Entry IDs follow the format `feed/{feedId}:entry/{guid}` — preserve them as strings
1006
+ - Timestamps are in milliseconds (Unix epoch) for `newerThan` / `olderThan` parameters
1007
+ - OPML import uses `multipart/form-data`, all other POST endpoints use `application/json`
1008
+ - The `readType` parameter is **required** for entry listing endpoints
1009
+ - Maximum `limit` is 1000 entries per request — use `offset` + `hasMore` for pagination
1010
+ - `id=all` means all feeds, `id=starred` means starred entries in category endpoints
1011
+ - Tags replace the full tag list on an entry — send the complete desired set
1012
+ - CEL filter expressions on subscriptions auto-mark non-matching entries as read