openclacky 1.1.2 → 1.1.4
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/.clacky/skills/gem-release/SKILL.md +27 -31
- data/CHANGELOG.md +30 -0
- data/Dockerfile +28 -0
- data/README.md +4 -0
- data/README_CN.md +198 -0
- data/docs/engineering-article.md +343 -0
- data/lib/clacky/agent/llm_caller.rb +2 -5
- data/lib/clacky/agent/session_serializer.rb +4 -0
- data/lib/clacky/agent.rb +22 -1
- data/lib/clacky/brand_config.rb +87 -5
- data/lib/clacky/cli.rb +1 -1
- data/lib/clacky/client.rb +15 -11
- data/lib/clacky/message_format/anthropic.rb +30 -2
- data/lib/clacky/message_format/bedrock.rb +13 -1
- data/lib/clacky/message_format/open_ai.rb +5 -1
- data/lib/clacky/providers.rb +34 -0
- data/lib/clacky/server/channel/adapters/dingtalk/adapter.rb +142 -5
- data/lib/clacky/server/channel/adapters/dingtalk/api_client.rb +309 -0
- data/lib/clacky/server/http_server.rb +130 -15
- data/lib/clacky/server/session_registry.rb +9 -6
- data/lib/clacky/ui2/ui_controller.rb +14 -0
- data/lib/clacky/ui_interface.rb +14 -0
- data/lib/clacky/utils/model_pricing.rb +96 -25
- data/lib/clacky/version.rb +1 -1
- data/lib/clacky/web/app.css +1286 -1116
- data/lib/clacky/web/brand.js +20 -5
- data/lib/clacky/web/i18n.js +42 -0
- data/lib/clacky/web/index.html +26 -7
- data/lib/clacky/web/onboard.js +6 -0
- data/lib/clacky/web/sessions.js +194 -11
- data/lib/clacky/web/settings.js +51 -10
- data/lib/clacky/web/skills.js +53 -31
- data/lib/clacky/web/vendor/hljs/highlight.min.js +1244 -0
- data/lib/clacky/web/vendor/hljs/hljs-theme.css +95 -0
- data/scripts/build/lib/apt.sh +30 -10
- data/scripts/build/lib/network.sh +3 -2
- data/scripts/install.sh +30 -9
- data/scripts/install_browser.sh +2 -1
- data/scripts/install_full.sh +2 -1
- data/scripts/install_rails_deps.sh +30 -9
- data/scripts/install_system_deps.sh +30 -9
- metadata +7 -17
- data/docs/HOW-TO-USE-CN.md +0 -96
- data/docs/HOW-TO-USE.md +0 -94
- data/docs/browser-cdp-native-design.md +0 -195
- data/docs/c-end-user-positioning.md +0 -64
- data/docs/config.example.yml +0 -27
- data/docs/deploy-architecture.md +0 -619
- data/docs/deploy_subagent_design.md +0 -540
- data/docs/install-script-simplification.md +0 -89
- data/docs/memory-architecture.md +0 -343
- data/docs/openclacky_cloud_api_reference.md +0 -584
- data/docs/security-design.md +0 -109
- data/docs/session-management-redesign.md +0 -202
- data/docs/system-skill-authoring-guide.md +0 -47
- data/docs/why-developer.md +0 -371
- data/docs/why-openclacky.md +0 -266
data/lib/clacky/web/skills.js
CHANGED
|
@@ -19,6 +19,8 @@ const Skills = (() => {
|
|
|
19
19
|
let _brandSkills = []; // skills from cloud license API
|
|
20
20
|
let _activeTab = "my-skills"; // "my-skills" | "brand-skills"
|
|
21
21
|
let _brandActivated = false; // whether a license is currently active
|
|
22
|
+
let _freeMode = false; // brand-skills tab is showing free-mode skills
|
|
23
|
+
let _paidSkillsCount = 0; // count of premium (encrypted) skills locked behind activation
|
|
22
24
|
let _domWired = false; // whether one-time DOM listeners have been bound
|
|
23
25
|
let _showSystemSkills = false; // whether system (source=default) skills are shown
|
|
24
26
|
|
|
@@ -55,38 +57,14 @@ const Skills = (() => {
|
|
|
55
57
|
const res = await fetch("/api/brand/skills");
|
|
56
58
|
const data = await res.json();
|
|
57
59
|
|
|
58
|
-
if (res.status === 403 || (data.ok === false && (data.error || "").toLowerCase().includes("not activated"))) {
|
|
59
|
-
// License not activated — show a friendly prompt instead of an error
|
|
60
|
-
const btn = document.createElement("button");
|
|
61
|
-
btn.className = "brand-skills-activate-btn";
|
|
62
|
-
btn.textContent = I18n.t("skills.brand.activateBtn");
|
|
63
|
-
btn.addEventListener("click", () => {
|
|
64
|
-
// Reuse the same behaviour as the top banner: navigate to Settings,
|
|
65
|
-
// scroll to the license section, flash it, and focus the input.
|
|
66
|
-
if (typeof Brand !== "undefined" && Brand.goToLicenseInput) {
|
|
67
|
-
Brand.goToLicenseInput();
|
|
68
|
-
} else {
|
|
69
|
-
Router.navigate("settings");
|
|
70
|
-
}
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
const wrapper = document.createElement("div");
|
|
74
|
-
wrapper.className = "brand-skills-unlicensed";
|
|
75
|
-
wrapper.innerHTML = `
|
|
76
|
-
<div class="brand-skills-unlicensed-icon">🔒</div>
|
|
77
|
-
<div class="brand-skills-unlicensed-msg">${I18n.t("skills.brand.needsActivation")}</div>`;
|
|
78
|
-
wrapper.appendChild(btn);
|
|
79
|
-
container.innerHTML = "";
|
|
80
|
-
container.appendChild(wrapper);
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
60
|
if (!res.ok || !data.ok) {
|
|
85
61
|
container.innerHTML = '<div class="brand-skills-error">' + escapeHtml(data.error || I18n.t("skills.brand.loadFailed")) + "</div>";
|
|
86
62
|
return;
|
|
87
63
|
}
|
|
88
64
|
|
|
89
|
-
_brandSkills
|
|
65
|
+
_brandSkills = data.skills || [];
|
|
66
|
+
_freeMode = !!data.free_mode;
|
|
67
|
+
_paidSkillsCount = Number(data.paid_skills_count) || 0;
|
|
90
68
|
|
|
91
69
|
// Soft warning: remote API unavailable but local skills returned.
|
|
92
70
|
// Prefer the server-provided warning_code for proper i18n; fall back to
|
|
@@ -126,7 +104,7 @@ const Skills = (() => {
|
|
|
126
104
|
if (!container) return;
|
|
127
105
|
container.innerHTML = "";
|
|
128
106
|
|
|
129
|
-
if (_brandSkills.length === 0) {
|
|
107
|
+
if (_brandSkills.length === 0 && !(_freeMode && _paidSkillsCount > 0)) {
|
|
130
108
|
container.innerHTML = `<div class="brand-skills-empty">${I18n.t("skills.brand.empty")}</div>`;
|
|
131
109
|
return;
|
|
132
110
|
}
|
|
@@ -135,6 +113,34 @@ const Skills = (() => {
|
|
|
135
113
|
const card = _renderBrandSkillCard(skill);
|
|
136
114
|
container.appendChild(card);
|
|
137
115
|
});
|
|
116
|
+
|
|
117
|
+
// Free mode + premium skills exist → show a footer hint inviting the user to activate.
|
|
118
|
+
if (_freeMode && _paidSkillsCount > 0) {
|
|
119
|
+
const hint = document.createElement("div");
|
|
120
|
+
hint.className = "brand-skills-paid-hint";
|
|
121
|
+
|
|
122
|
+
const msgEl = document.createElement("div");
|
|
123
|
+
msgEl.className = "brand-skills-paid-hint-msg";
|
|
124
|
+
msgEl.textContent = I18n.t("skills.brand.paidHint", { n: _paidSkillsCount });
|
|
125
|
+
msgEl.setAttribute("data-i18n", "skills.brand.paidHint");
|
|
126
|
+
msgEl.setAttribute("data-i18n-vars", `n=${_paidSkillsCount}`);
|
|
127
|
+
|
|
128
|
+
const btn = document.createElement("button");
|
|
129
|
+
btn.className = "brand-skills-activate-btn";
|
|
130
|
+
btn.textContent = I18n.t("skills.brand.activateBtn");
|
|
131
|
+
btn.setAttribute("data-i18n", "skills.brand.activateBtn");
|
|
132
|
+
btn.addEventListener("click", () => {
|
|
133
|
+
if (typeof Brand !== "undefined" && Brand.goToLicenseInput) {
|
|
134
|
+
Brand.goToLicenseInput();
|
|
135
|
+
} else {
|
|
136
|
+
Router.navigate("settings");
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
hint.appendChild(msgEl);
|
|
141
|
+
hint.appendChild(btn);
|
|
142
|
+
container.appendChild(hint);
|
|
143
|
+
}
|
|
138
144
|
}
|
|
139
145
|
|
|
140
146
|
/** Render a single brand skill card. */
|
|
@@ -164,8 +170,10 @@ const Skills = (() => {
|
|
|
164
170
|
<button class="btn-brand-use" data-name="${escapeHtml(name)}">${I18n.t("skills.brand.btn.use")}</button>`;
|
|
165
171
|
}
|
|
166
172
|
|
|
167
|
-
//
|
|
168
|
-
const
|
|
173
|
+
// Free skills show a "Free" badge; paid (encrypted) brand skills show "Private".
|
|
174
|
+
const badge = skill.is_free
|
|
175
|
+
? `<span class="brand-skill-badge-free" title="${I18n.t("skills.brand.freeTip")}">✨ ${I18n.t("skills.brand.free")}</span>`
|
|
176
|
+
: `<span class="brand-skill-badge-private" title="${I18n.t("skills.brand.privateTip")}">🔒 ${I18n.t("skills.brand.private")}</span>`;
|
|
169
177
|
|
|
170
178
|
// Choose description based on current language
|
|
171
179
|
const currentLang = I18n.lang();
|
|
@@ -180,7 +188,7 @@ const Skills = (() => {
|
|
|
180
188
|
<div class="brand-skill-info">
|
|
181
189
|
<div class="brand-skill-title">
|
|
182
190
|
<span class="brand-skill-name">${escapeHtml((currentLang === "zh" && skill.name_zh) ? skill.name_zh : name)}</span>
|
|
183
|
-
${
|
|
191
|
+
${badge}
|
|
184
192
|
</div>
|
|
185
193
|
<div class="brand-skill-desc">${escapeHtml(description)}</div>
|
|
186
194
|
</div>
|
|
@@ -357,6 +365,20 @@ const Skills = (() => {
|
|
|
357
365
|
});
|
|
358
366
|
}
|
|
359
367
|
|
|
368
|
+
// Flip tooltip below when toggle is near top of scroll container
|
|
369
|
+
const toggleLabel = card.querySelector(".skill-toggle");
|
|
370
|
+
if (toggleLabel) {
|
|
371
|
+
toggleLabel.addEventListener("mouseenter", () => {
|
|
372
|
+
const scroller = toggleLabel.closest(".skills-tab-content");
|
|
373
|
+
if (!scroller) return;
|
|
374
|
+
const toggleTop = toggleLabel.getBoundingClientRect().top;
|
|
375
|
+
const scrollerTop = scroller.getBoundingClientRect().top;
|
|
376
|
+
if (toggleTop - scrollerTop < 80) {
|
|
377
|
+
toggleLabel.classList.add("skill-toggle-flip");
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
|
|
360
382
|
// Bind "Use" button event
|
|
361
383
|
const useBtn = card.querySelector(".btn-skill-use");
|
|
362
384
|
if (useBtn) {
|