newsline-cli 1.0.1 β†’ 1.0.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 CHANGED
@@ -71,6 +71,7 @@ Re-run `newsline init`, or edit `~/.config/newsline/config.json`:
71
71
  | `maxlen` | `120` | max characters (`max` = no cut) |
72
72
  | `icon` | `πŸ“°` | leading glyph (`none` to hide) |
73
73
  | `color` | `magenta` | headline color: `default` `gray` `white` `cyan` `yellow` `green` `blue` `magenta` `red` |
74
+ | `clickhint` | `auto` | click-modifier hint before the headline (`auto` per terminal, `off` to hide, or custom text) |
74
75
 
75
76
  Set one directly with `newsline color <name>` (e.g. `newsline color gray`), or run
76
77
  `newsline color` with no name to step to the next color (default β†’ gray β†’ white β†’ cyan β†’
@@ -98,7 +99,13 @@ Claude Code plugin: run `/plugin marketplace update itdar`, then `/reload-plugin
98
99
  ## Uninstall
99
100
 
100
101
  ```sh
101
- newsline uninstall # restores your previous status line
102
+ newsline uninstall # restores your previous status line
103
+ newsline uninstall --purge # also removes the CLI binary + the PATH line install.sh added
102
104
  ```
103
105
 
104
106
  Needs `bash` + `python3` (macOS / Linux; Windows via WSL).
107
+
108
+ ## Contributing
109
+
110
+ Bug reports and PRs welcome β€” see [CONTRIBUTING.md](CONTRIBUTING.md) for local setup,
111
+ testing, and the release process.
package/install.sh CHANGED
@@ -34,6 +34,32 @@ ln -sf "$SHARE/newsline" "$BIN/newsline"
34
34
 
35
35
  echo "βœ” installed: $BIN/newsline"
36
36
 
37
+ # Garbage-collect stale PATH lines from earlier installs whose prefix no longer
38
+ # exists (e.g. throwaway test prefixes), so rc files don't pile up dead newsline
39
+ # entries that shadow the current install. Only drops blocks whose dir is gone.
40
+ prune_stale_newsline_path() { # $1 = rc file
41
+ [ -f "$1" ] || return 0
42
+ awk '
43
+ /^# added by newsline installer$/ {
44
+ marker=$0
45
+ if ((getline pl) > 0) {
46
+ keep=1
47
+ if (match(pl, /\/[^ "]*\/bin/)) {
48
+ d=substr(pl, RSTART, RLENGTH)
49
+ if (system("[ -d \"" d "\" ]") != 0) keep=0
50
+ }
51
+ if (keep) { print marker; print pl }
52
+ next
53
+ }
54
+ print marker; next
55
+ }
56
+ { print }
57
+ ' "$1" > "$1.nltmp" 2>/dev/null && mv "$1.nltmp" "$1"
58
+ }
59
+ for rc in "$HOME/.zshrc" "$HOME/.bashrc" "$HOME/.profile" "$HOME/.config/fish/config.fish"; do
60
+ prune_stale_newsline_path "$rc"
61
+ done
62
+
37
63
  # Register $BIN on PATH for future shells (idempotent), by shell rc file.
38
64
  case ":$PATH:" in
39
65
  *":$BIN:"*)
package/newsline CHANGED
@@ -80,7 +80,8 @@ import json,sys,shlex
80
80
  env={"lang":"NEWSLINE_LANG","topic":"NEWSLINE_TOPIC","rotate":"NEWSLINE_ROTATE",
81
81
  "count":"NEWSLINE_COUNT","maxlen":"NEWSLINE_MAXLEN","icon":"NEWSLINE_ICON",
82
82
  "color":"NEWSLINE_COLOR","linkhint":"NEWSLINE_LINKHINT","api":"NEWSLINE_API",
83
- "endpoint":"NEWSLINE_ENDPOINT","base_statusline":"NEWSLINE_BASE"}
83
+ "endpoint":"NEWSLINE_ENDPOINT","base_statusline":"NEWSLINE_BASE",
84
+ "clickhint":"NEWSLINE_CLICKHINT"}
84
85
  try: d=json.load(open(sys.argv[1]))
85
86
  except Exception: d={}
86
87
  for k,name in env.items():
@@ -154,12 +155,12 @@ cmd_once() {
154
155
  }
155
156
 
156
157
  cmd_init() {
157
- local lang="" topic="" color="" api="" endpoint="" rotate="6" count="15" compose="auto" yes="0" base_override="" a
158
+ local lang="" topic="" color="" api="" endpoint="" rotate="6" count="15" compose="auto" yes="0" base_override="" rotate_set=0 a
158
159
  while [ $# -gt 0 ]; do
159
160
  case "$1" in
160
161
  --lang) lang="$2"; shift 2;; --topic) topic="$2"; shift 2;;
161
162
  --api) api="$2"; shift 2;; --endpoint) endpoint="$2"; shift 2;;
162
- --rotate) rotate="$2"; shift 2;; --count) count="$2"; shift 2;;
163
+ --rotate) rotate="$2"; rotate_set=1; shift 2;; --count) count="$2"; shift 2;;
163
164
  --base) base_override="$2"; shift 2;;
164
165
  --compose) compose="yes"; shift;; --no-compose) compose="no"; shift;;
165
166
  --settings) SETTINGS="$2"; shift 2;; --yes|-y) yes="1"; shift;;
@@ -181,26 +182,42 @@ cmd_init() {
181
182
  local avail c; local -a menu=(auto "Auto-detect (now: $(lang_label "$detected"))")
182
183
  avail=$(python3 -c "import json,sys;d=json.load(open(sys.argv[1]));print(' '.join(k for k in d if k not in ('default','_comment')))" "$HERE/feeds.json" 2>/dev/null)
183
184
  for c in $avail; do menu+=("$c" "$(lang_label "$c")"); done
184
- echo "Select news language:" >&2
185
+ echo "Newsline language?" >&2
185
186
  choose "$detected" "${menu[@]}"; lang="$CHOICE"
186
187
  fi
187
188
  if [ -z "$topic" ]; then
188
- echo "Select topic:" >&2
189
+ echo "Newsline topic?" >&2
189
190
  choose general \
190
191
  general "General" tech "Tech" business "Business" world "World" \
191
192
  sports "Sports" science "Science" health "Health" entertainment "Entertainment"
192
193
  topic="$CHOICE"
193
194
  fi
194
195
  local cur_color; cur_color=$(cfg_get color)
195
- echo "Select headline color (change anytime: newsline color):" >&2
196
+ echo "Newsline color?" >&2
196
197
  choose "${cur_color:-magenta}" \
197
198
  default "Terminal default" cyan "Cyan" gray "Gray" white "White" \
198
199
  yellow "Yellow" green "Green" blue "Blue" magenta "Magenta" red "Red"
199
200
  color="$CHOICE"
201
+ if [ "$rotate_set" = "0" ]; then
202
+ # rotation interval β€” free-form seconds; blank/invalid keeps the current value
203
+ local cur_rotate; cur_rotate=$(cfg_get rotate); [ -n "$cur_rotate" ] || cur_rotate="$rotate"
204
+ echo "Newsline seconds?" >&2
205
+ printf " # [%s]: " "$cur_rotate" >&2
206
+ local sel_rotate; read -r sel_rotate < "${NL_TTY:-/dev/stdin}"
207
+ case "$sel_rotate" in
208
+ ''|*[!0-9]*) rotate="$cur_rotate" ;; # blank or non-numeric -> keep current
209
+ *) rotate="$sel_rotate" ;;
210
+ esac
211
+ [ "$rotate" -ge 1 ] 2>/dev/null || rotate=6
212
+ fi
200
213
  else
201
214
  # --yes: keep a color previously set via init/`newsline color`; magenta on first run
202
215
  lang="${lang:-$detected}"; topic="${topic:-general}"
203
216
  color=$(cfg_get color); [ -n "$color" ] || color="magenta"
217
+ # keep an existing rotation interval on re-init unless --rotate was given
218
+ if [ "$rotate_set" = "0" ]; then
219
+ local saved_rotate; saved_rotate=$(cfg_get rotate); [ -n "$saved_rotate" ] && rotate="$saved_rotate"
220
+ fi
204
221
  fi
205
222
 
206
223
  # Always KEEP an existing status line and show newsline below it (no prompt).
@@ -342,6 +359,8 @@ PY
342
359
  }
343
360
 
344
361
  cmd_uninstall() {
362
+ local purge=0 a
363
+ for a in "$@"; do case "$a" in --purge|-p) purge=1 ;; esac; done
345
364
  local base base_obj
346
365
  base=$(cfg_get base_statusline)
347
366
  base_obj=$(cfg_get_obj base_statusline_obj)
@@ -378,19 +397,62 @@ PY
378
397
  local cache="${NEWSLINE_CACHE:-$HOME/.cache/newsline}"
379
398
  rm -f "$CONFIG" "$cache/line" "$cache/feeds.resolved.json" "$cache/feeds.resolved.ok" "$cache/notice" 2>/dev/null
380
399
  echo "βœ” newsline uninstalled."
400
+
401
+ [ "$purge" = "1" ] || return 0
402
+ # --purge: also remove the CLI binary + the PATH line install.sh added. Only for
403
+ # the install.sh layout ($PREFIX/share/newsline + $PREFIX/bin/newsline); a
404
+ # package-managed install (brew/npm) is left to its own package manager.
405
+ case "$HERE" in
406
+ */share/newsline)
407
+ local prefix bin rc
408
+ prefix="$(cd "$HERE/../.." 2>/dev/null && pwd)"; bin="$prefix/bin"
409
+ if [ -e "$bin/newsline" ]; then
410
+ rm -f "$bin/newsline"
411
+ rm -rf "$HERE" # $PREFIX/share/newsline (this script's dir)
412
+ for rc in "$HOME/.zshrc" "$HOME/.bashrc" "$HOME/.profile" "$HOME/.config/fish/config.fish"; do
413
+ [ -f "$rc" ] || continue
414
+ awk -v bin="$bin" '
415
+ function strip(p){ sub(/^\/private/, "", p); return p } # macOS /var == /private/var
416
+ /^# added by newsline installer$/ {
417
+ marker=$0
418
+ if ((getline pl) > 0) {
419
+ drop=0
420
+ if (match(pl, /\/[^ "]*\/bin/)) {
421
+ d=substr(pl, RSTART, RLENGTH)
422
+ if (strip(d) == strip(bin)) drop=1 # our block -> drop marker + path line
423
+ }
424
+ if (drop) next
425
+ print marker; print pl; next
426
+ }
427
+ print marker; next
428
+ }
429
+ { print }
430
+ ' "$rc" > "$rc.nltmp" 2>/dev/null && mv "$rc.nltmp" "$rc"
431
+ done
432
+ echo "βœ” purged CLI: removed $bin/newsline and its PATH entry (open a new shell to refresh PATH)."
433
+ else
434
+ echo "β„Ή nothing to purge (no $bin/newsline)."
435
+ fi
436
+ ;;
437
+ *)
438
+ echo "β„Ή this CLI wasn't installed via install.sh (looks package-managed);"
439
+ echo " remove it with: brew uninstall newsline or npm uninstall -g newsline-cli"
440
+ ;;
441
+ esac
381
442
  }
382
443
 
383
444
  usage() {
384
445
  cat <<'EOF'
385
446
  newsline β€” local news status-line utility
386
447
  newsline init [--lang ko] [--topic tech] set up & wire the status line
387
- (asks language / topic / color)
448
+ [--rotate SECS] (asks language / topic / color / rotation)
388
449
  newsline color [name] set headline color (e.g. cyan, gray); with
389
450
  no name, step to the next: default β†’ gray β†’
390
451
  white β†’ cyan β†’ yellow β†’ green β†’ blue β†’
391
452
  magenta β†’ red β†’ back to default
392
453
  newsline colorlist list the available colors with a preview
393
- newsline uninstall remove & restore previous status line
454
+ newsline uninstall [--purge] remove & restore previous status line
455
+ (--purge also deletes the CLI + PATH line)
394
456
  EOF
395
457
  if [ "${1:-}" = "--all" ]; then
396
458
  cat <<'EOF'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "newsline-cli",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "Locale-aware one-line news in your Claude Code status line β€” local, composes with your existing status line.",
5
5
  "bin": {
6
6
  "newsline": "newsline"
package/readme/ko.md CHANGED
@@ -68,6 +68,7 @@ npm i -g newsline-cli && newsline init
68
68
  | `maxlen` | `120` | μ΅œλŒ€ κΈ€μžμˆ˜ (`max`=λ¬΄μ œν•œ) |
69
69
  | `icon` | `πŸ“°` | μ•ž μ•„μ΄μ½˜ (`none`이면 제거) |
70
70
  | `color` | `magenta` | λ‰΄μŠ€ κΈ€μž 색: `default` `gray` `white` `cyan` `yellow` `green` `blue` `magenta` `red` |
71
+ | `clickhint` | `auto` | ν—€λ“œλΌμΈ μ•ž 클릭 μ•ˆλ‚΄ (`auto`=터미널별 μžλ™, `off`=μˆ¨κΉ€, λ˜λŠ” 직접 μ§€μ •) |
71
72
 
72
73
  `newsline color <이름>`으둜 색을 λ°”λ‘œ μ§€μ •ν•˜κ±°λ‚˜(예: `newsline color gray`), 이름 없이
73
74
  `newsline color`λ₯Ό μ‹€ν–‰ν•˜λ©΄ λ‹€μŒ μƒ‰μœΌλ‘œ λ„˜μ–΄κ°‘λ‹ˆλ‹€ (default β†’ gray β†’ white β†’ cyan β†’
package/refresh.sh CHANGED
@@ -23,7 +23,7 @@ case "$API" in off|OFF|none|local) API="" ;; esac
23
23
  COUNT="${NEWSLINE_COUNT:-15}" # how many headlines to cache for rotation
24
24
  # Client version, sent to /feed as ?v= for the server-side update nudge.
25
25
  # Bump together with package.json when tagging a release.
26
- export NEWSLINE_VERSION="${NEWSLINE_VERSION:-1.0.1}"
26
+ export NEWSLINE_VERSION="${NEWSLINE_VERSION:-1.0.3}"
27
27
  LOCK="$CACHE_DIR/refresh.lock"
28
28
  mkdir -p "$CACHE_DIR" 2>/dev/null
29
29
 
package/statusline.sh CHANGED
@@ -42,6 +42,23 @@ colorize() { # wrap text in the configured color (no-op when unset); no newline
42
42
  if [ -n "$SGR" ]; then printf '\033[%sm%s\033[0m' "$SGR" "$1"; else printf '%s' "$1"; fi
43
43
  }
44
44
 
45
+ # --- per-terminal click hint, printed dim before the headline, e.g. "(Cmd+Click)" ---
46
+ # Headlines are OSC 8 hyperlinks; WHICH modifier opens them is the terminal's call,
47
+ # not ours (macOS terminals use Cmd, most others Ctrl, kitty Ctrl+Shift). Show the
48
+ # right one so users know how to click through. NEWSLINE_CLICKHINT: unset/auto/on =
49
+ # auto-detect; off/none/0 = hide; any other value is used verbatim (e.g. "⌘-click").
50
+ click_hint() {
51
+ case "$(printf '%s' "${NEWSLINE_CLICKHINT:-}" | tr '[:upper:]' '[:lower:]')" in
52
+ off|none|no|0|false) return 0 ;; # hidden
53
+ ''|auto|on|yes|1|true) ;; # auto-detect
54
+ *) printf '\033[90m(%s)\033[0m ' "$NEWSLINE_CLICKHINT"; return 0 ;; # custom label
55
+ esac
56
+ local sys="${OSTYPE:-}"; [ -n "$sys" ] || sys="$(uname -s 2>/dev/null)"
57
+ local mod; case "$sys" in [Dd]arwin*) mod="Cmd+Click" ;; *) mod="Ctrl+Click" ;; esac
58
+ { [ -n "${KITTY_WINDOW_ID:-}" ] || [ "${TERM:-}" = "xterm-kitty" ]; } && mod="Ctrl+Shift+Click"
59
+ printf '\033[90m(%s)\033[0m ' "$mod"
60
+ }
61
+
45
62
  # --- print one headline, rotating across the cached set by wall-clock time ---
46
63
  # The cache holds N headlines (one per line). We show a different one every
47
64
  # NEWSLINE_ROTATE seconds β€” a ticker feel with zero stored state.
@@ -53,6 +70,7 @@ if [ -s "$CACHE_FILE" ]; then
53
70
  [ "$rot" -ge 1 ] || rot=6 # 0 would divide by zero below
54
71
  now="${NEWSLINE_NOW:-$(date +%s)}" # NEWSLINE_NOW overrides clock (tests/debug)
55
72
  idx=$(( (now / rot) % n + 1 ))
73
+ click_hint # "(Cmd+Click)" etc., before the linked headline
56
74
  colorize "$(sed -n "${idx}p" "$CACHE_FILE")"; printf '\n'
57
75
  else
58
76
  colorize 'πŸ“° …'