@noorslvt/shev 0.2.0 → 0.2.1

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/Cargo.lock CHANGED
@@ -1320,7 +1320,7 @@ dependencies = [
1320
1320
 
1321
1321
  [[package]]
1322
1322
  name = "shev"
1323
- version = "0.2.0"
1323
+ version = "0.2.1"
1324
1324
  dependencies = [
1325
1325
  "ratatui",
1326
1326
  "reqwest",
package/Cargo.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "shev"
3
- version = "0.2.0"
3
+ version = "0.2.1"
4
4
  edition = "2024"
5
5
  description = "A simple Jev-like decision making tool, without the hype."
6
6
  license = "Apache-2.0"
package/README.md CHANGED
@@ -1,3 +1,5 @@
1
+ ![shev — decisions, without the hype](assets/readme/hero.jpg)
2
+
1
3
  # shev
2
4
 
3
5
  **A simple Jev-like decision maker. Without the hype.**
@@ -46,19 +48,32 @@ profile; open a new terminal and type `shev`—no manual shell edits needed.
46
48
  Prefer npm?
47
49
 
48
50
  ```sh
49
- npm install -g git+https://github.com/aditauqir/shev.git
51
+ npm install -g @noorslvt/shev
50
52
  shev
51
53
  ```
52
54
 
53
55
  This installs the package **`@noorslvt/shev`**, with the terminal commands
54
56
  `shev` and `shev-cli`. The unscoped npm registry name `shev` belongs to an
55
- unrelated project; **do not use `npm install -g shev`** for this tool. This
56
- GitHub install does not require a separate npm registry publication.
57
+ unrelated project; **do not use `npm install -g shev`** for this tool. You can
58
+ also install straight from the repo without the registry:
59
+
60
+ ```sh
61
+ npm install -g git+https://github.com/aditauqir/shev.git
62
+ ```
57
63
 
58
64
  Both installers prefer a matching prebuilt release and otherwise build with
59
- Rust (installing the minimal Rust toolchain if necessary). npm uses the source
60
- shipped in its package, not an unrelated registry binary. macOS/Linux are
61
- supported by the npm launcher; Windows users can use WSL.
65
+ Rust (installing the minimal Rust toolchain if necessary). npm runs a
66
+ `postinstall` step that fetches the right binary or builds it; macOS/Linux are
67
+ supported by the npm launcher, and Windows users can use WSL.
68
+
69
+ Recent npm releases gate install scripts. If your install is skipped, `shev`
70
+ tells you what to run; you can also fix it up front:
71
+
72
+ ```sh
73
+ # if npm blocked the install scripts:
74
+ npm approve-scripts @noorslvt/shev # or:
75
+ npm rebuild -g @noorslvt/shev --foreground-scripts
76
+ ```
62
77
 
63
78
  Got Rust already? Same thing, just as short:
64
79
 
@@ -145,6 +160,10 @@ Text panels use **`#0F0E08`** over the dimmed photo wallpaper. Type `/` for comm
145
160
  Panels are inset from the terminal edges. Skills/tools menus show names only;
146
161
  long menus scroll around the selected row instead of clipping it off-screen.
147
162
 
163
+ ![shev home screen](assets/readme/app-home.png)
164
+
165
+ ![shev decision view](assets/readme/app-decision.png)
166
+
148
167
  | Command | What it does |
149
168
  | --- | --- |
150
169
  | `/file <path>` | Load a message from a file (drag-and-drop works too) |
Binary file
Binary file
Binary file
package/install.sh CHANGED
@@ -8,7 +8,7 @@
8
8
  set -euo pipefail
9
9
 
10
10
  REPO="aditauqir/shev"
11
- VERSION="0.2.0"
11
+ VERSION="0.2.1"
12
12
  if [ -n "${SHEV_PREFIX:-}" ]; then
13
13
  PREFIX="$SHEV_PREFIX"
14
14
  elif [[ ":$PATH:" == *":$HOME/.local/bin:"* ]]; then
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@noorslvt/shev",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "A simple Jev-like decision making tool, without the hype.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": { "type": "git", "url": "https://github.com/aditauqir/shev.git" },
package/src/bin/shev.rs CHANGED
@@ -1970,6 +1970,17 @@ fn render_header(frame: &mut Frame, app: &mut App, area: Rect) {
1970
1970
  Span::styled("shev", accent_bold()),
1971
1971
  Span::styled(" — decisions, without the hype", dim()),
1972
1972
  ]);
1973
+ let title_width: u16 = title
1974
+ .spans
1975
+ .iter()
1976
+ .map(|span| span.content.chars().count() as u16)
1977
+ .sum();
1978
+ // the wallpaper paints half-block glyphs into every cell; without a solid fill
1979
+ // first, restyling the bar leaves those glyphs glowing after the title.
1980
+ frame.render_widget(
1981
+ Paragraph::new(Line::from(" ".repeat(area.width as usize))).style(base()),
1982
+ area,
1983
+ );
1973
1984
  frame.render_widget(Paragraph::new(title).style(base()), area);
1974
1985
 
1975
1986
  let button = if app.home {
@@ -1981,7 +1992,8 @@ fn render_header(frame: &mut Frame, app: &mut App, area: Rect) {
1981
1992
  return;
1982
1993
  };
1983
1994
  let width = label.chars().count() as u16;
1984
- if area.width < width + 4 {
1995
+ // only place the button when it will not sit on top of the tagline
1996
+ if area.width < title_width + width + 2 {
1985
1997
  return;
1986
1998
  }
1987
1999
  let rect = Rect {
@@ -2094,7 +2106,14 @@ fn mode_line(app: &App) -> Line<'static> {
2094
2106
  Stage::Intent => ("Intent", "drives the response".to_string()),
2095
2107
  Stage::Working => (
2096
2108
  "Build",
2097
- format!("{} asking DeepSeek…", SPINNER[app.spinner % SPINNER.len()]),
2109
+ format!(
2110
+ "{} asking {}…",
2111
+ SPINNER[app.spinner % SPINNER.len()],
2112
+ match app.provider {
2113
+ Provider::DeepSeek => "DeepSeek",
2114
+ Provider::Bonsai => "bonsai (local)",
2115
+ }
2116
+ ),
2098
2117
  ),
2099
2118
  Stage::Result => (
2100
2119
  "Build",
@@ -3706,6 +3725,50 @@ mod tests {
3706
3725
  assert!(screen.contains("/ commands"), "{screen}");
3707
3726
  }
3708
3727
 
3728
+ #[test]
3729
+ fn the_header_bar_does_not_let_the_wallpaper_bleed_through() {
3730
+ let mut app = app_for_test();
3731
+ app.stage = Stage::Message;
3732
+ let screen = draw_to_string(&mut app, 60, 24);
3733
+ let header = screen.lines().nth(1).expect("a header row");
3734
+ assert!(
3735
+ header.contains("shev — decisions, without the hype"),
3736
+ "{header:?}"
3737
+ );
3738
+ // the bar is inset two columns from each edge; the wallpaper lives in that
3739
+ // margin, but nothing inside the bar may keep its half-block glyphs, or the
3740
+ // leftover cells glow as a bright strip after the title
3741
+ let chars: Vec<char> = header.chars().collect();
3742
+ let bar: String = chars[2..chars.len() - 2].iter().collect();
3743
+ assert!(
3744
+ !bar.contains('▀'),
3745
+ "wallpaper glyphs leaked into the header bar: {bar:?}"
3746
+ );
3747
+ }
3748
+
3749
+ #[test]
3750
+ fn the_working_line_names_the_model_in_use() {
3751
+ let line = |app: &App| -> String {
3752
+ mode_line(app)
3753
+ .spans
3754
+ .iter()
3755
+ .map(|span| span.content.to_string())
3756
+ .collect()
3757
+ };
3758
+
3759
+ let mut app = app_for_test();
3760
+ app.stage = Stage::Working;
3761
+
3762
+ app.provider = Provider::Bonsai;
3763
+ let bonsai = line(&app);
3764
+ assert!(bonsai.contains("asking bonsai (local)"), "{bonsai}");
3765
+ assert!(!bonsai.to_lowercase().contains("deepseek"), "{bonsai}");
3766
+
3767
+ app.provider = Provider::DeepSeek;
3768
+ let deepseek = line(&app);
3769
+ assert!(deepseek.contains("asking DeepSeek"), "{deepseek}");
3770
+ }
3771
+
3709
3772
  #[test]
3710
3773
  fn shows_a_decision_as_a_clean_list_with_the_box_underneath() {
3711
3774
  let mut app = app_for_test();