openclacky 1.3.10 → 1.4.0
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 +48 -0
- data/lib/clacky/agent/skill_manager.rb +1 -0
- data/lib/clacky/brand_config.rb +32 -6
- data/lib/clacky/default_extensions/ext-studio/api/handler.rb +78 -2
- data/lib/clacky/default_extensions/ext-studio/panels/studio/view.js +390 -50
- 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 +13 -3
- 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/server/http_server.rb +107 -45
- data/lib/clacky/telemetry.rb +9 -0
- 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/file_processor.rb +13 -8
- data/lib/clacky/version.rb +1 -1
- data/lib/clacky/web/app.css +189 -24
- data/lib/clacky/web/features/extensions/store.js +14 -7
- data/lib/clacky/web/features/extensions/view.js +77 -13
- data/lib/clacky/web/features/skills/view.js +61 -1
- data/lib/clacky/web/i18n.js +38 -6
- data/lib/clacky/web/index.html +6 -11
- data/lib/clacky/web/sessions.js +12 -3
- 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
|
|
@@ -604,8 +604,7 @@ module Clacky
|
|
|
604
604
|
|
|
605
605
|
ext = File.extname(path).downcase
|
|
606
606
|
if LOCAL_MEDIA_EXTENSIONS.include?(ext) && File.exist?(path)
|
|
607
|
-
|
|
608
|
-
""
|
|
607
|
+
"})"
|
|
609
608
|
else
|
|
610
609
|
_match
|
|
611
610
|
end
|
|
@@ -622,8 +621,7 @@ module Clacky
|
|
|
622
621
|
|
|
623
622
|
ext = File.extname(path).downcase
|
|
624
623
|
if LOCAL_VIDEO_EXTENSIONS.include?(ext) && File.exist?(path)
|
|
625
|
-
|
|
626
|
-
"<video#{pre} src=\"/api/local-image?path=#{encoded}\"#{post}>"
|
|
624
|
+
"<video#{pre} src=\"#{local_image_proxy_url(href, path)}\"#{post}>"
|
|
627
625
|
else
|
|
628
626
|
_match
|
|
629
627
|
end
|
|
@@ -640,8 +638,7 @@ module Clacky
|
|
|
640
638
|
|
|
641
639
|
ext = File.extname(path).downcase
|
|
642
640
|
if LOCAL_AUDIO_EXTENSIONS.include?(ext) && File.exist?(path)
|
|
643
|
-
|
|
644
|
-
"<audio#{pre} src=\"/api/local-image?path=#{encoded}\"#{post}>"
|
|
641
|
+
"<audio#{pre} src=\"#{local_image_proxy_url(href, path)}\"#{post}>"
|
|
645
642
|
else
|
|
646
643
|
_match
|
|
647
644
|
end
|
|
@@ -658,8 +655,7 @@ module Clacky
|
|
|
658
655
|
ext = File.extname(path).downcase
|
|
659
656
|
if LOCAL_VIDEO_EXTENSIONS.include?(ext) || LOCAL_AUDIO_EXTENSIONS.include?(ext)
|
|
660
657
|
if File.exist?(path)
|
|
661
|
-
|
|
662
|
-
"[#{text}](/api/local-image?path=#{encoded})"
|
|
658
|
+
"[#{text}](#{local_image_proxy_url(href, path)})"
|
|
663
659
|
else
|
|
664
660
|
_match
|
|
665
661
|
end
|
|
@@ -670,6 +666,15 @@ module Clacky
|
|
|
670
666
|
|
|
671
667
|
content
|
|
672
668
|
end
|
|
669
|
+
|
|
670
|
+
# Build a proxy URL for a local media file, appending a version param
|
|
671
|
+
# derived from the file mtime so an overwritten same-name file produces a
|
|
672
|
+
# different URL and defeats the browser's in-memory image cache.
|
|
673
|
+
def self.local_image_proxy_url(href, path)
|
|
674
|
+
encoded = CGI.escape(href)
|
|
675
|
+
version = File.mtime(path).to_i
|
|
676
|
+
"/api/local-image?path=#{encoded}&v=#{version}"
|
|
677
|
+
end
|
|
673
678
|
end
|
|
674
679
|
end
|
|
675
680
|
end
|
data/lib/clacky/version.rb
CHANGED
data/lib/clacky/web/app.css
CHANGED
|
@@ -782,7 +782,7 @@ body {
|
|
|
782
782
|
color: var(--color-accent-primary);
|
|
783
783
|
}
|
|
784
784
|
|
|
785
|
-
/* ── Command-palette search overlay
|
|
785
|
+
/* ── Command-palette search overlay ──────────────────────────────────────── */
|
|
786
786
|
.cmd-palette-overlay {
|
|
787
787
|
position: fixed;
|
|
788
788
|
inset: 0;
|
|
@@ -6484,6 +6484,28 @@ body {
|
|
|
6484
6484
|
overflow-y: auto;
|
|
6485
6485
|
}
|
|
6486
6486
|
|
|
6487
|
+
/* ── Skills search ──────────────────────────────────────────────────────── */
|
|
6488
|
+
.skills-search {
|
|
6489
|
+
font-size: 0.8125rem;
|
|
6490
|
+
padding: 0.375rem 0.75rem;
|
|
6491
|
+
border-radius: 6px;
|
|
6492
|
+
border: 1px solid var(--color-border-primary);
|
|
6493
|
+
background: var(--color-bg-secondary);
|
|
6494
|
+
color: var(--color-text-primary);
|
|
6495
|
+
width: 12rem;
|
|
6496
|
+
}
|
|
6497
|
+
.skills-search::placeholder { color: var(--color-text-tertiary); }
|
|
6498
|
+
.skills-search:focus {
|
|
6499
|
+
outline: none;
|
|
6500
|
+
border-color: var(--color-accent, #6366f1);
|
|
6501
|
+
}
|
|
6502
|
+
.skills-search-empty {
|
|
6503
|
+
padding: 2rem 1rem;
|
|
6504
|
+
text-align: center;
|
|
6505
|
+
color: var(--color-text-tertiary);
|
|
6506
|
+
font-size: 0.875rem;
|
|
6507
|
+
}
|
|
6508
|
+
|
|
6487
6509
|
/* ── My Skills upload area (user-licensed only) ───────────────────────────── */
|
|
6488
6510
|
.my-skills-upload-area {
|
|
6489
6511
|
display: flex;
|
|
@@ -7072,6 +7094,12 @@ body {
|
|
|
7072
7094
|
}
|
|
7073
7095
|
|
|
7074
7096
|
/* ── Extension Market ─────────────────────────────────────────────────── */
|
|
7097
|
+
#extensions-panel {
|
|
7098
|
+
flex: 1;
|
|
7099
|
+
display: flex;
|
|
7100
|
+
flex-direction: column;
|
|
7101
|
+
overflow: hidden;
|
|
7102
|
+
}
|
|
7075
7103
|
#extensions-body {
|
|
7076
7104
|
flex: 1;
|
|
7077
7105
|
overflow-y: auto;
|
|
@@ -7165,26 +7193,38 @@ body {
|
|
|
7165
7193
|
background: var(--color-bg-secondary);
|
|
7166
7194
|
border: 1px solid var(--color-border-primary);
|
|
7167
7195
|
border-radius: 8px;
|
|
7168
|
-
padding: 0.
|
|
7196
|
+
padding: 0.6rem 1rem;
|
|
7169
7197
|
transition: border-color .15s;
|
|
7170
7198
|
}
|
|
7171
7199
|
.extension-card:hover { border-color: var(--color-text-muted); }
|
|
7172
7200
|
.extension-card-main {
|
|
7173
7201
|
display: flex;
|
|
7174
|
-
align-items:
|
|
7202
|
+
align-items: stretch;
|
|
7175
7203
|
gap: 0.75rem;
|
|
7176
7204
|
}
|
|
7177
7205
|
.extension-emoji {
|
|
7178
7206
|
font-size: 1.5rem;
|
|
7179
7207
|
line-height: 1.4;
|
|
7180
7208
|
flex-shrink: 0;
|
|
7209
|
+
display: flex;
|
|
7210
|
+
align-items: flex-start;
|
|
7211
|
+
}
|
|
7212
|
+
.extension-default-icon {
|
|
7213
|
+
width: 2rem;
|
|
7214
|
+
height: 100%;
|
|
7215
|
+
display: block;
|
|
7216
|
+
border-radius: 7px;
|
|
7217
|
+
}
|
|
7218
|
+
.extension-emoji-lg .extension-default-icon {
|
|
7219
|
+
width: 2.75rem;
|
|
7220
|
+
height: 2.75rem;
|
|
7221
|
+
border-radius: 10px;
|
|
7181
7222
|
}
|
|
7182
|
-
.extension-info { flex: 1; min-width: 0; }
|
|
7223
|
+
.extension-info { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 4px; }
|
|
7183
7224
|
.extension-title {
|
|
7184
7225
|
display: flex;
|
|
7185
7226
|
align-items: center;
|
|
7186
|
-
gap: 0.
|
|
7187
|
-
margin-bottom: 0.25rem;
|
|
7227
|
+
gap: 0.5rem;
|
|
7188
7228
|
flex-wrap: wrap;
|
|
7189
7229
|
}
|
|
7190
7230
|
.extension-name {
|
|
@@ -7197,18 +7237,24 @@ body {
|
|
|
7197
7237
|
color: var(--color-text-secondary);
|
|
7198
7238
|
background: var(--color-bg-tertiary, rgba(255,255,255,0.05));
|
|
7199
7239
|
border-radius: 4px;
|
|
7200
|
-
padding: 0.0625rem 0.375rem;
|
|
7201
7240
|
}
|
|
7202
7241
|
.extension-units {
|
|
7203
7242
|
font-size: 0.6875rem;
|
|
7204
7243
|
color: var(--color-text-muted);
|
|
7205
7244
|
}
|
|
7206
7245
|
.extension-installed {
|
|
7207
|
-
font-size: 0.
|
|
7246
|
+
font-size: 0.625rem;
|
|
7208
7247
|
color: var(--color-success, #16a34a);
|
|
7209
7248
|
background: var(--color-success-bg, rgba(22,163,74,0.12));
|
|
7210
7249
|
border-radius: 4px;
|
|
7211
|
-
padding: 0.
|
|
7250
|
+
padding: 0.2rem 0.375rem;
|
|
7251
|
+
}
|
|
7252
|
+
.extension-updatable {
|
|
7253
|
+
font-size: 0.625rem;
|
|
7254
|
+
color: var(--color-button-primary);
|
|
7255
|
+
background: color-mix(in srgb, var(--color-button-primary) 12%, transparent);
|
|
7256
|
+
border-radius: 4px;
|
|
7257
|
+
padding: 0.2rem 0.375rem;
|
|
7212
7258
|
}
|
|
7213
7259
|
.extension-unlisted {
|
|
7214
7260
|
font-size: 0.6875rem;
|
|
@@ -7221,11 +7267,10 @@ body {
|
|
|
7221
7267
|
font-size: 0.75rem;
|
|
7222
7268
|
color: var(--color-text-secondary);
|
|
7223
7269
|
line-height: 1.5;
|
|
7224
|
-
margin-bottom: 0.375rem;
|
|
7225
7270
|
}
|
|
7226
7271
|
.extension-desc-detail {
|
|
7227
7272
|
font-size: 0.8125rem;
|
|
7228
|
-
margin-bottom: 0
|
|
7273
|
+
margin-bottom: 0;
|
|
7229
7274
|
}
|
|
7230
7275
|
.extension-detail-author {
|
|
7231
7276
|
font-size: 0.6875rem;
|
|
@@ -7236,6 +7281,11 @@ body {
|
|
|
7236
7281
|
color: var(--color-text-muted);
|
|
7237
7282
|
font-weight: 400;
|
|
7238
7283
|
}
|
|
7284
|
+
.extension-installs {
|
|
7285
|
+
font-size: 0.75rem;
|
|
7286
|
+
color: var(--color-text-muted);
|
|
7287
|
+
font-weight: 400;
|
|
7288
|
+
}
|
|
7239
7289
|
.extension-meta {
|
|
7240
7290
|
display: flex;
|
|
7241
7291
|
align-items: center;
|
|
@@ -7282,8 +7332,13 @@ body {
|
|
|
7282
7332
|
padding-bottom: 1.25rem;
|
|
7283
7333
|
border-bottom: 1px solid var(--color-border-primary);
|
|
7284
7334
|
}
|
|
7285
|
-
.extension-detail-heading { flex: 1; min-width: 0; }
|
|
7335
|
+
.extension-detail-heading { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 6px; }
|
|
7286
7336
|
.extension-emoji-lg { font-size: 2.5rem; line-height: 1; margin-top: 0.125rem; }
|
|
7337
|
+
.extension-emoji-lg .extension-default-icon {
|
|
7338
|
+
width: 3.5rem;
|
|
7339
|
+
height: 3.5rem;
|
|
7340
|
+
border-radius: 12px;
|
|
7341
|
+
}
|
|
7287
7342
|
.extension-name-lg { font-size: 1.125rem; font-weight: 700; }
|
|
7288
7343
|
.extension-detail-block { margin-top: 1.5rem; }
|
|
7289
7344
|
.extension-detail-block-title {
|
|
@@ -7291,7 +7346,6 @@ body {
|
|
|
7291
7346
|
font-weight: 600;
|
|
7292
7347
|
text-transform: uppercase;
|
|
7293
7348
|
letter-spacing: 0.04em;
|
|
7294
|
-
color: var(--color-text-muted);
|
|
7295
7349
|
margin: 0 0 0.75rem;
|
|
7296
7350
|
}
|
|
7297
7351
|
.extension-detail-section { margin-bottom: 0.875rem; }
|
|
@@ -7338,33 +7392,78 @@ body {
|
|
|
7338
7392
|
margin-top: 0.125rem;
|
|
7339
7393
|
}
|
|
7340
7394
|
.extension-version-item {
|
|
7341
|
-
padding: 0.
|
|
7395
|
+
padding: 0.75rem 1rem;
|
|
7342
7396
|
border-bottom: 1px solid var(--color-border-primary);
|
|
7343
7397
|
}
|
|
7344
7398
|
.extension-version-item:last-child { border-bottom: none; }
|
|
7345
7399
|
.extension-version-row {
|
|
7346
7400
|
display: flex;
|
|
7347
|
-
align-items:
|
|
7401
|
+
align-items: baseline;
|
|
7348
7402
|
gap: 0.5rem;
|
|
7349
7403
|
}
|
|
7404
|
+
.extension-version-item:has(.extension-version-notes) .extension-version-row {
|
|
7405
|
+
padding-bottom: 0.5rem;
|
|
7406
|
+
margin-bottom: 0.5rem;
|
|
7407
|
+
border-bottom: 1px solid var(--color-border-primary);
|
|
7408
|
+
}
|
|
7409
|
+
.extension-version-row .extension-version {
|
|
7410
|
+
font-size: 0.8375rem;
|
|
7411
|
+
font-weight: 700;
|
|
7412
|
+
color: var(--color-text-primary);
|
|
7413
|
+
background: none;
|
|
7414
|
+
padding: 0;
|
|
7415
|
+
border-radius: 0;
|
|
7416
|
+
}
|
|
7417
|
+
.extension-version-separator {
|
|
7418
|
+
font-size: 0.8125rem;
|
|
7419
|
+
color: var(--color-text-secondary);
|
|
7420
|
+
}
|
|
7350
7421
|
.extension-version-date {
|
|
7351
7422
|
font-size: 0.6875rem;
|
|
7352
7423
|
color: var(--color-text-muted);
|
|
7353
7424
|
}
|
|
7354
7425
|
.extension-version-notes {
|
|
7355
|
-
font-size: 0.
|
|
7426
|
+
font-size: 0.8125rem;
|
|
7356
7427
|
color: var(--color-text-secondary);
|
|
7357
|
-
line-height: 1.
|
|
7358
|
-
margin-top: 0.
|
|
7359
|
-
|
|
7428
|
+
line-height: 1.6;
|
|
7429
|
+
margin-top: 0.5rem;
|
|
7430
|
+
}
|
|
7431
|
+
.extension-version-notes h1,
|
|
7432
|
+
.extension-version-notes h2,
|
|
7433
|
+
.extension-version-notes h3 {
|
|
7434
|
+
font-size: 0.8125rem;
|
|
7435
|
+
font-weight: 600;
|
|
7436
|
+
color: var(--color-text-primary);
|
|
7437
|
+
margin: 0.75rem 0 0.25rem;
|
|
7438
|
+
}
|
|
7439
|
+
.extension-version-notes h1 {
|
|
7440
|
+
font-size: 0.9375rem;
|
|
7441
|
+
}
|
|
7442
|
+
.extension-version-notes h1:first-child,
|
|
7443
|
+
.extension-version-notes h2:first-child,
|
|
7444
|
+
.extension-version-notes h3:first-child {
|
|
7445
|
+
margin-top: 0;
|
|
7446
|
+
}
|
|
7447
|
+
.extension-version-notes ul,
|
|
7448
|
+
.extension-version-notes ol {
|
|
7449
|
+
margin: 0.25rem 0;
|
|
7450
|
+
padding-left: 1.25rem;
|
|
7360
7451
|
}
|
|
7452
|
+
.extension-version-notes li {
|
|
7453
|
+
margin: 0.1rem 0;
|
|
7454
|
+
}
|
|
7455
|
+
.extension-version-notes p {
|
|
7456
|
+
margin: 0.25rem 0;
|
|
7457
|
+
}
|
|
7458
|
+
.extension-version-notes p:first-child { margin-top: 0; }
|
|
7459
|
+
.extension-version-notes p:last-child { margin-bottom: 0; }
|
|
7361
7460
|
.extension-detail-loading { display: flex; flex-direction: column; gap: 0.5rem; }
|
|
7362
7461
|
|
|
7363
7462
|
.extension-detail-actions {
|
|
7364
7463
|
display: flex;
|
|
7365
7464
|
align-items: center;
|
|
7366
7465
|
gap: 0.5rem;
|
|
7367
|
-
margin-top: 0.
|
|
7466
|
+
margin-top: 0.25rem;
|
|
7368
7467
|
}
|
|
7369
7468
|
.extension-disabled {
|
|
7370
7469
|
font-size: 0.6875rem;
|
|
@@ -7399,6 +7498,18 @@ body {
|
|
|
7399
7498
|
opacity: 0.5;
|
|
7400
7499
|
cursor: not-allowed;
|
|
7401
7500
|
}
|
|
7501
|
+
.extension-action-update {
|
|
7502
|
+
background: var(--color-button-primary);
|
|
7503
|
+
color: var(--color-button-primary-text);
|
|
7504
|
+
border-color: transparent;
|
|
7505
|
+
}
|
|
7506
|
+
.extension-action-update:hover {
|
|
7507
|
+
background: var(--color-button-primary-hover);
|
|
7508
|
+
}
|
|
7509
|
+
.extension-action-update:disabled {
|
|
7510
|
+
opacity: 0.5;
|
|
7511
|
+
cursor: not-allowed;
|
|
7512
|
+
}
|
|
7402
7513
|
.extension-action-disable,
|
|
7403
7514
|
.extension-action-enable {
|
|
7404
7515
|
color: var(--color-text-tertiary);
|
|
@@ -7560,7 +7671,7 @@ body {
|
|
|
7560
7671
|
color: #ffffff;
|
|
7561
7672
|
border: 1px solid var(--color-accent-primary);
|
|
7562
7673
|
}
|
|
7563
|
-
.btn-brand-update:hover { background: var(--color-
|
|
7674
|
+
.btn-brand-update:hover { background: var(--color-button-primary-hover); }
|
|
7564
7675
|
.btn-brand-install:disabled,
|
|
7565
7676
|
.btn-brand-update:disabled {
|
|
7566
7677
|
opacity: 0.5;
|
|
@@ -9984,7 +10095,7 @@ body.setup-mode[data-theme="dark"] {
|
|
|
9984
10095
|
|
|
9985
10096
|
.memory-card-actions {
|
|
9986
10097
|
display: flex;
|
|
9987
|
-
align-items:
|
|
10098
|
+
align-items: center;
|
|
9988
10099
|
gap: 0.375rem;
|
|
9989
10100
|
flex-shrink: 0;
|
|
9990
10101
|
}
|
|
@@ -10024,6 +10135,7 @@ body.setup-mode[data-theme="dark"] {
|
|
|
10024
10135
|
|
|
10025
10136
|
.btn-memory-expand {
|
|
10026
10137
|
min-width: 1.75rem;
|
|
10138
|
+
align-self: stretch;
|
|
10027
10139
|
justify-content: center;
|
|
10028
10140
|
color: var(--color-text-muted);
|
|
10029
10141
|
}
|
|
@@ -10537,7 +10649,7 @@ body.setup-mode[data-theme="dark"] {
|
|
|
10537
10649
|
|
|
10538
10650
|
/* ── Model Filter ────────────────────────────────────────────────────── */
|
|
10539
10651
|
.billing-model-filter {
|
|
10540
|
-
padding: 0.4375rem 0.75rem;
|
|
10652
|
+
padding: 0.4375rem 1.75rem 0.4375rem 0.75rem;
|
|
10541
10653
|
border: 1px solid var(--color-border-primary);
|
|
10542
10654
|
border-radius: 8px;
|
|
10543
10655
|
background: var(--color-bg-secondary);
|
|
@@ -10545,6 +10657,23 @@ body.setup-mode[data-theme="dark"] {
|
|
|
10545
10657
|
font-size: 0.8125rem;
|
|
10546
10658
|
cursor: pointer;
|
|
10547
10659
|
min-width: 130px;
|
|
10660
|
+
appearance: none;
|
|
10661
|
+
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' stroke='%23888' stroke-width='1.5' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
|
|
10662
|
+
background-repeat: no-repeat;
|
|
10663
|
+
background-position: right 0.625rem center;
|
|
10664
|
+
transition: border-color 0.15s ease;
|
|
10665
|
+
}
|
|
10666
|
+
.billing-model-filter:hover {
|
|
10667
|
+
border-color: var(--color-border-strong);
|
|
10668
|
+
}
|
|
10669
|
+
.billing-model-filter:focus {
|
|
10670
|
+
outline: none;
|
|
10671
|
+
border-color: var(--color-accent-primary);
|
|
10672
|
+
box-shadow: 0 0 0 3px var(--color-accent-soft);
|
|
10673
|
+
}
|
|
10674
|
+
.billing-model-filter option {
|
|
10675
|
+
background: var(--color-bg-secondary);
|
|
10676
|
+
color: var(--color-text-primary);
|
|
10548
10677
|
}
|
|
10549
10678
|
|
|
10550
10679
|
/* ── Action Buttons ──────────────────────────────────────────────────── */
|
|
@@ -12327,11 +12456,48 @@ body.setup-mode[data-theme="dark"] {
|
|
|
12327
12456
|
display: none;
|
|
12328
12457
|
}
|
|
12329
12458
|
|
|
12459
|
+
/* Collapse session search into the right-side header actions */
|
|
12460
|
+
#header-center {
|
|
12461
|
+
justify-content: flex-end;
|
|
12462
|
+
padding: 0;
|
|
12463
|
+
}
|
|
12464
|
+
#header-cmdbar {
|
|
12465
|
+
flex: 0 0 1.875rem;
|
|
12466
|
+
width: 1.875rem;
|
|
12467
|
+
max-width: 1.875rem;
|
|
12468
|
+
padding: 0;
|
|
12469
|
+
border: none;
|
|
12470
|
+
border-radius: var(--radius-sm);
|
|
12471
|
+
background: transparent;
|
|
12472
|
+
justify-content: center;
|
|
12473
|
+
gap: 0;
|
|
12474
|
+
margin-right: 0.125rem;
|
|
12475
|
+
}
|
|
12476
|
+
#header-cmdbar:hover {
|
|
12477
|
+
color: var(--color-text-primary);
|
|
12478
|
+
}
|
|
12479
|
+
#header-cmdbar svg {
|
|
12480
|
+
width: 1rem;
|
|
12481
|
+
height: 1rem;
|
|
12482
|
+
opacity: 1;
|
|
12483
|
+
}
|
|
12484
|
+
#header-cmdbar .cmdbar-ph,
|
|
12485
|
+
#header-cmdbar .cmdbar-kbd,
|
|
12486
|
+
#ext-slot-header-right:empty {
|
|
12487
|
+
display: none;
|
|
12488
|
+
}
|
|
12489
|
+
|
|
12330
12490
|
/* Tighten right-side icon gap on mobile */
|
|
12331
12491
|
#header-right {
|
|
12332
12492
|
gap: 0.125rem;
|
|
12333
12493
|
}
|
|
12334
12494
|
|
|
12495
|
+
@media (max-width: 360px) {
|
|
12496
|
+
#top-header {
|
|
12497
|
+
padding: 0 0.375rem;
|
|
12498
|
+
}
|
|
12499
|
+
}
|
|
12500
|
+
|
|
12335
12501
|
/* Session info bar: single-line, no hover-expand, font smaller */
|
|
12336
12502
|
#session-info-bar {
|
|
12337
12503
|
padding: 0.1875rem 0.5rem;
|
|
@@ -12748,4 +12914,3 @@ body.setup-mode[data-theme="dark"] {
|
|
|
12748
12914
|
background: var(--color-bg-primary);
|
|
12749
12915
|
}
|
|
12750
12916
|
}
|
|
12751
|
-
|
|
@@ -66,6 +66,7 @@ const ExtensionsStore = (() => {
|
|
|
66
66
|
|
|
67
67
|
/** Fetch the catalog from the server for the current query + sort. */
|
|
68
68
|
async load() {
|
|
69
|
+
if (_filterInstalled) return;
|
|
69
70
|
_loading = true;
|
|
70
71
|
_error = null;
|
|
71
72
|
_emit("extensions:loading");
|
|
@@ -79,12 +80,8 @@ const ExtensionsStore = (() => {
|
|
|
79
80
|
_allExtensions = data.extensions || [];
|
|
80
81
|
_error = data.warning || null;
|
|
81
82
|
_loading = false;
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
} else {
|
|
85
|
-
_extensions = _allExtensions;
|
|
86
|
-
_emit("extensions:changed", { extensions: _extensions, warning: _error });
|
|
87
|
-
}
|
|
83
|
+
_extensions = _allExtensions;
|
|
84
|
+
_emit("extensions:changed", { extensions: _extensions, warning: _error });
|
|
88
85
|
} catch (e) {
|
|
89
86
|
console.error("[Extensions] load failed", e);
|
|
90
87
|
_extensions = [];
|
|
@@ -99,6 +96,7 @@ const ExtensionsStore = (() => {
|
|
|
99
96
|
/** Set the search text and reload. */
|
|
100
97
|
setQuery(query) {
|
|
101
98
|
_query = (query || "").trim();
|
|
99
|
+
if (_filterInstalled) return Extensions.setFilterInstalled(true);
|
|
102
100
|
return Extensions.load();
|
|
103
101
|
},
|
|
104
102
|
|
|
@@ -117,7 +115,11 @@ const ExtensionsStore = (() => {
|
|
|
117
115
|
try {
|
|
118
116
|
const res = await fetch("/api/store/extensions/installed");
|
|
119
117
|
const data = await res.json();
|
|
120
|
-
|
|
118
|
+
const all = data.extensions || [];
|
|
119
|
+
_extensions = _query
|
|
120
|
+
? all.filter(e => [e.name, e.name_zh, e.description].some(
|
|
121
|
+
f => f && f.toLowerCase().includes(_query.toLowerCase())))
|
|
122
|
+
: all;
|
|
121
123
|
} catch (e) {
|
|
122
124
|
console.error("[Extensions] load installed failed", e);
|
|
123
125
|
_extensions = [];
|
|
@@ -210,6 +212,11 @@ const ExtensionsStore = (() => {
|
|
|
210
212
|
}
|
|
211
213
|
},
|
|
212
214
|
|
|
215
|
+
/** Update an installed extension to the latest marketplace version. */
|
|
216
|
+
async update(id) {
|
|
217
|
+
return Extensions.install(id);
|
|
218
|
+
},
|
|
219
|
+
|
|
213
220
|
/** Remove an installed extension, then return to the list. */
|
|
214
221
|
async uninstall(id) {
|
|
215
222
|
if (!id) return;
|