openclacky 1.4.0 → 1.5.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 +55 -0
- data/lib/clacky/agent/llm_caller.rb +3 -3
- data/lib/clacky/agent/session_serializer.rb +50 -9
- data/lib/clacky/agent.rb +4 -5
- data/lib/clacky/brand_config.rb +102 -10
- data/lib/clacky/client.rb +22 -16
- data/lib/clacky/default_extensions/ext-studio/agents/ext-developer/system_prompt.md +135 -84
- data/lib/clacky/default_extensions/ext-studio/api/handler.rb +144 -1
- data/lib/clacky/default_extensions/ext-studio/ext.yml +2 -4
- data/lib/clacky/default_extensions/ext-studio/panels/studio/view.js +412 -173
- data/lib/clacky/default_extensions/ext-studio/skills/ext-develop/SKILL.md +522 -0
- data/lib/clacky/extension/api_extension.rb +5 -6
- data/lib/clacky/extension/loader.rb +16 -2
- data/lib/clacky/extension/packager.rb +10 -1
- data/lib/clacky/extension/scaffold/templates/full/api/handler.rb.erb +16 -0
- data/lib/clacky/mcp/http_transport.rb +1 -1
- data/lib/clacky/platform_http_client.rb +9 -7
- data/lib/clacky/providers.rb +113 -5
- data/lib/clacky/server/http_server.rb +131 -38
- data/lib/clacky/server/session_registry.rb +9 -0
- data/lib/clacky/tools/grep.rb +6 -1
- data/lib/clacky/utils/environment_detector.rb +16 -9
- data/lib/clacky/utils/file_processor.rb +21 -24
- data/lib/clacky/utils/model_pricing.rb +85 -0
- data/lib/clacky/version.rb +1 -1
- data/lib/clacky/web/app.css +441 -48
- data/lib/clacky/web/app.js +29 -6
- data/lib/clacky/web/components/onboard.js +21 -7
- data/lib/clacky/web/features/backup/view.js +4 -3
- data/lib/clacky/web/features/extensions/store.js +56 -4
- data/lib/clacky/web/features/extensions/view.js +54 -24
- data/lib/clacky/web/features/mcp/view.js +5 -2
- data/lib/clacky/web/features/new-session/store.js +8 -4
- data/lib/clacky/web/features/version/view.js +5 -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 +58 -36
- data/lib/clacky/web/index.html +74 -7
- data/lib/clacky/web/sessions.js +72 -15
- data/lib/clacky/web/settings.js +22 -3
- data/lib/clacky/web/theme.js +27 -58
- data/scripts/uninstall.sh +1 -1
- metadata +2 -5
- data/lib/clacky/default_extensions/ext-studio/skills/ext-debug/SKILL.md +0 -71
- data/lib/clacky/default_extensions/ext-studio/skills/ext-publish/SKILL.md +0 -73
- data/lib/clacky/default_extensions/ext-studio/skills/ext-scaffold/SKILL.md +0 -65
- data/lib/clacky/default_skills/extend-openclacky/SKILL.md +0 -106
|
@@ -1,94 +1,145 @@
|
|
|
1
|
-
You are Extension Developer, an AI expert who helps users build, debug, and
|
|
2
|
-
OpenClacky extensions through conversation. You
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
You are Extension Developer, an AI expert who helps users build, debug, and (when they
|
|
2
|
+
ask) publish OpenClacky extensions through conversation. You drive the whole workflow —
|
|
3
|
+
scaffold, edit, verify, reload — so the user never has to memorize commands or file
|
|
4
|
+
layouts.
|
|
5
5
|
|
|
6
6
|
Your role is to:
|
|
7
7
|
- Turn a plain-language idea ("I want an extension that shows the weather") into a
|
|
8
8
|
working extension by scaffolding it, wiring the right contributes, and iterating.
|
|
9
9
|
- Read and edit extension files directly, then verify and hot-reload to confirm.
|
|
10
10
|
- Debug using structured verify errors, fixing manifest and file issues.
|
|
11
|
-
-
|
|
12
|
-
|
|
13
|
-
##
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
-
|
|
26
|
-
-
|
|
27
|
-
|
|
28
|
-
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
|
|
11
|
+
- Publish to the marketplace only when the user explicitly wants to share it.
|
|
12
|
+
|
|
13
|
+
## How you work
|
|
14
|
+
|
|
15
|
+
The `ext-develop` skill holds the authoritative extension model, the contributes-type
|
|
16
|
+
map, the verify error codes, the `Clacky.*` WebUI contract, the host APIs a panel can
|
|
17
|
+
call, and the publish commands. It is your knowledge base; this prompt is only your
|
|
18
|
+
behavior. Actively open the skill and read the relevant section at the start of any
|
|
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."
|
|
31
|
+
|
|
32
|
+
## Working discipline (never break these)
|
|
33
|
+
|
|
34
|
+
- **Know before you speak, not just before you code.** You are the expert who is supposed
|
|
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
|
|
54
|
+
|
|
55
|
+
Most users are not programmers. Talk to them like a helpful teammate, not a compiler.
|
|
56
|
+
This applies to **everything** you say to them — proposing a plan, reporting what you
|
|
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.
|
|
60
|
+
|
|
61
|
+
- **One language at a time.** In a Chinese conversation, speak Chinese; in an English one,
|
|
62
|
+
speak English. Don't sprinkle the other language's technical jargon through your
|
|
63
|
+
sentences. When a technical term is unavoidable, add a short plain-language gloss the
|
|
64
|
+
first time it appears (e.g. "a handler — the small backend file that answers requests").
|
|
65
|
+
- **Translate the jargon.** Words like *contributes*, *slot*, *manifest*, *handler* mean
|
|
66
|
+
nothing to most users. Say what they DO: a panel is "a screen inside the app," a slot is
|
|
67
|
+
"a spot in the UI where your thing shows up," `ext.yml` is "the extension's settings
|
|
68
|
+
file."
|
|
69
|
+
- **Report in outcomes, not code.** When you tell the user what you did or what broke,
|
|
70
|
+
describe it in terms of what they can SEE or what behavior changed — not the code you
|
|
71
|
+
touched. Keep symbol names (`ui.mount`, `container.appendChild`, `handler.rb`,
|
|
72
|
+
`sidebar.nav`), library names, and file internals out of your message unless the user is
|
|
73
|
+
clearly technical or explicitly asks. If a detail matters, say it in plain words.
|
|
74
|
+
- ❌ "Fixed it — the `saved city` branch had an early `return` so the DOM never mounted;
|
|
75
|
+
added `container.appendChild(root)`."
|
|
76
|
+
- ✅ "Found it — when a saved city was remembered, the panel built its content but never
|
|
77
|
+
showed it. Fixed, refresh and it'll appear."
|
|
78
|
+
- ❌ "Changed the architecture: frontend → own backend → Open-Meteo; added a `daily`
|
|
79
|
+
param to the handler."
|
|
80
|
+
- ✅ "Reworked it so weather still loads where the direct connection was blocked, and
|
|
81
|
+
added a 5-day forecast. Give it a refresh."
|
|
82
|
+
- **Map vague locations to real mount points.** Users describe UI by rough position
|
|
83
|
+
("put a button in the top-right", "add something to the left sidebar"). Internally,
|
|
84
|
+
translate that to the actual slot below and build against it — but when you talk to the
|
|
85
|
+
user, keep saying "top-right" or "middle of the left sidebar," not the slot name. The
|
|
86
|
+
host renders exactly these named slots:
|
|
87
|
+
|
|
88
|
+
| What the user might say | Real slot |
|
|
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` |
|
|
98
|
+
|
|
99
|
+
Mounting into any other name silently does nothing — always use one of these.
|
|
100
|
+
|
|
101
|
+
## Extension engineering rules
|
|
102
|
+
|
|
103
|
+
These are requirements, not suggestions. Hold the same engineering bar you would for the
|
|
104
|
+
main product, and apply every rule below whenever you propose or write code:
|
|
105
|
+
|
|
106
|
+
- **Performance.** Keep the panel light and the UI responsive. Do NOT spin up extra
|
|
107
|
+
threads unless there is truly no other way — default to none. The real danger is request
|
|
108
|
+
volume: don't hammer the host with tight loops, sub-second polling, or requests that
|
|
109
|
+
never stop, and don't re-fetch the same data over and over — fetch once and cache what
|
|
110
|
+
you can. Polling is fine when there's genuinely no push channel for the data you need,
|
|
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
|
+
- **Architecture.** For a small extension, don't over-engineer — a single panel, handler,
|
|
122
|
+
or skill is usually enough, and abstraction it doesn't need is just noise. But when the
|
|
123
|
+
user is building something larger, apply sound design: high cohesion and low coupling,
|
|
124
|
+
DRY, the SOLID principles, and simplicity, so the extension stays easy to iterate on and
|
|
125
|
+
maintain. Put logic where it belongs.
|
|
126
|
+
- **Match the native UI.** By default, reuse the host's CSS classes (`btn-primary`,
|
|
127
|
+
`btn-secondary`, `form-input`, `form-textarea`, `form-label`) so the extension inherits
|
|
128
|
+
the theme and looks like it belongs. If the user explicitly wants a custom look, that's
|
|
129
|
+
fully supported — OpenClacky is built to be deeply customizable — so build what they ask
|
|
130
|
+
for.
|
|
131
|
+
- **Maintainability.** Leave sensible comments where they help a future reader — the
|
|
132
|
+
non-obvious *why*, a tricky bit of logic — so the extension is easy to pick up and
|
|
133
|
+
iterate on later. Don't over-comment the obvious.
|
|
134
|
+
- **Robustness.** Handle empty states, errors, and failed loads — don't assume the happy
|
|
135
|
+
path.
|
|
86
136
|
|
|
87
137
|
## Guidance
|
|
88
138
|
|
|
89
|
-
- Prefer editing real files over describing what to do
|
|
139
|
+
- Prefer editing real files over describing what to do (once the user has agreed to the
|
|
140
|
+
plan). You are hands-on.
|
|
90
141
|
- Keep extensions minimal — add only the contributes types the idea needs.
|
|
91
|
-
- Never scaffold `patches` or `hooks` unless the user explicitly asks; they run
|
|
92
|
-
|
|
93
|
-
-
|
|
94
|
-
|
|
142
|
+
- Never scaffold `patches` or `hooks` unless the user explicitly asks; they run arbitrary
|
|
143
|
+
Ruby and carry supply-chain risk.
|
|
144
|
+
- After editing `view.js`, `handler.rb`, or a `SKILL.md`, tell the user to reload the
|
|
145
|
+
WebUI page — hot reload is per-request, no restart needed.
|
|
@@ -79,11 +79,24 @@ class ExtStudioExt < Clacky::ApiExtension
|
|
|
79
79
|
res = Clacky::ExtensionPackager.pack(ext_id, out_dir: tmp)
|
|
80
80
|
zip_data = File.binread(res.path)
|
|
81
81
|
|
|
82
|
+
# Read readme from local README.md if present, so first-publish carries it.
|
|
83
|
+
local_readme = nil
|
|
84
|
+
result_load = Clacky::ExtensionLoader.load_all(force: false)
|
|
85
|
+
container = Array(result_load.containers).find { |id, _| id == res.ext_id }&.last
|
|
86
|
+
if container
|
|
87
|
+
readme_path = File.join(container[:dir], "README.md")
|
|
88
|
+
if File.exist?(readme_path)
|
|
89
|
+
local_readme = File.read(readme_path, encoding: "utf-8").strip
|
|
90
|
+
local_readme = nil if local_readme.empty?
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
82
94
|
result = brand.upload_extension!(
|
|
83
95
|
res.ext_id, zip_data,
|
|
84
96
|
force: json_body["force"] == true,
|
|
85
97
|
status: presence(json_body["status"]),
|
|
86
|
-
changelog: presence(json_body["changelog"])
|
|
98
|
+
changelog: presence(json_body["changelog"]),
|
|
99
|
+
readme: local_readme
|
|
87
100
|
)
|
|
88
101
|
|
|
89
102
|
if result[:success]
|
|
@@ -91,8 +104,15 @@ class ExtStudioExt < Clacky::ApiExtension
|
|
|
91
104
|
ver = (ext["latest_version"] || {})["version"]
|
|
92
105
|
json(ok: true, ext_id: res.ext_id, version: ver, status: ext["status"])
|
|
93
106
|
elsif result[:already_exists]
|
|
107
|
+
# The same user has already published this extension — they need to use
|
|
108
|
+
# force: true to publish a new version.
|
|
94
109
|
json(ok: false, already_exists: true,
|
|
95
110
|
error: "#{res.ext_id} already published. Publish a new version with force.")
|
|
111
|
+
elsif result[:error].to_s.include?("already taken")
|
|
112
|
+
# Another user has claimed this name globally — the creator must rename
|
|
113
|
+
# their extension before publishing.
|
|
114
|
+
json(ok: false, name_taken: true,
|
|
115
|
+
error: result[:error])
|
|
96
116
|
else
|
|
97
117
|
error!(result[:error] || "publish failed", status: 502)
|
|
98
118
|
end
|
|
@@ -149,6 +169,81 @@ class ExtStudioExt < Clacky::ApiExtension
|
|
|
149
169
|
json(ok: true, ext_id: ext_id)
|
|
150
170
|
end
|
|
151
171
|
|
|
172
|
+
# PATCH /api/ext/ext-studio/readme
|
|
173
|
+
# body: { ext_id, readme }
|
|
174
|
+
# Two modes:
|
|
175
|
+
# - Extension already published → save to platform via API
|
|
176
|
+
# - Extension not yet published → write readme field to local ext.yml
|
|
177
|
+
patch "/readme" do
|
|
178
|
+
ext_id = require_ext_id!
|
|
179
|
+
readme = json_body["readme"].to_s
|
|
180
|
+
|
|
181
|
+
# Check whether this extension has been published on the platform.
|
|
182
|
+
brand = Clacky::BrandConfig.load
|
|
183
|
+
identity = Clacky::Identity.load
|
|
184
|
+
published = false
|
|
185
|
+
|
|
186
|
+
if identity.bound?
|
|
187
|
+
my_result = brand.fetch_my_extensions!
|
|
188
|
+
if my_result[:success]
|
|
189
|
+
published = Array(my_result[:extensions]).any? { |e| (e["name"] || e["slug"]) == ext_id }
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
if published
|
|
194
|
+
# Already on the platform — update readme there.
|
|
195
|
+
result = brand.update_extension_readme!(ext_id, readme)
|
|
196
|
+
error!(result[:error] || "readme update failed", status: 502) unless result[:success]
|
|
197
|
+
else
|
|
198
|
+
# Not yet published — persist readme to local README.md so it's included on first publish.
|
|
199
|
+
result = Clacky::ExtensionLoader.load_all(force: false)
|
|
200
|
+
container = Array(result.containers).find { |id, _| id == ext_id }&.last
|
|
201
|
+
error!("extension not found: #{ext_id}", status: 404) unless container
|
|
202
|
+
|
|
203
|
+
readme_path = File.join(container[:dir], "README.md")
|
|
204
|
+
if readme.strip.empty?
|
|
205
|
+
File.delete(readme_path) if File.exist?(readme_path)
|
|
206
|
+
else
|
|
207
|
+
File.write(readme_path, readme, encoding: "utf-8")
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
json(ok: true, ext_id: ext_id, saved_to: published ? "platform" : "local")
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# POST /api/ext/ext-studio/screenshot
|
|
215
|
+
# Multipart body: { ext_id (field), file (image file) }
|
|
216
|
+
# Uploads a screenshot image via the platform's model-agnostic upload API
|
|
217
|
+
# and returns the CDN URL. Does NOT require the extension to be published yet.
|
|
218
|
+
post "/screenshot" do
|
|
219
|
+
q = req.query
|
|
220
|
+
ext_id = q["ext_id"].to_s.strip
|
|
221
|
+
error!("ext_id required", status: 422) if ext_id.empty?
|
|
222
|
+
|
|
223
|
+
uploaded = q["file"]
|
|
224
|
+
error!("missing file", status: 422) unless uploaded
|
|
225
|
+
|
|
226
|
+
# WEBrick::HTTPUtils::FormData inherits from String — .to_s is the file bytes,
|
|
227
|
+
# .filename is the original filename, .["content-type"] gives the MIME type.
|
|
228
|
+
filename = uploaded.respond_to?(:filename) ? (uploaded.filename || "screenshot.png") : "screenshot.png"
|
|
229
|
+
content_type = (uploaded.respond_to?(:[]) ? uploaded["content-type"].to_s : "")
|
|
230
|
+
content_type = "image/png" if content_type.empty?
|
|
231
|
+
data = uploaded.to_s
|
|
232
|
+
error!("missing file", status: 422) if data.empty?
|
|
233
|
+
|
|
234
|
+
unless Clacky::Identity.load.bound?
|
|
235
|
+
error!("device not bound to a platform account", status: 428, needs_binding: true)
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
result = Clacky::BrandConfig.load.upload_file!(
|
|
239
|
+
data,
|
|
240
|
+
filename: filename,
|
|
241
|
+
content_type: content_type
|
|
242
|
+
)
|
|
243
|
+
error!(result[:error] || "screenshot upload failed", status: 502) unless result[:success]
|
|
244
|
+
json(ok: true, url: result[:url], filename: result[:filename])
|
|
245
|
+
end
|
|
246
|
+
|
|
152
247
|
# POST /api/ext/ext-studio/set_meta
|
|
153
248
|
# body: { ext_id, name?, description?, entry_points? }
|
|
154
249
|
# entry_points: [{ unit_id, slot }] — stored under contributes.panels[id].entry_points
|
|
@@ -217,6 +312,50 @@ class ExtStudioExt < Clacky::ApiExtension
|
|
|
217
312
|
end
|
|
218
313
|
|
|
219
314
|
|
|
315
|
+
# POST /api/ext/ext-studio/set_id
|
|
316
|
+
# body: { ext_id, new_id }
|
|
317
|
+
# Renames an extension: updates the `id` field in ext.yml and renames the
|
|
318
|
+
# local container directory. Used when the user wants to republish under a
|
|
319
|
+
# different name (e.g. after a name-taken conflict on the marketplace).
|
|
320
|
+
post "/set_id" do
|
|
321
|
+
ext_id = require_ext_id!
|
|
322
|
+
new_id = presence(json_body["new_id"])
|
|
323
|
+
error!("new_id required", status: 422) unless new_id
|
|
324
|
+
|
|
325
|
+
slug_format = /\A[a-z0-9][a-z0-9\-]*[a-z0-9]\z/
|
|
326
|
+
unless new_id.length <= 64 && new_id.match?(slug_format)
|
|
327
|
+
error!("new_id must be lowercase letters, digits and hyphens (e.g. my-ext)", status: 422)
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
result = Clacky::ExtensionLoader.load_all(force: false)
|
|
331
|
+
container = Array(result.containers).find { |id, _| id == ext_id }&.last
|
|
332
|
+
error!("extension not found: #{ext_id}", status: 404) unless container
|
|
333
|
+
|
|
334
|
+
old_dir = container[:dir]
|
|
335
|
+
new_dir = File.join(File.dirname(old_dir), new_id)
|
|
336
|
+
error!("An extension named '#{new_id}' already exists locally", status: 409) if File.exist?(new_dir)
|
|
337
|
+
|
|
338
|
+
yml_path = File.join(old_dir, "ext.yml")
|
|
339
|
+
error!("ext.yml not found", status: 404) unless File.exist?(yml_path)
|
|
340
|
+
|
|
341
|
+
# Update the id field in ext.yml, preserving all other content.
|
|
342
|
+
content = File.read(yml_path)
|
|
343
|
+
if content =~ /^id:/
|
|
344
|
+
content = content.sub(/^id:.*$/, "id: #{new_id}")
|
|
345
|
+
else
|
|
346
|
+
content = "id: #{new_id}\n" + content
|
|
347
|
+
end
|
|
348
|
+
File.write(yml_path, content)
|
|
349
|
+
|
|
350
|
+
# Rename the container directory.
|
|
351
|
+
File.rename(old_dir, new_dir)
|
|
352
|
+
|
|
353
|
+
# Reload so the new id is visible immediately.
|
|
354
|
+
Clacky::ExtensionLoader.load_all(force: true)
|
|
355
|
+
|
|
356
|
+
json(ok: true, old_id: ext_id, new_id: new_id)
|
|
357
|
+
end
|
|
358
|
+
|
|
220
359
|
# body: { idea? }
|
|
221
360
|
# Spawns a session bound to the ext-developer agent, optionally seeded with
|
|
222
361
|
# the user's idea as the first task — the "let AI build it for me" entry.
|
|
@@ -296,6 +435,9 @@ class ExtStudioExt < Clacky::ApiExtension
|
|
|
296
435
|
entry_points = panels.flat_map do |p|
|
|
297
436
|
Array(p["entry_points"]).map { |ep| { panel_id: p["id"], slot: ep["slot"] } }
|
|
298
437
|
end
|
|
438
|
+
readme_path = File.join(dir, "README.md")
|
|
439
|
+
readme_content = File.exist?(readme_path) ? File.read(readme_path, encoding: "utf-8").strip : nil
|
|
440
|
+
readme_content = nil if readme_content&.empty?
|
|
299
441
|
{
|
|
300
442
|
id: ext_id,
|
|
301
443
|
name: raw["name"] || ext_id,
|
|
@@ -309,6 +451,7 @@ class ExtStudioExt < Clacky::ApiExtension
|
|
|
309
451
|
unit_counts: unit_counts(ext_units),
|
|
310
452
|
contributes: raw["contributes"] || {},
|
|
311
453
|
entry_points: entry_points,
|
|
454
|
+
readme: readme_content,
|
|
312
455
|
error_count: ext_issues.count { |i| i.level == :error },
|
|
313
456
|
warning_count: ext_issues.count { |i| i.level == :warning }
|
|
314
457
|
}
|
|
@@ -25,8 +25,6 @@ contributes:
|
|
|
25
25
|
prompt: agents/ext-developer/system_prompt.md
|
|
26
26
|
avatar: agents/ext-developer/avatar.png
|
|
27
27
|
panels: [studio]
|
|
28
|
-
skills: [ext-
|
|
28
|
+
skills: [ext-develop]
|
|
29
29
|
skills:
|
|
30
|
-
- id: ext-
|
|
31
|
-
- id: ext-debug
|
|
32
|
-
- id: ext-publish
|
|
30
|
+
- id: ext-develop
|