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 +4 -4
- data/CHANGELOG.md +11 -0
- data/lib/clacky/agent.rb +14 -0
- data/lib/clacky/default_skills/browser-setup/SKILL.md +15 -5
- data/lib/clacky/default_skills/channel-setup/SKILL.md +1 -0
- data/lib/clacky/server/web_ui_controller.rb +8 -0
- data/lib/clacky/version.rb +1 -1
- data/lib/clacky/web/app.css +32 -2
- data/lib/clacky/web/app.js +5 -0
- data/lib/clacky/web/sessions.js +77 -5
- data/scripts/install.sh +8 -1
- data/scripts/install_browser.sh +42 -0
- data/scripts/install_full.sh +8 -1
- data/scripts/install_system_deps.sh +8 -1
- data/scripts/uninstall.sh +62 -45
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 65dad1ba4790fcffb30157bcc74a05a9f9cbd3e341ead0e007e6c768b0c11fd1
|
|
4
|
+
data.tar.gz: f507ade251d206b073eb1b88c236d8905e9c986718d93c62cf3f2b575fa908bb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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)
|
|
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
|
data/lib/clacky/version.rb
CHANGED
data/lib/clacky/web/app.css
CHANGED
|
@@ -1476,17 +1476,47 @@ body {
|
|
|
1476
1476
|
.tool-group.expanded .tool-group-body { display: flex; }
|
|
1477
1477
|
.tool-item {
|
|
1478
1478
|
display: flex;
|
|
1479
|
-
|
|
1480
|
-
gap:
|
|
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 {
|
data/lib/clacky/web/app.js
CHANGED
|
@@ -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)}`);
|
data/lib/clacky/web/sessions.js
CHANGED
|
@@ -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 =
|
|
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,"&").replace(/</g,"<").replace(/>/g,">");
|
|
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
|
-
|
|
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 (
|
|
876
|
-
|
|
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}
|
data/scripts/install_browser.sh
CHANGED
|
@@ -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
|
data/scripts/install_full.sh
CHANGED
|
@@ -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 "
|
|
89
|
-
echo
|
|
109
|
+
read -p "Remove configuration files (including API keys)? [y/N] " reply
|
|
90
110
|
|
|
91
|
-
if [
|
|
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 "║ 🗑️
|
|
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 "
|
|
133
|
+
print_warning "${DISPLAY_NAME} does not appear to be installed"
|
|
112
134
|
exit 0
|
|
113
135
|
fi
|
|
114
136
|
|
|
115
|
-
|
|
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
|
-
|
|
128
|
-
|
|
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 "
|
|
131
|
-
echo " -
|
|
144
|
+
echo " gem uninstall openclacky"
|
|
145
|
+
echo " rm -rf ~/.clacky"
|
|
132
146
|
exit 1
|
|
133
147
|
fi
|
|
134
148
|
|
|
135
|
-
print_success "
|
|
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
|
|
159
|
+
print_info "Thank you for using ${DISPLAY_NAME} 👋"
|
|
143
160
|
echo ""
|
|
144
161
|
}
|
|
145
162
|
|