@neurowire/cli 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +92 -2
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -7,12 +7,17 @@ import {
|
|
|
7
7
|
FORMATS,
|
|
8
8
|
MeshSchema,
|
|
9
9
|
isFormat,
|
|
10
|
+
parseDuration,
|
|
11
|
+
resolveWindow,
|
|
12
|
+
selectEntries,
|
|
10
13
|
serialize,
|
|
11
14
|
validateNwf
|
|
12
15
|
} from "@neurowire/core";
|
|
13
16
|
import { FeedTemplateSchema, fetchFeed, fetchMesh } from "@neurowire/ingest";
|
|
14
17
|
import { registerAllTaps } from "@neurowire/taps";
|
|
15
|
-
var VERSION = "0.
|
|
18
|
+
var VERSION = "0.3.0";
|
|
19
|
+
var SORT_KEYS = ["date", "title", "source"];
|
|
20
|
+
var SORT_ORDERS = ["asc", "desc"];
|
|
16
21
|
var HELP = `Neurowire ${VERSION} - turn any blog or feed into Atom and friends.
|
|
17
22
|
|
|
18
23
|
Usage:
|
|
@@ -29,6 +34,16 @@ Options:
|
|
|
29
34
|
-h, --help Show this help.
|
|
30
35
|
-v, --version Show the version.
|
|
31
36
|
|
|
37
|
+
Shape the output (applied before --format):
|
|
38
|
+
--sort <key> Sort by date, title, or source.
|
|
39
|
+
--order <dir> asc or desc (default: newest-first for date, A-Z otherwise).
|
|
40
|
+
-n, --limit <n> Keep at most n entries. Handy for integrations: --limit 10.
|
|
41
|
+
--since <age> Keep entries within this window, e.g. 24h, 90m, 7d.
|
|
42
|
+
--max-age <age> Drop entries older than this (same window as --since).
|
|
43
|
+
--today Keep entries since midnight UTC today.
|
|
44
|
+
--this-week Keep entries since Monday midnight UTC.
|
|
45
|
+
--between <a>..<b> Keep entries between two dates, e.g. 2026-01-01..2026-02-01.
|
|
46
|
+
|
|
32
47
|
Commands:
|
|
33
48
|
validate <file-or-url> Check that an nwf document is well-formed (exits non-zero if not).
|
|
34
49
|
|
|
@@ -42,7 +57,8 @@ Taps teach Neurowire to read sites with no RSS/Atom feed. Add your own with
|
|
|
42
57
|
Examples:
|
|
43
58
|
neurowire https://example.com/blog
|
|
44
59
|
neurowire https://example.com/feed.xml --format atom > feed.xml
|
|
45
|
-
neurowire --mesh ai-news.json --format
|
|
60
|
+
neurowire --mesh ai-news.json --format json --limit 10
|
|
61
|
+
neurowire --mesh ai-news.json --since 24h --sort date --format atom
|
|
46
62
|
neurowire validate feed.nwf
|
|
47
63
|
`;
|
|
48
64
|
var useColor = Boolean(process.stdout.isTTY) && !process.env.NO_COLOR;
|
|
@@ -99,6 +115,69 @@ async function runValidate(input) {
|
|
|
99
115
|
process.exitCode = 1;
|
|
100
116
|
}
|
|
101
117
|
}
|
|
118
|
+
function refineFeed(feed, values) {
|
|
119
|
+
const fail = (message) => {
|
|
120
|
+
process.stderr.write(`error: ${message}
|
|
121
|
+
`);
|
|
122
|
+
process.exitCode = 1;
|
|
123
|
+
return void 0;
|
|
124
|
+
};
|
|
125
|
+
const sort = values.sort;
|
|
126
|
+
if (sort !== void 0 && !SORT_KEYS.includes(sort)) {
|
|
127
|
+
return fail(`unknown --sort "${sort}". Use one of: ${SORT_KEYS.join(", ")}`);
|
|
128
|
+
}
|
|
129
|
+
const order = values.order;
|
|
130
|
+
if (order !== void 0 && !SORT_ORDERS.includes(order)) {
|
|
131
|
+
return fail(`unknown --order "${order}". Use asc or desc`);
|
|
132
|
+
}
|
|
133
|
+
let limit;
|
|
134
|
+
if (values.limit !== void 0) {
|
|
135
|
+
limit = Number(values.limit);
|
|
136
|
+
if (!Number.isInteger(limit) || limit < 0) {
|
|
137
|
+
return fail(`--limit must be a non-negative integer (got "${values.limit}")`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
const spec = {};
|
|
141
|
+
if (typeof values.since === "string") spec.since = values.since;
|
|
142
|
+
if (typeof values["max-age"] === "string") spec.maxAge = values["max-age"];
|
|
143
|
+
if (values.today) spec.today = true;
|
|
144
|
+
if (values["this-week"]) spec.thisWeek = true;
|
|
145
|
+
if (typeof values.between === "string") {
|
|
146
|
+
const parts = values.between.split("..");
|
|
147
|
+
if (parts.length !== 2 || !parts[0] || !parts[1]) {
|
|
148
|
+
return fail("--between expects <start>..<end>, e.g. 2026-01-01..2026-02-01");
|
|
149
|
+
}
|
|
150
|
+
spec.between = [parts[0], parts[1]];
|
|
151
|
+
}
|
|
152
|
+
for (const [flag, value] of [
|
|
153
|
+
["--since", spec.since],
|
|
154
|
+
["--max-age", spec.maxAge]
|
|
155
|
+
]) {
|
|
156
|
+
if (value !== void 0 && parseDuration(value) === void 0) {
|
|
157
|
+
return fail(`invalid ${flag} "${value}" (use e.g. 24h, 90m, 7d)`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
if (spec.between) {
|
|
161
|
+
const [a, b] = spec.between;
|
|
162
|
+
if (Number.isNaN(Date.parse(a)) || Number.isNaN(Date.parse(b))) {
|
|
163
|
+
return fail("--between needs two parseable dates, e.g. 2026-01-01..2026-02-01");
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
const window = resolveWindow(spec, Date.now());
|
|
167
|
+
const opts = {
|
|
168
|
+
...window,
|
|
169
|
+
sort,
|
|
170
|
+
order,
|
|
171
|
+
limit
|
|
172
|
+
};
|
|
173
|
+
const before = feed.entries.length;
|
|
174
|
+
const refined = selectEntries(feed, opts);
|
|
175
|
+
if (refined.entries.length !== before) {
|
|
176
|
+
process.stderr.write(`Refined to ${refined.entries.length} of ${before} entries
|
|
177
|
+
`);
|
|
178
|
+
}
|
|
179
|
+
return refined;
|
|
180
|
+
}
|
|
102
181
|
function renderTerminal(feed) {
|
|
103
182
|
const out = [bold(cyan(feed.title))];
|
|
104
183
|
const sub = [feed.home, `${feed.entries.length} entries`, `updated ${day(feed.updated)}`].filter(
|
|
@@ -133,6 +212,14 @@ async function main() {
|
|
|
133
212
|
template: { type: "string", short: "t" },
|
|
134
213
|
mesh: { type: "string", short: "m" },
|
|
135
214
|
taps: { type: "string", multiple: true },
|
|
215
|
+
sort: { type: "string" },
|
|
216
|
+
order: { type: "string" },
|
|
217
|
+
limit: { type: "string", short: "n" },
|
|
218
|
+
since: { type: "string" },
|
|
219
|
+
"max-age": { type: "string" },
|
|
220
|
+
today: { type: "boolean" },
|
|
221
|
+
"this-week": { type: "boolean" },
|
|
222
|
+
between: { type: "string" },
|
|
136
223
|
help: { type: "boolean", short: "h" },
|
|
137
224
|
version: { type: "boolean", short: "v" }
|
|
138
225
|
}
|
|
@@ -171,6 +258,9 @@ async function main() {
|
|
|
171
258
|
}
|
|
172
259
|
feed = await fetchFeed(url, { template });
|
|
173
260
|
}
|
|
261
|
+
const refined = refineFeed(feed, values);
|
|
262
|
+
if (!refined) return;
|
|
263
|
+
feed = refined;
|
|
174
264
|
if (values.format) {
|
|
175
265
|
if (!isFormat(values.format)) {
|
|
176
266
|
process.stderr.write(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@neurowire/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Neurowire command-line tool: turn any blog, feed, or mesh into Atom, JSON Feed, Markdown, or nwf.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -33,15 +33,15 @@
|
|
|
33
33
|
"url": "https://github.com/starside-io/neurowire/issues"
|
|
34
34
|
},
|
|
35
35
|
"engines": {
|
|
36
|
-
"node": ">=
|
|
36
|
+
"node": ">=24"
|
|
37
37
|
},
|
|
38
38
|
"publishConfig": {
|
|
39
39
|
"access": "public"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@neurowire/ingest": "0.
|
|
43
|
-
"@neurowire/core": "0.
|
|
44
|
-
"@neurowire/taps": "0.
|
|
42
|
+
"@neurowire/ingest": "0.2.0",
|
|
43
|
+
"@neurowire/core": "0.3.0",
|
|
44
|
+
"@neurowire/taps": "0.2.0"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"@types/node": "^22.10.5",
|