openclacky 0.9.25 → 0.9.26

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 931a504ff578fcee85cf5d361feeca8b772a0aafe3438c4f27cc7da52573453a
4
- data.tar.gz: f1cef39e28c531fef2b2b0fb3cf435ffb05729fdeaae12dadb4e338df073ec1e
3
+ metadata.gz: 65dad1ba4790fcffb30157bcc74a05a9f9cbd3e341ead0e007e6c768b0c11fd1
4
+ data.tar.gz: f507ade251d206b073eb1b88c236d8905e9c986718d93c62cf3f2b575fa908bb
5
5
  SHA512:
6
- metadata.gz: 8b8bc2a7dd702beb4570e40264b63b75f170c26dea56a354b20c9afdd1d97fb0ef1e2a8010457170d23823ed6bc82353bbfd390c9ef8f6137ad9bbf73e15ee17
7
- data.tar.gz: dc13201eda667f31435309a3664503fd53f1973983e0494f2b29b2ff5c0fca336b9801c265aa18830e1878e0a970237de16ab41833992119d8ea24f2eea66b62
6
+ metadata.gz: f70df4500b95cd35c2a3fb245e384f94bbf4051da6528d6b9bd76a9a38e81838844cc68276ab0b7fc3833982252b9d61c1c5c3b414e22422049e2a91f2c24a4b
7
+ data.tar.gz: 6cd678e1288d06f6997c01c056b898c900ba9b7fcde2bacdd3a35b113d0ab4d33d3e5e2c2fde9a31d1c9c351b615b93f28bcfa305eb7489b4deda1c1c801ce45
data/CHANGELOG.md CHANGED
@@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.9.26] - 2026-04-03
11
+
12
+ ### Added
13
+ - **Long-running shell output streaming**: shell commands that run for a long time now stream output progressively to the Web UI instead of waiting until completion — no more blank screen for slow commands
14
+
15
+ ### Fixed
16
+ - **Session rename for non-active sessions**: renaming a session that isn't currently active now updates immediately in the sidebar (previously required a page refresh)
17
+ - **Feishu channel setup timeout**: increased timeout to 180s to prevent setup failures on slow networks
18
+ - **WSL browser setup tip**: improved browser-setup skill instructions for WSL environments
19
+ - **ARM install mirror**: install scripts now correctly use the Aliyun mirror on ARM machines
20
+
10
21
  ## [0.9.25] - 2026-04-02
11
22
 
12
23
  ### Added
data/lib/clacky/agent.rb CHANGED
@@ -662,6 +662,20 @@ module Clacky
662
662
  sleep 2
663
663
  @ui.show_progress(progress_message, prefix_newline: false, output_buffer: output_buffer)
664
664
  progress_shown = true
665
+
666
+ # For shell commands: stream new stdout lines to WebUI as they arrive
667
+ if output_buffer && @ui.respond_to?(:show_tool_stdout)
668
+ last_sent_count = 0
669
+ loop do
670
+ sleep 1
671
+ stdout_lines = output_buffer[:stdout_lines]&.to_a || []
672
+ new_lines = stdout_lines[last_sent_count..]
673
+ if new_lines && !new_lines.empty?
674
+ @ui.show_tool_stdout(new_lines)
675
+ last_sent_count = stdout_lines.size
676
+ end
677
+ end
678
+ end
665
679
  end
666
680
  end
667
681
 
@@ -79,15 +79,25 @@ Tell the user:
79
79
  > I've opened `chrome://inspect/#remote-debugging` in your browser.
80
80
  > Please click **"Allow remote debugging for this browser instance"** and let me know when done.
81
81
 
82
- If `open` fails (e.g. on WSL or Linux), fall back to:
82
+ If `open` fails (e.g. on WSL or Linux):
83
+
84
+ **On WSL**, guide the user in simple steps (no PowerShell commands needed):
85
+
86
+ > To enable remote debugging, please follow these steps:
87
+ >
88
+ > 1. Open **Edge** on Windows
89
+ > 2. Type the following in the address bar and press Enter:
90
+ > ```
91
+ > edge://inspect/#remote-debugging
92
+ > ```
93
+ > 3. Click **"Allow remote debugging for this browser instance"**
94
+ > 4. Let me know when done and I'll continue ✅
95
+
96
+ **On Linux (non-WSL)**:
83
97
 
84
98
  > Please open this URL in Chrome or Edge:
85
99
  > `chrome://inspect/#remote-debugging`
86
100
  > Then click **"Allow remote debugging for this browser instance"** and let me know when done.
87
- >
88
- > On WSL: launch your browser from PowerShell with remote debugging enabled:
89
- > - Edge: `Start-Process msedge --ArgumentList "--remote-debugging-port=9222"`
90
- > - Chrome: `Start-Process chrome --ArgumentList "--remote-debugging-port=9222"`
91
101
 
92
102
  Wait for the user to confirm, then retry the connection once. If still failing, stop:
93
103
 
@@ -92,6 +92,7 @@ Run the setup script (full path is available in the supporting files list above)
92
92
  ```bash
93
93
  ruby "SKILL_DIR/feishu_setup.rb"
94
94
  ```
95
+ **Important**: call `safe_shell` with `timeout: 180` — the script may wait up to 90s for a WebSocket connection in Phase 4.
95
96
 
96
97
  **If exit code is 0:**
97
98
  - The script completed successfully.
@@ -208,6 +208,14 @@ module Clacky
208
208
  forward_to_subscribers { |sub| sub.show_progress(message) }
209
209
  end
210
210
 
211
+ # Stream shell stdout/stderr lines to the browser while a command is running.
212
+ # Called periodically from the progress_timer thread in agent.rb.
213
+ def show_tool_stdout(lines)
214
+ return if lines.nil? || lines.empty?
215
+ emit("tool_stdout", lines: lines)
216
+ # Not forwarded to IM subscribers — too noisy
217
+ end
218
+
211
219
  def clear_progress
212
220
  elapsed = @progress_start_time ? (Time.now - @progress_start_time).round(1) : 0
213
221
  @progress_start_time = nil
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Clacky
4
- VERSION = "0.9.25"
4
+ VERSION = "0.9.26"
5
5
  end
@@ -1476,17 +1476,47 @@ body {
1476
1476
  .tool-group.expanded .tool-group-body { display: flex; }
1477
1477
  .tool-item {
1478
1478
  display: flex;
1479
- align-items: baseline;
1480
- gap: 6px;
1479
+ flex-direction: column;
1480
+ gap: 0;
1481
1481
  padding: 2px 6px;
1482
1482
  border-radius: 4px;
1483
1483
  }
1484
+ .tool-item-header {
1485
+ display: flex;
1486
+ align-items: baseline;
1487
+ gap: 6px;
1488
+ }
1484
1489
  .tool-item-name { color: var(--color-warning); font-weight: 600; }
1485
1490
  .tool-item-arg { color: var(--color-text-secondary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 400px; }
1486
1491
  .tool-item-status { margin-left: auto; font-size: 11px; flex-shrink: 0; }
1487
1492
  .tool-item-status.ok { color: var(--color-success); }
1488
1493
  .tool-item-status.err { color: var(--color-error); }
1489
1494
  .tool-item-status.running { color: var(--color-accent-primary); animation: pulse 1.2s infinite; }
1495
+ .tool-item-stdout {
1496
+ margin: 4px 0 2px 0;
1497
+ padding: 6px 8px;
1498
+ background: var(--color-bg-secondary);
1499
+ border: 1px solid var(--color-border-secondary);
1500
+ border-radius: 4px;
1501
+ font-size: 11px;
1502
+ font-family: monospace;
1503
+ color: var(--color-text-secondary);
1504
+ white-space: pre-wrap;
1505
+ word-break: break-all;
1506
+ max-height: 200px;
1507
+ overflow-y: auto;
1508
+ line-height: 1.5;
1509
+ }
1510
+ /* ANSI color classes for tool stdout */
1511
+ .tool-item-stdout .ansi-red { color: #e06c75; }
1512
+ .tool-item-stdout .ansi-green { color: #98c379; }
1513
+ .tool-item-stdout .ansi-yellow { color: #e5c07b; }
1514
+ .tool-item-stdout .ansi-blue { color: #61afef; }
1515
+ .tool-item-stdout .ansi-magenta { color: #c678dd; }
1516
+ .tool-item-stdout .ansi-cyan { color: #56b6c2; }
1517
+ .tool-item-stdout .ansi-white { color: #abb2bf; }
1518
+ .tool-item-stdout .ansi-black { color: #5c6370; }
1519
+ .tool-item-stdout .ansi-bold { font-weight: 700; }
1490
1520
 
1491
1521
  /* ── Thinking block (collapsible <think>...</think> sections) ─────────────── */
1492
1522
  .thinking-block {
@@ -420,6 +420,11 @@ WS.onEvent(ev => {
420
420
  Sessions.appendToolResult(ev.result);
421
421
  break;
422
422
 
423
+ case "tool_stdout":
424
+ if (ev.session_id !== Sessions.activeId) break;
425
+ Sessions.appendToolStdout(ev.lines);
426
+ break;
427
+
423
428
  case "tool_error":
424
429
  if (ev.session_id !== Sessions.activeId) break;
425
430
  Sessions.appendMsg("info", `⚠ Tool error: ${escapeHtml(ev.error)}`);
@@ -153,10 +153,51 @@ const Sessions = (() => {
153
153
  : `<span class="tool-item-name">⚙ ${escapeHtml(name)}</span>` +
154
154
  (argSummary ? `<span class="tool-item-arg">${escapeHtml(argSummary)}</span>` : "");
155
155
 
156
- item.innerHTML = label + `<span class="tool-item-status running">…</span>`;
156
+ item.innerHTML =
157
+ `<div class="tool-item-header">` +
158
+ label +
159
+ `<span class="tool-item-status running">…</span>` +
160
+ `</div>` +
161
+ `<pre class="tool-item-stdout" style="display:none"></pre>`;
157
162
  return item;
158
163
  }
159
164
 
165
+ // Convert ANSI escape codes to HTML spans with color classes.
166
+ // Handles the common SGR codes used by shell scripts (colors + reset).
167
+ function _ansiToHtml(text) {
168
+ const ANSI_COLORS = {
169
+ "30": "ansi-black", "31": "ansi-red", "32": "ansi-green",
170
+ "33": "ansi-yellow", "34": "ansi-blue", "35": "ansi-magenta",
171
+ "36": "ansi-cyan", "37": "ansi-white",
172
+ "1;31": "ansi-bold ansi-red", "1;32": "ansi-bold ansi-green",
173
+ "1;33": "ansi-bold ansi-yellow","1;34": "ansi-bold ansi-blue",
174
+ "0;31": "ansi-red", "0;32": "ansi-green",
175
+ "0;33": "ansi-yellow","0;34": "ansi-blue",
176
+ };
177
+ let result = "";
178
+ let open = false;
179
+ // Split on ESC[ sequences
180
+ const parts = text.split(/\x1b\[([0-9;]*)m/);
181
+ for (let i = 0; i < parts.length; i++) {
182
+ if (i % 2 === 0) {
183
+ // Plain text — escape HTML
184
+ result += parts[i].replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
185
+ } else {
186
+ // Code
187
+ const code = parts[i];
188
+ if (open) { result += "</span>"; open = false; }
189
+ if (code === "0" || code === "") {
190
+ // reset — already closed above
191
+ } else {
192
+ const cls = ANSI_COLORS[code];
193
+ if (cls) { result += `<span class="${cls}">`; open = true; }
194
+ }
195
+ }
196
+ }
197
+ if (open) result += "</span>";
198
+ return result;
199
+ }
200
+
160
201
  // Produce a short one-line summary of tool arguments for the compact view.
161
202
  function _summariseArgs(toolName, args) {
162
203
  if (!args || typeof args !== "object") return String(args || "");
@@ -220,6 +261,9 @@ const Sessions = (() => {
220
261
  status.className = "tool-item-status ok";
221
262
  status.textContent = "✓";
222
263
  }
264
+ // If stdout area is empty, hide it; otherwise keep it visible (user may be reading)
265
+ const stdout = last.querySelector(".tool-item-stdout");
266
+ if (stdout && !stdout.textContent.trim()) stdout.style.display = "none";
223
267
  }
224
268
 
225
269
  // Collapse a tool group (called when AI responds or task finishes).
@@ -530,9 +574,11 @@ const Sessions = (() => {
530
574
  Sessions.deleteSession(s.id);
531
575
  };
532
576
 
533
- // Double-click to rename
577
+ // Double-click anywhere on the session row to rename
534
578
  const nameDiv = el.querySelector(".session-name");
535
- nameDiv.ondblclick = (e) => {
579
+ el.ondblclick = (e) => {
580
+ // Ignore double-click on the delete button
581
+ if (e.target.closest(".session-delete-btn")) return;
536
582
  e.stopPropagation();
537
583
  if (clickTimer) { clearTimeout(clickTimer); clickTimer = null; }
538
584
  Sessions._startRename(s.id, nameDiv, s.name);
@@ -872,8 +918,19 @@ const Sessions = (() => {
872
918
  headers: { "Content-Type": "application/json" },
873
919
  body: JSON.stringify({ name: newName })
874
920
  });
875
- if (!res.ok) console.error("Rename failed:", await res.text());
876
- // WS session_renamed event will update the list
921
+ if (res.ok) {
922
+ // Update local data and re-render immediately.
923
+ // Don't rely on WS session_renamed event — it won't arrive when
924
+ // renaming a non-active session (WS is scoped to the active session).
925
+ Sessions.patch(sessionId, { name: newName });
926
+ Sessions.renderList();
927
+ if (sessionId === Sessions.activeId) {
928
+ const titleEl = document.getElementById("chat-title");
929
+ if (titleEl) titleEl.textContent = newName;
930
+ }
931
+ } else {
932
+ console.error("Rename failed:", await res.text());
933
+ }
877
934
  } catch (err) {
878
935
  console.error("Rename error:", err);
879
936
  }
@@ -982,6 +1039,21 @@ const Sessions = (() => {
982
1039
  }
983
1040
  },
984
1041
 
1042
+ // Append stdout lines to the currently running tool-item.
1043
+ // Shows the stdout area automatically on first content.
1044
+ appendToolStdout(lines) {
1045
+ if (!Sessions._liveLastToolItem) return;
1046
+ const stdout = Sessions._liveLastToolItem.querySelector(".tool-item-stdout");
1047
+ if (!stdout) return;
1048
+ stdout.innerHTML += lines.map(_ansiToHtml).join("");
1049
+ if (stdout.style.display === "none") stdout.style.display = "";
1050
+ // Auto-scroll stdout area to bottom
1051
+ stdout.scrollTop = stdout.scrollHeight;
1052
+ // Also scroll message list
1053
+ const messages = $("messages");
1054
+ if (messages) messages.scrollTop = messages.scrollHeight;
1055
+ },
1056
+
985
1057
  // Append a token usage line directly to the message list.
986
1058
  // Server guarantees this event arrives AFTER assistant_message, so no buffering needed.
987
1059
  // Format mirrors CLI:
data/scripts/install.sh CHANGED
@@ -408,8 +408,15 @@ setup_apt_mirror() {
408
408
  if [ "$USE_CN_MIRRORS" = true ]; then
409
409
  print_info "Configuring apt mirror (Aliyun)..."
410
410
  local codename="${VERSION_CODENAME:-jammy}"
411
- local mirror_base="https://mirrors.aliyun.com/ubuntu/"
412
411
  local components="main restricted universe multiverse"
412
+ local arch
413
+ arch=$(dpkg --print-architecture 2>/dev/null || uname -m)
414
+ # arm64 uses ubuntu-ports mirror; amd64/i386 uses standard ubuntu mirror
415
+ if [ "$arch" = "arm64" ] || [ "$arch" = "aarch64" ]; then
416
+ local mirror_base="https://mirrors.aliyun.com/ubuntu-ports/"
417
+ else
418
+ local mirror_base="https://mirrors.aliyun.com/ubuntu/"
419
+ fi
413
420
  sudo tee /etc/apt/sources.list > /dev/null <<EOF
414
421
  deb ${mirror_base} ${codename} ${components}
415
422
  deb ${mirror_base} ${codename}-updates ${components}
@@ -19,6 +19,38 @@ print_step() { echo -e "\n${BLUE}==>${NC} $1"; }
19
19
 
20
20
  command_exists() { command -v "$1" >/dev/null 2>&1; }
21
21
 
22
+ detect_shell() {
23
+ local shell_name
24
+ shell_name=$(basename "$SHELL")
25
+
26
+ case "$shell_name" in
27
+ zsh)
28
+ CURRENT_SHELL="zsh"
29
+ SHELL_RC="$HOME/.zshrc"
30
+ ;;
31
+ bash)
32
+ CURRENT_SHELL="bash"
33
+ # macOS uses ~/.bash_profile; Linux uses ~/.bashrc
34
+ if [ "$(uname)" = "Darwin" ]; then
35
+ SHELL_RC="$HOME/.bash_profile"
36
+ else
37
+ SHELL_RC="$HOME/.bashrc"
38
+ fi
39
+ ;;
40
+ fish)
41
+ CURRENT_SHELL="fish"
42
+ SHELL_RC="$HOME/.config/fish/config.fish"
43
+ ;;
44
+ *)
45
+ # Fallback: treat as bash
46
+ CURRENT_SHELL="bash"
47
+ SHELL_RC="$HOME/.bashrc"
48
+ ;;
49
+ esac
50
+
51
+ print_info "Detected shell: $CURRENT_SHELL (rc file: $SHELL_RC)"
52
+ }
53
+
22
54
  # --------------------------------------------------------------------------
23
55
  # Network region detection (quick — only probes google + baidu)
24
56
  # --------------------------------------------------------------------------
@@ -139,6 +171,15 @@ ensure_mise() {
139
171
 
140
172
  print_info "Installing mise..."
141
173
  if curl -fsSL "$MISE_INSTALL_URL" | sh; then
174
+ # Add mise activation to the current shell's rc file
175
+ local mise_init_line='eval "$(~/.local/bin/mise activate '"$CURRENT_SHELL"')"'
176
+ if [ -f "$SHELL_RC" ]; then
177
+ echo "$mise_init_line" >> "$SHELL_RC"
178
+ else
179
+ echo "$mise_init_line" > "$SHELL_RC"
180
+ fi
181
+ print_info "Added mise activation to $SHELL_RC"
182
+
142
183
  export PATH="$HOME/.local/bin:$PATH"
143
184
  eval "$(~/.local/bin/mise activate bash 2>/dev/null)" 2>/dev/null || true
144
185
  print_success "mise installed"
@@ -223,6 +264,7 @@ main() {
223
264
  echo "Browser Automation Setup"
224
265
  echo "========================"
225
266
 
267
+ detect_shell
226
268
  detect_network_region
227
269
  # Install Chrome first on macOS if not present
228
270
  [[ "$(uname)" == "Darwin" ]] && ensure_chrome_macos
@@ -641,8 +641,15 @@ install_ubuntu_dependencies() {
641
641
  if [ "$USE_CN_MIRRORS" = true ]; then
642
642
  print_info "Configuring apt mirror (Aliyun)..."
643
643
  local codename="${VERSION_CODENAME:-jammy}"
644
- local mirror_base="https://mirrors.aliyun.com/ubuntu/"
645
644
  local common_components="main restricted universe multiverse"
645
+ local arch
646
+ arch=$(dpkg --print-architecture 2>/dev/null || uname -m)
647
+ # arm64 uses ubuntu-ports mirror; amd64/i386 uses standard ubuntu mirror
648
+ if [ "$arch" = "arm64" ] || [ "$arch" = "aarch64" ]; then
649
+ local mirror_base="https://mirrors.aliyun.com/ubuntu-ports/"
650
+ else
651
+ local mirror_base="https://mirrors.aliyun.com/ubuntu/"
652
+ fi
646
653
  sudo tee /etc/apt/sources.list > /dev/null <<EOF
647
654
  deb ${mirror_base} ${codename} ${common_components}
648
655
  deb ${mirror_base} ${codename}-updates ${common_components}
@@ -153,8 +153,15 @@ setup_apt_mirror() {
153
153
  if ! _is_slow "$baidu"; then
154
154
  print_info "Region: China — configuring Aliyun apt mirror"
155
155
  local codename="${VERSION_CODENAME:-jammy}"
156
- local mirror="https://mirrors.aliyun.com/ubuntu/"
157
156
  local components="main restricted universe multiverse"
157
+ local arch
158
+ arch=$(dpkg --print-architecture 2>/dev/null || uname -m)
159
+ # arm64 uses ubuntu-ports mirror; amd64/i386 uses standard ubuntu mirror
160
+ if [ "$arch" = "arm64" ] || [ "$arch" = "aarch64" ]; then
161
+ local mirror="https://mirrors.aliyun.com/ubuntu-ports/"
162
+ else
163
+ local mirror="https://mirrors.aliyun.com/ubuntu/"
164
+ fi
158
165
  sudo tee /etc/apt/sources.list > /dev/null <<EOF
159
166
  deb ${mirror} ${codename} ${components}
160
167
  deb ${mirror} ${codename}-updates ${components}
data/scripts/uninstall.sh CHANGED
@@ -35,34 +35,29 @@ command_exists() {
35
35
  command -v "$1" >/dev/null 2>&1
36
36
  }
37
37
 
38
+ # --------------------------------------------------------------------------
39
+ # Load brand config from ~/.clacky/brand.yml
40
+ # --------------------------------------------------------------------------
41
+ BRAND_NAME=""
42
+ BRAND_COMMAND=""
43
+ DISPLAY_NAME="OpenClacky"
44
+
45
+ load_brand() {
46
+ local brand_file="$HOME/.clacky/brand.yml"
47
+ if [ -f "$brand_file" ]; then
48
+ BRAND_NAME=$(grep 'product_name:' "$brand_file" | sed 's/product_name: *"//' | sed 's/"//' | tr -d '[:space:]') || true
49
+ BRAND_COMMAND=$(grep 'package_name:' "$brand_file" | sed 's/package_name: *"//' | sed 's/"//' | tr -d '[:space:]') || true
50
+ [ -n "$BRAND_NAME" ] && DISPLAY_NAME="$BRAND_NAME"
51
+ fi
52
+ }
53
+
38
54
  # Check if OpenClacky is installed
39
55
  check_installation() {
40
56
  if command_exists clacky || command_exists openclacky; then
41
57
  return 0
42
- else
43
- return 1
44
58
  fi
45
- }
46
-
47
- # Uninstall via Homebrew
48
- uninstall_homebrew() {
49
- if command_exists brew; then
50
- if brew list openclacky >/dev/null 2>&1; then
51
- print_step "Uninstalling via Homebrew..."
52
- brew uninstall openclacky
53
-
54
- # Optionally untap
55
- if brew tap | grep -q "clacky-ai/openclacky"; then
56
- read -p "$(echo -e ${YELLOW}?${NC}) Remove Homebrew tap (clacky-ai/openclacky)? [y/N] " -n 1 -r
57
- echo
58
- if [[ $REPLY =~ ^[Yy]$ ]]; then
59
- brew untap clacky-ai/openclacky
60
- print_success "Tap removed"
61
- fi
62
- fi
63
-
64
- return 0
65
- fi
59
+ if [ -n "$BRAND_COMMAND" ] && command_exists "$BRAND_COMMAND"; then
60
+ return 0
66
61
  fi
67
62
  return 1
68
63
  }
@@ -79,16 +74,41 @@ uninstall_gem() {
79
74
  return 1
80
75
  }
81
76
 
77
+ # Remove brand wrapper binary
78
+ remove_brand() {
79
+ if [ -n "$BRAND_COMMAND" ]; then
80
+ local clacky_bin dir
81
+ clacky_bin=$(command -v openclacky 2>/dev/null || true)
82
+ if [ -n "$clacky_bin" ]; then
83
+ dir=$(dirname "$clacky_bin")
84
+ if [ -f "$dir/$BRAND_COMMAND" ]; then
85
+ rm -f "$dir/$BRAND_COMMAND"
86
+ print_success "Brand wrapper removed: $dir/$BRAND_COMMAND"
87
+ fi
88
+ fi
89
+ fi
90
+ }
91
+
92
+ # Restore original gemrc if we backed it up during install
93
+ restore_gemrc() {
94
+ if [ -f "$HOME/.gemrc_clackybak" ]; then
95
+ if [ -f "$HOME/.gemrc" ]; then
96
+ rm -f "$HOME/.gemrc"
97
+ fi
98
+ mv "$HOME/.gemrc_clackybak" "$HOME/.gemrc"
99
+ print_success "gem source restored from backup"
100
+ fi
101
+ }
102
+
82
103
  # Remove configuration files
83
104
  remove_config() {
84
105
  CONFIG_DIR="$HOME/.clacky"
85
106
 
86
107
  if [ -d "$CONFIG_DIR" ]; then
87
108
  print_warning "Configuration directory found: $CONFIG_DIR"
88
- read -p "$(echo -e ${YELLOW}?${NC}) Remove configuration files (including API keys)? [y/N] " -n 1 -r
89
- echo
109
+ read -p "Remove configuration files (including API keys)? [y/N] " reply
90
110
 
91
- if [[ $REPLY =~ ^[Yy]$ ]]; then
111
+ if [ "$reply" = "y" ] || [ "$reply" = "Y" ]; then
92
112
  rm -rf "$CONFIG_DIR"
93
113
  print_success "Configuration removed"
94
114
  else
@@ -99,47 +119,44 @@ remove_config() {
99
119
 
100
120
  # Main uninstallation
101
121
  main() {
122
+ load_brand
123
+
102
124
  echo ""
103
125
  echo "╔═══════════════════════════════════════════════════════════╗"
104
126
  echo "║ ║"
105
- echo "║ 🗑️ OpenClacky Uninstallation ║"
127
+ echo -e "║ 🗑️ ${DISPLAY_NAME} Uninstallation ║"
106
128
  echo "║ ║"
107
129
  echo "╚═══════════════════════════════════════════════════════════╝"
108
130
  echo ""
109
131
 
110
132
  if ! check_installation; then
111
- print_warning "OpenClacky does not appear to be installed"
133
+ print_warning "${DISPLAY_NAME} does not appear to be installed"
112
134
  exit 0
113
135
  fi
114
136
 
115
- UNINSTALLED=false
116
-
117
- # Try Homebrew first
118
- if uninstall_homebrew; then
119
- UNINSTALLED=true
120
- fi
121
-
122
- # Try gem
123
- if uninstall_gem; then
124
- UNINSTALLED=true
125
- fi
137
+ # Remove brand wrapper first (needs openclacky still in PATH)
138
+ remove_brand
126
139
 
127
- if [ "$UNINSTALLED" = false ]; then
128
- print_error "Could not automatically uninstall OpenClacky"
140
+ # Uninstall openclacky gem
141
+ if ! uninstall_gem; then
142
+ print_error "Could not automatically uninstall ${DISPLAY_NAME}"
129
143
  print_info "You may need to uninstall manually:"
130
- echo " - Via Homebrew: brew uninstall openclacky"
131
- echo " - Via RubyGems: gem uninstall openclacky"
144
+ echo " gem uninstall openclacky"
145
+ echo " rm -rf ~/.clacky"
132
146
  exit 1
133
147
  fi
134
148
 
135
- print_success "OpenClacky uninstalled successfully"
149
+ print_success "${DISPLAY_NAME} uninstalled successfully"
150
+
151
+ # Restore original gemrc
152
+ restore_gemrc
136
153
 
137
154
  # Ask about config removal
138
155
  remove_config
139
156
 
140
157
  echo ""
141
158
  print_success "Uninstallation complete!"
142
- print_info "Thank you for using OpenClacky 👋"
159
+ print_info "Thank you for using ${DISPLAY_NAME} 👋"
143
160
  echo ""
144
161
  }
145
162
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: openclacky
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.25
4
+ version: 0.9.26
5
5
  platform: ruby
6
6
  authors:
7
7
  - windy