newsline-cli 0.1.1 → 0.1.3
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/README.md +32 -62
- package/feeds.json +1 -1
- package/newsline +104 -37
- package/package.json +1 -1
- package/readme/de.md +58 -0
- package/readme/es.md +58 -0
- package/readme/fr.md +58 -0
- package/readme/ja.md +57 -0
- package/readme/ko.md +31 -59
- package/readme/pt.md +57 -0
- package/readme/zh.md +54 -0
- package/refresh.sh +57 -3
- package/resolve.py +38 -17
- package/statusline.sh +9 -0
package/README.md
CHANGED
|
@@ -1,86 +1,56 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
**composes with your existing status line** (e.g. an HUD) instead of replacing it.
|
|
3
|
+
**English** · [한국어](readme/ko.md) · [日本語](readme/ja.md) · [Español](readme/es.md) · [Français](readme/fr.md) · [Deutsch](readme/de.md) · [Português](readme/pt.md) · [中文](readme/zh.md)
|
|
5
4
|
|
|
6
|
-
|
|
5
|
+
</div>
|
|
7
6
|
|
|
8
|
-
|
|
7
|
+
# newsline
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
headline at the bottom of your session
|
|
12
|
-
|
|
13
|
-
prompts, files, or Claude conversations.
|
|
9
|
+
**Waiting for Claude Code to finish? Read the latest one-line news right in your status line.**
|
|
10
|
+
A rotating regional headline sits at the bottom of your session — so a long wait turns into a
|
|
11
|
+
quick news check. It shows *below* your existing status line (your HUD stays).
|
|
14
12
|
|
|
15
|
-
## Install
|
|
13
|
+
## Install & run — one line
|
|
16
14
|
|
|
17
|
-
**curl | sh** (macOS / Linux / WSL):
|
|
18
15
|
```sh
|
|
16
|
+
# curl (macOS / Linux / WSL) — installs and sets up right away
|
|
19
17
|
curl -fsSL https://raw.githubusercontent.com/itdar/cc-plugin/master/install.sh | sh
|
|
20
|
-
```
|
|
21
18
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
brew install itdar/tap/newsline
|
|
25
|
-
```
|
|
19
|
+
# Homebrew
|
|
20
|
+
brew install itdar/tap/newsline && newsline init
|
|
26
21
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
git clone https://github.com/itdar/cc-plugin && cd cc-plugin && ./install.sh
|
|
22
|
+
# npm
|
|
23
|
+
npm i -g newsline-cli && newsline init
|
|
30
24
|
```
|
|
31
25
|
|
|
32
|
-
The
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
```
|
|
37
|
-
`init` asks for a language and topic (a menu — just pick a number), then registers the
|
|
38
|
-
status line, **keeping whatever status line you already have** (your HUD stays visible,
|
|
39
|
-
news shows on the line below). It appears on your next message to Claude — Claude Code
|
|
40
|
-
hot-reloads settings, so no restart is needed.
|
|
26
|
+
The news line appears on your **next message** — no restart. Setup asks for a language & topic
|
|
27
|
+
and keeps your existing status line.
|
|
28
|
+
|
|
29
|
+
## What it does
|
|
41
30
|
|
|
42
|
-
|
|
31
|
+
- **Your machine fetches and shows the news** — it never touches your code, prompts, files, or
|
|
32
|
+
Claude conversations.
|
|
33
|
+
- A small **edge service** picks the best regional sources and routes headline clicks, so sources
|
|
34
|
+
stay fresh without reinstalling. If it's unreachable, newsline **falls back to built-in feeds**.
|
|
35
|
+
- The status line is instant (served from a cache); refreshes run in the background.
|
|
43
36
|
|
|
44
37
|
## Configure
|
|
45
38
|
|
|
46
|
-
|
|
39
|
+
Re-run `newsline init`, or edit `~/.config/newsline/config.json`:
|
|
47
40
|
|
|
48
|
-
| Key
|
|
41
|
+
| Key | Default | Meaning |
|
|
49
42
|
|---|---|---|
|
|
50
|
-
| `lang`
|
|
51
|
-
| `topic`
|
|
52
|
-
| `rotate`
|
|
53
|
-
| `count`
|
|
54
|
-
| `maxlen`
|
|
55
|
-
| `icon`
|
|
56
|
-
| `linkhint` / `NEWSLINE_LINKHINT` | off | append a `↗` "opens externally" hint |
|
|
57
|
-
| `api` / `NEWSLINE_API` | — | optional curation API (server-first, local fallback) |
|
|
58
|
-
| `endpoint` / `NEWSLINE_ENDPOINT` | — | click-redirect wrapper |
|
|
59
|
-
|
|
60
|
-
## How it works
|
|
61
|
-
|
|
62
|
-
- **`newsline render`** (what the status line runs) prints instantly from cache and never
|
|
63
|
-
blocks; it kicks off a background refresh when the cache is stale.
|
|
64
|
-
- **`newsline refresh`** fetches a keyless regional RSS/Atom feed locally, parses the top
|
|
65
|
-
headlines, dedupes, and caches them.
|
|
66
|
-
- Headlines are clickable [OSC 8] links. Whether a plain click vs `Cmd`/`Ctrl`+click opens
|
|
67
|
-
them is decided by your terminal, not by newsline.
|
|
43
|
+
| `lang` | `auto` | `ko` `ja` `en` `es` `fr` `de` `pt` `zh`, or `auto` |
|
|
44
|
+
| `topic` | `general` | `general` `tech` `business` `world` `sports` `science` `health` `entertainment` |
|
|
45
|
+
| `rotate` | `6` | seconds per headline |
|
|
46
|
+
| `count` | `15` | headlines in rotation |
|
|
47
|
+
| `maxlen` | `120` | max characters (`max` = no cut) |
|
|
48
|
+
| `icon` | `📰` | leading glyph (`none` to hide) |
|
|
68
49
|
|
|
69
50
|
## Uninstall
|
|
70
51
|
|
|
71
52
|
```sh
|
|
72
|
-
newsline uninstall #
|
|
53
|
+
newsline uninstall # restores your previous status line
|
|
73
54
|
```
|
|
74
55
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
Runs locally; detects locale from your system settings (no IP geolocation); fetches public
|
|
78
|
-
feeds directly; never accesses your code, prompts, files, or Claude conversations. Clicks
|
|
79
|
-
pass through a redirect so the project can measure engagement and, in future, use affiliate
|
|
80
|
-
links to fund itself — disclosed here.
|
|
81
|
-
|
|
82
|
-
## Requirements
|
|
83
|
-
|
|
84
|
-
`bash`, `python3`. macOS and Linux natively; Windows via WSL or Git Bash.
|
|
85
|
-
|
|
86
|
-
[OSC 8]: https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda
|
|
56
|
+
Needs `bash` + `python3` (macOS / Linux; Windows via WSL).
|
package/feeds.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"_comment": "locale language -> ordered keyless RSS/Atom feeds. National outlet first (in-language, higher quality), Google News locale feed last as a guaranteed fallback. fetch.py tries each in order; the first that yields an item wins, so a dead/blocked feed is skipped gracefully. All URLs here were reachability-validated. When adding feeds, also add the article-link domain to
|
|
2
|
+
"_comment": "locale language -> ordered keyless RSS/Atom feeds. National outlet first (in-language, higher quality), Google News locale feed last as a guaranteed fallback. fetch.py tries each in order; the first that yields an item wins, so a dead/blocked feed is skipped gracefully. All URLs here were reachability-validated. When adding feeds, also add the article-link domain to the server allowlist (cc-server/worker.js allowlistCfg).",
|
|
3
3
|
"en": [
|
|
4
4
|
"https://feeds.bbci.co.uk/news/rss.xml",
|
|
5
5
|
"https://feeds.npr.org/1001/rss.xml",
|
package/newsline
CHANGED
|
@@ -40,6 +40,15 @@ v=d.get(sys.argv[2])
|
|
|
40
40
|
print("" if v is None else v)
|
|
41
41
|
PY
|
|
42
42
|
}
|
|
43
|
+
cfg_get_obj() { # cfg_get_obj <key> — print an object-valued config key as JSON
|
|
44
|
+
python3 - "$CONFIG" "$1" <<'PY' 2>/dev/null
|
|
45
|
+
import json,sys
|
|
46
|
+
try: d=json.load(open(sys.argv[1]))
|
|
47
|
+
except Exception: d={}
|
|
48
|
+
v=d.get(sys.argv[2])
|
|
49
|
+
print(json.dumps(v,ensure_ascii=False) if isinstance(v,dict) else "")
|
|
50
|
+
PY
|
|
51
|
+
}
|
|
43
52
|
settings_statusline_cmd() { # print current statusLine.command of a settings file
|
|
44
53
|
python3 - "$1" <<'PY' 2>/dev/null
|
|
45
54
|
import json,sys
|
|
@@ -49,22 +58,37 @@ sl=d.get("statusLine")
|
|
|
49
58
|
print(sl.get("command","") if isinstance(sl,dict) else "")
|
|
50
59
|
PY
|
|
51
60
|
}
|
|
61
|
+
settings_statusline_json() { # print the whole statusLine object of a settings file
|
|
62
|
+
python3 - "$1" <<'PY' 2>/dev/null
|
|
63
|
+
import json,sys
|
|
64
|
+
try: d=json.load(open(sys.argv[1]))
|
|
65
|
+
except Exception: d={}
|
|
66
|
+
sl=d.get("statusLine")
|
|
67
|
+
print(json.dumps(sl,ensure_ascii=False) if isinstance(sl,dict) and sl.get("command") else "")
|
|
68
|
+
PY
|
|
69
|
+
}
|
|
52
70
|
|
|
71
|
+
# One python spawn for the whole config (render runs on every status-line tick;
|
|
72
|
+
# per-key cfg_get calls here would cost ~10 interpreter startups per tick).
|
|
53
73
|
load_config() {
|
|
54
74
|
[ -f "$CONFIG" ] || return 0
|
|
55
|
-
local
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
75
|
+
local exports
|
|
76
|
+
exports=$(python3 - "$CONFIG" <<'PY' 2>/dev/null
|
|
77
|
+
import json,sys,shlex
|
|
78
|
+
env={"lang":"NEWSLINE_LANG","topic":"NEWSLINE_TOPIC","rotate":"NEWSLINE_ROTATE",
|
|
79
|
+
"count":"NEWSLINE_COUNT","maxlen":"NEWSLINE_MAXLEN","icon":"NEWSLINE_ICON",
|
|
80
|
+
"linkhint":"NEWSLINE_LINKHINT","api":"NEWSLINE_API","endpoint":"NEWSLINE_ENDPOINT",
|
|
81
|
+
"base_statusline":"NEWSLINE_BASE"}
|
|
82
|
+
try: d=json.load(open(sys.argv[1]))
|
|
83
|
+
except Exception: d={}
|
|
84
|
+
for k,name in env.items():
|
|
85
|
+
v=d.get(k)
|
|
86
|
+
if v is None or v=="": continue
|
|
87
|
+
if k=="lang" and v=="auto": continue
|
|
88
|
+
print("export %s=%s" % (name, shlex.quote(str(v))))
|
|
89
|
+
PY
|
|
90
|
+
)
|
|
91
|
+
[ -n "$exports" ] && eval "$exports"
|
|
68
92
|
}
|
|
69
93
|
|
|
70
94
|
detect_lang() {
|
|
@@ -110,11 +134,10 @@ choose() {
|
|
|
110
134
|
# ---- subcommands ----
|
|
111
135
|
cmd_render() { # base (existing HUD) first, newsline below — never blocks
|
|
112
136
|
load_config
|
|
113
|
-
local
|
|
114
|
-
base=$(cfg_get base_statusline)
|
|
137
|
+
local input=""
|
|
115
138
|
[ -t 0 ] || input=$(cat 2>/dev/null) # consume the session JSON if piped
|
|
116
|
-
if [ -n "$
|
|
117
|
-
printf '%s' "$input" | eval "$
|
|
139
|
+
if [ -n "${NEWSLINE_BASE:-}" ]; then
|
|
140
|
+
printf '%s' "$input" | eval "$NEWSLINE_BASE" 2>/dev/null # your HUD, unchanged
|
|
118
141
|
printf '\n'
|
|
119
142
|
fi
|
|
120
143
|
"$HERE/statusline.sh" # the news line, below
|
|
@@ -172,60 +195,104 @@ cmd_init() {
|
|
|
172
195
|
|
|
173
196
|
# Always KEEP an existing status line and show newsline below it (no prompt).
|
|
174
197
|
# Only --no-compose (or an explicit --base) changes this.
|
|
175
|
-
local existing base=""
|
|
198
|
+
local existing base="" base_obj=""
|
|
176
199
|
if [ -n "$base_override" ]; then
|
|
177
200
|
base="$base_override" # caller supplied the base explicitly
|
|
178
201
|
elif [ "$compose" != "no" ]; then
|
|
179
202
|
existing=$(settings_statusline_cmd "$SETTINGS")
|
|
180
203
|
if [ -n "$existing" ] && ! printf '%s' "$existing" | grep -q "newsline"; then
|
|
181
204
|
base="$existing" # first run: capture the real status line
|
|
205
|
+
base_obj=$(settings_statusline_json "$SETTINGS") # verbatim, for uninstall
|
|
182
206
|
else
|
|
183
207
|
base=$(cfg_get base_statusline) # re-init: KEEP the base we already saved
|
|
208
|
+
base_obj=$(cfg_get_obj base_statusline_obj)
|
|
184
209
|
fi
|
|
185
210
|
fi
|
|
186
211
|
|
|
187
212
|
mkdir -p "$CONFIG_DIR"
|
|
188
|
-
python3 - "$CONFIG" "$lang" "$topic" "$rotate" "$count" "$api" "$endpoint" "$base" <<'PY'
|
|
189
|
-
import json,sys
|
|
190
|
-
_,path,lang,topic,rotate,count,api,endpoint,base=sys.argv
|
|
191
|
-
|
|
213
|
+
python3 - "$CONFIG" "$lang" "$topic" "$rotate" "$count" "$api" "$endpoint" "$base" "$base_obj" <<'PY' || { echo "✗ could not write $CONFIG" >&2; return 1; }
|
|
214
|
+
import json,os,sys
|
|
215
|
+
_,path,lang,topic,rotate,count,api,endpoint,base,base_obj=sys.argv
|
|
216
|
+
try:
|
|
217
|
+
d={"lang":lang,"topic":topic,"rotate":int(rotate),"count":int(count)}
|
|
218
|
+
except ValueError:
|
|
219
|
+
sys.stderr.write("newsline: --rotate/--count must be integers\n"); sys.exit(3)
|
|
192
220
|
if api: d["api"]=api
|
|
193
221
|
if endpoint: d["endpoint"]=endpoint
|
|
194
222
|
if base: d["base_statusline"]=base
|
|
195
|
-
|
|
223
|
+
if base_obj:
|
|
224
|
+
try: d["base_statusline_obj"]=json.loads(base_obj)
|
|
225
|
+
except Exception: pass
|
|
226
|
+
tmp=path+".tmp"
|
|
227
|
+
with open(tmp,"w") as f: json.dump(d,f,indent=2,ensure_ascii=False)
|
|
228
|
+
os.replace(tmp,path)
|
|
196
229
|
PY
|
|
197
230
|
|
|
198
|
-
python3 - "$SETTINGS" "$RENDER_CMD" <<'PY'
|
|
231
|
+
python3 - "$SETTINGS" "$RENDER_CMD" <<'PY' || { echo "✗ could not update $SETTINGS — fix the file and re-run: newsline init" >&2; return 1; }
|
|
199
232
|
import json,os,sys
|
|
200
233
|
path,cmd=sys.argv[1],sys.argv[2]
|
|
201
|
-
|
|
202
|
-
|
|
234
|
+
d={}
|
|
235
|
+
if os.path.exists(path):
|
|
236
|
+
try:
|
|
237
|
+
with open(path) as f: raw=f.read().strip()
|
|
238
|
+
d=json.loads(raw) if raw else {}
|
|
239
|
+
if not isinstance(d,dict): raise ValueError("settings root is not an object")
|
|
240
|
+
except Exception:
|
|
241
|
+
# never clobber a settings file we can't parse
|
|
242
|
+
sys.stderr.write("newsline: %s is not valid JSON; refusing to modify it\n" % path)
|
|
243
|
+
sys.exit(3)
|
|
203
244
|
d["statusLine"]={"type":"command","command":cmd,"refreshInterval":5}
|
|
204
245
|
os.makedirs(os.path.dirname(path) or ".",exist_ok=True)
|
|
205
|
-
|
|
246
|
+
tmp=path+".tmp"
|
|
247
|
+
with open(tmp,"w") as f: json.dump(d,f,indent=2,ensure_ascii=False)
|
|
248
|
+
os.replace(tmp,path)
|
|
206
249
|
PY
|
|
207
250
|
|
|
251
|
+
# apply the new language/topic on the next render, not after the old cache expires
|
|
252
|
+
local cache="${NEWSLINE_CACHE:-$HOME/.cache/newsline}"
|
|
253
|
+
rm -f "$cache/line" "$cache/feeds.resolved.json" "$cache/feeds.resolved.ok" "$cache/notice" 2>/dev/null
|
|
254
|
+
|
|
208
255
|
echo "✔ newsline installed (lang=$lang, topic=$topic)."
|
|
209
256
|
[ -n "$base" ] && echo " composing with your existing status line — it stays visible above the news."
|
|
210
257
|
echo " It appears on your next message to Claude — no restart needed."
|
|
211
258
|
}
|
|
212
259
|
|
|
213
260
|
cmd_uninstall() {
|
|
214
|
-
local base
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
261
|
+
local base base_obj
|
|
262
|
+
base=$(cfg_get base_statusline)
|
|
263
|
+
base_obj=$(cfg_get_obj base_statusline_obj)
|
|
264
|
+
python3 - "$SETTINGS" "$base" "$base_obj" <<'PY'
|
|
265
|
+
import json,os,sys
|
|
266
|
+
path,base,base_obj=sys.argv[1],sys.argv[2],sys.argv[3]
|
|
267
|
+
try:
|
|
268
|
+
with open(path) as f: raw=f.read().strip()
|
|
269
|
+
d=json.loads(raw) if raw else {}
|
|
270
|
+
if not isinstance(d,dict): raise ValueError("settings root is not an object")
|
|
271
|
+
except FileNotFoundError:
|
|
272
|
+
print("no settings file; nothing to revert"); sys.exit(0)
|
|
273
|
+
except Exception:
|
|
274
|
+
print("settings file is not valid JSON; left as-is"); sys.exit(0)
|
|
220
275
|
sl=d.get("statusLine"); cmd=sl.get("command","") if isinstance(sl,dict) else ""
|
|
221
276
|
if "newsline" in cmd:
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
277
|
+
obj=None
|
|
278
|
+
if base_obj:
|
|
279
|
+
try: obj=json.loads(base_obj)
|
|
280
|
+
except Exception: obj=None
|
|
281
|
+
if isinstance(obj,dict) and obj.get("command"):
|
|
282
|
+
d["statusLine"]=obj # full original (keeps padding etc.)
|
|
283
|
+
elif base:
|
|
284
|
+
d["statusLine"]={"type":"command","command":base}
|
|
285
|
+
else:
|
|
286
|
+
d.pop("statusLine",None)
|
|
287
|
+
tmp=path+".tmp"
|
|
288
|
+
with open(tmp,"w") as f: json.dump(d,f,indent=2,ensure_ascii=False)
|
|
289
|
+
os.replace(tmp,path)
|
|
290
|
+
print("reverted status line")
|
|
225
291
|
else:
|
|
226
292
|
print("status line not managed by newsline; left as-is")
|
|
227
293
|
PY
|
|
228
|
-
|
|
294
|
+
local cache="${NEWSLINE_CACHE:-$HOME/.cache/newsline}"
|
|
295
|
+
rm -f "$CONFIG" "$cache/line" "$cache/feeds.resolved.json" "$cache/feeds.resolved.ok" "$cache/notice" 2>/dev/null
|
|
229
296
|
echo "✔ newsline uninstalled."
|
|
230
297
|
}
|
|
231
298
|
|
package/package.json
CHANGED
package/readme/de.md
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
[English](../README.md) · [한국어](ko.md) · [日本語](ja.md) · [Español](es.md) · [Français](fr.md) · **Deutsch** · [Português](pt.md) · [中文](zh.md)
|
|
4
|
+
|
|
5
|
+
</div>
|
|
6
|
+
|
|
7
|
+
# newsline
|
|
8
|
+
|
|
9
|
+
**Wartest du darauf, dass Claude Code fertig wird? Lies die neuesten Ein-Zeilen-News direkt in deiner Statusleiste.**
|
|
10
|
+
Eine rotierende regionale Schlagzeile sitzt am unteren Rand deiner Sitzung – so wird aus langem Warten
|
|
11
|
+
ein schneller Blick in die Nachrichten. Sie erscheint *unter* deiner bestehenden Statusleiste (dein HUD
|
|
12
|
+
bleibt erhalten).
|
|
13
|
+
|
|
14
|
+
## Installieren & starten — eine Zeile
|
|
15
|
+
|
|
16
|
+
```sh
|
|
17
|
+
# curl (macOS / Linux / WSL) — installiert und richtet sofort ein
|
|
18
|
+
curl -fsSL https://raw.githubusercontent.com/itdar/cc-plugin/master/install.sh | sh
|
|
19
|
+
|
|
20
|
+
# Homebrew
|
|
21
|
+
brew install itdar/tap/newsline && newsline init
|
|
22
|
+
|
|
23
|
+
# npm
|
|
24
|
+
npm i -g newsline-cli && newsline init
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Die News-Zeile erscheint bei deiner **nächsten Nachricht** – kein Neustart nötig. Die Einrichtung fragt
|
|
28
|
+
nach Sprache und Thema und behält deine bestehende Statusleiste bei.
|
|
29
|
+
|
|
30
|
+
## Was es macht
|
|
31
|
+
|
|
32
|
+
- **Dein Rechner holt und zeigt die Nachrichten** – er rührt niemals deinen Code, deine Prompts, Dateien
|
|
33
|
+
oder Claude-Unterhaltungen an.
|
|
34
|
+
- Ein kleiner **Edge-Dienst** wählt die besten regionalen Quellen aus und leitet Klicks auf Schlagzeilen
|
|
35
|
+
weiter, sodass die Quellen ohne Neuinstallation aktuell bleiben. Ist er nicht erreichbar, **greift
|
|
36
|
+
newsline auf die integrierten Feeds zurück**.
|
|
37
|
+
- Die Statusleiste ist sofort da (aus einem Cache); Aktualisierungen laufen im Hintergrund.
|
|
38
|
+
|
|
39
|
+
## Konfigurieren
|
|
40
|
+
|
|
41
|
+
Führe `newsline init` erneut aus oder bearbeite `~/.config/newsline/config.json`:
|
|
42
|
+
|
|
43
|
+
| Schlüssel | Standard | Bedeutung |
|
|
44
|
+
|---|---|---|
|
|
45
|
+
| `lang` | `auto` | `ko` `ja` `en` `es` `fr` `de` `pt` `zh` oder `auto` |
|
|
46
|
+
| `topic` | `general` | `general` `tech` `business` `world` `sports` `science` `health` `entertainment` |
|
|
47
|
+
| `rotate` | `6` | Sekunden pro Schlagzeile |
|
|
48
|
+
| `count` | `15` | Schlagzeilen im Wechsel |
|
|
49
|
+
| `maxlen` | `120` | maximale Zeichen (`max` = kein Abschneiden) |
|
|
50
|
+
| `icon` | `📰` | vorangestelltes Symbol (`none` zum Ausblenden) |
|
|
51
|
+
|
|
52
|
+
## Deinstallieren
|
|
53
|
+
|
|
54
|
+
```sh
|
|
55
|
+
newsline uninstall # stellt deine vorherige Statusleiste wieder her
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Benötigt `bash` + `python3` (macOS / Linux; Windows über WSL).
|
package/readme/es.md
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
[English](../README.md) · [한국어](ko.md) · [日本語](ja.md) · **Español** · [Français](fr.md) · [Deutsch](de.md) · [Português](pt.md) · [中文](zh.md)
|
|
4
|
+
|
|
5
|
+
</div>
|
|
6
|
+
|
|
7
|
+
# newsline
|
|
8
|
+
|
|
9
|
+
**¿Esperando a que Claude Code termine? Lee las últimas noticias en una línea, justo en tu barra de estado.**
|
|
10
|
+
Un titular regional rotativo aparece en la parte inferior de tu sesión, así una larga espera se convierte
|
|
11
|
+
en un vistazo rápido a las noticias. Se muestra *debajo* de tu barra de estado actual (tu HUD se mantiene).
|
|
12
|
+
|
|
13
|
+
## Instalar y ejecutar — una línea
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
# curl (macOS / Linux / WSL) — instala y configura al instante
|
|
17
|
+
curl -fsSL https://raw.githubusercontent.com/itdar/cc-plugin/master/install.sh | sh
|
|
18
|
+
|
|
19
|
+
# Homebrew
|
|
20
|
+
brew install itdar/tap/newsline && newsline init
|
|
21
|
+
|
|
22
|
+
# npm
|
|
23
|
+
npm i -g newsline-cli && newsline init
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
La línea de noticias aparece en tu **próximo mensaje**, sin reiniciar. La configuración te pregunta el
|
|
27
|
+
idioma y el tema, y conserva tu barra de estado actual.
|
|
28
|
+
|
|
29
|
+
## Qué hace
|
|
30
|
+
|
|
31
|
+
- **Tu equipo obtiene y muestra las noticias** — nunca toca tu código, prompts, archivos ni
|
|
32
|
+
conversaciones con Claude.
|
|
33
|
+
- Un pequeño **servicio edge** elige las mejores fuentes regionales y enruta los clics en los titulares,
|
|
34
|
+
de modo que las fuentes se mantienen actualizadas sin reinstalar. Si no está disponible, newsline
|
|
35
|
+
**recurre a los feeds integrados**.
|
|
36
|
+
- La barra de estado es instantánea (servida desde una caché); las actualizaciones se ejecutan en
|
|
37
|
+
segundo plano.
|
|
38
|
+
|
|
39
|
+
## Configurar
|
|
40
|
+
|
|
41
|
+
Vuelve a ejecutar `newsline init` o edita `~/.config/newsline/config.json`:
|
|
42
|
+
|
|
43
|
+
| Clave | Predeterminado | Significado |
|
|
44
|
+
|---|---|---|
|
|
45
|
+
| `lang` | `auto` | `ko` `ja` `en` `es` `fr` `de` `pt` `zh`, o `auto` |
|
|
46
|
+
| `topic` | `general` | `general` `tech` `business` `world` `sports` `science` `health` `entertainment` |
|
|
47
|
+
| `rotate` | `6` | segundos por titular |
|
|
48
|
+
| `count` | `15` | titulares en rotación |
|
|
49
|
+
| `maxlen` | `120` | caracteres máximos (`max` = sin recorte) |
|
|
50
|
+
| `icon` | `📰` | icono inicial (`none` para ocultarlo) |
|
|
51
|
+
|
|
52
|
+
## Desinstalar
|
|
53
|
+
|
|
54
|
+
```sh
|
|
55
|
+
newsline uninstall # restaura tu barra de estado anterior
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Necesita `bash` + `python3` (macOS / Linux; Windows mediante WSL).
|
package/readme/fr.md
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
[English](../README.md) · [한국어](ko.md) · [日本語](ja.md) · [Español](es.md) · **Français** · [Deutsch](de.md) · [Português](pt.md) · [中文](zh.md)
|
|
4
|
+
|
|
5
|
+
</div>
|
|
6
|
+
|
|
7
|
+
# newsline
|
|
8
|
+
|
|
9
|
+
**En attendant que Claude Code termine ? Lisez l'actualité en une ligne, directement dans votre barre d'état.**
|
|
10
|
+
Un titre régional défile en bas de votre session : une longue attente se transforme en un coup d'œil
|
|
11
|
+
rapide à l'actualité. Il s'affiche *sous* votre barre d'état existante (votre HUD reste en place).
|
|
12
|
+
|
|
13
|
+
## Installer et lancer — une ligne
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
# curl (macOS / Linux / WSL) — installe et configure immédiatement
|
|
17
|
+
curl -fsSL https://raw.githubusercontent.com/itdar/cc-plugin/master/install.sh | sh
|
|
18
|
+
|
|
19
|
+
# Homebrew
|
|
20
|
+
brew install itdar/tap/newsline && newsline init
|
|
21
|
+
|
|
22
|
+
# npm
|
|
23
|
+
npm i -g newsline-cli && newsline init
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
La ligne d'actualité apparaît dès votre **prochain message** — sans redémarrage. La configuration vous
|
|
27
|
+
demande une langue et un thème, et conserve votre barre d'état existante.
|
|
28
|
+
|
|
29
|
+
## Ce que ça fait
|
|
30
|
+
|
|
31
|
+
- **Votre machine récupère et affiche l'actualité** — elle ne touche jamais à votre code, vos prompts,
|
|
32
|
+
vos fichiers ni vos conversations avec Claude.
|
|
33
|
+
- Un petit **service edge** sélectionne les meilleures sources régionales et route les clics sur les
|
|
34
|
+
titres, afin que les sources restent à jour sans réinstallation. S'il est injoignable, newsline
|
|
35
|
+
**bascule sur les flux intégrés**.
|
|
36
|
+
- La barre d'état est instantanée (servie depuis un cache) ; les rafraîchissements s'exécutent en
|
|
37
|
+
arrière-plan.
|
|
38
|
+
|
|
39
|
+
## Configurer
|
|
40
|
+
|
|
41
|
+
Relancez `newsline init`, ou modifiez `~/.config/newsline/config.json` :
|
|
42
|
+
|
|
43
|
+
| Clé | Par défaut | Signification |
|
|
44
|
+
|---|---|---|
|
|
45
|
+
| `lang` | `auto` | `ko` `ja` `en` `es` `fr` `de` `pt` `zh`, ou `auto` |
|
|
46
|
+
| `topic` | `general` | `general` `tech` `business` `world` `sports` `science` `health` `entertainment` |
|
|
47
|
+
| `rotate` | `6` | secondes par titre |
|
|
48
|
+
| `count` | `15` | titres en rotation |
|
|
49
|
+
| `maxlen` | `120` | caractères maximum (`max` = sans coupure) |
|
|
50
|
+
| `icon` | `📰` | icône de tête (`none` pour masquer) |
|
|
51
|
+
|
|
52
|
+
## Désinstaller
|
|
53
|
+
|
|
54
|
+
```sh
|
|
55
|
+
newsline uninstall # restaure votre barre d'état précédente
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Nécessite `bash` + `python3` (macOS / Linux ; Windows via WSL).
|
package/readme/ja.md
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
[English](../README.md) · [한국어](ko.md) · **日本語** · [Español](es.md) · [Français](fr.md) · [Deutsch](de.md) · [Português](pt.md) · [中文](zh.md)
|
|
4
|
+
|
|
5
|
+
</div>
|
|
6
|
+
|
|
7
|
+
# newsline
|
|
8
|
+
|
|
9
|
+
**Claude Code の処理が終わるのを待っていますか?最新のひとことニュースをステータスラインで読みましょう。**
|
|
10
|
+
地域のヘッドラインがセッション下部で切り替わり表示され、長い待ち時間がサッとニュースをチェックする
|
|
11
|
+
時間に変わります。既存のステータスライン(HUD)の **下の行に** 表示されるので、HUD はそのまま残ります。
|
|
12
|
+
|
|
13
|
+
## インストールと実行 — 一行で
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
# curl (macOS / Linux / WSL) — インストールとセットアップを一気に
|
|
17
|
+
curl -fsSL https://raw.githubusercontent.com/itdar/cc-plugin/master/install.sh | sh
|
|
18
|
+
|
|
19
|
+
# Homebrew
|
|
20
|
+
brew install itdar/tap/newsline && newsline init
|
|
21
|
+
|
|
22
|
+
# npm
|
|
23
|
+
npm i -g newsline-cli && newsline init
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
ニュース行は **次のメッセージ** で表示されます — 再起動は不要です。セットアップで言語とトピックを
|
|
27
|
+
尋ね、既存のステータスラインはそのまま保持します。
|
|
28
|
+
|
|
29
|
+
## 何をするか
|
|
30
|
+
|
|
31
|
+
- **ニュースの取得と表示はあなたのマシン上で** 行われます — コード・プロンプト・ファイル・Claude との
|
|
32
|
+
会話には一切触れません。
|
|
33
|
+
- 小さな **エッジサービス** が地域に最適なソースを選び、ヘッドラインのクリックをルーティングします。
|
|
34
|
+
おかげで再インストールなしでソースが最新に保たれ、エッジに接続できない場合は **内蔵フィードに自動
|
|
35
|
+
フォールバック** します。
|
|
36
|
+
- ステータスラインはキャッシュから **即座に** 表示され、更新はバックグラウンドで実行されます。
|
|
37
|
+
|
|
38
|
+
## 設定
|
|
39
|
+
|
|
40
|
+
`newsline init` を再実行するか、`~/.config/newsline/config.json` を編集します:
|
|
41
|
+
|
|
42
|
+
| キー | 既定値 | 意味 |
|
|
43
|
+
|---|---|---|
|
|
44
|
+
| `lang` | `auto` | `ko` `ja` `en` `es` `fr` `de` `pt` `zh`、または `auto` |
|
|
45
|
+
| `topic` | `general` | `general` `tech` `business` `world` `sports` `science` `health` `entertainment` |
|
|
46
|
+
| `rotate` | `6` | ヘッドラインごとの表示秒数 |
|
|
47
|
+
| `count` | `15` | 切り替えるヘッドライン数 |
|
|
48
|
+
| `maxlen` | `120` | 最大文字数(`max` = 無制限) |
|
|
49
|
+
| `icon` | `📰` | 先頭アイコン(`none` で非表示) |
|
|
50
|
+
|
|
51
|
+
## アンインストール
|
|
52
|
+
|
|
53
|
+
```sh
|
|
54
|
+
newsline uninstall # 以前のステータスラインを復元します
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
`bash` と `python3` が必要です(macOS / Linux、Windows は WSL 経由)。
|
package/readme/ko.md
CHANGED
|
@@ -1,83 +1,55 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
**덮지 않고 그 아래에 붙입니다(compose)**.
|
|
3
|
+
[English](../README.md) · **한국어** · [日本語](ja.md) · [Español](es.md) · [Français](fr.md) · [Deutsch](de.md) · [Português](pt.md) · [中文](zh.md)
|
|
5
4
|
|
|
6
|
-
|
|
5
|
+
</div>
|
|
7
6
|
|
|
8
|
-
|
|
7
|
+
# newsline
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
**Claude Code 작업 끝나길 기다리는 동안, 상태줄에서 최신 한줄 뉴스를 확인하세요.**
|
|
10
|
+
긴 작업을 기다리는 시간이 뉴스 한 번 훑는 시간이 됩니다 — 세션 하단에 지역 맞춤 헤드라인이
|
|
11
|
+
회전 표시됩니다. 기존 상태줄(HUD) **아래 줄에** 붙어서, HUD는 그대로 보입니다.
|
|
13
12
|
|
|
14
|
-
## 설치
|
|
13
|
+
## 설치 & 실행 — 한 줄
|
|
15
14
|
|
|
16
|
-
**curl | sh** (macOS / Linux / WSL):
|
|
17
15
|
```sh
|
|
16
|
+
# curl (macOS / Linux / WSL) — 설치 + 셋업까지 바로
|
|
18
17
|
curl -fsSL https://raw.githubusercontent.com/itdar/cc-plugin/master/install.sh | sh
|
|
19
|
-
```
|
|
20
18
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
brew install itdar/tap/newsline
|
|
24
|
-
```
|
|
19
|
+
# Homebrew
|
|
20
|
+
brew install itdar/tap/newsline && newsline init
|
|
25
21
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
git clone https://github.com/itdar/cc-plugin && cd cc-plugin && ./install.sh
|
|
22
|
+
# npm
|
|
23
|
+
npm i -g newsline-cli && newsline init
|
|
29
24
|
```
|
|
30
25
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
```
|
|
36
|
-
`init`이 언어·분야를 물어보고(메뉴에서 번호 선택), 상태줄을 등록합니다. **기존 상태줄은 그대로
|
|
37
|
-
두고**(HUD 유지) 그 아래 줄에 뉴스를 보여줍니다. Claude Code가 설정을 핫리로드하므로 **재시작
|
|
38
|
-
없이 다음 메시지 때 바로** 나타납니다.
|
|
26
|
+
뉴스 줄은 **다음 메시지 때** 나타납니다 — 재시작 불필요. 셋업은 언어·분야를 물어보고, 기존
|
|
27
|
+
상태줄은 유지합니다.
|
|
28
|
+
|
|
29
|
+
## 무엇을 하나
|
|
39
30
|
|
|
40
|
-
|
|
31
|
+
- **뉴스 수집·표시는 내 컴퓨터에서** 이뤄집니다 — 코드·프롬프트·파일·Claude 대화는 건드리지 않아요.
|
|
32
|
+
- 작은 **엣지 서비스**가 지역에 맞는 최적 소스를 골라주고 헤드라인 클릭을 라우팅합니다. 덕분에
|
|
33
|
+
재설치 없이 소스가 최신으로 유지되고, 엣지가 불통이면 **내장 피드로 자동 폴백**합니다.
|
|
34
|
+
- 상태줄은 캐시에서 **즉시** 표시되고, 갱신은 백그라운드에서 돕니다.
|
|
41
35
|
|
|
42
36
|
## 설정
|
|
43
37
|
|
|
44
|
-
`newsline init
|
|
38
|
+
`newsline init` 재실행 또는 `~/.config/newsline/config.json` 편집:
|
|
45
39
|
|
|
46
|
-
| 키
|
|
40
|
+
| 키 | 기본값 | 의미 |
|
|
47
41
|
|---|---|---|
|
|
48
|
-
| `lang`
|
|
49
|
-
| `topic`
|
|
50
|
-
| `rotate`
|
|
51
|
-
| `count`
|
|
52
|
-
| `maxlen`
|
|
53
|
-
| `icon`
|
|
54
|
-
| `linkhint` / `NEWSLINE_LINKHINT` | off | 끝에 `↗`(외부 열기) 힌트 추가 |
|
|
55
|
-
| `api` / `NEWSLINE_API` | — | 선택: 큐레이션 API (서버 우선, 실패 시 로컬 폴백) |
|
|
56
|
-
| `endpoint` / `NEWSLINE_ENDPOINT` | — | 클릭 리다이렉트 래퍼 |
|
|
57
|
-
|
|
58
|
-
## 동작 방식
|
|
59
|
-
|
|
60
|
-
- **`newsline render`** (상태줄이 호출) — 캐시에서 **즉시** 출력하고 절대 막지 않으며, 캐시가
|
|
61
|
-
낡았으면 백그라운드 refresh를 트리거합니다.
|
|
62
|
-
- **`newsline refresh`** — keyless 지역 RSS/Atom 피드를 로컬에서 받아 상위 헤드라인을 파싱·중복
|
|
63
|
-
제거·캐시합니다.
|
|
64
|
-
- 헤드라인은 클릭 가능한 [OSC 8] 링크입니다. 그냥 클릭 vs `Cmd`/`Ctrl`+클릭으로 열리는지는
|
|
65
|
-
newsline이 아니라 **터미널 설정**이 결정합니다.
|
|
42
|
+
| `lang` | `auto` | `ko` `ja` `en` `es` `fr` `de` `pt` `zh` 또는 `auto` |
|
|
43
|
+
| `topic` | `general` | `general` `tech` `business` `world` `sports` `science` `health` `entertainment` |
|
|
44
|
+
| `rotate` | `6` | 헤드라인당 노출 초 |
|
|
45
|
+
| `count` | `15` | 회전 헤드라인 수 |
|
|
46
|
+
| `maxlen` | `120` | 최대 글자수 (`max`=무제한) |
|
|
47
|
+
| `icon` | `📰` | 앞 아이콘 (`none`이면 제거) |
|
|
66
48
|
|
|
67
49
|
## 제거
|
|
68
50
|
|
|
69
51
|
```sh
|
|
70
|
-
newsline uninstall #
|
|
52
|
+
newsline uninstall # 이전 상태줄로 복구
|
|
71
53
|
```
|
|
72
54
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
로컬에서 실행되며, 로케일은 시스템 설정에서 감지(IP 지오로케이션 없음), 공개 피드를 직접
|
|
76
|
-
받아옵니다. 코드·프롬프트·파일·Claude 대화는 접근하지 않습니다. 클릭은 리다이렉트를 거치는데,
|
|
77
|
-
이는 참여도 측정과 향후 제휴 링크(운영 자금)를 위한 것이며 여기에 명시합니다.
|
|
78
|
-
|
|
79
|
-
## 요구사항
|
|
80
|
-
|
|
81
|
-
`bash`, `python3`. macOS·Linux는 기본 지원, Windows는 WSL 또는 Git Bash.
|
|
82
|
-
|
|
83
|
-
[OSC 8]: https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda
|
|
55
|
+
`bash` + `python3` 필요 (macOS / Linux; Windows는 WSL).
|
package/readme/pt.md
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
[English](../README.md) · [한국어](ko.md) · [日本語](ja.md) · [Español](es.md) · [Français](fr.md) · [Deutsch](de.md) · **Português** · [中文](zh.md)
|
|
4
|
+
|
|
5
|
+
</div>
|
|
6
|
+
|
|
7
|
+
# newsline
|
|
8
|
+
|
|
9
|
+
**Esperando o Claude Code terminar? Leia as últimas notícias em uma linha, direto na sua barra de status.**
|
|
10
|
+
Uma manchete regional rotativa fica na parte de baixo da sua sessão — então uma longa espera vira uma
|
|
11
|
+
olhada rápida nas notícias. Ela aparece *abaixo* da sua barra de status atual (seu HUD permanece).
|
|
12
|
+
|
|
13
|
+
## Instalar e executar — uma linha
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
# curl (macOS / Linux / WSL) — instala e configura na hora
|
|
17
|
+
curl -fsSL https://raw.githubusercontent.com/itdar/cc-plugin/master/install.sh | sh
|
|
18
|
+
|
|
19
|
+
# Homebrew
|
|
20
|
+
brew install itdar/tap/newsline && newsline init
|
|
21
|
+
|
|
22
|
+
# npm
|
|
23
|
+
npm i -g newsline-cli && newsline init
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
A linha de notícias aparece na sua **próxima mensagem** — sem reiniciar. A configuração pergunta o
|
|
27
|
+
idioma e o tema, e mantém a sua barra de status atual.
|
|
28
|
+
|
|
29
|
+
## O que faz
|
|
30
|
+
|
|
31
|
+
- **Sua máquina busca e exibe as notícias** — ela nunca toca no seu código, prompts, arquivos ou
|
|
32
|
+
conversas com o Claude.
|
|
33
|
+
- Um pequeno **serviço edge** escolhe as melhores fontes regionais e roteia os cliques nas manchetes,
|
|
34
|
+
mantendo as fontes atualizadas sem reinstalar. Se estiver inacessível, o newsline **usa os feeds
|
|
35
|
+
integrados como alternativa**.
|
|
36
|
+
- A barra de status é instantânea (servida de um cache); as atualizações rodam em segundo plano.
|
|
37
|
+
|
|
38
|
+
## Configurar
|
|
39
|
+
|
|
40
|
+
Execute `newsline init` novamente ou edite `~/.config/newsline/config.json`:
|
|
41
|
+
|
|
42
|
+
| Chave | Padrão | Significado |
|
|
43
|
+
|---|---|---|
|
|
44
|
+
| `lang` | `auto` | `ko` `ja` `en` `es` `fr` `de` `pt` `zh`, ou `auto` |
|
|
45
|
+
| `topic` | `general` | `general` `tech` `business` `world` `sports` `science` `health` `entertainment` |
|
|
46
|
+
| `rotate` | `6` | segundos por manchete |
|
|
47
|
+
| `count` | `15` | manchetes na rotação |
|
|
48
|
+
| `maxlen` | `120` | máximo de caracteres (`max` = sem corte) |
|
|
49
|
+
| `icon` | `📰` | ícone inicial (`none` para ocultar) |
|
|
50
|
+
|
|
51
|
+
## Desinstalar
|
|
52
|
+
|
|
53
|
+
```sh
|
|
54
|
+
newsline uninstall # restaura a sua barra de status anterior
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Precisa de `bash` + `python3` (macOS / Linux; Windows via WSL).
|
package/readme/zh.md
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
[English](../README.md) · [한국어](ko.md) · [日本語](ja.md) · [Español](es.md) · [Français](fr.md) · [Deutsch](de.md) · [Português](pt.md) · **中文**
|
|
4
|
+
|
|
5
|
+
</div>
|
|
6
|
+
|
|
7
|
+
# newsline
|
|
8
|
+
|
|
9
|
+
**等待 Claude Code 完成任务时,直接在状态栏里读一行最新新闻。**
|
|
10
|
+
一条轮播的区域头条位于会话底部——漫长的等待就变成了快速浏览新闻的时间。它显示在你现有状态栏的
|
|
11
|
+
*下方*(你的 HUD 保持不变)。
|
|
12
|
+
|
|
13
|
+
## 安装并运行 — 一行搞定
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
# curl (macOS / Linux / WSL) — 安装并立即完成设置
|
|
17
|
+
curl -fsSL https://raw.githubusercontent.com/itdar/cc-plugin/master/install.sh | sh
|
|
18
|
+
|
|
19
|
+
# Homebrew
|
|
20
|
+
brew install itdar/tap/newsline && newsline init
|
|
21
|
+
|
|
22
|
+
# npm
|
|
23
|
+
npm i -g newsline-cli && newsline init
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
新闻行会在你的 **下一条消息** 时出现——无需重启。设置会询问语言和主题,并保留你现有的状态栏。
|
|
27
|
+
|
|
28
|
+
## 它做什么
|
|
29
|
+
|
|
30
|
+
- **抓取和显示新闻都在你的电脑上完成**——绝不触碰你的代码、提示词、文件或与 Claude 的对话。
|
|
31
|
+
- 一个小型 **边缘服务** 挑选最合适的区域新闻源并路由头条点击,因此无需重装即可保持新闻源常新。
|
|
32
|
+
若无法连接,newsline 会 **回退到内置源**。
|
|
33
|
+
- 状态栏即时显示(来自缓存);刷新在后台进行。
|
|
34
|
+
|
|
35
|
+
## 配置
|
|
36
|
+
|
|
37
|
+
重新运行 `newsline init`,或编辑 `~/.config/newsline/config.json`:
|
|
38
|
+
|
|
39
|
+
| 键 | 默认值 | 含义 |
|
|
40
|
+
|---|---|---|
|
|
41
|
+
| `lang` | `auto` | `ko` `ja` `en` `es` `fr` `de` `pt` `zh`,或 `auto` |
|
|
42
|
+
| `topic` | `general` | `general` `tech` `business` `world` `sports` `science` `health` `entertainment` |
|
|
43
|
+
| `rotate` | `6` | 每条头条显示秒数 |
|
|
44
|
+
| `count` | `15` | 轮播头条数量 |
|
|
45
|
+
| `maxlen` | `120` | 最大字符数(`max` = 不截断) |
|
|
46
|
+
| `icon` | `📰` | 前置图标(`none` 为隐藏) |
|
|
47
|
+
|
|
48
|
+
## 卸载
|
|
49
|
+
|
|
50
|
+
```sh
|
|
51
|
+
newsline uninstall # 恢复你之前的状态栏
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
需要 `bash` + `python3`(macOS / Linux;Windows 通过 WSL)。
|
package/refresh.sh
CHANGED
|
@@ -16,10 +16,20 @@ FEEDS="${NEWSLINE_FEEDS:-$HERE/feeds.json}"
|
|
|
16
16
|
# through it so monetization can be switched on later WITHOUT re-shipping.
|
|
17
17
|
ENDPOINT="${NEWSLINE_ENDPOINT:-https://newsline.thesockerrr.workers.dev/r}"
|
|
18
18
|
COUNT="${NEWSLINE_COUNT:-15}" # how many headlines to cache for rotation
|
|
19
|
+
# Client version, sent to /feed as ?v= for the server-side update nudge.
|
|
20
|
+
# Bump together with package.json when tagging a release.
|
|
21
|
+
export NEWSLINE_VERSION="${NEWSLINE_VERSION:-0.1.0}"
|
|
19
22
|
LOCK="$CACHE_DIR/refresh.lock"
|
|
20
23
|
mkdir -p "$CACHE_DIR" 2>/dev/null
|
|
21
24
|
|
|
22
25
|
# --- single-flight: if another refresh is already running, bail quietly ---
|
|
26
|
+
# A refresh killed before its EXIT trap runs (SIGKILL, reboot, closed terminal)
|
|
27
|
+
# leaves the lock behind; expire locks older than 120s so refreshes never wedge.
|
|
28
|
+
if [ -d "$LOCK" ]; then
|
|
29
|
+
now=$(date +%s)
|
|
30
|
+
lmt=$(stat -f %m "$LOCK" 2>/dev/null || stat -c %Y "$LOCK" 2>/dev/null || echo 0)
|
|
31
|
+
[ $((now - lmt)) -gt 120 ] && rmdir "$LOCK" 2>/dev/null
|
|
32
|
+
fi
|
|
23
33
|
if ! mkdir "$LOCK" 2>/dev/null; then exit 0; fi
|
|
24
34
|
trap 'rmdir "$LOCK" 2>/dev/null' EXIT
|
|
25
35
|
|
|
@@ -49,14 +59,58 @@ esac
|
|
|
49
59
|
# --- server-first feed selection (COARSE context only), local fallback ---
|
|
50
60
|
# resolve.py tries $NEWSLINE_API/feed; on any failure it returns local feeds.json.
|
|
51
61
|
# We send lang/country/localtime/dow/tz only — no personal data, no tracking id.
|
|
62
|
+
# The resolved list is reused for NEWSLINE_API_TTL seconds (feed curation changes
|
|
63
|
+
# rarely) so the curation API is hit at most hourly, not on every refresh.
|
|
52
64
|
RESOLVED="$CACHE_DIR/feeds.resolved.json"
|
|
65
|
+
RESOLVE_STAMP="$CACHE_DIR/feeds.resolved.ok"
|
|
66
|
+
RESOLVE_TTL="${NEWSLINE_API_TTL:-3600}"
|
|
53
67
|
export NEWSLINE_COUNTRY="$country"
|
|
54
68
|
export NEWSLINE_LOCALTIME="$(date +%H%M)"
|
|
55
69
|
export NEWSLINE_DOW="$(date +%u)"
|
|
56
70
|
export NEWSLINE_TZ="$(date +%z)"
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
71
|
+
need_resolve=1
|
|
72
|
+
if [ -s "$RESOLVED" ] && [ -f "$RESOLVE_STAMP" ]; then
|
|
73
|
+
now=$(date +%s)
|
|
74
|
+
smt=$(stat -f %m "$RESOLVE_STAMP" 2>/dev/null || stat -c %Y "$RESOLVE_STAMP" 2>/dev/null || echo 0)
|
|
75
|
+
[ $((now - smt)) -lt "$RESOLVE_TTL" ] && need_resolve=0
|
|
76
|
+
fi
|
|
77
|
+
if [ "$need_resolve" = "1" ]; then
|
|
78
|
+
if python3 "$HERE/resolve.py" "$lang" "$FEEDS" "${NEWSLINE_API:-https://newsline.thesockerrr.workers.dev}" > "$RESOLVED.tmp" 2>/dev/null \
|
|
79
|
+
&& [ -s "$RESOLVED.tmp" ]; then
|
|
80
|
+
mv -f "$RESOLVED.tmp" "$RESOLVED"
|
|
81
|
+
touch "$RESOLVE_STAMP"
|
|
82
|
+
else
|
|
83
|
+
rm -f "$RESOLVED.tmp" 2>/dev/null
|
|
84
|
+
[ -s "$RESOLVED" ] || cp "$FEEDS" "$RESOLVED" 2>/dev/null # last-ditch fallback to bundled feeds
|
|
85
|
+
fi
|
|
86
|
+
fi
|
|
87
|
+
|
|
88
|
+
# --- server-driven update nudge: one extra line below the news --------------
|
|
89
|
+
# If the server flagged this client as too old (RESOLVED has "_update"),
|
|
90
|
+
# write the notice line statusline.sh appends; otherwise clear it. Skipped
|
|
91
|
+
# (cleared) when this client already meets minVersion, so the nudge stops
|
|
92
|
+
# right after an update even while the old RESOLVED is still cached.
|
|
93
|
+
NOTICE="$CACHE_DIR/notice"
|
|
94
|
+
msg=$(python3 -c '
|
|
95
|
+
import json, os, sys
|
|
96
|
+
try:
|
|
97
|
+
u = json.load(open(sys.argv[1])).get("_update") or {}
|
|
98
|
+
except Exception:
|
|
99
|
+
u = {}
|
|
100
|
+
def pv(s): return [int(x) if x.isdigit() else 0 for x in str(s).split(".")]
|
|
101
|
+
mv, cur = u.get("minVersion", ""), os.environ.get("NEWSLINE_VERSION", "")
|
|
102
|
+
print("" if (mv and cur and pv(cur) >= pv(mv)) else u.get("message", ""))
|
|
103
|
+
' "$RESOLVED" 2>/dev/null | tr -d '\000-\037')
|
|
104
|
+
if [ -n "$msg" ]; then
|
|
105
|
+
case "$HERE" in # install channel by where we live
|
|
106
|
+
*"/node_modules/"*) ucmd="npm i -g newsline-cli" ;;
|
|
107
|
+
*[Cc]ellar*|*linuxbrew*) ucmd="brew upgrade newsline" ;;
|
|
108
|
+
*) ucmd="curl -fsSL https://raw.githubusercontent.com/itdar/cc-plugin/master/install.sh | sh" ;;
|
|
109
|
+
esac
|
|
110
|
+
# yellow line, command in bold bright yellow — mirrors Claude Code's own update nudge
|
|
111
|
+
printf '\033[33m[%s] Run: \033[1;93m%s\033[0m\n' "$msg" "$ucmd" > "$NOTICE.tmp" && mv -f "$NOTICE.tmp" "$NOTICE"
|
|
112
|
+
else
|
|
113
|
+
rm -f "$NOTICE" 2>/dev/null
|
|
60
114
|
fi
|
|
61
115
|
|
|
62
116
|
# --- fetch + parse + build the line; write atomically, keep old cache on failure ---
|
package/resolve.py
CHANGED
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
"""newsline · resolve which feeds to use — server-first, local fallback, topic-aware.
|
|
3
3
|
|
|
4
4
|
Tries the operator's curation API (NEWSLINE_API) with a short timeout, sending only
|
|
5
|
-
COARSE CONTEXT (lang, country, localtime, dow, tz, topic) — never personal data.
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
COARSE CONTEXT (lang, country, localtime, dow, tz, topic) — never personal data.
|
|
6
|
+
The server's list is used AS-IS (it handles topic: vertical partners first, then
|
|
7
|
+
Google's topic feed). Only when the server is unreachable does the local fallback
|
|
8
|
+
kick in: bundled feeds.json, with a keyless Google News topic feed prepended if a
|
|
9
|
+
topic preference is set (NEWSLINE_TOPIC) so the topic still leads offline.
|
|
8
10
|
|
|
9
11
|
Prints a feeds.json-shaped object fetch.py consumes: {"<lang>": [...], "default": [...]}
|
|
10
12
|
|
|
@@ -32,6 +34,10 @@ GN_TOPICS = {
|
|
|
32
34
|
}
|
|
33
35
|
LANG_COUNTRY = {"ko": "KR", "ja": "JP", "en": "US", "de": "DE",
|
|
34
36
|
"fr": "FR", "es": "ES", "pt": "BR", "zh": "CN"}
|
|
37
|
+
# Google News wants regioned codes for some languages (bare "pt"/"zh" 404s or
|
|
38
|
+
# serves the wrong edition).
|
|
39
|
+
GN_HL = {"pt": "pt-BR", "zh": "zh-CN", "en": "en-US"}
|
|
40
|
+
GN_CEID_LANG = {"pt": "pt-419", "zh": "zh-Hans"}
|
|
35
41
|
|
|
36
42
|
|
|
37
43
|
def topic_feed(lang, country, topic):
|
|
@@ -42,8 +48,10 @@ def topic_feed(lang, country, topic):
|
|
|
42
48
|
if not t:
|
|
43
49
|
return None
|
|
44
50
|
gl = country or LANG_COUNTRY.get(lang, lang.upper())
|
|
51
|
+
hl = GN_HL.get(lang, lang)
|
|
52
|
+
cl = GN_CEID_LANG.get(lang, lang)
|
|
45
53
|
return (f"https://news.google.com/rss/headlines/section/topic/{t}"
|
|
46
|
-
f"?hl={
|
|
54
|
+
f"?hl={hl}&gl={gl}&ceid={gl}:{cl}")
|
|
47
55
|
|
|
48
56
|
|
|
49
57
|
def local_feeds(lang, path):
|
|
@@ -56,6 +64,7 @@ def local_feeds(lang, path):
|
|
|
56
64
|
|
|
57
65
|
|
|
58
66
|
def server_feeds(lang, api_base):
|
|
67
|
+
"""Return (feeds_or_None, update_or_None) from the curation API."""
|
|
59
68
|
params = {
|
|
60
69
|
"lang": lang,
|
|
61
70
|
"country": os.environ.get("NEWSLINE_COUNTRY", ""),
|
|
@@ -63,14 +72,22 @@ def server_feeds(lang, api_base):
|
|
|
63
72
|
"dow": os.environ.get("NEWSLINE_DOW", ""),
|
|
64
73
|
"tz": os.environ.get("NEWSLINE_TZ", ""),
|
|
65
74
|
"topic": os.environ.get("NEWSLINE_TOPIC", ""),
|
|
75
|
+
"v": os.environ.get("NEWSLINE_VERSION", ""),
|
|
66
76
|
}
|
|
67
77
|
url = api_base.rstrip("/") + "/feed?" + urllib.parse.urlencode(params)
|
|
68
78
|
req = urllib.request.Request(url, headers={"User-Agent": "newsline/0.1"})
|
|
69
79
|
obj = json.loads(urllib.request.urlopen(req, timeout=TIMEOUT).read())
|
|
80
|
+
update = obj.get("update")
|
|
81
|
+
if not isinstance(update, dict):
|
|
82
|
+
update = None
|
|
70
83
|
feeds = obj.get("feeds")
|
|
71
|
-
if isinstance(feeds, list)
|
|
72
|
-
|
|
73
|
-
|
|
84
|
+
if isinstance(feeds, list):
|
|
85
|
+
# fetch.py urlopens these — accept only http(s) URLs
|
|
86
|
+
feeds = [x for x in feeds
|
|
87
|
+
if isinstance(x, str) and x.startswith(("http://", "https://"))]
|
|
88
|
+
else:
|
|
89
|
+
feeds = None
|
|
90
|
+
return (feeds or None), update
|
|
74
91
|
|
|
75
92
|
|
|
76
93
|
def main():
|
|
@@ -81,22 +98,26 @@ def main():
|
|
|
81
98
|
|
|
82
99
|
local_urls, default = local_feeds(lang, local_path)
|
|
83
100
|
|
|
84
|
-
feeds = None
|
|
101
|
+
feeds = update = None
|
|
85
102
|
if api_base:
|
|
86
103
|
try:
|
|
87
|
-
feeds = server_feeds(lang, api_base) # server-first
|
|
104
|
+
feeds, update = server_feeds(lang, api_base) # server-first, used AS-IS
|
|
88
105
|
except Exception:
|
|
89
|
-
feeds = None
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
106
|
+
feeds, update = None, None # any issue -> local fallback
|
|
107
|
+
if not feeds:
|
|
108
|
+
# server unreachable: bundled feeds, topic preference prepended locally
|
|
109
|
+
feeds = local_urls or default
|
|
110
|
+
tfeed = topic_feed(lang, os.environ.get("NEWSLINE_COUNTRY", ""),
|
|
111
|
+
os.environ.get("NEWSLINE_TOPIC", ""))
|
|
112
|
+
if tfeed:
|
|
113
|
+
feeds = [tfeed] + [f for f in feeds if f != tfeed]
|
|
96
114
|
|
|
97
115
|
if not feeds:
|
|
98
116
|
return 1
|
|
99
|
-
|
|
117
|
+
out = {lang: feeds, "default": default or feeds}
|
|
118
|
+
if update:
|
|
119
|
+
out["_update"] = update # refresh.sh turns this into the notice line
|
|
120
|
+
sys.stdout.write(json.dumps(out, ensure_ascii=False))
|
|
100
121
|
return 0
|
|
101
122
|
|
|
102
123
|
|
package/statusline.sh
CHANGED
|
@@ -31,6 +31,8 @@ if [ -s "$CACHE_FILE" ]; then
|
|
|
31
31
|
n=$(awk 'END{print NR}' "$CACHE_FILE" 2>/dev/null)
|
|
32
32
|
if [ "${n:-0}" -gt 0 ] 2>/dev/null; then
|
|
33
33
|
rot="${NEWSLINE_ROTATE:-6}" # seconds each headline stays up
|
|
34
|
+
case "$rot" in ''|*[!0-9]*) rot=6 ;; esac
|
|
35
|
+
[ "$rot" -ge 1 ] || rot=6 # 0 would divide by zero below
|
|
34
36
|
now="${NEWSLINE_NOW:-$(date +%s)}" # NEWSLINE_NOW overrides clock (tests/debug)
|
|
35
37
|
idx=$(( (now / rot) % n + 1 ))
|
|
36
38
|
sed -n "${idx}p" "$CACHE_FILE"
|
|
@@ -40,3 +42,10 @@ if [ -s "$CACHE_FILE" ]; then
|
|
|
40
42
|
else
|
|
41
43
|
printf '📰 …' # first run, before the first refresh lands
|
|
42
44
|
fi
|
|
45
|
+
|
|
46
|
+
# --- server-driven update nudge (written/cleared by refresh.sh), below the news ---
|
|
47
|
+
NOTICE_FILE="$CACHE_DIR/notice"
|
|
48
|
+
if [ -s "$NOTICE_FILE" ]; then
|
|
49
|
+
[ -s "$CACHE_FILE" ] || printf '\n' # placeholder path prints without a newline
|
|
50
|
+
head -1 "$NOTICE_FILE"
|
|
51
|
+
fi
|