openclacky 1.3.7 → 1.3.9
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 +42 -0
- data/lib/clacky/agent/hook_manager.rb +10 -1
- data/lib/clacky/agent/system_prompt_builder.rb +1 -1
- data/lib/clacky/agent.rb +9 -2
- data/lib/clacky/agent_profile.rb +7 -4
- data/lib/clacky/billing/billing_store.rb +3 -2
- data/lib/clacky/brand_config.rb +2 -1
- data/lib/clacky/default_extensions/coding/agents/coding/avatar.png +0 -0
- data/lib/clacky/default_extensions/coding/ext.yml +4 -4
- data/lib/clacky/default_extensions/ext-studio/agents/ext-developer/avatar.png +0 -0
- 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 +4 -3
- data/lib/clacky/default_extensions/ext-studio/ext.yml +1 -1
- data/lib/clacky/default_extensions/ext-studio/panels/studio/view.js +118 -50
- data/lib/clacky/default_extensions/ext-studio/skills/ext-publish/SKILL.md +1 -2
- data/lib/clacky/default_extensions/general/agents/general/avatar.png +0 -0
- data/lib/clacky/default_extensions/general/ext.yml +4 -4
- data/lib/clacky/default_extensions/git/ext.yml +0 -1
- data/lib/clacky/default_extensions/git/panels/git/view.js +3 -3
- data/lib/clacky/default_extensions/meeting/ext.yml +0 -1
- data/lib/clacky/default_extensions/meeting/panels/meeting/view.js +1 -2
- 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/loader.rb +6 -0
- data/lib/clacky/extension/packager.rb +0 -6
- data/lib/clacky/extension/verifier.rb +1 -1
- data/lib/clacky/server/http_server.rb +149 -5
- data/lib/clacky/shell_hook_loader.rb +266 -22
- data/lib/clacky/ui2/components/welcome_banner.rb +1 -1
- data/lib/clacky/utils/workspace_rules.rb +2 -2
- data/lib/clacky/version.rb +1 -1
- data/lib/clacky/web/app.css +334 -73
- data/lib/clacky/web/core/ext.js +130 -24
- data/lib/clacky/web/features/extensions/store.js +41 -6
- data/lib/clacky/web/features/extensions/view.js +44 -13
- data/lib/clacky/web/features/new-session/store.js +13 -0
- data/lib/clacky/web/features/new-session/view.js +314 -14
- data/lib/clacky/web/features/trash/view.js +4 -5
- data/lib/clacky/web/i18n.js +44 -2
- data/lib/clacky/web/index.html +54 -16
- data/lib/clacky/web/sessions.js +3 -3
- data/lib/clacky/web/skills.js +86 -48
- data/lib/clacky/web/theme.js +3 -0
- data/lib/clacky/web/ws-dispatcher.js +11 -2
- metadata +7 -7
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"verify.errors": "{{n}} error(s)",
|
|
30
30
|
"verify.warnings": "{{n}} warning(s)",
|
|
31
31
|
"verify.hint": "Hint",
|
|
32
|
-
"hint.reload": "After fixing, reload this page
|
|
32
|
+
"hint.reload": "After fixing, reload this page to apply changes.",
|
|
33
33
|
"publish.status": "Status",
|
|
34
34
|
"publish.status.draft": "Draft",
|
|
35
35
|
"publish.status.published": "Published",
|
|
@@ -39,9 +39,9 @@
|
|
|
39
39
|
"btn.publish": "Publish to marketplace",
|
|
40
40
|
"btn.publishing": "Publishing…",
|
|
41
41
|
"btn.pack": "Pack (.zip)",
|
|
42
|
+
"btn.packing": "Packing…",
|
|
42
43
|
"publish.needLicense": "Publishing requires an activated user license.",
|
|
43
44
|
"publish.done": "Published {{id}} {{ver}} — {{status}}",
|
|
44
|
-
"publish.packed": "Packed → {{path}}",
|
|
45
45
|
"publish.already": "Already published. Enable \"publish a new version\" to ship.",
|
|
46
46
|
"published.title": "Your published extensions",
|
|
47
47
|
"published.empty": "You haven't published anything yet.",
|
|
@@ -98,6 +98,7 @@
|
|
|
98
98
|
"extlist.btn.publish": "Publish",
|
|
99
99
|
"extlist.btn.update": "Update",
|
|
100
100
|
"extlist.btn.pack": "Pack",
|
|
101
|
+
"extlist.btn.packing": "Packing…",
|
|
101
102
|
"extlist.btn.unpublish": "Unpublish",
|
|
102
103
|
"extlist.btn.iterate": "Iterate",
|
|
103
104
|
"extlist.iterate.seed": "Iterate on extension {{id}}",
|
|
@@ -105,7 +106,6 @@
|
|
|
105
106
|
"extlist.overwrite.confirm": "\"{{id}}\" is already published. Publish a new version?",
|
|
106
107
|
"extlist.unpublish.confirm": "Unpublish {{id}} from the marketplace?",
|
|
107
108
|
"extlist.publishing": "Publishing…",
|
|
108
|
-
"extlist.packed": "Packed → {{path}}",
|
|
109
109
|
"extlist.needLicense": "Publishing requires an activated user license.",
|
|
110
110
|
"extlist.newExt.label": "Create a new extension",
|
|
111
111
|
"extlist.newExt.hint": "Opens an AI session that scaffolds and builds it for you.",
|
|
@@ -159,7 +159,7 @@
|
|
|
159
159
|
"verify.errors": "{{n}} 个错误",
|
|
160
160
|
"verify.warnings": "{{n}} 个警告",
|
|
161
161
|
"verify.hint": "提示",
|
|
162
|
-
"hint.reload": "
|
|
162
|
+
"hint.reload": "修复问题后,刷新页面即可生效。",
|
|
163
163
|
"publish.status": "状态",
|
|
164
164
|
"publish.status.draft": "草稿",
|
|
165
165
|
"publish.status.published": "已发布",
|
|
@@ -169,9 +169,9 @@
|
|
|
169
169
|
"btn.publish": "发布到市场",
|
|
170
170
|
"btn.publishing": "发布中…",
|
|
171
171
|
"btn.pack": "打包 (.zip)",
|
|
172
|
+
"btn.packing": "打包中…",
|
|
172
173
|
"publish.needLicense": "发布需要已激活的用户授权。",
|
|
173
174
|
"publish.done": "已发布 {{id}} {{ver}} — {{status}}",
|
|
174
|
-
"publish.packed": "已打包 → {{path}}",
|
|
175
175
|
"publish.already": "已发布过。勾选「发布新版本」后再试。",
|
|
176
176
|
"published.title": "你发布的扩展",
|
|
177
177
|
"published.empty": "你还没有发布任何扩展。",
|
|
@@ -228,6 +228,7 @@
|
|
|
228
228
|
"extlist.btn.publish": "发布",
|
|
229
229
|
"extlist.btn.update": "更新",
|
|
230
230
|
"extlist.btn.pack": "打包",
|
|
231
|
+
"extlist.btn.packing": "打包中…",
|
|
231
232
|
"extlist.btn.unpublish": "下架",
|
|
232
233
|
"extlist.btn.iterate": "迭代",
|
|
233
234
|
"extlist.iterate.seed": "迭代扩展 {{id}}",
|
|
@@ -235,7 +236,6 @@
|
|
|
235
236
|
"extlist.overwrite.confirm": "「{{id}}」已经发布过了。要发布新版本吗?",
|
|
236
237
|
"extlist.unpublish.confirm": "确定要从市场下架 {{id}} 吗?",
|
|
237
238
|
"extlist.publishing": "发布中…",
|
|
238
|
-
"extlist.packed": "已打包 → {{path}}",
|
|
239
239
|
"extlist.needLicense": "发布需要已激活的用户授权。",
|
|
240
240
|
"extlist.newExt.label": "新建扩展",
|
|
241
241
|
"extlist.newExt.hint": "打开一个 AI 会话,帮你生成并开发它。",
|
|
@@ -302,6 +302,28 @@
|
|
|
302
302
|
return data;
|
|
303
303
|
}
|
|
304
304
|
|
|
305
|
+
async function downloadPack(ext_id) {
|
|
306
|
+
const res = await fetch(api("/pack"), {
|
|
307
|
+
method: "POST",
|
|
308
|
+
headers: { "Content-Type": "application/json" },
|
|
309
|
+
body: JSON.stringify({ ext_id }),
|
|
310
|
+
});
|
|
311
|
+
if (!res.ok) {
|
|
312
|
+
let msg = `Request failed (${res.status})`;
|
|
313
|
+
try { msg = (await res.json()).error || msg; } catch (_e) {}
|
|
314
|
+
throw new Error(msg);
|
|
315
|
+
}
|
|
316
|
+
const blob = await res.blob();
|
|
317
|
+
const url = URL.createObjectURL(blob);
|
|
318
|
+
const a = document.createElement("a");
|
|
319
|
+
a.href = url;
|
|
320
|
+
a.download = `${ext_id}.zip`;
|
|
321
|
+
document.body.appendChild(a);
|
|
322
|
+
a.click();
|
|
323
|
+
document.body.removeChild(a);
|
|
324
|
+
URL.revokeObjectURL(url);
|
|
325
|
+
}
|
|
326
|
+
|
|
305
327
|
// Skills tab talks to host-owned endpoints (not the ext prefix). We surface
|
|
306
328
|
// the HTTP status so the caller can treat 403 as "locked" rather than error.
|
|
307
329
|
async function getHost(path) {
|
|
@@ -452,29 +474,26 @@
|
|
|
452
474
|
const version = ext.version;
|
|
453
475
|
const isUpdate = !!prevVersion;
|
|
454
476
|
|
|
455
|
-
const intro = el("p", { class: "studio-modal-
|
|
477
|
+
const intro = el("p", { class: "studio-modal-intro", text: t("pub.intro", { name: ext.name }) });
|
|
456
478
|
|
|
457
479
|
const verText = !version
|
|
458
480
|
? t("pub.version.missing")
|
|
459
481
|
: isUpdate
|
|
460
482
|
? t("pub.version.update", { prev: prevVersion, ver: version })
|
|
461
483
|
: t("pub.version.new", { ver: version });
|
|
462
|
-
const verLine = el("p", { class: "studio-modal-code", text: verText });
|
|
463
484
|
|
|
464
|
-
const
|
|
465
|
-
|
|
466
|
-
? el("p", { class: "studio-skill-hint", text: t("pub.units", { units: unitList }) })
|
|
467
|
-
: null;
|
|
485
|
+
const metaBlock = el("div", { class: "studio-pub-meta" });
|
|
486
|
+
metaBlock.appendChild(el("span", { class: "studio-pub-version", text: verText }));
|
|
468
487
|
|
|
469
|
-
const
|
|
488
|
+
const notesField = el("div", { class: "studio-field" });
|
|
489
|
+
notesField.appendChild(el("label", { class: "studio-label", text: t("pub.notes.label") }));
|
|
470
490
|
const notes = el("textarea", { class: "studio-textarea", rows: "3", placeholder: t("pub.notes.placeholder") });
|
|
491
|
+
notesField.appendChild(notes);
|
|
471
492
|
|
|
472
493
|
const status = el("p", { class: "studio-modal-status" });
|
|
473
494
|
status.style.display = "none";
|
|
474
495
|
|
|
475
|
-
const bodyChildren = [intro,
|
|
476
|
-
if (unitsLine) bodyChildren.push(unitsLine);
|
|
477
|
-
bodyChildren.push(notesLabel, notes, status);
|
|
496
|
+
const bodyChildren = [intro, metaBlock, notesField, status];
|
|
478
497
|
|
|
479
498
|
let done = false;
|
|
480
499
|
const modal = openModal({
|
|
@@ -586,6 +605,7 @@
|
|
|
586
605
|
function createDebugPanel() {
|
|
587
606
|
let container = null;
|
|
588
607
|
let unsub = null;
|
|
608
|
+
let summaryFadeTimer = null;
|
|
589
609
|
|
|
590
610
|
async function runVerify() {
|
|
591
611
|
const ext = store.selected();
|
|
@@ -597,6 +617,8 @@
|
|
|
597
617
|
renderVerify(data);
|
|
598
618
|
} catch (e) {
|
|
599
619
|
renderError(e);
|
|
620
|
+
} finally {
|
|
621
|
+
if (status) status.textContent = t("btn.recheck");
|
|
600
622
|
}
|
|
601
623
|
}
|
|
602
624
|
|
|
@@ -607,16 +629,22 @@
|
|
|
607
629
|
const errs = (data.issues || []).filter((i) => i.level === "error");
|
|
608
630
|
const warns = (data.issues || []).filter((i) => i.level === "warning");
|
|
609
631
|
|
|
610
|
-
const summary =
|
|
611
|
-
if (
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
632
|
+
const summary = container.querySelector(".studio-verify-summary");
|
|
633
|
+
if (summary) {
|
|
634
|
+
clearTimeout(summaryFadeTimer);
|
|
635
|
+
summary.textContent = "";
|
|
636
|
+
if (data.ok && !warns.length) {
|
|
637
|
+
summary.textContent = "✓ " + t("verify.ok");
|
|
638
|
+
summary.className = "studio-verify-summary studio-verify-ok";
|
|
639
|
+
summaryFadeTimer = setTimeout(() => { summary.textContent = ""; }, 3000);
|
|
640
|
+
} else {
|
|
641
|
+
const parts = [];
|
|
642
|
+
if (errs.length) parts.push(t("verify.errors", { n: errs.length }));
|
|
643
|
+
if (warns.length) parts.push(t("verify.warnings", { n: warns.length }));
|
|
644
|
+
summary.textContent = parts.join(" · ");
|
|
645
|
+
summary.className = "studio-verify-summary studio-verify-fail";
|
|
646
|
+
}
|
|
618
647
|
}
|
|
619
|
-
box.appendChild(summary);
|
|
620
648
|
|
|
621
649
|
(data.issues || []).forEach((i) => {
|
|
622
650
|
const item = el("div", { class: "studio-issue studio-issue-" + i.level });
|
|
@@ -630,7 +658,9 @@
|
|
|
630
658
|
|
|
631
659
|
function renderError(e) {
|
|
632
660
|
const box = container.querySelector(".studio-verify");
|
|
633
|
-
|
|
661
|
+
const summary = container.querySelector(".studio-verify-summary");
|
|
662
|
+
if (summary) { summary.textContent = t("err.generic", { msg: e.message }); summary.className = "studio-verify-summary studio-verify-fail"; }
|
|
663
|
+
if (box) { box.innerHTML = ""; }
|
|
634
664
|
}
|
|
635
665
|
|
|
636
666
|
function renderDetail() {
|
|
@@ -647,7 +677,6 @@
|
|
|
647
677
|
const row = (label, value) => { meta.appendChild(el("dt", { text: label })); meta.appendChild(el("dd", { text: value || "—" })); };
|
|
648
678
|
row(t("detail.version"), ext.version);
|
|
649
679
|
row(t("detail.layer"), ext.layer);
|
|
650
|
-
row(t("detail.origin"), ext.origin);
|
|
651
680
|
detail.appendChild(meta);
|
|
652
681
|
|
|
653
682
|
detail.appendChild(el("div", { class: "studio-label", text: t("detail.units") }));
|
|
@@ -674,6 +703,7 @@
|
|
|
674
703
|
|
|
675
704
|
const bar = el("div", { class: "studio-actions" });
|
|
676
705
|
bar.appendChild(el("button", { class: "studio-btn studio-btn-primary studio-verify-status", text: t("btn.recheck"), onclick: runVerify }));
|
|
706
|
+
bar.appendChild(el("span", { class: "studio-verify-summary" }));
|
|
677
707
|
container.appendChild(bar);
|
|
678
708
|
|
|
679
709
|
container.appendChild(el("div", { class: "studio-verify" }));
|
|
@@ -722,11 +752,15 @@
|
|
|
722
752
|
async function doPack() {
|
|
723
753
|
const ext = store.selected();
|
|
724
754
|
if (!ext) return;
|
|
755
|
+
const btn = container.querySelector(".studio-pack-btn");
|
|
756
|
+
const orig = btn ? btn.textContent : null;
|
|
757
|
+
if (btn) { btn.disabled = true; btn.textContent = t("btn.packing"); }
|
|
725
758
|
try {
|
|
726
|
-
|
|
727
|
-
feedback(t("publish.packed", { path: data.path }), "success");
|
|
759
|
+
await downloadPack(ext.id);
|
|
728
760
|
} catch (e) {
|
|
729
761
|
feedback(t("err.generic", { msg: e.message }), "error");
|
|
762
|
+
} finally {
|
|
763
|
+
if (btn) { btn.disabled = false; btn.textContent = orig; }
|
|
730
764
|
}
|
|
731
765
|
}
|
|
732
766
|
|
|
@@ -766,7 +800,7 @@
|
|
|
766
800
|
|
|
767
801
|
const bar = el("div", { class: "studio-actions" });
|
|
768
802
|
bar.appendChild(el("button", { class: "studio-btn studio-btn-primary studio-publish-btn", text: t("btn.publish"), onclick: doPublish }));
|
|
769
|
-
bar.appendChild(el("button", { class: "studio-btn studio-btn-ghost", text: t("btn.pack"), onclick: doPack }));
|
|
803
|
+
bar.appendChild(el("button", { class: "studio-btn studio-btn-ghost studio-pack-btn", text: t("btn.pack"), onclick: doPack }));
|
|
770
804
|
container.appendChild(bar);
|
|
771
805
|
|
|
772
806
|
container.appendChild(el("div", { class: "studio-feedback" }));
|
|
@@ -787,6 +821,23 @@
|
|
|
787
821
|
};
|
|
788
822
|
}
|
|
789
823
|
|
|
824
|
+
function _skeletonHtml() {
|
|
825
|
+
return Array.from({ length: 3 }).map(() => `
|
|
826
|
+
<div class="studio-skill-card" style="pointer-events:none">
|
|
827
|
+
<div class="studio-skill-head">
|
|
828
|
+
<span class="skel" style="display:inline-block;height:1rem;width:40%"></span>
|
|
829
|
+
<span class="skel" style="display:inline-block;height:1.2rem;width:3.5rem;border-radius:4px;"></span>
|
|
830
|
+
</div>
|
|
831
|
+
<div class="studio-skill-meta" style="margin-top:6px">
|
|
832
|
+
<span class="skel" style="display:inline-block;height:0.85rem;width:20%"></span>
|
|
833
|
+
</div>
|
|
834
|
+
<div class="studio-actions">
|
|
835
|
+
<span class="skel" style="display:inline-block;height:1.75rem;width:5rem;border-radius:6px;"></span>
|
|
836
|
+
<span class="skel" style="display:inline-block;height:1.75rem;width:4rem;border-radius:6px;"></span>
|
|
837
|
+
</div>
|
|
838
|
+
</div>`).join("");
|
|
839
|
+
}
|
|
840
|
+
|
|
790
841
|
// ── Extensions tab (full-page): cloud + local extension cards ──────────────
|
|
791
842
|
// Reuses backend endpoints GET /published (cloud), the shared `store` (local,
|
|
792
843
|
// GET /extensions), POST /publish|/pack|/unpublish, and POST /develop to open
|
|
@@ -873,17 +924,21 @@
|
|
|
873
924
|
actions.appendChild(pub);
|
|
874
925
|
|
|
875
926
|
actions.appendChild(el("button", { class: "studio-btn", text: t("extlist.btn.iterate"), onclick: () => createExtension(t("extlist.iterate.seed", { id: ext.id })) }));
|
|
876
|
-
|
|
877
|
-
|
|
927
|
+
const packBtn = el("button", { class: "studio-btn studio-btn-ghost", text: t("extlist.btn.pack") });
|
|
928
|
+
packBtn.addEventListener("click", () => doPack(ext, packBtn));
|
|
929
|
+
actions.appendChild(packBtn);
|
|
878
930
|
return card;
|
|
879
931
|
}
|
|
880
932
|
|
|
881
|
-
async function doPack(ext) {
|
|
933
|
+
async function doPack(ext, btn) {
|
|
934
|
+
const orig = btn ? btn.textContent : null;
|
|
935
|
+
if (btn) { btn.disabled = true; btn.textContent = t("extlist.btn.packing"); }
|
|
882
936
|
try {
|
|
883
|
-
|
|
884
|
-
alert(t("extlist.packed", { path: data.path }));
|
|
937
|
+
await downloadPack(ext.id);
|
|
885
938
|
} catch (e) {
|
|
886
939
|
alert(t("err.generic", { msg: e.message }));
|
|
940
|
+
} finally {
|
|
941
|
+
if (btn) { btn.disabled = false; btn.textContent = orig; }
|
|
887
942
|
}
|
|
888
943
|
}
|
|
889
944
|
|
|
@@ -926,7 +981,7 @@
|
|
|
926
981
|
async attach(root) {
|
|
927
982
|
container = el("div", { class: "studio-panel" });
|
|
928
983
|
root.appendChild(container);
|
|
929
|
-
container.
|
|
984
|
+
container.innerHTML = _skeletonHtml();
|
|
930
985
|
try { await reload(); rebuild(); }
|
|
931
986
|
catch (e) { container.innerHTML = ""; container.appendChild(el("p", { class: "studio-empty", text: t("err.generic", { msg: e.message }) })); }
|
|
932
987
|
},
|
|
@@ -1090,7 +1145,7 @@
|
|
|
1090
1145
|
async attach(root) {
|
|
1091
1146
|
container = el("div", { class: "studio-panel" });
|
|
1092
1147
|
root.appendChild(container);
|
|
1093
|
-
container.
|
|
1148
|
+
container.innerHTML = _skeletonHtml();
|
|
1094
1149
|
try { await reload(); rebuild(); }
|
|
1095
1150
|
catch (e) { container.innerHTML = ""; container.appendChild(el("p", { class: "studio-empty", text: t("err.generic", { msg: e.message }) })); }
|
|
1096
1151
|
},
|
|
@@ -1146,7 +1201,7 @@
|
|
|
1146
1201
|
createSkillsPanel().attach(root);
|
|
1147
1202
|
}
|
|
1148
1203
|
|
|
1149
|
-
function navRow(
|
|
1204
|
+
function navRow(labelKey, onClick) {
|
|
1150
1205
|
const item = el("div", { class: "task-item task-item-summary" });
|
|
1151
1206
|
item.innerHTML =
|
|
1152
1207
|
'<div class="task-row">' +
|
|
@@ -1159,7 +1214,9 @@
|
|
|
1159
1214
|
'</svg>' +
|
|
1160
1215
|
'<div class="task-info"><span class="task-name"></span></div>' +
|
|
1161
1216
|
'</div>';
|
|
1162
|
-
item.querySelector(".task-name")
|
|
1217
|
+
const nameEl = item.querySelector(".task-name");
|
|
1218
|
+
nameEl.textContent = t(labelKey);
|
|
1219
|
+
document.addEventListener("langchange", () => { nameEl.textContent = t(labelKey); });
|
|
1163
1220
|
item.addEventListener("click", onClick);
|
|
1164
1221
|
return item;
|
|
1165
1222
|
}
|
|
@@ -1170,7 +1227,7 @@
|
|
|
1170
1227
|
});
|
|
1171
1228
|
|
|
1172
1229
|
Clacky.ext.ui.mount("sidebar.nav.bottom", function () {
|
|
1173
|
-
return navRow(
|
|
1230
|
+
return navRow("nav.entry", function () { Clacky.ext.ui.openWorkspace(WS_ID); });
|
|
1174
1231
|
}, { workspace: WS_ID });
|
|
1175
1232
|
|
|
1176
1233
|
// Extension-developer session tools: debug + publish, as aside tabs. These
|
|
@@ -1191,6 +1248,8 @@
|
|
|
1191
1248
|
.studio-page .studio-panel { padding: 0; }
|
|
1192
1249
|
.studio-field { margin-bottom: 14px; }
|
|
1193
1250
|
.studio-label { display: block; font-size: 12px; font-weight: 600; color: var(--color-text-tertiary); margin-bottom: 6px; }
|
|
1251
|
+
.studio-field { display: flex; flex-direction: column; gap: 6px; margin-bottom: 16px; }
|
|
1252
|
+
.studio-label { font-size: 12px; font-weight: 500; color: var(--color-text-secondary); }
|
|
1194
1253
|
.studio-select, .studio-textarea { width: 100%; box-sizing: border-box; background: var(--color-bg-input); border: 1px solid var(--color-border-primary); border-radius: var(--radius-sm); padding: 7px 8px; color: var(--color-text-primary); font-size: 13px; font-family: inherit; }
|
|
1195
1254
|
.studio-select:focus, .studio-textarea:focus { border-color: var(--color-accent-primary); outline: none; }
|
|
1196
1255
|
.studio-empty { color: var(--color-text-muted); font-size: 12px; margin: 4px 0; }
|
|
@@ -1203,7 +1262,7 @@
|
|
|
1203
1262
|
.studio-meta dd { margin: 0; color: var(--color-text-primary); }
|
|
1204
1263
|
.studio-units { display: flex; flex-wrap: wrap; gap: 6px; }
|
|
1205
1264
|
.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; }
|
|
1206
|
-
.studio-actions { display: flex; gap: 8px; margin: 12px 0; }
|
|
1265
|
+
.studio-actions { display: flex; align-items: center; gap: 8px; margin: 12px 0; }
|
|
1207
1266
|
.studio-skill-promo .studio-actions { margin: 12px 0 0; }
|
|
1208
1267
|
.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; }
|
|
1209
1268
|
.studio-btn:hover { background: var(--color-bg-hover); color: var(--color-text-primary); }
|
|
@@ -1212,11 +1271,13 @@
|
|
|
1212
1271
|
.studio-btn-primary:disabled { opacity: 0.6; cursor: default; }
|
|
1213
1272
|
.studio-btn-danger { color: var(--color-error); border-color: var(--color-error-border); padding: 4px 10px; }
|
|
1214
1273
|
.studio-btn-danger:hover { background: var(--color-error-bg); color: var(--color-error); }
|
|
1215
|
-
.studio-btn-ghost {
|
|
1216
|
-
.studio-btn-ghost:hover { background: var(--color-bg-hover); color: var(--color-text-
|
|
1274
|
+
.studio-btn-ghost { color: var(--color-text-secondary); font-weight: 500; }
|
|
1275
|
+
.studio-btn-ghost:hover { background: var(--color-bg-hover); color: var(--color-text-primary); }
|
|
1217
1276
|
.studio-verify { margin-top: 8px; }
|
|
1218
|
-
.studio-verify-
|
|
1219
|
-
.studio-verify-
|
|
1277
|
+
.studio-verify-summary { font-size: 13px; font-weight: 600; }
|
|
1278
|
+
.studio-verify-ok { color: var(--color-success); font-size: 12px; font-weight: 600; }
|
|
1279
|
+
.studio-verify-fail { color: var(--color-error); font-size: 12px; font-weight: 600; }
|
|
1280
|
+
.studio-verify > *:first-child { margin-bottom: 8px; }
|
|
1220
1281
|
.studio-issue { border-left: 3px solid var(--color-border-primary); padding: 6px 10px; margin-bottom: 8px; background: var(--color-bg-secondary); border-radius: 0 var(--radius-sm) var(--radius-sm) 0; }
|
|
1221
1282
|
.studio-issue-error { border-left-color: var(--color-error); }
|
|
1222
1283
|
.studio-issue-warning { border-left-color: var(--color-warning, var(--color-text-tertiary)); }
|
|
@@ -1225,7 +1286,7 @@
|
|
|
1225
1286
|
.studio-issue-file { font-size: 11px; font-family: monospace; color: var(--color-text-muted); margin-top: 2px; }
|
|
1226
1287
|
.studio-issue-hint { font-size: 11px; color: var(--color-text-secondary); margin-top: 4px; font-style: italic; }
|
|
1227
1288
|
.studio-check { display: flex; align-items: center; gap: 6px; font-size: 12px; color: var(--color-text-secondary); margin-bottom: 12px; cursor: pointer; }
|
|
1228
|
-
.studio-feedback { font-size: 12px; margin:
|
|
1289
|
+
.studio-feedback { font-size: 12px; margin: 6px 0 0; line-height: 1.4; }
|
|
1229
1290
|
.studio-feedback-success { color: var(--color-success); }
|
|
1230
1291
|
.studio-feedback-error { color: var(--color-error); }
|
|
1231
1292
|
.studio-feedback-warn { color: var(--color-warning, var(--color-text-secondary)); }
|
|
@@ -1256,14 +1317,21 @@
|
|
|
1256
1317
|
.studio-skill-meta { display: flex; gap: 12px; font-size: 11px; color: var(--color-text-muted); margin-bottom: 8px; }
|
|
1257
1318
|
.studio-skill-promo { border: 1px solid var(--color-border-primary); border-radius: var(--radius-md, 8px); padding: 16px 18px; margin: 0 0 24px; background: var(--color-bg-secondary); }
|
|
1258
1319
|
.studio-skill-promo-text { margin: 0 0 4px; font-size: 14px; font-weight: 600; color: var(--color-text-primary); }
|
|
1259
|
-
.studio-modal-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.5); display: flex; align-items: center; justify-content: center; z-index: 9999; }
|
|
1260
|
-
.studio-modal { background: var(--color-bg-primary, var(--color-bg-secondary)); border: 1px solid var(--color-border-primary); border-radius: var(--radius-md, 8px); padding:
|
|
1261
|
-
|
|
1320
|
+
.studio-modal-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.5); display: flex; align-items: center; justify-content: center; z-index: 9999; animation: studio-overlay-in 0.15s ease; }
|
|
1321
|
+
.studio-modal { background: var(--color-bg-primary, var(--color-bg-secondary)); border: 1px solid var(--color-border-primary); border-radius: var(--radius-md, 8px); padding: 28px 28px 22px; width: 520px; max-width: calc(100vw - 40px); box-shadow: 0 12px 40px rgba(0,0,0,0.35); animation: studio-modal-in 0.2s cubic-bezier(0.34,1.4,0.64,1); }
|
|
1322
|
+
@keyframes studio-overlay-in { from { opacity: 0; } to { opacity: 1; } }
|
|
1323
|
+
@keyframes studio-modal-in { from { opacity: 0; transform: scale(0.93) translateY(6px); } to { opacity: 1; transform: scale(1) translateY(0); } }
|
|
1324
|
+
.studio-modal-title { margin: 0 0 16px; font-size: 16px; font-weight: 600; color: var(--color-text-primary); }
|
|
1325
|
+
.studio-modal-intro { margin: 0 0 16px; font-size: 13px; color: var(--color-text-secondary); line-height: 1.6; }
|
|
1326
|
+
.studio-pub-meta { display: flex; align-items: center; gap: 10px; background: var(--color-bg-secondary); border: 1px solid var(--color-border-primary); border-radius: var(--radius-sm, 6px); padding: 10px 14px; margin-bottom: 16px; }
|
|
1327
|
+
.studio-pub-version { font-size: 13px; font-weight: 600; color: var(--color-text-primary); font-family: monospace; }
|
|
1328
|
+
.studio-pub-units { font-size: 12px; color: var(--color-text-tertiary); }
|
|
1329
|
+
.studio-pub-units::before { content: "·"; margin-right: 10px; }
|
|
1262
1330
|
.studio-modal-body { font-size: 13px; color: var(--color-text-secondary); line-height: 1.6; }
|
|
1263
1331
|
.studio-modal-status { margin: 0 0 8px; font-size: 13px; color: var(--color-text-secondary); line-height: 1.6; }
|
|
1264
1332
|
.studio-modal-code { margin: 0 0 8px; font-size: 13px; font-family: monospace; color: var(--color-text-primary); }
|
|
1265
1333
|
.studio-modal-link { display: inline-block; font-size: 13px; color: var(--color-accent-primary); }
|
|
1266
|
-
.studio-modal-footer { display: flex; justify-content: flex-end; gap: 8px; margin-top:
|
|
1334
|
+
.studio-modal-footer { display: flex; justify-content: flex-end; gap: 8px; margin-top: 20px; padding-top: 16px; border-top: 1px solid var(--color-border-primary); }
|
|
1267
1335
|
`;
|
|
1268
1336
|
document.head.appendChild(style);
|
|
1269
1337
|
})();
|
|
@@ -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.
|
|
Binary file
|
|
@@ -5,14 +5,14 @@ version: "0.1.0"
|
|
|
5
5
|
author: OpenClacky
|
|
6
6
|
homepage: https://www.openclacky.com
|
|
7
7
|
license: MIT
|
|
8
|
-
origin: self
|
|
9
8
|
contributes:
|
|
10
9
|
agents:
|
|
11
10
|
- id: general
|
|
12
11
|
title: General
|
|
13
|
-
title_zh:
|
|
14
|
-
description:
|
|
15
|
-
description_zh:
|
|
12
|
+
title_zh: 日常工作
|
|
13
|
+
description: Handles your everyday work
|
|
14
|
+
description_zh: 帮你打理日常工作,查资料、写东西、整理归纳都行
|
|
16
15
|
order: 1
|
|
17
16
|
prompt: agents/general/system_prompt.md
|
|
17
|
+
avatar: agents/general/avatar.png
|
|
18
18
|
panels: [meeting/meeting]
|
|
@@ -187,12 +187,12 @@
|
|
|
187
187
|
);
|
|
188
188
|
}
|
|
189
189
|
|
|
190
|
-
Clacky.ext.ui.mount("session.aside", (ctx) => {
|
|
191
|
-
if (!ctx || !ctx.sessionId) return
|
|
190
|
+
Clacky.ext.ui.mount("session.aside", (container, ctx) => {
|
|
191
|
+
if (!ctx || !ctx.sessionId) return;
|
|
192
192
|
const body = el("div", { class: "changes-panel" });
|
|
193
193
|
const root = el("div", { class: "changes-root", "data-panel": "changes" }, body);
|
|
194
|
+
container.appendChild(root);
|
|
194
195
|
refresh(ctx.sessionId, root, body, ctx);
|
|
195
|
-
return root;
|
|
196
196
|
}, {
|
|
197
197
|
order: 10,
|
|
198
198
|
tab: { id: "changes", label: () => t("changes.tab") },
|
|
@@ -893,7 +893,6 @@
|
|
|
893
893
|
// Every tab activation gets a fresh empty container; wire it into state
|
|
894
894
|
// and paint the current state onto it.
|
|
895
895
|
attach(container) {
|
|
896
|
-
container.className = "meeting-container";
|
|
897
896
|
state.container = container;
|
|
898
897
|
renderUI(container);
|
|
899
898
|
},
|
|
@@ -923,7 +922,7 @@
|
|
|
923
922
|
// Inject minimal styles
|
|
924
923
|
const style = document.createElement("style");
|
|
925
924
|
style.textContent = `
|
|
926
|
-
.meeting-
|
|
925
|
+
.meeting-panel { padding: 16px; font-size: 13px; color: var(--color-text-secondary); }
|
|
927
926
|
.meeting-btn { padding: 7px 14px; border-radius: var(--radius-sm); border: 1px solid transparent; cursor: pointer; font-size: 13px; font-weight: 500; transition: background .15s, border-color .15s; }
|
|
928
927
|
.meeting-btn-start { background: var(--color-button-primary); color: var(--color-button-primary-text); }
|
|
929
928
|
.meeting-btn-start:hover { background: var(--color-button-primary-hover); }
|
|
@@ -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
|
|
|
@@ -391,6 +391,10 @@ module Clacky
|
|
|
391
391
|
return nil
|
|
392
392
|
end
|
|
393
393
|
|
|
394
|
+
avatar_rel = spec["avatar"].to_s
|
|
395
|
+
avatar_abs = avatar_rel.empty? ? "" : File.join(container[:dir], avatar_rel)
|
|
396
|
+
avatar_abs = "" unless !avatar_abs.empty? && File.file?(avatar_abs)
|
|
397
|
+
|
|
394
398
|
Unit.new(kind: :agent, id: spec["id"].to_s, ext_id: ext_id,
|
|
395
399
|
layer: container[:layer], origin: container[:origin],
|
|
396
400
|
dir: container[:dir],
|
|
@@ -399,6 +403,8 @@ module Clacky
|
|
|
399
403
|
"title_zh" => spec["title_zh"].to_s,
|
|
400
404
|
"prompt" => spec["prompt"],
|
|
401
405
|
"prompt_abs" => prompt_abs,
|
|
406
|
+
"avatar" => avatar_rel,
|
|
407
|
+
"avatar_abs" => avatar_abs,
|
|
402
408
|
"description" => spec["description"].to_s,
|
|
403
409
|
"description_zh" => spec["description_zh"].to_s,
|
|
404
410
|
"order" => spec["order"],
|
|
@@ -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
|
|
|
@@ -22,7 +22,7 @@ module Clacky
|
|
|
22
22
|
PANEL_KEYS = %w[id title title_zh description description_zh view order attach].freeze
|
|
23
23
|
API_KEYS = %w[id handler].freeze
|
|
24
24
|
SKILL_KEYS = %w[id dir protected].freeze
|
|
25
|
-
AGENT_KEYS = %w[id title title_zh description description_zh order prompt panels skills].freeze
|
|
25
|
+
AGENT_KEYS = %w[id title title_zh description description_zh order prompt panels skills avatar].freeze
|
|
26
26
|
CHANNEL_KEYS = %w[id platform adapter].freeze
|
|
27
27
|
PATCH_KEYS = %w[target file fingerprint on_mismatch].freeze
|
|
28
28
|
HOOK_KEYS = %w[event file].freeze
|