openclacky 1.5.13 → 1.5.14
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 +51 -0
- data/lib/clacky/agent/chunk_index.rb +83 -0
- data/lib/clacky/agent/history_navigation.rb +239 -0
- data/lib/clacky/agent/session_serializer.rb +38 -97
- data/lib/clacky/agent.rb +281 -276
- data/lib/clacky/agent_config.rb +1 -1
- data/lib/clacky/billing/billing_store.rb +2 -2
- data/lib/clacky/billing/platform_billing.rb +6 -0
- data/lib/clacky/brand_config.rb +13 -2
- data/lib/clacky/cli.rb +27 -0
- data/lib/clacky/client.rb +2 -2
- data/lib/clacky/default_extensions/ext-studio/agents/ext-developer/system_prompt.md +70 -134
- data/lib/clacky/default_extensions/ext-studio/panels/studio/view.js +91 -5
- data/lib/clacky/default_extensions/ext-studio/skills/ext-develop/SKILL.md +179 -577
- data/lib/clacky/default_extensions/git/panels/git/view.js +34 -14
- data/lib/clacky/default_extensions/preview/ext.yml +20 -0
- data/lib/clacky/default_extensions/preview/panels/preview/view.js +475 -0
- data/lib/clacky/default_extensions/time_machine/panels/time_machine/view.js +1 -10
- data/lib/clacky/extension/api_extension.rb +1 -1
- data/lib/clacky/extension/verifier.rb +1 -1
- data/lib/clacky/media/openai_compat.rb +70 -30
- data/lib/clacky/message_format/bedrock.rb +6 -1
- data/lib/clacky/prompts/base.md +1 -1
- data/lib/clacky/providers.rb +38 -3
- data/lib/clacky/rich_ui/rich_ui_controller.rb +1 -1
- data/lib/clacky/search_config.rb +3 -3
- data/lib/clacky/server/channel/channel_manager.rb +8 -5
- data/lib/clacky/server/git_panel.rb +10 -2
- data/lib/clacky/server/http_server.rb +161 -14
- data/lib/clacky/server/preview.rb +351 -0
- data/lib/clacky/server/web_ui_controller.rb +1 -1
- data/lib/clacky/skill.rb +48 -0
- data/lib/clacky/tools/browser.rb +176 -19
- data/lib/clacky/tools/web_search.rb +79 -8
- data/lib/clacky/ui2/components/command_suggestions.rb +2 -1
- data/lib/clacky/ui2/components/input_area.rb +22 -2
- data/lib/clacky/ui2/components/modal_component.rb +35 -6
- data/lib/clacky/ui2/ui_controller.rb +44 -7
- data/lib/clacky/utils/file_processor.rb +19 -7
- data/lib/clacky/utils/mac_app_detector.rb +186 -0
- data/lib/clacky/utils/model_pricing.rb +114 -66
- data/lib/clacky/utils/windows_app_detector.rb +334 -0
- data/lib/clacky/version.rb +1 -1
- data/lib/clacky/web/app.css +596 -130
- data/lib/clacky/web/components/chat-navigator.js +489 -202
- data/lib/clacky/web/components/code-editor.js +191 -5
- data/lib/clacky/web/components/model-picker.js +2 -1
- data/lib/clacky/web/components/quote-select.js +235 -0
- data/lib/clacky/web/core/aside.js +157 -8
- data/lib/clacky/web/core/ext.js +16 -0
- data/lib/clacky/web/features/billing/view.js +75 -21
- data/lib/clacky/web/features/skills/store.js +18 -1
- data/lib/clacky/web/features/skills/view.js +128 -7
- data/lib/clacky/web/features/workspace/store.js +80 -7
- data/lib/clacky/web/features/workspace/view.js +392 -40
- data/lib/clacky/web/i18n.js +90 -5
- data/lib/clacky/web/index.html +15 -6
- data/lib/clacky/web/sessions.js +193 -38
- data/lib/clacky/web/utils.js +34 -0
- data/lib/clacky/web/vendor/codemirror/codemirror.min.js +24 -19
- data/lib/clacky/web/vendor/codemirror/entry.js +130 -0
- data/lib/clacky/web/vendor/codemirror/package.json +29 -0
- data/lib/clacky/web/ws-dispatcher.js +20 -0
- data/lib/clacky.rb +2 -0
- metadata +11 -1
data/lib/clacky/agent_config.rb
CHANGED
|
@@ -1161,7 +1161,7 @@ module Clacky
|
|
|
1161
1161
|
# find_by_base_url only matches registered preset URLs, so localhost
|
|
1162
1162
|
# and other non-openclacky endpoints correctly return nil.
|
|
1163
1163
|
provider_id = Clacky::Providers.find_by_base_url(m["base_url"])
|
|
1164
|
-
return nil unless provider_id ==
|
|
1164
|
+
return nil unless provider_id == Clacky::Providers::OPENCLACKY_ID
|
|
1165
1165
|
|
|
1166
1166
|
fallback_url = Clacky::Providers.fallback_base_url(provider_id)
|
|
1167
1167
|
return nil unless fallback_url
|
|
@@ -20,7 +20,7 @@ module Clacky
|
|
|
20
20
|
# list never drifts from providers.rb as models are added or removed.
|
|
21
21
|
def openclacky_models
|
|
22
22
|
@openclacky_models ||= begin
|
|
23
|
-
preset = Clacky::Providers.get(
|
|
23
|
+
preset = Clacky::Providers.get(Clacky::Providers::OPENCLACKY_ID)
|
|
24
24
|
return [] unless preset
|
|
25
25
|
OPENCLACKY_MODEL_KINDS.flat_map { |kind| preset[kind] || [] }.uniq
|
|
26
26
|
end
|
|
@@ -30,7 +30,7 @@ module Clacky
|
|
|
30
30
|
# carry an explicit provider id; legacy records (written before the
|
|
31
31
|
# field existed) are detected by matching the full openclacky model list.
|
|
32
32
|
def openclacky_record?(record)
|
|
33
|
-
return true if record.provider ==
|
|
33
|
+
return true if record.provider == Clacky::Providers::OPENCLACKY_ID
|
|
34
34
|
openclacky_models.include?(record.model.to_s)
|
|
35
35
|
end
|
|
36
36
|
|
|
@@ -17,9 +17,15 @@ module Clacky
|
|
|
17
17
|
# "primary" id where the gateway can dispatch an alias two ways.
|
|
18
18
|
ALIAS_TO_REAL = {
|
|
19
19
|
# deepseek (dsk-)
|
|
20
|
+
"dsk-deepseek-flash" => "deepseek-flash",
|
|
20
21
|
"dsk-deepseek-v4-pro" => "deepseek-v4-pro",
|
|
21
22
|
"dsk-deepseek-v4-flash" => "deepseek-v4-flash",
|
|
22
23
|
"dsk-deepseek-v4-flash-vision-exp" => "deepseek-v4-flash-vision-exp",
|
|
24
|
+
# gpt via bedrock (abs-, OpenAI-compatible endpoint)
|
|
25
|
+
"abs-gpt-6-astra" => "global.openai.gpt-6-astra",
|
|
26
|
+
"abs-gpt-5.6-sol" => "global.openai.gpt-5.6-sol",
|
|
27
|
+
"abs-gpt-5.6-terra" => "global.openai.gpt-5.6-terra",
|
|
28
|
+
"abs-gpt-5.6-luna" => "global.openai.gpt-5.6-luna",
|
|
23
29
|
# claude via bedrock (abs-)
|
|
24
30
|
"abs-claude-fable-5-1" => "global.anthropic.claude-fable-5-1",
|
|
25
31
|
"abs-claude-fable-5" => "global.anthropic.claude-fable-5",
|
data/lib/clacky/brand_config.rb
CHANGED
|
@@ -53,7 +53,7 @@ module Clacky
|
|
|
53
53
|
attr_reader :product_name, :package_name, :license_key, :license_activated_at,
|
|
54
54
|
:license_expires_at, :license_last_heartbeat, :device_id,
|
|
55
55
|
:logo_url, :support_contact, :license_user_id,
|
|
56
|
-
:support_qr_url, :theme_color,
|
|
56
|
+
:support_qr_url, :theme_color,
|
|
57
57
|
:distribution_last_refreshed_at, :license_last_heartbeat_failure
|
|
58
58
|
|
|
59
59
|
def initialize(attrs = {})
|
|
@@ -1649,6 +1649,17 @@ module Clacky
|
|
|
1649
1649
|
# Non-fatal — metadata write failure should not break the upload flow
|
|
1650
1650
|
end
|
|
1651
1651
|
|
|
1652
|
+
# Vendors may configure a bare domain ("example.com"). Without a scheme the
|
|
1653
|
+
# browser treats it as a relative path and resolves it against the local
|
|
1654
|
+
# server, so normalize on read to cover every consumer at once.
|
|
1655
|
+
def homepage_url
|
|
1656
|
+
raw = @homepage_url.to_s.strip
|
|
1657
|
+
return nil if raw.empty?
|
|
1658
|
+
return raw if raw.match?(%r{\A[a-z][a-z0-9+.\-]*:}i)
|
|
1659
|
+
|
|
1660
|
+
"https://#{raw}"
|
|
1661
|
+
end
|
|
1662
|
+
|
|
1652
1663
|
# Returns a hash representation for JSON serialization (e.g. /api/brand).
|
|
1653
1664
|
def to_h
|
|
1654
1665
|
{
|
|
@@ -1658,7 +1669,7 @@ module Clacky
|
|
|
1658
1669
|
support_contact: @support_contact,
|
|
1659
1670
|
support_qr_url: @support_qr_url,
|
|
1660
1671
|
theme_color: @theme_color,
|
|
1661
|
-
homepage_url:
|
|
1672
|
+
homepage_url: homepage_url,
|
|
1662
1673
|
branded: branded?,
|
|
1663
1674
|
activated: activated?,
|
|
1664
1675
|
expired: expired?,
|
data/lib/clacky/cli.rb
CHANGED
|
@@ -345,6 +345,29 @@ module Clacky
|
|
|
345
345
|
ui_controller.show_success("Switched to model: #{config.model_name}")
|
|
346
346
|
end
|
|
347
347
|
|
|
348
|
+
# Handle the `/think` slash command — pick the reasoning effort level
|
|
349
|
+
# for the current session. "off" normalizes to nil (provider default),
|
|
350
|
+
# matching the Web UI's reasoning_effort switcher semantics.
|
|
351
|
+
private def handle_think_command(ui_controller, agent, session_manager = nil)
|
|
352
|
+
choice = ui_controller.show_reasoning_effort_menu(agent.reasoning_effort)
|
|
353
|
+
return if choice.nil?
|
|
354
|
+
|
|
355
|
+
agent.reasoning_effort = choice
|
|
356
|
+
|
|
357
|
+
# The override lives in the session file (not config.yml), so persist
|
|
358
|
+
# it now — otherwise it would be lost if the user quits before the
|
|
359
|
+
# next task.
|
|
360
|
+
session_manager&.save(agent.to_session_data(updated_at: Time.now))
|
|
361
|
+
|
|
362
|
+
# Reflect the change in the session bar (appended after the model name)
|
|
363
|
+
ui_controller.config[:reasoning_effort] = agent.reasoning_effort
|
|
364
|
+
ui_controller.update_sessionbar
|
|
365
|
+
|
|
366
|
+
current = agent.reasoning_effort
|
|
367
|
+
message = current ? "Thinking level set to #{current}" : "Thinking level: off (provider default)"
|
|
368
|
+
ui_controller.show_success(message)
|
|
369
|
+
end
|
|
370
|
+
|
|
348
371
|
private def handle_time_machine_command(ui_controller, agent, session_manager)
|
|
349
372
|
# Get task history from agent
|
|
350
373
|
history = agent.get_task_history(limit: 10)
|
|
@@ -874,6 +897,7 @@ module Clacky
|
|
|
874
897
|
working_dir: working_dir,
|
|
875
898
|
mode: agent_config.permission_mode.to_s,
|
|
876
899
|
model: agent_config.model_name,
|
|
900
|
+
reasoning_effort: agent.reasoning_effort,
|
|
877
901
|
theme: theme_name
|
|
878
902
|
)
|
|
879
903
|
end
|
|
@@ -997,6 +1021,9 @@ module Clacky
|
|
|
997
1021
|
when "/model"
|
|
998
1022
|
handle_model_command(ui_controller, agent_config, agent, session_manager)
|
|
999
1023
|
next
|
|
1024
|
+
when "/think"
|
|
1025
|
+
handle_think_command(ui_controller, agent, session_manager)
|
|
1026
|
+
next
|
|
1000
1027
|
when "/undo"
|
|
1001
1028
|
handle_time_machine_command(ui_controller, agent, session_manager)
|
|
1002
1029
|
next
|
data/lib/clacky/client.rb
CHANGED
|
@@ -720,12 +720,12 @@ module Clacky
|
|
|
720
720
|
conn.headers["x-api-key"] = @api_key
|
|
721
721
|
conn.headers["anthropic-version"] = "2023-06-01"
|
|
722
722
|
conn.headers["anthropic-dangerous-direct-browser-access"] = "true"
|
|
723
|
-
if @provider_id ==
|
|
723
|
+
if @provider_id == Clacky::Providers::OPENROUTER_ID
|
|
724
724
|
conn.headers["Authorization"] = "Bearer #{@api_key}"
|
|
725
725
|
end
|
|
726
726
|
# Moonshot's Kimi Code (Coding Plan) endpoint enforces a User-Agent
|
|
727
727
|
# prefix whitelist limited to first-party coding agents.
|
|
728
|
-
if @provider_id ==
|
|
728
|
+
if @provider_id == Clacky::Providers::KIMI_CODING_ID
|
|
729
729
|
conn.headers["User-Agent"] = "claude-cli/1.0.51 (external, cli)"
|
|
730
730
|
end
|
|
731
731
|
conn.options.timeout = @read_timeout || 300
|
|
@@ -1,147 +1,83 @@
|
|
|
1
|
-
You are Extension Developer, an AI expert who helps users build, debug, and
|
|
2
|
-
|
|
3
|
-
scaffold, edit, verify, reload — so the user never has to memorize commands or file
|
|
4
|
-
layouts.
|
|
5
|
-
|
|
6
|
-
Your role is to:
|
|
7
|
-
- Turn a plain-language idea ("I want an extension that shows the weather") into a
|
|
8
|
-
working extension by scaffolding it, wiring the right contributes, and iterating.
|
|
9
|
-
- Read and edit extension files directly, then verify and hot-reload to confirm.
|
|
10
|
-
- Debug using structured verify errors, fixing manifest and file issues.
|
|
11
|
-
- Publish to the marketplace only when the user explicitly wants to share it.
|
|
1
|
+
You are Extension Developer, an AI expert who helps users build, debug, and, only
|
|
2
|
+
when requested, publish OpenClacky extensions through conversation.
|
|
12
3
|
|
|
13
4
|
## How you work
|
|
14
5
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
extension work — don't wait for it to surface on its own, and never work from memory when
|
|
20
|
-
the skill has the answer. You own the flow and decide when each section applies:
|
|
21
|
-
|
|
22
|
-
- **Scaffold** — when the user wants to start a new extension. Clarify the idea in one
|
|
23
|
-
question if it's ambiguous (what should it DO, and where — a panel, a skill, an agent,
|
|
24
|
-
a backend?), map it to the smallest set of contributes types, then `clacky ext new`.
|
|
25
|
-
Don't over-scope: most extensions are one panel + one handler, or one skill.
|
|
26
|
-
- **Debug & verify** — when something is broken, `verify` reports errors, or a change
|
|
27
|
-
didn't take effect. `clacky ext verify` is your compiler; fix by error code until clean.
|
|
28
|
-
- **Publish** — only when the user explicitly asks to share/ship it. Publishing is NOT a
|
|
29
|
-
required step; many extensions are built for the user's own use. Never publish on your
|
|
30
|
-
own initiative or as a "wrap up."
|
|
6
|
+
First call `invoke_skill` for `ext-develop`, including for an approved repair. It owns
|
|
7
|
+
the workflow and reference index; linked articles own interface contracts. Behavior-only
|
|
8
|
+
discussion needs no API inventory. Read only the current decision's reference, reuse it
|
|
9
|
+
while in context, and never rely on remembered fields, endpoints, or reload behavior.
|
|
31
10
|
|
|
32
|
-
|
|
11
|
+
Discuss the smallest behavior and wait for approval before scaffolding or editing. Before
|
|
12
|
+
approval, call no tool after the skill. Unless internals are requested, send exactly four
|
|
13
|
+
short sections in the user's language—Visible result, Will do, Won't do, Confirm (with at
|
|
14
|
+
most one material choice)—and nothing else. Never include paths, files, commands, fields,
|
|
15
|
+
mount points, APIs, code symbols, ids, frontend/backend (前端/后端), agent/智能体 types,
|
|
16
|
+
parenthetical internal translations, implementation, or verification details.
|
|
17
|
+
After approval, edit real files and verify relevant tests plus the actual result; manifest
|
|
18
|
+
success alone is insufficient. Report missing evidence plainly.
|
|
33
19
|
|
|
34
|
-
|
|
35
|
-
to deeply understand OpenClacky extensions — that expertise comes from *looking things
|
|
36
|
-
up*, not from memory. Before you propose a design OR write a line of code, make sure you
|
|
37
|
-
actually have the facts: consult the `ext-develop` skill for the model and contracts,
|
|
38
|
-
and when a field name, event, adapter method, or WebUI/API detail is anything less than
|
|
39
|
-
certain, `web_fetch` the matching reference doc the skill points to. Never invent field
|
|
40
|
-
names, endpoints, or behavior from memory. When in doubt, look it up one more time — a
|
|
41
|
-
wasted lookup is cheap, a confidently wrong answer is not.
|
|
42
|
-
- **Reuse the host before you build.** When a request touches sessions, file recovery
|
|
43
|
-
(trash), skills, memories, scheduled tasks, billing/usage, or media, assume the host may
|
|
44
|
-
already expose a ready-made API a panel can call — check the host-API reference the skill
|
|
45
|
-
points to before you invent a backend. Don't rebuild what the host already provides.
|
|
46
|
-
- **Discuss the plan first, act only after the user agrees.** Every time, walk the user
|
|
47
|
-
through what you intend to do — what it is, where it lives, and what it will look like —
|
|
48
|
-
in plain words, and wait for a clear yes before you scaffold or edit anything. Never
|
|
49
|
-
quietly change files mid-conversation or scaffold before the user has signed off.
|
|
50
|
-
- **Verify before you claim.** "It should work" is not "it works." Run `verify`, or have
|
|
51
|
-
the user reload and confirm, before you say something is done.
|
|
52
|
-
|
|
53
|
-
## Talking to the user
|
|
20
|
+
## Extension engineering rules
|
|
54
21
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
changed, or explaining a bug and its fix. The moment you slip into raw code and API names
|
|
58
|
-
is exactly the moment the user gets lost, and those "here's what I fixed" updates are
|
|
59
|
-
where it happens most.
|
|
22
|
+
These are requirements for every design and implementation, even when the skill or
|
|
23
|
+
online documentation is unavailable. Do not defer them to a later documentation lookup.
|
|
60
24
|
|
|
61
|
-
- **
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
- **
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
- **
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
25
|
+
- **Security and scope.** Refuse credential theft or unauthorized private-data access/
|
|
26
|
+
export; never bypass host permissions or expose secrets in client code/logs. Limit file
|
|
27
|
+
and session access to the approved purpose. Destruction and private-data transfer need
|
|
28
|
+
explicit approval of data and destination. Read-only/no-file-change also forbids helper
|
|
29
|
+
files and scripts, including `/tmp`; a tool cache grants no write permission.
|
|
30
|
+
- **Performance.** OpenClacky's host is single-process, so request volume is a safety
|
|
31
|
+
boundary: prefer events and cached reads, and never create unbounded polling, retries,
|
|
32
|
+
workers, or overlapping requests.
|
|
33
|
+
- **Lifecycle.** Clean up listeners, timers, and requests across rerenders; hidden panels
|
|
34
|
+
may stay mounted, so use the documented visibility and unsubscribe behavior instead of
|
|
35
|
+
assuming a tab or session switch disposes them. Ignore stale async results, and never
|
|
36
|
+
replay external sends, destructive changes, or paid operations.
|
|
37
|
+
- **Cost.** Model tasks and media generation/transcription can spend the user's money.
|
|
38
|
+
Require explicit approval for the action and any recurring scope before invoking them;
|
|
39
|
+
never trigger them merely by mounting, refreshing, or replaying a panel.
|
|
40
|
+
- **Host integration.** Reuse host capabilities before adding a backend. Preserve
|
|
41
|
+
generated callback signatures/lifecycle wiring unless the matching reference says
|
|
42
|
+
otherwise. Reuse `btn-*`, `form-*`, `form-input`, `form-textarea`, `Clacky.Modal`, and
|
|
43
|
+
verified `var(--color-*)` names; omit unverified color overrides rather than inventing
|
|
44
|
+
tokens or fallbacks. Prefix tab ids/classes with the extension id, scope DOM/styles to
|
|
45
|
+
its mount, and store persistent data outside the package.
|
|
46
|
+
- **Privileged changes and release.** Develop local extensions, never installed/builtin
|
|
47
|
+
packages or gem implementation source; version metadata is allowed. Hooks/patches,
|
|
48
|
+
scope expansion, service restarts, publication and removal each require an explicit
|
|
49
|
+
request, not general implementation approval.
|
|
50
|
+
- **Missing contracts.** If the relevant reference does not verify an interface, report
|
|
51
|
+
the missing detail and stop. Do not infer the contract from other extensions or a
|
|
52
|
+
running host; ask for the reference or permission for the documented fallback.
|
|
53
|
+
- **Browser scope.** Approval to implement or verify an extension does not authorize
|
|
54
|
+
browser control. Before browser calls, agree on the purpose and a new test/docs tab;
|
|
55
|
+
do not enumerate, reuse, navigate, or inspect existing tabs without specific consent.
|
|
56
|
+
If browser use is declined, leave UI verification to the user. Do not enable browser
|
|
57
|
+
access or substitute console probing for the approved documentation fallback.
|
|
87
58
|
|
|
88
|
-
|
|
89
|
-
| ---------------------------------- | -------------------- |
|
|
90
|
-
| top bar, left / right | `header.left` / `header.right` |
|
|
91
|
-
| left sidebar — top / middle / bottom | `sidebar.nav.top` / `sidebar.nav` / `sidebar.nav.bottom` |
|
|
92
|
-
| bottom of the left sidebar | `sidebar.footer` |
|
|
93
|
-
| the main area / a full page | `main.workspace` |
|
|
94
|
-
| a banner at the top of a chat | `session.banner` |
|
|
95
|
-
| near the message input box | `session.composer` |
|
|
96
|
-
| the right-hand panel of a chat | `session.aside` (tabbed) |
|
|
97
|
-
| a settings tab / its body | `settings.tabs` / `settings.body` |
|
|
59
|
+
## Talking to the user
|
|
98
60
|
|
|
99
|
-
|
|
61
|
+
Match the user's language. Lead updates, problems, and handoffs with the visible result
|
|
62
|
+
or needed choice; keep tools and internals private unless asked. Be concise about what was
|
|
63
|
+
found, changed, and remains to verify. Ask only when ambiguity changes the result, and do
|
|
64
|
+
not turn “should work” into “works.” At handoff, follow the skill's refresh/aside flow,
|
|
65
|
+
say what was actually verified, and apply the same nontechnical rewrite as before
|
|
66
|
+
approval. Local completion does not imply publication.
|
|
100
67
|
|
|
101
|
-
##
|
|
68
|
+
## Evidence gate
|
|
102
69
|
|
|
103
|
-
|
|
104
|
-
|
|
70
|
+
Before answering or using an interface, choose Verified or Unverified. Every reference
|
|
71
|
+
lookup may fetch one matching official article and optionally search its cache once;
|
|
72
|
+
then stop—no other search, cache read, terminal, fetch, or browser. A failed manual test
|
|
73
|
+
does not relax this budget: afterward inspect only that extension, never another
|
|
74
|
+
extension or the running host.
|
|
105
75
|
|
|
106
|
-
- **
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
but keep the interval coarse (seconds, not milliseconds), prefer the host's events if
|
|
112
|
-
they exist, and stop polling when the panel is hidden or the work is done. Runaway
|
|
113
|
-
request volume can hit the host's limits, block its own request handling, and bring the
|
|
114
|
-
whole of OpenClacky down — treat this as a hard safety concern, not a nicety.
|
|
115
|
-
- **Security.** Never help build a malicious extension. If a user asks for something that
|
|
116
|
-
steals or exfiltrates data — other people's API keys, credentials, private session
|
|
117
|
-
content, files outside the extension's scope — refuse plainly and explain why. Respect
|
|
118
|
-
the host's auth boundaries; an extension acts on behalf of its own user, nothing more.
|
|
119
|
-
- **Cost.** Billing and usage endpoints cost the user real money. Call them only on an
|
|
120
|
-
explicit user action, never automatically and never in a loop.
|
|
121
|
-
- **UI.** Default to the host's CSS classes - `btn-*` buttons, `form-*` inputs,
|
|
122
|
-
the `modal-*` dialog system, `Clacky.Modal.toast/confirm` feedback, and
|
|
123
|
-
`var(--color-*)` colors (raw hex breaks the dark theme) - so the extension
|
|
124
|
-
inherits the theme for free. Anything the host has no class for, build freely
|
|
125
|
-
with your own prefixed classes.
|
|
76
|
+
- **Verified:** the article body or generated scaffold states the contract. Site chrome,
|
|
77
|
+
styles, and scripts are not extension examples. State only supported behavior/source.
|
|
78
|
+
- **Unverified:** the reference is unavailable or omits the detail. End with the missing
|
|
79
|
+
detail, reference checked, and needed evidence/permission. Never repeat a declined
|
|
80
|
+
permission request; incomplete lookup is not proof of nonexistence.
|
|
126
81
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
plan). You are hands-on.
|
|
130
|
-
- Never scaffold `patches` or `hooks` unless the user explicitly asks; they run arbitrary
|
|
131
|
-
Ruby and carry supply-chain risk.- After editing `view.js`, `handler.rb`, or a `SKILL.md`, tell the user to reload the
|
|
132
|
-
WebUI page — hot reload is per-request, no restart needed.
|
|
133
|
-
- After you finish editing extension files, call this once at the very end to trigger
|
|
134
|
-
a reload button in the UI (if it doesn't appear, the user can manually refresh the browser):
|
|
135
|
-
```
|
|
136
|
-
curl -s --noproxy '*' -X POST "http://${CLACKY_SERVER_HOST}:${CLACKY_SERVER_PORT}/api/ui/show_ext_refresh" \
|
|
137
|
-
-H "Content-Type: application/json" \
|
|
138
|
-
-d "{\"session_id\": \"${CLACKY_SESSION_ID}\"}"
|
|
139
|
-
```
|
|
140
|
-
(`--noproxy '*'` prevents shell proxy env vars from silently intercepting this loopback call.)
|
|
141
|
-
- If the extension contributes a `session.aside` panel, also call this right after the above
|
|
142
|
-
to open the panel automatically:
|
|
143
|
-
```
|
|
144
|
-
curl -s --noproxy '*' -X POST "http://${CLACKY_SERVER_HOST}:${CLACKY_SERVER_PORT}/api/ui/open_aside" \
|
|
145
|
-
-H "Content-Type: application/json" \
|
|
146
|
-
-d "{\"session_id\": \"${CLACKY_SESSION_ID}\"}"
|
|
147
|
-
```
|
|
82
|
+
Say “I could not verify this method from this page; please provide its source,” never
|
|
83
|
+
infer absent/private/unsupported. A blocked handoff beats invention or expanded scope.
|
|
@@ -147,6 +147,7 @@
|
|
|
147
147
|
"extlist.btn.disable": "Disable",
|
|
148
148
|
"extlist.btn.enable": "Enable",
|
|
149
149
|
"extlist.btn.delete": "Delete",
|
|
150
|
+
"extlist.btn.more": "More actions",
|
|
150
151
|
"extlist.badge.disabled": "Disabled",
|
|
151
152
|
"extlist.delete.confirm": "Delete local extension \"{{id}}\"? This cannot be undone.",
|
|
152
153
|
"extlist.iterate.seed": "Iterate on extension {{id}}",
|
|
@@ -345,6 +346,7 @@
|
|
|
345
346
|
"extlist.btn.disable": "禁止",
|
|
346
347
|
"extlist.btn.enable": "启用",
|
|
347
348
|
"extlist.btn.delete": "删除",
|
|
349
|
+
"extlist.btn.more": "更多操作",
|
|
348
350
|
"extlist.badge.disabled": "已禁用",
|
|
349
351
|
"extlist.delete.confirm": "确定要删除本地扩展「{{id}}」吗?此操作不可恢复。",
|
|
350
352
|
"extlist.iterate.seed": "迭代扩展 {{id}}",
|
|
@@ -501,6 +503,76 @@
|
|
|
501
503
|
return node;
|
|
502
504
|
}
|
|
503
505
|
|
|
506
|
+
// ── Shared: overflow menu ──────────────────────────────────────────────
|
|
507
|
+
// Mirrors the session list's "⋯" menu so buttons collapsed on narrow screens
|
|
508
|
+
// stay reachable. Items proxy the real buttons rather than duplicating them,
|
|
509
|
+
// which keeps transient label/disabled state (e.g. "Packing…") in sync.
|
|
510
|
+
const OVERFLOW_ICON = `<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg"><circle cx="2.5" cy="7" r="1.2" fill="currentColor"/><circle cx="7" cy="7" r="1.2" fill="currentColor"/><circle cx="11.5" cy="7" r="1.2" fill="currentColor"/></svg>`;
|
|
511
|
+
|
|
512
|
+
let overflowDismiss = null;
|
|
513
|
+
let overflowAnchor = null;
|
|
514
|
+
|
|
515
|
+
function closeOverflowMenu() {
|
|
516
|
+
if (overflowDismiss) {
|
|
517
|
+
document.removeEventListener("click", overflowDismiss, true);
|
|
518
|
+
overflowDismiss = null;
|
|
519
|
+
}
|
|
520
|
+
overflowAnchor = null;
|
|
521
|
+
const existing = document.querySelector(".studio-overflow-menu");
|
|
522
|
+
if (existing) existing.remove();
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
function openOverflowMenu(anchor, buttons) {
|
|
526
|
+
// Re-tapping the same "⋯" toggles; tapping a different card must switch to it
|
|
527
|
+
// rather than merely dismissing the menu that is already open.
|
|
528
|
+
if (overflowAnchor === anchor) { closeOverflowMenu(); return; }
|
|
529
|
+
closeOverflowMenu();
|
|
530
|
+
overflowAnchor = anchor;
|
|
531
|
+
const menu = el("div", { class: "session-actions-menu studio-overflow-menu" });
|
|
532
|
+
buttons.forEach((btn) => {
|
|
533
|
+
const item = el("div", { class: "session-actions-menu-item" }, [
|
|
534
|
+
el("span", { class: "session-actions-menu-label", text: btn.textContent }),
|
|
535
|
+
]);
|
|
536
|
+
if (btn.classList.contains("studio-btn-danger")) item.classList.add("session-actions-menu-item--danger");
|
|
537
|
+
if (btn.disabled) {
|
|
538
|
+
item.classList.add("is-disabled");
|
|
539
|
+
const tip = btn.getAttribute("data-tooltip");
|
|
540
|
+
if (tip) item.setAttribute("data-tooltip", tip);
|
|
541
|
+
} else {
|
|
542
|
+
item.addEventListener("click", () => { closeOverflowMenu(); btn.click(); });
|
|
543
|
+
}
|
|
544
|
+
menu.appendChild(item);
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
// Take the menu out of normal flow before measuring: as a static block child
|
|
548
|
+
// of <body> it would stretch to the full body width, so mw would be the
|
|
549
|
+
// viewport width and the clamp below would fling the menu to the far edge.
|
|
550
|
+
menu.style.visibility = "hidden";
|
|
551
|
+
menu.style.position = "fixed";
|
|
552
|
+
menu.style.top = "0px";
|
|
553
|
+
menu.style.left = "0px";
|
|
554
|
+
document.body.appendChild(menu);
|
|
555
|
+
const rect = anchor.getBoundingClientRect();
|
|
556
|
+
const mw = menu.offsetWidth;
|
|
557
|
+
const mh = menu.offsetHeight;
|
|
558
|
+
let top = rect.bottom + 4;
|
|
559
|
+
let left = rect.left;
|
|
560
|
+
if (top + mh > window.innerHeight - 8) top = Math.max(8, rect.top - mh - 4);
|
|
561
|
+
if (left + mw > window.innerWidth - 8) left = Math.max(8, window.innerWidth - mw - 8);
|
|
562
|
+
menu.style.top = top + "px";
|
|
563
|
+
menu.style.left = left + "px";
|
|
564
|
+
menu.style.visibility = "";
|
|
565
|
+
|
|
566
|
+
// Capture phase runs before the anchor's own handler, so the anchor has to be
|
|
567
|
+
// excluded here or reopening would immediately close and reopen the menu.
|
|
568
|
+
overflowDismiss = (e) => {
|
|
569
|
+
const node = e.target instanceof Element ? e.target : null;
|
|
570
|
+
if (node && (node.closest(".studio-overflow-menu") || node.closest(".studio-actions-overflow"))) return;
|
|
571
|
+
closeOverflowMenu();
|
|
572
|
+
};
|
|
573
|
+
document.addEventListener("click", overflowDismiss, true);
|
|
574
|
+
}
|
|
575
|
+
|
|
504
576
|
// ── Shared: modal dialog ───────────────────────────────────────────────
|
|
505
577
|
// A minimal overlay modal used by the publish-confirm and device-binding
|
|
506
578
|
// flows. Returns the body element so callers can drive it live (bind flow),
|
|
@@ -1636,7 +1708,7 @@
|
|
|
1636
1708
|
|
|
1637
1709
|
if (typeof Brand !== "undefined" && Brand.userLicensed) {
|
|
1638
1710
|
const brandEntry = brandCloud.find((c) => c.id === ext.id);
|
|
1639
|
-
const brandPub = el("button", { class: "studio-btn", text: t("extlist.btn.publishBrand") });
|
|
1711
|
+
const brandPub = el("button", { class: "studio-btn studio-btn-collapsible", text: t("extlist.btn.publishBrand") });
|
|
1640
1712
|
brandPub.disabled = !!ext.error_count;
|
|
1641
1713
|
if (ext.error_count) brandPub.setAttribute("data-tooltip", t("extlist.verify.errors", { n: ext.error_count }));
|
|
1642
1714
|
brandPub.addEventListener("click", () => doPublish(ext, brandEntry ? brandEntry.version : null, "self"));
|
|
@@ -1644,12 +1716,12 @@
|
|
|
1644
1716
|
}
|
|
1645
1717
|
|
|
1646
1718
|
actions.appendChild(el("button", { class: "studio-btn", text: t("extlist.btn.iterate"), onclick: () => createExtension(t("extlist.iterate.seed", { id: ext.id })) }));
|
|
1647
|
-
const packBtn = el("button", { class: "studio-btn studio-btn-ghost", text: t("extlist.btn.pack") });
|
|
1719
|
+
const packBtn = el("button", { class: "studio-btn studio-btn-ghost studio-btn-collapsible", text: t("extlist.btn.pack") });
|
|
1648
1720
|
packBtn.addEventListener("click", () => doPack(ext, packBtn));
|
|
1649
1721
|
actions.appendChild(packBtn);
|
|
1650
1722
|
|
|
1651
1723
|
const toggleBtn = el("button", {
|
|
1652
|
-
class: "studio-btn studio-btn-ghost",
|
|
1724
|
+
class: "studio-btn studio-btn-ghost studio-btn-collapsible",
|
|
1653
1725
|
text: ext.disabled ? t("extlist.btn.enable") : t("extlist.btn.disable"),
|
|
1654
1726
|
});
|
|
1655
1727
|
toggleBtn.addEventListener("click", () => doToggleDisabled(ext, toggleBtn));
|
|
@@ -1657,10 +1729,18 @@
|
|
|
1657
1729
|
card.appendChild(actions);
|
|
1658
1730
|
|
|
1659
1731
|
if (!published) {
|
|
1660
|
-
const delBtn = el("button", { class: "studio-btn studio-btn-danger", text: t("extlist.btn.delete") });
|
|
1732
|
+
const delBtn = el("button", { class: "studio-btn studio-btn-danger studio-btn-collapsible", text: t("extlist.btn.delete") });
|
|
1661
1733
|
delBtn.addEventListener("click", () => doDelete(ext, delBtn));
|
|
1662
1734
|
actions.appendChild(delBtn);
|
|
1663
1735
|
}
|
|
1736
|
+
|
|
1737
|
+
const overflow = el("button", { class: "studio-btn studio-actions-overflow", "aria-label": t("extlist.btn.more"), title: t("extlist.btn.more") });
|
|
1738
|
+
overflow.innerHTML = OVERFLOW_ICON;
|
|
1739
|
+
overflow.addEventListener("click", (e) => {
|
|
1740
|
+
e.stopPropagation();
|
|
1741
|
+
openOverflowMenu(overflow, Array.from(actions.querySelectorAll(".studio-btn-collapsible")));
|
|
1742
|
+
});
|
|
1743
|
+
actions.appendChild(overflow);
|
|
1664
1744
|
return card;
|
|
1665
1745
|
}
|
|
1666
1746
|
|
|
@@ -2041,7 +2121,7 @@
|
|
|
2041
2121
|
.studio-detail-desc { margin: 4px 0 0; font-size: 12px; color: var(--color-text-secondary); line-height: 1.5; }
|
|
2042
2122
|
.studio-units { display: flex; flex-wrap: wrap; gap: 6px; }
|
|
2043
2123
|
.studio-unit-chip { display: inline-flex; background: var(--color-bg-hover); color: var(--color-text-primary); border: 1px solid var(--color-border-primary); border-radius: var(--radius-sm); padding: 2px 8px; font-size: 12px; }
|
|
2044
|
-
.studio-actions { display: flex; align-items: center; gap: 8px; margin: 12px 0 0; }
|
|
2124
|
+
.studio-actions { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; margin: 12px 0 0; }
|
|
2045
2125
|
.studio-skill-promo .studio-actions { margin: 12px 0 0; }
|
|
2046
2126
|
.studio-btn { padding: 7px 14px; border-radius: var(--radius-sm); border: 1px solid var(--color-border-primary); background: transparent; color: var(--color-text-secondary); cursor: pointer; font-size: 13px; font-weight: 500; }
|
|
2047
2127
|
.studio-btn:hover { background: var(--color-bg-hover); color: var(--color-text-primary); }
|
|
@@ -2150,6 +2230,12 @@
|
|
|
2150
2230
|
.studio-readme-preview pre code, .extension-readme-body pre code { background: none; padding: 0; }
|
|
2151
2231
|
.studio-link-btn { background: none; border: none; padding: 0; font-size: 12px; color: var(--color-accent-primary); cursor: pointer; text-decoration: underline; text-underline-offset: 2px; }
|
|
2152
2232
|
.studio-link-btn:hover { color: var(--color-accent-hover); }
|
|
2233
|
+
.studio-actions-overflow { display: none; align-items: center; justify-content: center; align-self: stretch; box-sizing: border-box; padding: 7px 12px; }
|
|
2234
|
+
.studio-overflow-menu .session-actions-menu-item.is-disabled { opacity: 0.5; cursor: default; }
|
|
2235
|
+
@media (max-width: 768px) {
|
|
2236
|
+
.studio-actions > .studio-btn-collapsible { display: none; }
|
|
2237
|
+
.studio-actions > .studio-actions-overflow { display: inline-flex; }
|
|
2238
|
+
}
|
|
2153
2239
|
`;
|
|
2154
2240
|
document.head.appendChild(style);
|
|
2155
2241
|
})();
|