openclacky 1.3.7 → 1.3.8

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.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +24 -0
  3. data/lib/clacky/agent/hook_manager.rb +10 -1
  4. data/lib/clacky/agent/system_prompt_builder.rb +1 -1
  5. data/lib/clacky/agent.rb +9 -2
  6. data/lib/clacky/agent_profile.rb +7 -4
  7. data/lib/clacky/billing/billing_store.rb +3 -2
  8. data/lib/clacky/default_extensions/coding/agents/coding/avatar.png +0 -0
  9. data/lib/clacky/default_extensions/coding/ext.yml +4 -3
  10. data/lib/clacky/default_extensions/ext-studio/agents/ext-developer/avatar.png +0 -0
  11. data/lib/clacky/default_extensions/ext-studio/ext.yml +1 -0
  12. data/lib/clacky/default_extensions/ext-studio/panels/studio/view.js +24 -5
  13. data/lib/clacky/default_extensions/general/agents/general/avatar.png +0 -0
  14. data/lib/clacky/default_extensions/general/ext.yml +4 -3
  15. data/lib/clacky/default_extensions/git/panels/git/view.js +3 -3
  16. data/lib/clacky/default_extensions/meeting/panels/meeting/view.js +1 -2
  17. data/lib/clacky/extension/loader.rb +6 -0
  18. data/lib/clacky/extension/verifier.rb +1 -1
  19. data/lib/clacky/server/http_server.rb +129 -5
  20. data/lib/clacky/shell_hook_loader.rb +266 -22
  21. data/lib/clacky/ui2/components/welcome_banner.rb +1 -1
  22. data/lib/clacky/utils/workspace_rules.rb +2 -2
  23. data/lib/clacky/version.rb +1 -1
  24. data/lib/clacky/web/app.css +230 -46
  25. data/lib/clacky/web/core/ext.js +130 -24
  26. data/lib/clacky/web/features/extensions/view.js +4 -3
  27. data/lib/clacky/web/features/new-session/store.js +13 -0
  28. data/lib/clacky/web/features/new-session/view.js +314 -14
  29. data/lib/clacky/web/features/trash/view.js +4 -5
  30. data/lib/clacky/web/i18n.js +34 -0
  31. data/lib/clacky/web/index.html +43 -11
  32. data/lib/clacky/web/sessions.js +3 -3
  33. data/lib/clacky/web/skills.js +86 -48
  34. data/lib/clacky/web/theme.js +3 -0
  35. data/lib/clacky/web/ws-dispatcher.js +11 -2
  36. metadata +7 -7
@@ -30,6 +30,25 @@ const SkillAC = (() => {
30
30
 
31
31
  let _ime = null; // IME tracker for #user-input, set up in _initDOMBindings
32
32
 
33
+ // The active DOM/behavior config. Defaults to the chat composer; `attach()`
34
+ // can swap in a different set of element ids (e.g. the new-session page).
35
+ // `fetchSkills` returns the skill list; `onSend` fires on bare Enter.
36
+ const _chatCfg = {
37
+ input: "user-input",
38
+ dropdown: "skill-autocomplete",
39
+ list: "skill-autocomplete-list",
40
+ slashBtn: "btn-slash",
41
+ systemChk: "chk-ac-show-system-skills",
42
+ fetchSkills: null, // null → use session-scoped default fetch
43
+ onSend: () => Sessions.sendMessage(),
44
+ };
45
+ let _cfg = _chatCfg;
46
+
47
+ const _inputEl = () => $(_cfg.input);
48
+ const _dropdownEl = () => $(_cfg.dropdown);
49
+ const _listEl = () => $(_cfg.list);
50
+ const _slashBtnEl = () => $(_cfg.slashBtn);
51
+
33
52
  /** Called whenever the active session changes — just store the id, no prefetch. */
34
53
  function _loadForSession(sessionId) {
35
54
  _currentSession = sessionId || null;
@@ -37,6 +56,14 @@ const SkillAC = (() => {
37
56
 
38
57
  /** Fetch live skill list from server for the current session. */
39
58
  async function _fetchSkills() {
59
+ if (_cfg.fetchSkills) {
60
+ try {
61
+ return (await _cfg.fetchSkills()) || [];
62
+ } catch (e) {
63
+ console.error("[SkillAC] custom fetchSkills failed", e);
64
+ return [];
65
+ }
66
+ }
40
67
  if (!_currentSession) return [];
41
68
  try {
42
69
  const res = await fetch(`/api/sessions/${_currentSession}/skills`);
@@ -130,7 +157,7 @@ const SkillAC = (() => {
130
157
 
131
158
  _items = scored.map(({ skill }) => skill);
132
159
 
133
- const list = $("skill-autocomplete-list");
160
+ const list = _listEl();
134
161
  list.innerHTML = "";
135
162
 
136
163
  if (_items.length === 0) {
@@ -139,7 +166,7 @@ const SkillAC = (() => {
139
166
  emptyEl.className = "skill-ac-empty";
140
167
  emptyEl.textContent = I18n.t("skills.ac.empty");
141
168
  list.appendChild(emptyEl);
142
- $("skill-autocomplete").style.display = "";
169
+ _dropdownEl().style.display = "";
143
170
  _visible = true;
144
171
  _createOverlay();
145
172
  return;
@@ -222,17 +249,17 @@ const SkillAC = (() => {
222
249
  list.appendChild(item);
223
250
  });
224
251
 
225
- $("skill-autocomplete").style.display = "";
252
+ _dropdownEl().style.display = "";
226
253
  _visible = true;
227
254
  _createOverlay();
228
255
  }
229
256
 
230
257
  function _hide() {
231
- $("skill-autocomplete").style.display = "none";
258
+ _dropdownEl().style.display = "none";
232
259
  _visible = false;
233
260
  _activeIndex = -1;
234
261
  _items = [];
235
- $("btn-slash")?.classList.remove("active");
262
+ _slashBtnEl()?.classList.remove("active");
236
263
  _removeOverlay();
237
264
  }
238
265
 
@@ -260,7 +287,7 @@ const SkillAC = (() => {
260
287
  function _select(idx) {
261
288
  const skill = _items[idx];
262
289
  if (!skill) return;
263
- const input = $("user-input");
290
+ const input = _inputEl();
264
291
  input.value = "/" + skill.name + " ";
265
292
  input.style.height = "auto";
266
293
  input.style.height = Math.min(input.scrollHeight, 200) + "px";
@@ -272,7 +299,7 @@ const SkillAC = (() => {
272
299
  if (!_visible || _items.length === 0) return;
273
300
  _activeIndex = (_activeIndex + delta + _items.length) % _items.length;
274
301
  // Re-render to apply active class
275
- const list = $("skill-autocomplete-list");
302
+ const list = _listEl();
276
303
  list.querySelectorAll(".skill-ac-item").forEach((el, i) => {
277
304
  el.classList.toggle("active", i === _activeIndex);
278
305
  if (i === _activeIndex) el.scrollIntoView({ block: "nearest" });
@@ -283,7 +310,7 @@ const SkillAC = (() => {
283
310
  async function _openAll() {
284
311
  _activeIndex = 0; // Default to first item
285
312
  await _render("");
286
- $("user-input").focus();
313
+ _inputEl().focus();
287
314
  }
288
315
 
289
316
  /** Toggle the dropdown (open if hidden, close if visible). */
@@ -297,16 +324,17 @@ const SkillAC = (() => {
297
324
 
298
325
  // ── DOM bindings: composer keyboard/composition/input + slash button + ────
299
326
  // ── skill-panel create/import buttons. Called once from init(). ──
300
- function _initDOMBindings() {
327
+ function _initDOMBindings(cfg) {
301
328
  // / button: set input to "/" and open skill autocomplete.
302
329
  // mousedown + preventDefault prevents the textarea from losing focus
303
330
  // (which would trigger the blur→hide timer and immediately close
304
331
  // the dropdown we're about to open).
305
- $("btn-slash").addEventListener("mousedown", e => {
306
- e.preventDefault(); // keep focus on user-input
332
+ $(cfg.slashBtn).addEventListener("mousedown", e => {
333
+ e.preventDefault(); // keep focus on the input
307
334
  });
308
- $("btn-slash").addEventListener("click", () => {
309
- const input = $("user-input");
335
+ $(cfg.slashBtn).addEventListener("click", () => {
336
+ _cfg = cfg;
337
+ const input = $(cfg.input);
310
338
  if (input.value === "" || input.value === "/") {
311
339
  input.value = "/";
312
340
  input.style.height = "auto";
@@ -314,28 +342,31 @@ const SkillAC = (() => {
314
342
  }
315
343
  _toggle(); // Toggle dropdown instead of always opening
316
344
  if (_visible) {
317
- $("btn-slash").classList.add("active");
345
+ $(cfg.slashBtn).classList.add("active");
318
346
  }
319
347
  input.focus();
320
348
  });
321
349
 
322
350
  // IME composition tracker: shared by main keydown + AC _handleKey.
323
- _ime = IME.track($("user-input"));
351
+ const ime = IME.track($(cfg.input));
324
352
 
325
353
  // Main composer keydown: SkillAC consumes nav keys first, then Enter → send.
326
- $("user-input").addEventListener("keydown", e => {
354
+ $(cfg.input).addEventListener("keydown", e => {
355
+ _cfg = cfg;
356
+ _ime = ime;
327
357
  // Let skill autocomplete consume arrow/enter/escape first
328
358
  if (_handleKey(e)) return;
329
359
 
330
- if (e.key === "Enter" && !e.shiftKey && !_ime.isComposing(e)) {
360
+ if (e.key === "Enter" && !e.shiftKey && !ime.isComposing(e)) {
331
361
  e.preventDefault();
332
- Sessions.sendMessage();
362
+ cfg.onSend();
333
363
  }
334
364
  });
335
365
 
336
366
  // Composer input: auto-grow textarea, normalize full-width slash, drive AC.
337
- $("user-input").addEventListener("input", () => {
338
- const el = $("user-input");
367
+ $(cfg.input).addEventListener("input", () => {
368
+ _cfg = cfg;
369
+ const el = $(cfg.input);
339
370
  el.style.height = "auto";
340
371
  el.style.height = Math.min(el.scrollHeight, 200) + "px";
341
372
 
@@ -350,11 +381,25 @@ const SkillAC = (() => {
350
381
  _update(el.value);
351
382
  });
352
383
 
353
- // Skills panel action buttons (domain belongs to Skills, but the
354
- // bindings live here because this is where all composer/skill-related
355
- // DOM wiring was historically colocated).
356
- $("btn-create-skill").addEventListener("click", () => Skills.createInSession());
357
- $("btn-import-skill").addEventListener("click", () => Skills.toggleImportBar());
384
+ const chk = $(cfg.systemChk);
385
+ if (chk) {
386
+ chk.checked = _showSystemSkills;
387
+ chk.addEventListener("change", async () => {
388
+ _cfg = cfg;
389
+ _showSystemSkills = chk.checked;
390
+ localStorage.setItem("skill-ac-show-system", _showSystemSkills ? "true" : "false");
391
+ if (_visible) {
392
+ const query = _getSlashQuery($(cfg.input).value);
393
+ if (query !== null) await _render(query);
394
+ }
395
+ });
396
+ }
397
+
398
+ // Skills panel action buttons only exist in the chat composer.
399
+ if (cfg === _chatCfg) {
400
+ $("btn-create-skill").addEventListener("click", () => Skills.createInSession());
401
+ $("btn-import-skill").addEventListener("click", () => Skills.toggleImportBar());
402
+ }
358
403
  }
359
404
 
360
405
  // Update handler — driven from the input event above. Exposed on the
@@ -407,30 +452,23 @@ const SkillAC = (() => {
407
452
  if (_initialized) return;
408
453
  _initialized = true;
409
454
 
410
- const chk = $("chk-ac-show-system-skills");
411
-
412
- if (chk) {
413
- // Restore state from localStorage
414
- chk.checked = _showSystemSkills;
415
-
416
- chk.addEventListener("change", async () => {
417
- _showSystemSkills = chk.checked;
418
- // Persist to localStorage
419
- localStorage.setItem("skill-ac-show-system", _showSystemSkills ? "true" : "false");
420
-
421
- // If dropdown is visible, re-fetch and re-render
422
- if (_visible) {
423
- const input = $("user-input");
424
- const query = _getSlashQuery(input.value);
425
- if (query !== null) {
426
- await _render(query);
427
- }
428
- }
429
- });
430
- }
455
+ // Wire up all composer/slash DOM bindings for the chat composer.
456
+ _initDOMBindings(_chatCfg);
457
+ },
431
458
 
432
- // Wire up all composer/slash DOM bindings.
433
- _initDOMBindings();
459
+ /**
460
+ * Attach the autocomplete to a second composer (e.g. the new-session page).
461
+ * `config` overrides element ids and provides `fetchSkills` / `onSend`.
462
+ */
463
+ attach(config) {
464
+ const cfg = Object.assign({}, _chatCfg, config);
465
+ _initDOMBindings(cfg);
466
+ return {
467
+ /** Programmatic input handler (call from the input event if needed). */
468
+ update: (value) => { _cfg = cfg; _update(value); },
469
+ hide: _hide,
470
+ get visible() { return _visible; },
471
+ };
434
472
  },
435
473
 
436
474
  /** Called on every `input` event — decide whether to show/hide/update. */
@@ -77,6 +77,9 @@ const Theme = (() => {
77
77
  if (mq.addEventListener) mq.addEventListener("change", onChange);
78
78
  else if (mq.addListener) mq.addListener(onChange); // Safari < 14
79
79
  }
80
+
81
+ // Re-apply title text when language changes.
82
+ document.addEventListener("langchange", () => _updateToggleIcon(current()));
80
83
  }
81
84
 
82
85
  // Explicit apply (used by toggle). Persists the choice unless it equals
@@ -216,8 +216,17 @@ WS.onEvent(ev => {
216
216
  // send it now — after restoreFromHash has settled — so appendMsg won't be wiped.
217
217
  const pendingMsg = Sessions.takePendingMessage();
218
218
  if (pendingMsg && pendingMsg.session_id === ev.session_id) {
219
- Sessions.appendMsg("user", escapeHtml(pendingMsg.display || pendingMsg.content), { time: new Date() });
220
- WS.send({ type: "message", session_id: pendingMsg.session_id, content: pendingMsg.content, lang: I18n.lang() });
219
+ const html = pendingMsg.files && pendingMsg.files.length
220
+ ? (pendingMsg.display || escapeHtml(pendingMsg.content))
221
+ : escapeHtml(pendingMsg.display || pendingMsg.content);
222
+ Sessions.appendMsg("user", html, { time: new Date() });
223
+ WS.send({
224
+ type: "message",
225
+ session_id: pendingMsg.session_id,
226
+ content: pendingMsg.content,
227
+ files: pendingMsg.files || undefined,
228
+ lang: I18n.lang(),
229
+ });
221
230
  }
222
231
  break;
223
232
  }
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: openclacky
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.7
4
+ version: 1.3.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - windy
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2026-07-06 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: faraday
@@ -284,8 +283,8 @@ email:
284
283
  - yafei@dao42.com
285
284
  executables:
286
285
  - clacky
287
- - openclacky
288
286
  - clarky
287
+ - openclacky
289
288
  extensions: []
290
289
  extra_rdoc_files: []
291
290
  files:
@@ -364,8 +363,10 @@ files:
364
363
  - lib/clacky/brand_config.rb
365
364
  - lib/clacky/cli.rb
366
365
  - lib/clacky/client.rb
366
+ - lib/clacky/default_extensions/coding/agents/coding/avatar.png
367
367
  - lib/clacky/default_extensions/coding/agents/coding/system_prompt.md
368
368
  - lib/clacky/default_extensions/coding/ext.yml
369
+ - lib/clacky/default_extensions/ext-studio/agents/ext-developer/avatar.png
369
370
  - lib/clacky/default_extensions/ext-studio/agents/ext-developer/system_prompt.md
370
371
  - lib/clacky/default_extensions/ext-studio/api/handler.rb
371
372
  - lib/clacky/default_extensions/ext-studio/ext.yml
@@ -373,6 +374,7 @@ files:
373
374
  - lib/clacky/default_extensions/ext-studio/skills/ext-debug/SKILL.md
374
375
  - lib/clacky/default_extensions/ext-studio/skills/ext-publish/SKILL.md
375
376
  - lib/clacky/default_extensions/ext-studio/skills/ext-scaffold/SKILL.md
377
+ - lib/clacky/default_extensions/general/agents/general/avatar.png
376
378
  - lib/clacky/default_extensions/general/agents/general/system_prompt.md
377
379
  - lib/clacky/default_extensions/general/ext.yml
378
380
  - lib/clacky/default_extensions/git/ext.yml
@@ -728,7 +730,6 @@ metadata:
728
730
  homepage_uri: https://github.com/clacky-ai/openclacky
729
731
  source_code_uri: https://github.com/clacky-ai/openclacky
730
732
  changelog_uri: https://github.com/clacky-ai/openclacky/blob/main/CHANGELOG.md
731
- post_install_message:
732
733
  rdoc_options: []
733
734
  require_paths:
734
735
  - lib
@@ -746,8 +747,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
746
747
  - !ruby/object:Gem::Version
747
748
  version: '0'
748
749
  requirements: []
749
- rubygems_version: 3.5.22
750
- signing_key:
750
+ rubygems_version: 3.6.9
751
751
  specification_version: 4
752
752
  summary: The most Token-efficient open-source AI Agent — BYOK, Skill-driven, IM-integrated.
753
753
  test_files: []