openclacky 1.3.11 → 1.4.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +59 -0
- data/lib/clacky/agent/session_serializer.rb +11 -4
- data/lib/clacky/agent/skill_manager.rb +1 -0
- data/lib/clacky/agent.rb +4 -5
- data/lib/clacky/brand_config.rb +53 -3
- data/lib/clacky/default_extensions/ext-studio/api/handler.rb +202 -3
- data/lib/clacky/default_extensions/ext-studio/panels/studio/view.js +610 -60
- data/lib/clacky/default_skills/media-gen/SKILL.md +182 -7
- data/lib/clacky/default_skills/media-gen/scripts/video_seq.sh +5 -3
- data/lib/clacky/extension/packager.rb +21 -1
- data/lib/clacky/extension/verifier.rb +1 -1
- data/lib/clacky/media/generator.rb +41 -0
- data/lib/clacky/media/volcengine.rb +394 -0
- data/lib/clacky/platform_http_client.rb +9 -7
- data/lib/clacky/providers.rb +4 -4
- data/lib/clacky/server/http_server.rb +67 -22
- data/lib/clacky/tools/grep.rb +6 -1
- data/lib/clacky/tools/terminal.rb +24 -8
- data/lib/clacky/ui2/layout_manager.rb +4 -0
- data/lib/clacky/ui2/screen_buffer.rb +31 -3
- data/lib/clacky/utils/environment_detector.rb +16 -9
- data/lib/clacky/utils/file_processor.rb +30 -28
- data/lib/clacky/utils/model_pricing.rb +49 -0
- data/lib/clacky/version.rb +1 -1
- data/lib/clacky/web/app.css +229 -31
- data/lib/clacky/web/components/onboard.js +14 -4
- data/lib/clacky/web/features/backup/view.js +4 -3
- data/lib/clacky/web/features/extensions/store.js +2 -2
- data/lib/clacky/web/features/extensions/view.js +37 -7
- data/lib/clacky/web/features/mcp/view.js +5 -2
- data/lib/clacky/web/features/skills/view.js +61 -1
- data/lib/clacky/web/features/workspace/store.js +11 -0
- data/lib/clacky/web/features/workspace/view.js +10 -4
- data/lib/clacky/web/i18n.js +14 -2
- data/lib/clacky/web/index.html +29 -13
- data/lib/clacky/web/sessions.js +45 -8
- data/scripts/uninstall.sh +1 -1
- metadata +2 -1
|
@@ -607,9 +607,22 @@ module Clacky
|
|
|
607
607
|
""
|
|
608
608
|
)
|
|
609
609
|
|
|
610
|
-
# Pass 1: anchored strip — the
|
|
611
|
-
#
|
|
612
|
-
|
|
610
|
+
# Pass 1: anchored strip — remove ONLY the wrapper's echoed head
|
|
611
|
+
# line (`{ USER_CMD`) at the very start of the buffer, and ONLY
|
|
612
|
+
# when a matching wrapper tail fingerprint (`}...; __clacky_ec=$?`)
|
|
613
|
+
# actually exists somewhere in the buffer. The guard is what keeps
|
|
614
|
+
# us from eating a real first line of user output that merely
|
|
615
|
+
# happens to start with `{` (e.g. JSON like `{"key": ...}`).
|
|
616
|
+
# We test the tail with a separate anchor-free `match?` (linear, no
|
|
617
|
+
# backtracking) rather than a lookahead, to avoid catastrophic
|
|
618
|
+
# regex backtracking on large `{`-prefixed output. We strip a
|
|
619
|
+
# single line (`[^\n]*`, not cross-line `.*?`): a multi-line command
|
|
620
|
+
# echoed in cooked mode puts the wrapper tail *after* the real
|
|
621
|
+
# output, so cross-line greed would swallow that output whole. The
|
|
622
|
+
# tail itself is removed by the fingerprint passes below.
|
|
623
|
+
if text.start_with?("{") && text.match?(/\}[^\n;]*; *__clacky_ec=\$\?/)
|
|
624
|
+
text = text.sub(/\A\{[^\n]*\n/, "")
|
|
625
|
+
end
|
|
613
626
|
|
|
614
627
|
# Pass 2: token-aware global strip — remove any leftover wrapper
|
|
615
628
|
# echo fragment, wherever it sits. Requires the session token so
|
|
@@ -629,9 +642,11 @@ module Clacky
|
|
|
629
642
|
# 2b. Single-line shape: everything collapsed onto one line.
|
|
630
643
|
# Strip from the wrapper's `}; __clacky_ec=$?` pivot (or the
|
|
631
644
|
# opening `{` if still present on that line) through the end of
|
|
632
|
-
# the printf invocation (`"$__clacky_ec"`).
|
|
645
|
+
# the printf invocation (`"$__clacky_ec"`). The optional
|
|
646
|
+
# `[^\n;]*` between `}` and `;` absorbs a WSL `</dev/null`
|
|
647
|
+
# stdin redirect spliced onto the group.
|
|
633
648
|
text = text.gsub(
|
|
634
|
-
/[^\n]*\}; *__clacky_ec=\$\?; *printf[^\n]*__CLACKY_DONE_#{token_re}_[^\n]*"\$__clacky_ec"[^\n]*\n?/,
|
|
649
|
+
/[^\n]*\}[^\n;]*; *__clacky_ec=\$\?; *printf[^\n]*__CLACKY_DONE_#{token_re}_[^\n]*"\$__clacky_ec"[^\n]*\n?/,
|
|
635
650
|
""
|
|
636
651
|
)
|
|
637
652
|
|
|
@@ -653,9 +668,10 @@ module Clacky
|
|
|
653
668
|
# private double-underscore var name that user code effectively
|
|
654
669
|
# never emits — so we strip anything between them (non-greedy,
|
|
655
670
|
# multiline-aware) to also handle width-wrap that inserted
|
|
656
|
-
# real \n breaks inside the echo.
|
|
671
|
+
# real \n breaks inside the echo. `[^\n;]*` between `}` and `;`
|
|
672
|
+
# absorbs a WSL `</dev/null` stdin redirect on the group.
|
|
657
673
|
text = text.gsub(
|
|
658
|
-
/[^\n]*\}; *__clacky_ec=\$\?.*?"\$__clacky_ec"[^\n]*\n?/m,
|
|
674
|
+
/[^\n]*\}[^\n;]*; *__clacky_ec=\$\?.*?"\$__clacky_ec"[^\n]*\n?/m,
|
|
659
675
|
""
|
|
660
676
|
)
|
|
661
677
|
|
|
@@ -663,7 +679,7 @@ module Clacky
|
|
|
663
679
|
# truncation cut off everything after `__clacky_ec=$?`). Still a
|
|
664
680
|
# reliable fingerprint thanks to the `__clacky_ec` var name.
|
|
665
681
|
text = text.gsub(
|
|
666
|
-
/[^\n]*\}; *__clacky_ec=\$\?;?[^\n]*\n?/,
|
|
682
|
+
/[^\n]*\}[^\n;]*; *__clacky_ec=\$\?;?[^\n]*\n?/,
|
|
667
683
|
""
|
|
668
684
|
)
|
|
669
685
|
|
|
@@ -402,6 +402,10 @@ module Clacky
|
|
|
402
402
|
screen.show_cursor
|
|
403
403
|
screen.flush
|
|
404
404
|
end
|
|
405
|
+
# Restore cooked mode so the terminal is usable after exit, regardless
|
|
406
|
+
# of which exit path was taken (some call exit(0) and bypass the
|
|
407
|
+
# input_loop ensure). Idempotent; safe to call more than once.
|
|
408
|
+
screen.disable_raw_mode
|
|
405
409
|
end
|
|
406
410
|
|
|
407
411
|
# /clear: wipe output area + buffer, keep fixed area.
|
|
@@ -118,14 +118,42 @@ module Clacky
|
|
|
118
118
|
@height = TTY::Screen.height
|
|
119
119
|
end
|
|
120
120
|
|
|
121
|
-
# Enable raw mode (disable line buffering)
|
|
121
|
+
# Enable raw mode (disable line buffering). The very first time it runs it
|
|
122
|
+
# snapshots the terminal's original settings (via `stty -g`) so the exact
|
|
123
|
+
# pre-clacky state can be restored on exit — `IO#cooked!` only applies a
|
|
124
|
+
# generic cooked profile and does not restore flow-control (IXON) and other
|
|
125
|
+
# bits, which on macOS leaves the shell in a broken input state.
|
|
122
126
|
def enable_raw_mode
|
|
127
|
+
return unless $stdin.tty?
|
|
128
|
+
|
|
129
|
+
@original_stty ||= capture_stty
|
|
123
130
|
$stdin.raw!
|
|
124
131
|
end
|
|
125
132
|
|
|
126
|
-
# Disable raw mode
|
|
133
|
+
# Disable raw mode. Idempotent and tty-guarded so it can be safely called
|
|
134
|
+
# from any exit path. Restores the exact original terminal settings when a
|
|
135
|
+
# snapshot exists, then flushes pending input so stray bytes don't leak to
|
|
136
|
+
# the shell. Falls back to `IO#cooked!` when no snapshot is available.
|
|
127
137
|
def disable_raw_mode
|
|
128
|
-
$stdin.
|
|
138
|
+
return unless $stdin.tty?
|
|
139
|
+
|
|
140
|
+
$stdin.cooked! unless @original_stty && restore_stty(@original_stty)
|
|
141
|
+
$stdin.ioflush
|
|
142
|
+
rescue IOError, Errno::ENOTTY
|
|
143
|
+
nil
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
private def capture_stty
|
|
147
|
+
out = Clacky::Utils::Encoding.cmd_to_utf8(`stty -g 2>/dev/null`).strip
|
|
148
|
+
out.empty? ? nil : out
|
|
149
|
+
rescue StandardError
|
|
150
|
+
nil
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
private def restore_stty(state)
|
|
154
|
+
system("stty", state, out: File::NULL, err: File::NULL)
|
|
155
|
+
rescue StandardError
|
|
156
|
+
false
|
|
129
157
|
end
|
|
130
158
|
|
|
131
159
|
# Read a single character without echo
|
|
@@ -52,17 +52,24 @@ module Clacky
|
|
|
52
52
|
end
|
|
53
53
|
end
|
|
54
54
|
|
|
55
|
-
# Convert a Windows-style path
|
|
56
|
-
#
|
|
57
|
-
# Returns the original path unchanged on non-WSL or if already a Linux path.
|
|
58
|
-
# @param path [String]
|
|
59
|
-
# @return [String]
|
|
55
|
+
# Convert a Windows-style path (incl. the "/C:/" three-slash-URL form) to
|
|
56
|
+
# a WSL /mnt path. No-op on non-WSL or non-drive-letter paths.
|
|
60
57
|
def self.win_to_linux_path(path)
|
|
61
|
-
return path unless os_type == :wsl
|
|
58
|
+
return path unless os_type == :wsl
|
|
59
|
+
|
|
60
|
+
m = path.match(%r{\A/?([A-Za-z]):[/\\](.*)}m)
|
|
61
|
+
return path unless m
|
|
62
|
+
|
|
63
|
+
"/mnt/#{m[1].downcase}/#{m[2].gsub("\\", "/")}"
|
|
64
|
+
end
|
|
62
65
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
+
# Resolve a "file://" URL or bare path into a real local path:
|
|
67
|
+
# strip "file://", percent-decode, WSL drive-letter normalize, then expand.
|
|
68
|
+
def self.resolve_local_path(href)
|
|
69
|
+
path = href.to_s.sub(%r{\Afile://}, "")
|
|
70
|
+
path = CGI.unescape(path)
|
|
71
|
+
path = win_to_linux_path(path)
|
|
72
|
+
File.expand_path(path)
|
|
66
73
|
end
|
|
67
74
|
|
|
68
75
|
# Convert a Linux-side path to a Windows-style path via wslpath.
|
|
@@ -6,6 +6,7 @@ require "securerandom"
|
|
|
6
6
|
require "stringio"
|
|
7
7
|
|
|
8
8
|
require_relative "parser_manager"
|
|
9
|
+
require_relative "environment_detector"
|
|
9
10
|
require "zip"
|
|
10
11
|
|
|
11
12
|
module Clacky
|
|
@@ -595,71 +596,63 @@ module Clacky
|
|
|
595
596
|
return content if content.nil? || content.empty?
|
|
596
597
|
|
|
597
598
|
# Rewrite markdown image syntax  → proxy URL
|
|
598
|
-
content = content.gsub(/!\[([^\]]*)\]\(((?:file:\/\/)
|
|
599
|
+
content = content.gsub(/!\[([^\]]*)\]\(((?:file:\/\/)?(?:~\/|\/)[^)]+)\)/) do |_match|
|
|
599
600
|
alt = Regexp.last_match(1)
|
|
600
601
|
href = Regexp.last_match(2)
|
|
601
602
|
|
|
602
|
-
|
|
603
|
-
path = CGI.unescape(path)
|
|
603
|
+
real = Clacky::Utils::EnvironmentDetector.resolve_local_path(href)
|
|
604
604
|
|
|
605
|
-
ext = File.extname(
|
|
606
|
-
if LOCAL_MEDIA_EXTENSIONS.include?(ext) && File.exist?(
|
|
607
|
-
|
|
608
|
-
""
|
|
605
|
+
ext = File.extname(real).downcase
|
|
606
|
+
if LOCAL_MEDIA_EXTENSIONS.include?(ext) && File.exist?(real)
|
|
607
|
+
"})"
|
|
609
608
|
else
|
|
610
609
|
_match
|
|
611
610
|
end
|
|
612
611
|
end
|
|
613
612
|
|
|
614
613
|
# Rewrite <video src="file:///path/vid.mp4" ...> → proxy URL
|
|
615
|
-
content = content.gsub(/<video\b([^>]*)\bsrc="((?:file:\/\/)
|
|
614
|
+
content = content.gsub(/<video\b([^>]*)\bsrc="((?:file:\/\/)?(?:~\/|\/)[^"]+)"([^>]*)>/) do |_match|
|
|
616
615
|
pre = Regexp.last_match(1) || ""
|
|
617
616
|
href = Regexp.last_match(2)
|
|
618
617
|
post = Regexp.last_match(3) || ""
|
|
619
618
|
|
|
620
|
-
|
|
621
|
-
path = CGI.unescape(path)
|
|
619
|
+
real = Clacky::Utils::EnvironmentDetector.resolve_local_path(href)
|
|
622
620
|
|
|
623
|
-
ext = File.extname(
|
|
624
|
-
if LOCAL_VIDEO_EXTENSIONS.include?(ext) && File.exist?(
|
|
625
|
-
|
|
626
|
-
"<video#{pre} src=\"/api/local-image?path=#{encoded}\"#{post}>"
|
|
621
|
+
ext = File.extname(real).downcase
|
|
622
|
+
if LOCAL_VIDEO_EXTENSIONS.include?(ext) && File.exist?(real)
|
|
623
|
+
"<video#{pre} src=\"#{local_image_proxy_url(href, real)}\"#{post}>"
|
|
627
624
|
else
|
|
628
625
|
_match
|
|
629
626
|
end
|
|
630
627
|
end
|
|
631
628
|
|
|
632
629
|
# Rewrite <audio src="file:///path/audio.wav" ...> → proxy URL
|
|
633
|
-
content = content.gsub(/<audio\b([^>]*)\bsrc="((?:file:\/\/)
|
|
630
|
+
content = content.gsub(/<audio\b([^>]*)\bsrc="((?:file:\/\/)?(?:~\/|\/)[^"]+)"([^>]*)>/) do |_match|
|
|
634
631
|
pre = Regexp.last_match(1) || ""
|
|
635
632
|
href = Regexp.last_match(2)
|
|
636
633
|
post = Regexp.last_match(3) || ""
|
|
637
634
|
|
|
638
|
-
|
|
639
|
-
path = CGI.unescape(path)
|
|
635
|
+
real = Clacky::Utils::EnvironmentDetector.resolve_local_path(href)
|
|
640
636
|
|
|
641
|
-
ext = File.extname(
|
|
642
|
-
if LOCAL_AUDIO_EXTENSIONS.include?(ext) && File.exist?(
|
|
643
|
-
|
|
644
|
-
"<audio#{pre} src=\"/api/local-image?path=#{encoded}\"#{post}>"
|
|
637
|
+
ext = File.extname(real).downcase
|
|
638
|
+
if LOCAL_AUDIO_EXTENSIONS.include?(ext) && File.exist?(real)
|
|
639
|
+
"<audio#{pre} src=\"#{local_image_proxy_url(href, real)}\"#{post}>"
|
|
645
640
|
else
|
|
646
641
|
_match
|
|
647
642
|
end
|
|
648
643
|
end
|
|
649
644
|
|
|
650
645
|
# Rewrite video/audio markdown links [text](file:///path) → proxy URL
|
|
651
|
-
content = content.gsub(/(?<!!)\[([^\]]*)\]\(((?:file:\/\/)
|
|
646
|
+
content = content.gsub(/(?<!!)\[([^\]]*)\]\(((?:file:\/\/)?(?:~\/|\/)[^)]+)\)/) do |_match|
|
|
652
647
|
text = Regexp.last_match(1)
|
|
653
648
|
href = Regexp.last_match(2)
|
|
654
649
|
|
|
655
|
-
|
|
656
|
-
path = CGI.unescape(path)
|
|
650
|
+
real = Clacky::Utils::EnvironmentDetector.resolve_local_path(href)
|
|
657
651
|
|
|
658
|
-
ext = File.extname(
|
|
652
|
+
ext = File.extname(real).downcase
|
|
659
653
|
if LOCAL_VIDEO_EXTENSIONS.include?(ext) || LOCAL_AUDIO_EXTENSIONS.include?(ext)
|
|
660
|
-
if File.exist?(
|
|
661
|
-
|
|
662
|
-
"[#{text}](/api/local-image?path=#{encoded})"
|
|
654
|
+
if File.exist?(real)
|
|
655
|
+
"[#{text}](#{local_image_proxy_url(href, real)})"
|
|
663
656
|
else
|
|
664
657
|
_match
|
|
665
658
|
end
|
|
@@ -670,6 +663,15 @@ module Clacky
|
|
|
670
663
|
|
|
671
664
|
content
|
|
672
665
|
end
|
|
666
|
+
|
|
667
|
+
# Build a proxy URL for a local media file, appending a version param
|
|
668
|
+
# derived from the file mtime so an overwritten same-name file produces a
|
|
669
|
+
# different URL and defeats the browser's in-memory image cache.
|
|
670
|
+
def self.local_image_proxy_url(href, path)
|
|
671
|
+
encoded = CGI.escape(href)
|
|
672
|
+
version = File.mtime(path).to_i
|
|
673
|
+
"/api/local-image?path=#{encoded}&v=#{version}"
|
|
674
|
+
end
|
|
673
675
|
end
|
|
674
676
|
end
|
|
675
677
|
end
|
|
@@ -237,6 +237,55 @@ module Clacky
|
|
|
237
237
|
}
|
|
238
238
|
},
|
|
239
239
|
|
|
240
|
+
# Kimi K3 flagship model (1M context, native vision, tool calling).
|
|
241
|
+
# Source: https://platform.moonshot.ai (USD / 1M tokens)
|
|
242
|
+
"kimi-k3" => {
|
|
243
|
+
input: {
|
|
244
|
+
default: 3.00, # $3.00/MTok cache miss
|
|
245
|
+
over_200k: 3.00
|
|
246
|
+
},
|
|
247
|
+
output: {
|
|
248
|
+
default: 15.00, # $15.00/MTok
|
|
249
|
+
over_200k: 15.00
|
|
250
|
+
},
|
|
251
|
+
cache: {
|
|
252
|
+
write: 3.00, # no separate write charge; bill at miss rate
|
|
253
|
+
read: 0.30 # $0.30/MTok cache hit
|
|
254
|
+
}
|
|
255
|
+
},
|
|
256
|
+
|
|
257
|
+
# Kimi K2.7 Code (256K context, multimodal coding model).
|
|
258
|
+
# Source: https://platform.moonshot.ai (USD / 1M tokens)
|
|
259
|
+
"kimi-k2.7-code" => {
|
|
260
|
+
input: {
|
|
261
|
+
default: 0.95, # $0.95/MTok cache miss
|
|
262
|
+
over_200k: 0.95
|
|
263
|
+
},
|
|
264
|
+
output: {
|
|
265
|
+
default: 4.00, # $4.00/MTok
|
|
266
|
+
over_200k: 4.00
|
|
267
|
+
},
|
|
268
|
+
cache: {
|
|
269
|
+
write: 0.95, # no separate write charge; bill at miss rate
|
|
270
|
+
read: 0.19 # $0.19/MTok cache hit
|
|
271
|
+
}
|
|
272
|
+
},
|
|
273
|
+
|
|
274
|
+
"kimi-k2.7-code-highspeed" => {
|
|
275
|
+
input: {
|
|
276
|
+
default: 1.90, # $1.90/MTok cache miss
|
|
277
|
+
over_200k: 1.90
|
|
278
|
+
},
|
|
279
|
+
output: {
|
|
280
|
+
default: 8.00, # $8.00/MTok
|
|
281
|
+
over_200k: 8.00
|
|
282
|
+
},
|
|
283
|
+
cache: {
|
|
284
|
+
write: 1.90, # no separate write charge; bill at miss rate
|
|
285
|
+
read: 0.38 # $0.38/MTok cache hit
|
|
286
|
+
}
|
|
287
|
+
},
|
|
288
|
+
|
|
240
289
|
# Google Gemini 3 series (via Vertex AI). Tiered at 200K input tokens
|
|
241
290
|
# for Pro; Flash has flat pricing.
|
|
242
291
|
"gemini-3.1-pro" => {
|
data/lib/clacky/version.rb
CHANGED