openclacky 1.3.8 → 1.3.10
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 +40 -0
- data/lib/clacky/brand_config.rb +2 -1
- data/lib/clacky/default_extensions/coding/ext.yml +0 -1
- data/lib/clacky/default_extensions/ext-studio/agents/ext-developer/system_prompt.md +0 -1
- data/lib/clacky/default_extensions/ext-studio/api/handler.rb +31 -4
- data/lib/clacky/default_extensions/ext-studio/ext.yml +2 -3
- data/lib/clacky/default_extensions/ext-studio/panels/studio/view.js +221 -114
- data/lib/clacky/default_extensions/ext-studio/skills/ext-publish/SKILL.md +1 -2
- data/lib/clacky/default_extensions/general/ext.yml +0 -1
- data/lib/clacky/default_extensions/git/ext.yml +0 -1
- data/lib/clacky/default_extensions/meeting/ext.yml +0 -1
- data/lib/clacky/default_extensions/time_machine/ext.yml +0 -1
- data/lib/clacky/extension/api_extension.rb +11 -2
- data/lib/clacky/extension/dispatcher.rb +3 -2
- data/lib/clacky/extension/packager.rb +0 -6
- data/lib/clacky/locales/en.rb +1 -1
- data/lib/clacky/locales/zh.rb +1 -1
- data/lib/clacky/prompts/base.md +1 -1
- data/lib/clacky/server/http_server.rb +115 -7
- data/lib/clacky/version.rb +1 -1
- data/lib/clacky/web/app.css +123 -32
- data/lib/clacky/web/features/extensions/store.js +62 -9
- data/lib/clacky/web/features/extensions/view.js +54 -17
- data/lib/clacky/web/features/new-session/view.js +1 -1
- data/lib/clacky/web/i18n.js +16 -4
- data/lib/clacky/web/index.html +12 -6
- data/lib/clacky/web/sessions.js +32 -2
- metadata +1 -1
|
@@ -11,8 +11,7 @@ it's live.
|
|
|
11
11
|
## Before publishing
|
|
12
12
|
|
|
13
13
|
- The extension must live in the **local** layer (`~/.clacky/ext/local/<id>/`). Only
|
|
14
|
-
local containers can be packed.
|
|
15
|
-
containers are rejected.
|
|
14
|
+
local containers can be packed. Encrypted (`SKILL.md.enc`) containers are rejected.
|
|
16
15
|
- Publishing requires an **activated user license** — it proves creator identity, and
|
|
17
16
|
the platform attributes the extension to that account. If activation is missing, tell
|
|
18
17
|
the user to activate first; don't try to work around it.
|
|
@@ -30,13 +30,14 @@ module Clacky
|
|
|
30
30
|
Route = Struct.new(:method, :pattern, :regex, :param_names, :block, :options, keyword_init: true)
|
|
31
31
|
|
|
32
32
|
class Halt < StandardError
|
|
33
|
-
attr_reader :status, :payload, :content_type
|
|
33
|
+
attr_reader :status, :payload, :content_type, :extra_headers
|
|
34
34
|
|
|
35
|
-
def initialize(status, payload, content_type)
|
|
35
|
+
def initialize(status, payload, content_type, extra_headers: {})
|
|
36
36
|
super("api_ext halt #{status}")
|
|
37
37
|
@status = status
|
|
38
38
|
@payload = payload
|
|
39
39
|
@content_type = content_type
|
|
40
|
+
@extra_headers = extra_headers
|
|
40
41
|
end
|
|
41
42
|
end
|
|
42
43
|
|
|
@@ -201,6 +202,14 @@ module Clacky
|
|
|
201
202
|
raise Halt.new(status, str.to_s, "text/plain; charset=utf-8")
|
|
202
203
|
end
|
|
203
204
|
|
|
205
|
+
def send_data(bytes, content_type:, filename: nil, status: 200)
|
|
206
|
+
disposition = filename ? "attachment; filename=\"#{filename}\"" : "attachment"
|
|
207
|
+
raise Halt.new(status, bytes, content_type, extra_headers: {
|
|
208
|
+
"Content-Disposition" => disposition,
|
|
209
|
+
"Content-Length" => bytes.bytesize.to_s
|
|
210
|
+
})
|
|
211
|
+
end
|
|
212
|
+
|
|
204
213
|
def error!(message, status: 400, **extra)
|
|
205
214
|
payload = { error: message.to_s }
|
|
206
215
|
payload.merge!(extra) unless extra.empty?
|
|
@@ -97,7 +97,7 @@ module Clacky
|
|
|
97
97
|
# Handler exited without writing — empty 204
|
|
98
98
|
empty_response(res)
|
|
99
99
|
rescue Clacky::ApiExtension::Halt => halt
|
|
100
|
-
write_response(res, halt.status, halt.payload, halt.content_type)
|
|
100
|
+
write_response(res, halt.status, halt.payload, halt.content_type, halt.extra_headers)
|
|
101
101
|
rescue Timeout::Error
|
|
102
102
|
Clacky::Logger.warn("[api_ext:#{klass.ext_id}] Timed out after #{timeout_sec}s on #{route.method.upcase} #{route.pattern}")
|
|
103
103
|
write_json(res, 503, error: "request timed out")
|
|
@@ -106,10 +106,11 @@ module Clacky
|
|
|
106
106
|
write_json(res, 500, error: e.message)
|
|
107
107
|
end
|
|
108
108
|
|
|
109
|
-
private def write_response(res, status, body, content_type)
|
|
109
|
+
private def write_response(res, status, body, content_type, extra_headers = {})
|
|
110
110
|
res.status = status
|
|
111
111
|
res.content_type = content_type
|
|
112
112
|
res["Access-Control-Allow-Origin"] = "*"
|
|
113
|
+
extra_headers.each { |k, v| res[k] = v }
|
|
113
114
|
res.body = body
|
|
114
115
|
end
|
|
115
116
|
|
|
@@ -98,12 +98,6 @@ module Clacky
|
|
|
98
98
|
# carrying encrypted skills is produced by the platform pipeline; refuse
|
|
99
99
|
# to re-pack it so we never smuggle marketplace artifacts out of band.
|
|
100
100
|
private def refuse_protected!(slug, container_dir)
|
|
101
|
-
manifest = YAML.safe_load(File.read(File.join(container_dir, MANIFEST)), permitted_classes: [Symbol]) || {}
|
|
102
|
-
origin = (manifest["origin"] || "self").to_s
|
|
103
|
-
if origin == "marketplace"
|
|
104
|
-
raise Error, "refusing to pack #{slug}: origin=marketplace is produced by the platform, not hand-packed"
|
|
105
|
-
end
|
|
106
|
-
|
|
107
101
|
enc = Dir.glob(File.join(container_dir, "**", "SKILL.md.enc"))
|
|
108
102
|
return if enc.empty?
|
|
109
103
|
|
data/lib/clacky/locales/en.rb
CHANGED
|
@@ -5,7 +5,7 @@ module Clacky
|
|
|
5
5
|
EN = {
|
|
6
6
|
"llm.error.insufficient_credit" => "Insufficient credit, please top up your account to continue",
|
|
7
7
|
"llm.error.rate_limit_400" => "Rate limit or service issue, retrying...",
|
|
8
|
-
"llm.error.invalid_api_key" => "
|
|
8
|
+
"llm.error.invalid_api_key" => "API key is invalid or expired, please update it in Settings",
|
|
9
9
|
"llm.error.403.model_not_allowed" => "This model is not available on your current plan",
|
|
10
10
|
"llm.error.403.api_key_revoked" => "API key has been revoked, please generate a new one",
|
|
11
11
|
"llm.error.403.api_key_expired" => "API key has expired, please generate a new one",
|
data/lib/clacky/locales/zh.rb
CHANGED
|
@@ -5,7 +5,7 @@ module Clacky
|
|
|
5
5
|
ZH = {
|
|
6
6
|
"llm.error.insufficient_credit" => "账户余额不足,请前往控制台充值后继续使用",
|
|
7
7
|
"llm.error.rate_limit_400" => "请求频率过高或服务暂时不可用,正在重试...",
|
|
8
|
-
"llm.error.invalid_api_key" => "API
|
|
8
|
+
"llm.error.invalid_api_key" => "API Key 无效或已过期,请到设置中重新配置",
|
|
9
9
|
"llm.error.403.model_not_allowed" => "当前模型不支持免费试用,请升级套餐或切换其他模型",
|
|
10
10
|
"llm.error.403.api_key_revoked" => "API 密钥已被撤销,请前往控制台重新生成",
|
|
11
11
|
"llm.error.403.api_key_expired" => "API 密钥已过期,请前往控制台重新生成",
|
data/lib/clacky/prompts/base.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
- Ask clarifying questions if requirements are unclear.
|
|
4
4
|
- Break down complex tasks into manageable steps.
|
|
5
5
|
- **USE TOOLS to create/modify files** — don't just return content.
|
|
6
|
-
- When the user asks to send/download a file or you generate one for them, append `[filename](file://~/path/to/file)` at the end of your reply.
|
|
6
|
+
- When the user asks to send/download a file or you generate one for them, append `[filename](file://~/path/to/file)` at the end of your reply. Use the raw file path as-is (keep non-ASCII characters and spaces literal); never URL-encode it.
|
|
7
7
|
|
|
8
8
|
## Tool Usage Rules
|
|
9
9
|
|
|
@@ -117,6 +117,7 @@ module Clacky
|
|
|
117
117
|
WEB_ROOT = File.expand_path("../web", __dir__)
|
|
118
118
|
EXCHANGE_RATE_PRIMARY_BASE_URL = "https://open.er-api.com/v6/latest"
|
|
119
119
|
EXCHANGE_RATE_FALLBACK_URL = "https://api.frankfurter.app/latest"
|
|
120
|
+
OSS_CDN_BASE = "https://oss.1024code.com/openclacky"
|
|
120
121
|
|
|
121
122
|
# Default SOUL.md written when the user skips the onboard conversation.
|
|
122
123
|
# A richer version is created by the Agent during the soul_setup phase.
|
|
@@ -538,8 +539,10 @@ module Clacky
|
|
|
538
539
|
when ["POST", "/api/onboard/complete"] then api_onboard_complete(req, res)
|
|
539
540
|
when ["POST", "/api/onboard/skip-soul"] then api_onboard_skip_soul(req, res)
|
|
540
541
|
when ["GET", "/api/store/skills"] then api_store_skills(res)
|
|
541
|
-
when ["GET", "/api/store/extensions"]
|
|
542
|
+
when ["GET", "/api/store/extensions"] then api_store_extensions(req, res)
|
|
543
|
+
when ["GET", "/api/store/extensions/installed"] then api_store_extensions_installed(res)
|
|
542
544
|
when ["GET", "/api/store/extension"] then api_store_extension_detail(req, res)
|
|
545
|
+
when ["POST", "/api/store/extension/install"] then api_store_extension_install(req, res)
|
|
543
546
|
when ["POST", "/api/store/extension/disable"] then api_store_extension_disable(req, res)
|
|
544
547
|
when ["POST", "/api/store/extension/enable"] then api_store_extension_enable(req, res)
|
|
545
548
|
when ["DELETE", "/api/store/extension"] then api_store_extension_uninstall(req, res)
|
|
@@ -2346,6 +2349,66 @@ module Clacky
|
|
|
2346
2349
|
end
|
|
2347
2350
|
end
|
|
2348
2351
|
|
|
2352
|
+
# GET /api/store/extensions/installed
|
|
2353
|
+
#
|
|
2354
|
+
# Returns all locally installed extensions (all layers: builtin, installed,
|
|
2355
|
+
# local) regardless of whether they are still listed on the marketplace.
|
|
2356
|
+
def api_store_extensions_installed(res)
|
|
2357
|
+
result = Clacky::ExtensionLoader.load_all
|
|
2358
|
+
disabled = Clacky::ExtensionLoader.disabled_ids
|
|
2359
|
+
|
|
2360
|
+
local_entries = Array(result&.containers).filter_map do |ext_id, container|
|
|
2361
|
+
next unless container[:layer] == :installed
|
|
2362
|
+
|
|
2363
|
+
[ext_id, container]
|
|
2364
|
+
end.to_h
|
|
2365
|
+
|
|
2366
|
+
market_by_slug = fetch_batch_market_data(local_entries.keys)
|
|
2367
|
+
|
|
2368
|
+
extensions = local_entries.map do |ext_id, container|
|
|
2369
|
+
market = market_by_slug[ext_id]
|
|
2370
|
+
{
|
|
2371
|
+
"id" => ext_id,
|
|
2372
|
+
"name" => market ? (market["name"] || ext_id) : ext_id,
|
|
2373
|
+
"name_zh" => market&.dig("name_zh"),
|
|
2374
|
+
"name_en" => market&.dig("name_en"),
|
|
2375
|
+
"slug" => ext_id,
|
|
2376
|
+
"version" => market ? (market["version"] || container[:version]) : container[:version],
|
|
2377
|
+
"description" => market&.dig("description"),
|
|
2378
|
+
"author" => market&.dig("author"),
|
|
2379
|
+
"icon_url" => market&.dig("icon_url"),
|
|
2380
|
+
"units" => market&.dig("units"),
|
|
2381
|
+
"homepage" => market ? (market["homepage"] || "") : "",
|
|
2382
|
+
"origin" => market ? (market["origin"] || container[:origin]) : container[:origin],
|
|
2383
|
+
"hub_active" => market&.dig("hub_active"),
|
|
2384
|
+
"unlisted" => market.nil?,
|
|
2385
|
+
"layer" => container[:layer].to_s,
|
|
2386
|
+
"installed" => true,
|
|
2387
|
+
"removable" => true,
|
|
2388
|
+
"disabled" => disabled.include?(ext_id),
|
|
2389
|
+
}
|
|
2390
|
+
end
|
|
2391
|
+
|
|
2392
|
+
json_response(res, 200, { ok: true, extensions: extensions })
|
|
2393
|
+
rescue StandardError => e
|
|
2394
|
+
json_response(res, 500, { ok: false, error: e.message })
|
|
2395
|
+
end
|
|
2396
|
+
|
|
2397
|
+
private def fetch_batch_market_data(slugs)
|
|
2398
|
+
return {} if slugs.empty?
|
|
2399
|
+
|
|
2400
|
+
client = Clacky::PlatformHttpClient.new
|
|
2401
|
+
slugs.each_slice(50).each_with_object({}) do |batch, result|
|
|
2402
|
+
ids_param = batch.join(",")
|
|
2403
|
+
response = client.get("/api/v1/extensions/batch?ids=#{ids_param}")
|
|
2404
|
+
next unless response[:success]
|
|
2405
|
+
|
|
2406
|
+
Array(response.dig(:data, "extensions")).each { |ext| result[ext["name"]] = ext }
|
|
2407
|
+
end
|
|
2408
|
+
rescue StandardError
|
|
2409
|
+
{}
|
|
2410
|
+
end
|
|
2411
|
+
|
|
2349
2412
|
# Slugs of every extension container currently loaded (any layer), used to
|
|
2350
2413
|
# flag "installed" on the public marketplace catalog.
|
|
2351
2414
|
def installed_extension_slugs
|
|
@@ -2380,7 +2443,32 @@ module Clacky
|
|
|
2380
2443
|
)
|
|
2381
2444
|
json_response(res, 200, { ok: true, extension: ext })
|
|
2382
2445
|
else
|
|
2383
|
-
|
|
2446
|
+
container = extension_container(id)
|
|
2447
|
+
if container && container[:layer] == :installed
|
|
2448
|
+
market = fetch_batch_market_data([id])[id]
|
|
2449
|
+
ext = {
|
|
2450
|
+
"id" => id,
|
|
2451
|
+
"name" => market ? (market["name"] || id) : id,
|
|
2452
|
+
"name_zh" => market&.dig("name_zh"),
|
|
2453
|
+
"name_en" => market&.dig("name_en"),
|
|
2454
|
+
"slug" => id,
|
|
2455
|
+
"version" => market ? (market["version"] || container[:version]) : container[:version],
|
|
2456
|
+
"description" => market&.dig("description"),
|
|
2457
|
+
"author" => market&.dig("author"),
|
|
2458
|
+
"icon_url" => market&.dig("icon_url"),
|
|
2459
|
+
"units" => market&.dig("units"),
|
|
2460
|
+
"homepage" => market ? (market["homepage"] || "") : "",
|
|
2461
|
+
"origin" => market ? (market["origin"] || container[:origin]) : container[:origin],
|
|
2462
|
+
"hub_active" => market&.dig("hub_active"),
|
|
2463
|
+
"unlisted" => market.nil?,
|
|
2464
|
+
"installed" => true,
|
|
2465
|
+
"removable" => true,
|
|
2466
|
+
"disabled" => container[:disabled] == true,
|
|
2467
|
+
}
|
|
2468
|
+
json_response(res, 200, { ok: true, extension: ext })
|
|
2469
|
+
else
|
|
2470
|
+
json_response(res, 404, { ok: false, error: result[:error] || "Not found" })
|
|
2471
|
+
end
|
|
2384
2472
|
end
|
|
2385
2473
|
end
|
|
2386
2474
|
|
|
@@ -2419,6 +2507,25 @@ module Clacky
|
|
|
2419
2507
|
end
|
|
2420
2508
|
|
|
2421
2509
|
# DELETE /api/store/extension body: { id: <slug> }
|
|
2510
|
+
def api_store_extension_install(req, res)
|
|
2511
|
+
body = parse_json_body(req)
|
|
2512
|
+
download_url = body["download_url"].to_s.strip
|
|
2513
|
+
name = body["name"].to_s.strip
|
|
2514
|
+
|
|
2515
|
+
if download_url.empty?
|
|
2516
|
+
json_response(res, 400, { ok: false, error: "Missing download_url." })
|
|
2517
|
+
return
|
|
2518
|
+
end
|
|
2519
|
+
|
|
2520
|
+
Clacky::ExtensionPackager.install(download_url, force: true)
|
|
2521
|
+
Clacky::ExtensionLoader.invalidate_cache!
|
|
2522
|
+
json_response(res, 200, { ok: true, name: name })
|
|
2523
|
+
rescue Clacky::ExtensionPackager::Error => e
|
|
2524
|
+
json_response(res, 422, { ok: false, error: e.message })
|
|
2525
|
+
rescue StandardError => e
|
|
2526
|
+
json_response(res, 500, { ok: false, error: e.message })
|
|
2527
|
+
end
|
|
2528
|
+
|
|
2422
2529
|
def api_store_extension_uninstall(req, res)
|
|
2423
2530
|
id = parse_json_body(req)["id"].to_s
|
|
2424
2531
|
container = extension_container(id)
|
|
@@ -2879,7 +2986,7 @@ module Clacky
|
|
|
2879
2986
|
require "net/http"
|
|
2880
2987
|
require "uri"
|
|
2881
2988
|
|
|
2882
|
-
oss_base =
|
|
2989
|
+
oss_base = OSS_CDN_BASE
|
|
2883
2990
|
latest_url = "#{oss_base}/latest.txt"
|
|
2884
2991
|
|
|
2885
2992
|
Clacky::Logger.info("[Upgrade] Non-official source — fetching latest version from OSS CDN")
|
|
@@ -3051,11 +3158,12 @@ module Clacky
|
|
|
3051
3158
|
end
|
|
3052
3159
|
|
|
3053
3160
|
# Query the latest openclacky version.
|
|
3054
|
-
# Strategy:
|
|
3055
|
-
# then fall back to `gem list -r`
|
|
3056
|
-
# Uses Terminal (PTY + login shell) so rbenv/mise shims and gem mirrors work correctly.
|
|
3161
|
+
# Strategy: OSS CDN latest.txt first (fast, CDN-accelerated), then RubyGems API,
|
|
3162
|
+
# then fall back to `gem list -r` as a last resort.
|
|
3057
3163
|
private def fetch_latest_version_from_gem
|
|
3058
|
-
|
|
3164
|
+
fetch_oss_latest_version("#{OSS_CDN_BASE}/latest.txt") ||
|
|
3165
|
+
fetch_latest_version_from_rubygems_api ||
|
|
3166
|
+
fetch_latest_version_from_gem_command
|
|
3059
3167
|
end
|
|
3060
3168
|
|
|
3061
3169
|
# Try RubyGems official REST API — fast and always up-to-date.
|
data/lib/clacky/version.rb
CHANGED
data/lib/clacky/web/app.css
CHANGED
|
@@ -1874,9 +1874,7 @@ body {
|
|
|
1874
1874
|
flex-direction: column;
|
|
1875
1875
|
gap: 1.25rem;
|
|
1876
1876
|
width: 100%;
|
|
1877
|
-
max-
|
|
1878
|
-
margin: 0 auto;
|
|
1879
|
-
padding: 5.5rem 1.5rem 1rem;
|
|
1877
|
+
padding: 6rem max(1.5rem, calc(50% - 30rem)) 1rem;
|
|
1880
1878
|
}
|
|
1881
1879
|
.new-session-composer {
|
|
1882
1880
|
position: relative;
|
|
@@ -2007,6 +2005,11 @@ body {
|
|
|
2007
2005
|
font-size: 0.78125rem;
|
|
2008
2006
|
color: var(--color-text-tertiary);
|
|
2009
2007
|
line-height: 1.4;
|
|
2008
|
+
flex: 1;
|
|
2009
|
+
overflow: hidden;
|
|
2010
|
+
display: -webkit-box;
|
|
2011
|
+
-webkit-line-clamp: 2;
|
|
2012
|
+
-webkit-box-orient: vertical;
|
|
2010
2013
|
}
|
|
2011
2014
|
.agent-card-badge {
|
|
2012
2015
|
position: absolute;
|
|
@@ -2026,7 +2029,6 @@ body {
|
|
|
2026
2029
|
color: var(--color-accent-primary);
|
|
2027
2030
|
}
|
|
2028
2031
|
.agent-card-author {
|
|
2029
|
-
margin-top: 0.25rem;
|
|
2030
2032
|
font-size: 0.72rem;
|
|
2031
2033
|
color: var(--color-text-tertiary);
|
|
2032
2034
|
opacity: 0.8;
|
|
@@ -2096,7 +2098,7 @@ body {
|
|
|
2096
2098
|
flex-direction: column;
|
|
2097
2099
|
gap: 0.5rem;
|
|
2098
2100
|
align-items: flex-start;
|
|
2099
|
-
margin-top: -0.
|
|
2101
|
+
margin-top: -0.5rem;
|
|
2100
2102
|
}
|
|
2101
2103
|
.new-session-advanced-toggle {
|
|
2102
2104
|
display: inline-flex;
|
|
@@ -7079,13 +7081,45 @@ body {
|
|
|
7079
7081
|
gap: 1.25rem;
|
|
7080
7082
|
}
|
|
7081
7083
|
.extensions-toolbar {
|
|
7084
|
+
display: flex;
|
|
7085
|
+
align-items: center;
|
|
7086
|
+
justify-content: space-between;
|
|
7087
|
+
gap: 0.75rem;
|
|
7088
|
+
margin-bottom: 0.25rem;
|
|
7089
|
+
}
|
|
7090
|
+
.extensions-toolbar-right {
|
|
7082
7091
|
display: flex;
|
|
7083
7092
|
align-items: center;
|
|
7084
7093
|
gap: 0.5rem;
|
|
7085
7094
|
}
|
|
7095
|
+
.extensions-filter-tabs {
|
|
7096
|
+
display: flex;
|
|
7097
|
+
align-items: center;
|
|
7098
|
+
gap: 0;
|
|
7099
|
+
flex-shrink: 0;
|
|
7100
|
+
}
|
|
7101
|
+
.extensions-filter-tab {
|
|
7102
|
+
font-size: 0.875rem;
|
|
7103
|
+
font-weight: 500;
|
|
7104
|
+
padding: 0.25rem 0.875rem 0.5rem;
|
|
7105
|
+
border: none;
|
|
7106
|
+
background: transparent;
|
|
7107
|
+
color: var(--color-text-tertiary);
|
|
7108
|
+
cursor: pointer;
|
|
7109
|
+
white-space: nowrap;
|
|
7110
|
+
border-bottom: 2px solid transparent;
|
|
7111
|
+
transition: color .15s, border-color .15s;
|
|
7112
|
+
}
|
|
7113
|
+
.extensions-filter-tab:hover {
|
|
7114
|
+
color: var(--color-text-primary);
|
|
7115
|
+
}
|
|
7116
|
+
.extensions-filter-tab-active {
|
|
7117
|
+
color: var(--color-text-primary);
|
|
7118
|
+
border-bottom-color: var(--color-accent, #6366f1);
|
|
7119
|
+
}
|
|
7086
7120
|
.extensions-search {
|
|
7087
7121
|
flex: 1;
|
|
7088
|
-
min-width:
|
|
7122
|
+
min-width: 15rem;
|
|
7089
7123
|
font-size: 0.8125rem;
|
|
7090
7124
|
padding: 0.4375rem 0.75rem;
|
|
7091
7125
|
border-radius: 6px;
|
|
@@ -7137,7 +7171,7 @@ body {
|
|
|
7137
7171
|
.extension-card:hover { border-color: var(--color-text-muted); }
|
|
7138
7172
|
.extension-card-main {
|
|
7139
7173
|
display: flex;
|
|
7140
|
-
align-items:
|
|
7174
|
+
align-items: center;
|
|
7141
7175
|
gap: 0.75rem;
|
|
7142
7176
|
}
|
|
7143
7177
|
.extension-emoji {
|
|
@@ -7176,12 +7210,32 @@ body {
|
|
|
7176
7210
|
border-radius: 4px;
|
|
7177
7211
|
padding: 0.0625rem 0.375rem;
|
|
7178
7212
|
}
|
|
7213
|
+
.extension-unlisted {
|
|
7214
|
+
font-size: 0.6875rem;
|
|
7215
|
+
color: var(--color-text-secondary);
|
|
7216
|
+
background: var(--color-surface-2, rgba(0,0,0,0.06));
|
|
7217
|
+
border-radius: 4px;
|
|
7218
|
+
padding: 0.0625rem 0.375rem;
|
|
7219
|
+
}
|
|
7179
7220
|
.extension-desc {
|
|
7180
7221
|
font-size: 0.75rem;
|
|
7181
7222
|
color: var(--color-text-secondary);
|
|
7182
7223
|
line-height: 1.5;
|
|
7183
7224
|
margin-bottom: 0.375rem;
|
|
7184
7225
|
}
|
|
7226
|
+
.extension-desc-detail {
|
|
7227
|
+
font-size: 0.8125rem;
|
|
7228
|
+
margin-bottom: 0.5rem;
|
|
7229
|
+
}
|
|
7230
|
+
.extension-detail-author {
|
|
7231
|
+
font-size: 0.6875rem;
|
|
7232
|
+
color: var(--color-text-muted);
|
|
7233
|
+
}
|
|
7234
|
+
.extension-author {
|
|
7235
|
+
font-size: 0.75rem;
|
|
7236
|
+
color: var(--color-text-muted);
|
|
7237
|
+
font-weight: 400;
|
|
7238
|
+
}
|
|
7185
7239
|
.extension-meta {
|
|
7186
7240
|
display: flex;
|
|
7187
7241
|
align-items: center;
|
|
@@ -7224,19 +7278,21 @@ body {
|
|
|
7224
7278
|
.extension-detail-hero {
|
|
7225
7279
|
display: flex;
|
|
7226
7280
|
align-items: flex-start;
|
|
7227
|
-
gap:
|
|
7228
|
-
padding-bottom:
|
|
7281
|
+
gap: 1rem;
|
|
7282
|
+
padding-bottom: 1.25rem;
|
|
7229
7283
|
border-bottom: 1px solid var(--color-border-primary);
|
|
7230
7284
|
}
|
|
7231
7285
|
.extension-detail-heading { flex: 1; min-width: 0; }
|
|
7232
|
-
.extension-emoji-lg { font-size: 2.
|
|
7233
|
-
.extension-name-lg { font-size: 1.
|
|
7234
|
-
.extension-detail-block { margin-top: 1.
|
|
7286
|
+
.extension-emoji-lg { font-size: 2.5rem; line-height: 1; margin-top: 0.125rem; }
|
|
7287
|
+
.extension-name-lg { font-size: 1.125rem; font-weight: 700; }
|
|
7288
|
+
.extension-detail-block { margin-top: 1.5rem; }
|
|
7235
7289
|
.extension-detail-block-title {
|
|
7236
|
-
font-size: 0.
|
|
7290
|
+
font-size: 0.75rem;
|
|
7237
7291
|
font-weight: 600;
|
|
7238
|
-
|
|
7239
|
-
|
|
7292
|
+
text-transform: uppercase;
|
|
7293
|
+
letter-spacing: 0.04em;
|
|
7294
|
+
color: var(--color-text-muted);
|
|
7295
|
+
margin: 0 0 0.75rem;
|
|
7240
7296
|
}
|
|
7241
7297
|
.extension-detail-section { margin-bottom: 0.875rem; }
|
|
7242
7298
|
.extension-detail-section-title {
|
|
@@ -7248,18 +7304,30 @@ body {
|
|
|
7248
7304
|
margin: 0 0 0.375rem;
|
|
7249
7305
|
}
|
|
7250
7306
|
.extension-contrib-list,
|
|
7251
|
-
.extension-version-list {
|
|
7252
|
-
|
|
7253
|
-
|
|
7254
|
-
|
|
7307
|
+
.extension-version-list {
|
|
7308
|
+
list-style: none;
|
|
7309
|
+
margin: 0;
|
|
7310
|
+
padding: 0;
|
|
7255
7311
|
border: 1px solid var(--color-border-primary);
|
|
7256
|
-
border-radius:
|
|
7257
|
-
|
|
7312
|
+
border-radius: 8px;
|
|
7313
|
+
overflow: hidden;
|
|
7314
|
+
background: var(--color-bg-secondary);
|
|
7315
|
+
}
|
|
7316
|
+
.extension-contrib-item {
|
|
7317
|
+
padding: 0.625rem 0.875rem;
|
|
7318
|
+
border-bottom: 1px solid var(--color-border-primary);
|
|
7319
|
+
background: transparent;
|
|
7320
|
+
border-radius: 0;
|
|
7321
|
+
border-left: none;
|
|
7322
|
+
border-right: none;
|
|
7323
|
+
border-top: none;
|
|
7324
|
+
margin-bottom: 0;
|
|
7258
7325
|
}
|
|
7326
|
+
.extension-contrib-item:last-child { border-bottom: none; }
|
|
7259
7327
|
.extension-contrib-title {
|
|
7260
7328
|
display: block;
|
|
7261
7329
|
font-size: 0.8125rem;
|
|
7262
|
-
font-weight:
|
|
7330
|
+
font-weight: 500;
|
|
7263
7331
|
color: var(--color-text-primary);
|
|
7264
7332
|
}
|
|
7265
7333
|
.extension-contrib-desc {
|
|
@@ -7270,7 +7338,7 @@ body {
|
|
|
7270
7338
|
margin-top: 0.125rem;
|
|
7271
7339
|
}
|
|
7272
7340
|
.extension-version-item {
|
|
7273
|
-
padding: 0.
|
|
7341
|
+
padding: 0.625rem 0.875rem;
|
|
7274
7342
|
border-bottom: 1px solid var(--color-border-primary);
|
|
7275
7343
|
}
|
|
7276
7344
|
.extension-version-item:last-child { border-bottom: none; }
|
|
@@ -7306,20 +7374,43 @@ body {
|
|
|
7306
7374
|
padding: 0.0625rem 0.375rem;
|
|
7307
7375
|
}
|
|
7308
7376
|
.extension-action {
|
|
7377
|
+
display: inline-flex;
|
|
7378
|
+
align-items: center;
|
|
7309
7379
|
font-size: 0.75rem;
|
|
7310
|
-
padding: 0.
|
|
7311
|
-
border-radius:
|
|
7312
|
-
border: 1px solid var(--color-border);
|
|
7313
|
-
background: var(--color-
|
|
7314
|
-
color: var(--color-text);
|
|
7380
|
+
padding: 0.25rem 0.625rem;
|
|
7381
|
+
border-radius: 5px;
|
|
7382
|
+
border: 1px solid var(--color-border-primary);
|
|
7383
|
+
background: var(--color-bg-primary);
|
|
7384
|
+
color: var(--color-text-secondary);
|
|
7315
7385
|
cursor: pointer;
|
|
7386
|
+
transition: background .15s, border-color .15s, opacity .15s;
|
|
7387
|
+
white-space: nowrap;
|
|
7388
|
+
}
|
|
7389
|
+
.extension-action:hover { background: var(--color-bg-hover); }
|
|
7390
|
+
.extension-action-install {
|
|
7391
|
+
background: var(--color-button-primary);
|
|
7392
|
+
color: var(--color-button-primary-text);
|
|
7393
|
+
border-color: transparent;
|
|
7394
|
+
}
|
|
7395
|
+
.extension-action-install:hover {
|
|
7396
|
+
background: var(--color-button-primary-hover);
|
|
7397
|
+
}
|
|
7398
|
+
.extension-action-install:disabled {
|
|
7399
|
+
opacity: 0.5;
|
|
7400
|
+
cursor: not-allowed;
|
|
7401
|
+
}
|
|
7402
|
+
.extension-action-disable,
|
|
7403
|
+
.extension-action-enable {
|
|
7404
|
+
color: var(--color-text-tertiary);
|
|
7316
7405
|
}
|
|
7317
|
-
.extension-action:hover { background: var(--color-surface-hover, rgba(0,0,0,0.04)); }
|
|
7318
7406
|
.extension-action-remove {
|
|
7319
|
-
color: var(--color-
|
|
7320
|
-
border-color: var(--color-
|
|
7407
|
+
color: var(--color-error);
|
|
7408
|
+
border-color: var(--color-error-border);
|
|
7409
|
+
}
|
|
7410
|
+
.extension-action-remove:hover {
|
|
7411
|
+
background: var(--color-error-bg);
|
|
7412
|
+
border-color: var(--color-error);
|
|
7321
7413
|
}
|
|
7322
|
-
.extension-action-remove:hover { background: var(--color-danger-bg, rgba(220,38,38,0.08)); }
|
|
7323
7414
|
|
|
7324
7415
|
/* Brand Skill card */
|
|
7325
7416
|
.brand-skill-card {
|