@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.
- package/.claude/settings.local.json +13 -0
- package/CLAUDE.md +55 -0
- package/LICENSE +1 -1
- package/README.md +208 -39
- package/bin/cli.js +39 -0
- package/package.json +30 -17
- package/registry/registry.json +166 -1
- package/registry/schema.json +10 -0
- package/src/commands/skills/add.js +194 -0
- package/src/commands/skills/list.js +52 -0
- package/src/commands/skills/remove.js +27 -0
- package/src/commands/skills/update.js +74 -0
- package/src/config.js +5 -0
- package/src/skills/codex-cli/SKILL.md +265 -0
- package/src/skills/commafeed-api/SKILL.md +1012 -0
- package/src/skills/gemini-cli/SKILL.md +379 -0
- package/src/skills/gemini-cli/references/commands.md +145 -0
- package/src/skills/gemini-cli/references/configuration.md +182 -0
- package/src/skills/gemini-cli/references/headless-and-scripting.md +181 -0
- package/src/skills/gemini-cli/references/mcp-and-extensions.md +254 -0
- package/src/skills/n8n-api/SKILL.md +623 -0
- package/src/skills/notebook-lm/SKILL.md +217 -0
- package/src/skills/notebook-lm/references/artifact-options.md +168 -0
- package/src/skills/notebook-lm/references/auth.md +58 -0
- package/src/skills/notebook-lm/references/workflows.md +144 -0
- package/src/skills/screen-recording/SKILL.md +309 -0
- package/src/skills/screen-recording/references/approach1-programmatic.md +311 -0
- package/src/skills/screen-recording/references/approach2-xvfb.md +232 -0
- package/src/skills/screen-recording/references/design-patterns.md +168 -0
- package/src/skills/test-mobile-app/SKILL.md +212 -0
- package/src/skills/test-mobile-app/references/report-template.md +95 -0
- package/src/skills/test-mobile-app/references/setup-appium.md +154 -0
- package/src/skills/test-mobile-app/scripts/analyze_apk.py +164 -0
- package/src/skills/test-mobile-app/scripts/check_environment.py +116 -0
- package/src/skills/test-mobile-app/scripts/generate_report.py +250 -0
- package/src/skills/test-mobile-app/scripts/run_tests.py +326 -0
- package/src/skills/test-web-ui/SKILL.md +232 -0
- package/src/skills/test-web-ui/references/test_case_schema.md +102 -0
- package/src/skills/test-web-ui/scripts/discover.py +176 -0
- package/src/skills/test-web-ui/scripts/generate_report.py +237 -0
- package/src/skills/test-web-ui/scripts/run_tests.py +296 -0
- package/src/skills/text-to-speech/SKILL.md +236 -0
- package/src/skills/text-to-speech/references/espeak-cli.md +277 -0
- package/src/skills/text-to-speech/references/kokoro-onnx.md +124 -0
- package/src/skills/text-to-speech/references/online-engines.md +128 -0
- package/src/skills/text-to-speech/references/pyttsx3-espeak.md +143 -0
- package/src/skills/tm-search/SKILL.md +240 -0
- package/src/skills/tm-search/references/field-guide.md +79 -0
- package/src/skills/tm-search/references/scraping-fallback.md +140 -0
- package/src/skills/tm-search/scripts/tm_search.py +375 -0
- package/src/skills/wp-rest-api/SKILL.md +114 -0
- package/src/skills/wp-rest-api/references/authentication.md +18 -0
- package/src/skills/wp-rest-api/references/custom-content-types.md +20 -0
- package/src/skills/wp-rest-api/references/discovery-and-params.md +20 -0
- package/src/skills/wp-rest-api/references/responses-and-fields.md +30 -0
- package/src/skills/wp-rest-api/references/routes-and-endpoints.md +36 -0
- package/src/skills/wp-rest-api/references/schema.md +22 -0
- package/src/skills/youtube-search/SKILL.md +412 -0
- package/src/skills/youtube-search/references/parsing-examples.md +159 -0
- package/src/skills/youtube-search/references/youtube-api-quota.md +85 -0
- package/src/skills/youtube-thumbnail/SKILL.md +1060 -0
- package/tests/commands/info.test.js +49 -0
- package/tests/commands/install.test.js +36 -0
- package/tests/commands/list.test.js +66 -0
- package/tests/commands/publish.test.js +182 -0
- package/tests/commands/search.test.js +45 -0
- package/tests/commands/uninstall.test.js +29 -0
- package/tests/commands/update.test.js +59 -0
- package/tests/functional/skills-lifecycle.test.js +293 -0
- package/tests/helpers/fixtures.js +63 -0
- package/tests/integration/cli.test.js +83 -0
- package/tests/skills/add.test.js +138 -0
- package/tests/skills/list.test.js +63 -0
- package/tests/skills/remove.test.js +38 -0
- package/tests/skills/update.test.js +60 -0
- package/tests/unit/config.test.js +31 -0
- package/tests/unit/registry.test.js +79 -0
- package/tests/unit/utils.test.js +150 -0
- package/tests/validation/registry-schema.test.js +112 -0
- 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
|