openclacky 1.3.2 → 1.3.3

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 (76) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +28 -0
  3. data/Dockerfile +3 -0
  4. data/README.md +1 -1
  5. data/README_JA.md +237 -0
  6. data/lib/clacky/agent/session_serializer.rb +49 -5
  7. data/lib/clacky/agent/time_machine.rb +247 -26
  8. data/lib/clacky/agent.rb +12 -1
  9. data/lib/clacky/agent_config.rb +14 -2
  10. data/lib/clacky/default_agents/_panels/git/panel.js +201 -0
  11. data/lib/clacky/default_agents/_panels/time_machine/panel.js +640 -0
  12. data/lib/clacky/default_agents/coding/profile.yml +3 -0
  13. data/lib/clacky/default_agents/coding/webui/.gitkeep +0 -0
  14. data/lib/clacky/default_skills/cron-task-creator/SKILL.md +1 -1
  15. data/lib/clacky/default_skills/extend-openclacky/SKILL.md +6 -4
  16. data/lib/clacky/default_skills/media-gen/SKILL.md +30 -6
  17. data/lib/clacky/media/openai_compat.rb +64 -1
  18. data/lib/clacky/media/output_dir.rb +43 -0
  19. data/lib/clacky/message_history.rb +9 -0
  20. data/lib/clacky/server/channel/channel_manager.rb +26 -0
  21. data/lib/clacky/server/git_panel.rb +115 -0
  22. data/lib/clacky/server/http_server.rb +497 -12
  23. data/lib/clacky/server/server_master.rb +6 -4
  24. data/lib/clacky/version.rb +1 -1
  25. data/lib/clacky/web/app.css +473 -60
  26. data/lib/clacky/web/app.js +30 -7
  27. data/lib/clacky/web/components/code-editor.js +197 -0
  28. data/lib/clacky/web/{notify.js → components/notify.js} +1 -1
  29. data/lib/clacky/web/core/aside.js +112 -0
  30. data/lib/clacky/web/core/ext.js +387 -0
  31. data/lib/clacky/web/features/backup/store.js +92 -0
  32. data/lib/clacky/web/features/backup/view.js +94 -0
  33. data/lib/clacky/web/features/billing/store.js +163 -0
  34. data/lib/clacky/web/{billing.js → features/billing/view.js} +132 -240
  35. data/lib/clacky/web/features/brand/store.js +110 -0
  36. data/lib/clacky/web/{brand.js → features/brand/view.js} +49 -199
  37. data/lib/clacky/web/features/channels/store.js +103 -0
  38. data/lib/clacky/web/{channels.js → features/channels/view.js} +50 -127
  39. data/lib/clacky/web/features/creator/store.js +81 -0
  40. data/lib/clacky/web/{creator.js → features/creator/view.js} +53 -102
  41. data/lib/clacky/web/features/mcp/store.js +158 -0
  42. data/lib/clacky/web/{mcp.js → features/mcp/view.js} +57 -134
  43. data/lib/clacky/web/features/model-tester/store.js +77 -0
  44. data/lib/clacky/web/features/model-tester/view.js +7 -0
  45. data/lib/clacky/web/features/profile/store.js +170 -0
  46. data/lib/clacky/web/{profile.js → features/profile/view.js} +94 -144
  47. data/lib/clacky/web/features/share/store.js +145 -0
  48. data/lib/clacky/web/{share.js → features/share/view.js} +66 -202
  49. data/lib/clacky/web/features/skills/store.js +303 -0
  50. data/lib/clacky/web/features/skills/view.js +550 -0
  51. data/lib/clacky/web/features/tasks/store.js +135 -0
  52. data/lib/clacky/web/features/tasks/view.js +241 -0
  53. data/lib/clacky/web/features/trash/store.js +242 -0
  54. data/lib/clacky/web/{trash.js → features/trash/view.js} +102 -293
  55. data/lib/clacky/web/features/version/store.js +165 -0
  56. data/lib/clacky/web/features/version/view.js +323 -0
  57. data/lib/clacky/web/features/workspace/store.js +99 -0
  58. data/lib/clacky/web/features/workspace/view.js +305 -0
  59. data/lib/clacky/web/i18n.js +56 -6
  60. data/lib/clacky/web/index.html +117 -58
  61. data/lib/clacky/web/sessions.js +221 -25
  62. data/lib/clacky/web/settings.js +118 -22
  63. data/lib/clacky/web/skills.js +3 -863
  64. data/lib/clacky/web/vendor/codemirror/codemirror.min.js +29 -0
  65. data/lib/clacky.rb +1 -0
  66. metadata +45 -20
  67. data/lib/clacky/web/backup.js +0 -119
  68. data/lib/clacky/web/model-tester.js +0 -66
  69. data/lib/clacky/web/tasks.js +0 -373
  70. data/lib/clacky/web/version.js +0 -449
  71. data/lib/clacky/web/workspace.js +0 -316
  72. /data/lib/clacky/web/{notify.mp3 → assets/notify.mp3} +0 -0
  73. /data/lib/clacky/web/{datepicker.js → components/datepicker.js} +0 -0
  74. /data/lib/clacky/web/{onboard.js → components/onboard.js} +0 -0
  75. /data/lib/clacky/web/{sidebar.js → components/sidebar.js} +0 -0
  76. /data/lib/clacky/web/{marked.min.js → vendor/marked/marked.min.js} +0 -0
@@ -1,449 +0,0 @@
1
- // ── Version — version check and upgrade flow ───────────────────────────────
2
- //
3
- // Badge states:
4
- // (none) → up-to-date, muted version text
5
- // has-update → amber pulsing dot: new version available
6
- // is-upgrading → spinning ring: gem install in progress
7
- // needs-restart → orange bouncing dot: upgrade done, waiting for restart
8
- // upgrade-done → green check: restarted & reconnected successfully
9
- //
10
- // Flow:
11
- // 1. Page load → checkVersion() → badge shows version number
12
- // 2. needs_update: badge shows amber pulsing dot
13
- // 3. Click badge → fixed popover (confirm state)
14
- // 4. Click "Upgrade" → popover → progress state (live log)
15
- // 5. upgrade_complete (success) → badge: needs-restart; popover: restart button
16
- // 6. Click "Restart" → /api/restart → popover: reconnecting spinner
17
- // → poll /api/version until server back → badge: upgrade-done (green ✓) → reload
18
- // ─────────────────────────────────────────────────────────────────────────
19
-
20
- const Version = (() => {
21
- // ── State ──────────────────────────────────────────────────────────────
22
- let _current = null;
23
- let _latest = null;
24
- let _needsUpdate = false;
25
- let _upgrading = false;
26
- let _needsRestart = false; // upgrade done, waiting for restart
27
- let _reconnecting = false; // restart sent, polling for server to come back
28
- let _upgradeDone = false; // restarted and reconnected successfully
29
- let _restartFailed = false; // 30s passed, server still not responding
30
- let _popoverOpen = false;
31
- let _reconnectTimer = null;
32
- let _reconnectDeadline = 0;
33
- let _logLines = [];
34
- let _cliCommand = "openclacky";
35
-
36
- const RECONNECT_TIMEOUT_MS = 30_000;
37
-
38
- // ── DOM helpers ────────────────────────────────────────────────────────
39
- const $ = id => document.getElementById(id);
40
- const el = (tag, attrs = {}, ...children) => {
41
- const e = document.createElement(tag);
42
- Object.entries(attrs).forEach(([k, v]) => {
43
- if (k === "className") e.className = v;
44
- else if (k === "innerHTML") e.innerHTML = v;
45
- else e.setAttribute(k, v);
46
- });
47
- children.forEach(c => c && e.appendChild(typeof c === "string" ? document.createTextNode(c) : c));
48
- return e;
49
- };
50
-
51
- // ── Version check ──────────────────────────────────────────────────────
52
- async function checkVersion() {
53
- try {
54
- const res = await fetch("/api/version");
55
- if (!res.ok) return;
56
- const data = await res.json();
57
- _current = data.current;
58
- _latest = data.latest;
59
- _needsUpdate = !!data.needs_update;
60
- if (data.cli_command) _cliCommand = data.cli_command;
61
- _renderBadge();
62
- } catch (e) {
63
- console.warn("[Version] check failed:", e);
64
- }
65
- }
66
-
67
- // ── Badge render ───────────────────────────────────────────────────────
68
- function _renderBadge() {
69
- const badge = $("version-badge");
70
- const text = $("version-text");
71
- const dot = $("version-update-dot");
72
- const restartDot = $("version-restart-dot");
73
- const check = $("version-done-check");
74
- const spinner = $("version-spinner");
75
- if (!badge || !text) return;
76
-
77
- text.textContent = _current ? `v${_current}` : "";
78
-
79
- // Reset all indicators
80
- if (dot) dot.style.display = "none";
81
- if (restartDot) restartDot.style.display = "none";
82
- if (check) check.style.display = "none";
83
- if (spinner) spinner.style.display = "none";
84
- badge.className = "version-badge";
85
-
86
- if (_upgrading) {
87
- // Spinning ring: gem install running
88
- badge.classList.add("is-upgrading");
89
- badge.title = I18n.t("upgrade.tooltip.upgrading");
90
- if (spinner) spinner.style.display = "inline-block";
91
- } else if (_needsRestart) {
92
- // Orange bouncing dot: upgrade done, please restart
93
- badge.classList.add("needs-restart");
94
- badge.title = I18n.t("upgrade.tooltip.needs_restart");
95
- if (restartDot) restartDot.style.display = "inline-block";
96
- } else if (_upgradeDone) {
97
- // Green check: restarted successfully
98
- badge.classList.add("upgrade-done");
99
- badge.title = I18n.t("upgrade.tooltip.done");
100
- if (check) check.style.display = "inline-block";
101
- } else if (_needsUpdate) {
102
- // Amber pulsing dot: new version available
103
- badge.classList.add("has-update");
104
- badge.title = I18n.t("upgrade.tooltip.new", { latest: _latest });
105
- if (dot) dot.style.display = "inline-block";
106
- } else {
107
- badge.title = I18n.t("upgrade.tooltip.ok", { current: _current });
108
- }
109
-
110
- badge.style.display = "flex";
111
- }
112
-
113
- // ── Popover (fixed, positioned above badge) ────────────────────────────
114
- function _getOrCreatePopover() {
115
- let pop = $("version-upgrade-popover");
116
- if (pop) return pop;
117
-
118
- pop = el("div", { id: "version-upgrade-popover", className: "vup" });
119
- document.body.appendChild(pop);
120
- return pop;
121
- }
122
-
123
- function _positionPopover() {
124
- const badge = $("version-badge");
125
- const pop = $("version-upgrade-popover");
126
- if (!badge || !pop) return;
127
-
128
- const rect = badge.getBoundingClientRect();
129
- // Appear above the badge, right-aligned to sidebar edge
130
- pop.style.left = rect.left + "px";
131
- pop.style.bottom = (window.innerHeight - rect.top + 8) + "px";
132
- pop.style.top = "auto";
133
- }
134
-
135
- function _openPopover() {
136
- if (_popoverOpen) { _positionPopover(); return; }
137
- _popoverOpen = true;
138
-
139
- const pop = _getOrCreatePopover();
140
- pop.innerHTML = "";
141
-
142
- if (_restartFailed) {
143
- _renderRestartFailedState(pop);
144
- } else if (_reconnecting) {
145
- _renderReconnectState(pop);
146
- } else if (_upgrading) {
147
- _renderProgressState(pop);
148
- } else if (_needsRestart) {
149
- _renderDoneState(pop);
150
- } else if (_upgradeDone) {
151
- _renderDoneState(pop);
152
- } else if (_needsUpdate) {
153
- _renderConfirmState(pop);
154
- } else {
155
- _renderUpToDateState(pop);
156
- }
157
-
158
- pop.style.display = "block";
159
- _positionPopover();
160
-
161
- // Animate in
162
- requestAnimationFrame(() => pop.classList.add("vup--visible"));
163
- }
164
-
165
- function _closePopover() {
166
- // Don't allow closing while upgrading or waiting for server to come back
167
- if (_upgrading || _reconnecting) return;
168
- const pop = $("version-upgrade-popover");
169
- if (!pop) return;
170
- pop.classList.remove("vup--visible");
171
- setTimeout(() => {
172
- pop.style.display = "none";
173
- _popoverOpen = false;
174
- }, 180);
175
- }
176
-
177
- // ── Popover states ─────────────────────────────────────────────────────
178
-
179
- /** State 0: already up to date */
180
- function _renderUpToDateState(pop) {
181
- pop.innerHTML = `
182
- <p class="vup-up-to-date">
183
- <span class="vup-check-icon">✓</span>
184
- ${I18n.t("upgrade.tooltip.ok", { current: _current })}
185
- </p>
186
- `;
187
- // Auto-close after 2 s so user doesn't need to click away
188
- setTimeout(() => { if (_popoverOpen) _closePopover(); }, 2000);
189
- }
190
-
191
- /** State 1: confirm upgrade */
192
- function _renderConfirmState(pop) {
193
- pop.innerHTML = `
194
- <p class="vup-desc">${I18n.t("upgrade.desc")}</p>
195
- <p class="vup-versions">v${_current} <span class="vup-arrow">→</span> v${_latest}</p>
196
- <div class="vup-actions">
197
- <button id="vup-btn-upgrade" class="vup-btn-primary">${I18n.t("upgrade.btn.upgrade")}</button>
198
- <button id="vup-btn-cancel" class="vup-btn-cancel">${I18n.t("upgrade.btn.cancel")}</button>
199
- </div>
200
- `;
201
- $("vup-btn-upgrade").addEventListener("click", () => _startUpgrade(pop));
202
- $("vup-btn-cancel").addEventListener("click", _closePopover);
203
- }
204
-
205
- /** State 2: upgrading — show live log */
206
- function _renderProgressState(pop) {
207
- pop.innerHTML = `
208
- <div class="vup-progress-header">
209
- <span class="vup-installing-dot"></span>
210
- <span class="vup-installing-label">${I18n.t("upgrade.installing")}</span>
211
- </div>
212
- <pre id="vup-log" class="vup-log"></pre>
213
- `;
214
- // Replay any logs already received
215
- const logEl = $("vup-log");
216
- if (logEl && _logLines.length) {
217
- logEl.textContent = _logLines.join("\n");
218
- logEl.scrollTop = logEl.scrollHeight;
219
- }
220
- }
221
-
222
- /** State 3: done — show restart button */
223
- function _renderDoneState(pop) {
224
- pop.innerHTML = `
225
- <div class="vup-done-header">
226
- <span class="vup-done-icon">✓</span>
227
- <span>${I18n.t("upgrade.done")}</span>
228
- </div>
229
- <button id="vup-btn-restart" class="vup-btn-restart">${I18n.t("upgrade.btn.restart")}</button>
230
- `;
231
- $("vup-btn-restart").addEventListener("click", _startRestart);
232
- }
233
-
234
- /** State 4: reconnecting after restart */
235
- function _renderReconnectState(pop) {
236
- pop.innerHTML = `
237
- <div class="vup-reconnect">
238
- <div class="vup-reconnect-spinner"></div>
239
- <p class="vup-reconnect-msg">${I18n.t("upgrade.reconnecting")}</p>
240
- </div>
241
- `;
242
- }
243
-
244
- /** State 5: restart timed out — show both recovery paths (tray + CLI) */
245
- function _renderRestartFailedState(pop) {
246
- const safeCmd = String(_cliCommand).replace(/[&<>"']/g, c => (
247
- { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" }[c]
248
- ));
249
- const cmd = `<code class="vup-cmd">${safeCmd} server</code>`;
250
- pop.innerHTML = `
251
- <div class="vup-restart-failed">
252
- <p class="vup-restart-failed-title">⚠ ${I18n.t("upgrade.restart.timeout.title")}</p>
253
- <p class="vup-restart-failed-desc">${I18n.t("upgrade.restart.timeout.desc")}</p>
254
- <ul class="vup-restart-failed-options">
255
- <li>${I18n.t("upgrade.restart.timeout.tray")}</li>
256
- <li>${I18n.t("upgrade.restart.timeout.cli", { cmd })}</li>
257
- </ul>
258
- <div class="vup-actions">
259
- <button id="vup-btn-retry" class="vup-btn-primary">${I18n.t("upgrade.restart.timeout.retry")}</button>
260
- </div>
261
- </div>
262
- `;
263
- const retry = $("vup-btn-retry");
264
- if (retry) retry.addEventListener("click", _retryReconnect);
265
- }
266
-
267
- // ── Upgrade ────────────────────────────────────────────────────────────
268
- async function _startUpgrade(pop) {
269
- if (_upgrading || _upgradeDone) return;
270
- _upgrading = true;
271
- _logLines = [];
272
- _renderBadge();
273
- _renderProgressState(pop);
274
-
275
- try {
276
- await fetch("/api/version/upgrade", { method: "POST" });
277
- } catch (e) {
278
- console.warn("[Version] upgrade request failed:", e);
279
- _upgrading = false;
280
- _renderBadge();
281
- }
282
- }
283
-
284
- // ── Restart ────────────────────────────────────────────────────────────
285
- async function _startRestart() {
286
- _reconnecting = true;
287
-
288
- // Ensure popover is open and showing the reconnect spinner
289
- const pop = _getOrCreatePopover();
290
- _renderReconnectState(pop);
291
- if (!_popoverOpen) {
292
- _popoverOpen = true;
293
- pop.style.display = "block";
294
- _positionPopover();
295
- requestAnimationFrame(() => pop.classList.add("vup--visible"));
296
- }
297
-
298
- try {
299
- fetch("/api/restart", { method: "POST" }).catch(() => {});
300
- } catch (_) {}
301
-
302
- _waitForReconnect();
303
- }
304
-
305
- function _waitForReconnect() {
306
- if (_reconnectTimer) clearInterval(_reconnectTimer);
307
- _reconnectDeadline = Date.now() + RECONNECT_TIMEOUT_MS;
308
- setTimeout(() => {
309
- _reconnectTimer = setInterval(async () => {
310
- if (Date.now() > _reconnectDeadline) {
311
- clearInterval(_reconnectTimer);
312
- _reconnectTimer = null;
313
- _reconnecting = false;
314
- _restartFailed = true;
315
- _renderBadge();
316
- const pop = $("version-upgrade-popover");
317
- if (pop && _popoverOpen) _renderRestartFailedState(pop);
318
- return;
319
- }
320
- try {
321
- const res = await fetch("/api/version", { cache: "no-store" });
322
- if (res.ok) {
323
- clearInterval(_reconnectTimer);
324
- _reconnectTimer = null;
325
- // Server is back — close popover, badge → green check, then reload
326
- _reconnecting = false;
327
- _needsRestart = false;
328
- _upgradeDone = true;
329
- _renderBadge();
330
- _closePopover();
331
- setTimeout(() => window.location.reload(), 800);
332
- }
333
- } catch (_) { /* server not yet up */ }
334
- }, 2000);
335
- }, 2500);
336
- }
337
-
338
- function _retryReconnect() {
339
- _restartFailed = false;
340
- _reconnecting = true;
341
- const pop = $("version-upgrade-popover");
342
- if (pop && _popoverOpen) _renderReconnectState(pop);
343
- _renderBadge();
344
- _waitForReconnect();
345
- }
346
-
347
- // ── WebSocket events ───────────────────────────────────────────────────
348
- function _handleWsEvent(event) {
349
- if (event.type === "upgrade_log") {
350
- const line = event.line || "";
351
- _logLines.push(line);
352
- // Append to live log if popover is open
353
- const logEl = $("vup-log");
354
- if (logEl) {
355
- logEl.textContent += (logEl.textContent ? "\n" : "") + line;
356
- logEl.scrollTop = logEl.scrollHeight;
357
- }
358
- } else if (event.type === "upgrade_complete") {
359
- _upgrading = false;
360
- if (event.success) {
361
- _needsUpdate = false;
362
- _needsRestart = true; // badge: orange bouncing dot
363
- _upgradeDone = false;
364
- }
365
- // On failure, _needsUpdate stays true so badge stays amber
366
- _renderBadge();
367
- // Morph popover to done/error state
368
- const pop = $("version-upgrade-popover");
369
- if (pop && _popoverOpen) {
370
- if (event.success) {
371
- _renderDoneState(pop);
372
- } else {
373
- pop.innerHTML = `<p class="vup-error">${I18n.t("upgrade.failed")}</p>`;
374
- }
375
- }
376
- }
377
- }
378
-
379
- // ── Init ───────────────────────────────────────────────────────────────
380
- let _hoverTimer = null;
381
-
382
- function init() {
383
- const badge = $("version-badge");
384
- if (badge) {
385
- // Click still works (e.g. during reconnect to keep popover visible)
386
- badge.addEventListener("click", e => {
387
- e.stopPropagation();
388
- if (_reconnecting) { if (!_popoverOpen) _openPopover(); return; }
389
- });
390
-
391
- // Hover to open
392
- badge.addEventListener("mouseenter", () => {
393
- if (!_current) return;
394
- clearTimeout(_hoverTimer);
395
- _openPopover();
396
- });
397
-
398
- // Leave badge or popover → close (with small delay so mouse can move to popover)
399
- badge.addEventListener("mouseleave", () => {
400
- _hoverTimer = setTimeout(() => {
401
- const pop = $("version-upgrade-popover");
402
- if (pop && pop.matches(":hover")) return;
403
- _closePopover();
404
- }, 200);
405
- });
406
- }
407
-
408
- // Keep popover open while hovering it; close when leaving
409
- document.addEventListener("mouseover", e => {
410
- const pop = $("version-upgrade-popover");
411
- if (pop && e.target.closest("#version-upgrade-popover")) {
412
- clearTimeout(_hoverTimer);
413
- }
414
- });
415
- document.addEventListener("mouseout", e => {
416
- const pop = $("version-upgrade-popover");
417
- if (!pop) return;
418
- if (e.target.closest("#version-upgrade-popover") && !e.relatedTarget?.closest("#version-upgrade-popover") && !e.relatedTarget?.closest("#version-badge")) {
419
- _hoverTimer = setTimeout(() => _closePopover(), 200);
420
- }
421
- });
422
-
423
- // Click outside still closes (e.g. keyboard users, edge cases)
424
- document.addEventListener("click", e => {
425
- if (!e.target.closest("#version-badge") && !e.target.closest("#version-upgrade-popover")) {
426
- if (_popoverOpen && !_upgrading && !_reconnecting) _closePopover();
427
- }
428
- });
429
-
430
- // Reposition on window resize
431
- window.addEventListener("resize", () => {
432
- if (_popoverOpen) _positionPopover();
433
- });
434
-
435
- if (typeof WS !== "undefined") {
436
- WS.onEvent(_handleWsEvent);
437
- }
438
-
439
- checkVersion();
440
- }
441
-
442
- if (document.readyState === "loading") {
443
- document.addEventListener("DOMContentLoaded", init);
444
- } else {
445
- init();
446
- }
447
-
448
- return { checkVersion };
449
- })();