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,316 +0,0 @@
1
- // Workspace panel — lazy file tree for the active session's working directory.
2
- // Lists one directory level at a time via GET /api/sessions/:id/files,
3
- // expands/collapses folders in place, and downloads files on click via
4
- // POST /api/file-action.
5
- "use strict";
6
-
7
- const Workspace = (() => {
8
- const STORAGE_KEY = "clacky.workspace.open";
9
-
10
- let _sessionId = null;
11
- let _workingDir = null;
12
- let _open = false;
13
-
14
- const $ = (id) => document.getElementById(id);
15
- const t = (key) => (typeof I18n !== "undefined" ? I18n.t(key) : key);
16
-
17
- const ICON_FOLDER = '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg>';
18
- const ICON_FILE = '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>';
19
- const ICON_CARET = '<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"/></svg>';
20
-
21
- function formatSize(bytes) {
22
- if (bytes == null) return "";
23
- if (bytes < 1024) return `${bytes} B`;
24
- const units = ["KB", "MB", "GB", "TB"];
25
- let n = bytes / 1024, i = 0;
26
- while (n >= 1024 && i < units.length - 1) { n /= 1024; i++; }
27
- return `${n < 10 ? n.toFixed(1) : Math.round(n)} ${units[i]}`;
28
- }
29
-
30
- async function fetchEntries(relPath) {
31
- const url = `/api/sessions/${encodeURIComponent(_sessionId)}/files?path=${encodeURIComponent(relPath || "")}`;
32
- const resp = await fetch(url);
33
- if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
34
- const data = await resp.json();
35
- return data.entries || [];
36
- }
37
-
38
- function renderEntries(entries) {
39
- const frag = document.createDocumentFragment();
40
- if (!entries.length) {
41
- const empty = document.createElement("div");
42
- empty.className = "wt-empty";
43
- empty.textContent = t("workspace.empty");
44
- frag.appendChild(empty);
45
- return frag;
46
- }
47
- for (const entry of entries) {
48
- frag.appendChild(buildNode(entry));
49
- }
50
- return frag;
51
- }
52
-
53
- function buildNode(entry) {
54
- const node = document.createElement("div");
55
- node.className = "wt-node";
56
-
57
- const row = document.createElement("div");
58
- row.className = "wt-row";
59
- row.title = entry.name;
60
-
61
- const caret = document.createElement("span");
62
- caret.className = "wt-caret" + (entry.type === "dir" ? "" : " leaf");
63
- if (entry.type === "dir") caret.innerHTML = ICON_CARET;
64
-
65
- const icon = document.createElement("span");
66
- icon.className = "wt-icon";
67
- icon.innerHTML = entry.type === "dir" ? ICON_FOLDER : ICON_FILE;
68
-
69
- const name = document.createElement("span");
70
- name.className = "wt-name";
71
- name.textContent = entry.name;
72
-
73
- row.appendChild(caret);
74
- row.appendChild(icon);
75
- row.appendChild(name);
76
-
77
- if (entry.type === "file") {
78
- const size = document.createElement("span");
79
- size.className = "wt-size";
80
- size.textContent = formatSize(entry.size);
81
- row.appendChild(size);
82
- }
83
-
84
- node.appendChild(row);
85
-
86
- if (entry.type === "dir") {
87
- const children = document.createElement("div");
88
- children.className = "wt-children";
89
- children.style.display = "none";
90
- node.appendChild(children);
91
- row.addEventListener("click", () => toggleDir(entry, caret, children));
92
- } else {
93
- row.addEventListener("click", () => downloadFile(entry));
94
- }
95
-
96
- row.addEventListener("contextmenu", (e) => {
97
- e.preventDefault();
98
- showContextMenu(e, entry);
99
- });
100
-
101
- return node;
102
- }
103
-
104
- function showContextMenu(e, entry) {
105
- closeContextMenu();
106
-
107
- const iconFolder = '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg>';
108
-
109
- const menu = document.createElement("div");
110
- menu.className = "wt-context-menu session-context-menu";
111
- menu.innerHTML = `
112
- <div class="session-actions-menu-item" data-action="reveal">
113
- <span class="session-actions-menu-icon">${iconFolder}</span>
114
- <span class="session-actions-menu-label">${t("workspace.revealInFinder")}</span>
115
- </div>
116
- `;
117
-
118
- document.body.appendChild(menu);
119
- menu.addEventListener("contextmenu", (e) => e.preventDefault());
120
- menu.style.position = "fixed";
121
- menu.style.top = e.clientY + "px";
122
- menu.style.left = e.clientX + "px";
123
- requestAnimationFrame(() => {
124
- const r = menu.getBoundingClientRect();
125
- if (r.right > window.innerWidth) menu.style.left = (window.innerWidth - r.width - 8) + "px";
126
- if (r.bottom > window.innerHeight) menu.style.top = (window.innerHeight - r.height - 8) + "px";
127
- });
128
-
129
- menu.addEventListener("click", async (ev) => {
130
- const item = ev.target.closest(".session-actions-menu-item");
131
- if (!item) return;
132
- closeContextMenu();
133
- if (item.dataset.action === "reveal") await revealFile(entry);
134
- });
135
-
136
- setTimeout(() => {
137
- document.addEventListener("click", closeContextMenu, { once: true });
138
- }, 0);
139
- }
140
-
141
- function closeContextMenu() {
142
- const existing = document.querySelector(".wt-context-menu");
143
- if (existing) existing.remove();
144
- }
145
-
146
- async function revealFile(entry) {
147
- const fullPath = _workingDir.replace(/\/+$/, "") + "/" + entry.path;
148
- try {
149
- const resp = await fetch("/api/file-action", {
150
- method: "POST",
151
- headers: { "Content-Type": "application/json" },
152
- body: JSON.stringify({ path: fullPath, action: "reveal" })
153
- });
154
- if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
155
- } catch (err) {
156
- console.error("reveal failed:", err);
157
- if (typeof Modal !== "undefined") Modal.toast(t("workspace.revealFailed"), "error");
158
- }
159
- }
160
-
161
- async function toggleDir(entry, caret, children) {
162
- const isOpen = caret.classList.contains("open");
163
- if (isOpen) {
164
- caret.classList.remove("open");
165
- children.style.display = "none";
166
- return;
167
- }
168
- caret.classList.add("open");
169
- children.style.display = "";
170
- if (children.dataset.loaded === "1") return;
171
-
172
- children.innerHTML = `<div class="wt-loading">${t("workspace.loading")}</div>`;
173
- try {
174
- const entries = await fetchEntries(entry.path);
175
- children.innerHTML = "";
176
- children.appendChild(renderEntries(entries));
177
- children.dataset.loaded = "1";
178
- } catch (err) {
179
- console.error("workspace load failed:", err);
180
- children.innerHTML = `<div class="wt-error">${t("workspace.error")}</div>`;
181
- }
182
- }
183
-
184
- async function downloadFile(entry) {
185
- const fullPath = _workingDir.replace(/\/+$/, "") + "/" + entry.path;
186
- try {
187
- const resp = await fetch("/api/file-action", {
188
- method: "POST",
189
- headers: { "Content-Type": "application/json" },
190
- body: JSON.stringify({ path: fullPath, action: "download" })
191
- });
192
- if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
193
- const blob = await resp.blob();
194
- const url = URL.createObjectURL(blob);
195
- const a = document.createElement("a");
196
- a.href = url;
197
- a.download = entry.name;
198
- document.body.appendChild(a);
199
- a.click();
200
- a.remove();
201
- URL.revokeObjectURL(url);
202
- } catch (err) {
203
- console.error("download failed:", err);
204
- if (typeof Modal !== "undefined") Modal.toast(t("workspace.downloadFailed"), "error");
205
- }
206
- }
207
-
208
- async function loadRoot() {
209
- const tree = $("workspace-tree");
210
- if (!tree || !_sessionId) return;
211
- tree.innerHTML = `<div class="wt-loading">${t("workspace.loading")}</div>`;
212
- try {
213
- const entries = await fetchEntries("");
214
- tree.innerHTML = "";
215
- tree.appendChild(renderEntries(entries));
216
- } catch (err) {
217
- console.error("workspace load failed:", err);
218
- tree.innerHTML = `<div class="wt-error">${t("workspace.error")}</div>`;
219
- }
220
- }
221
-
222
- function applyOpenState() {
223
- const panel = $("workspace-panel");
224
- const opener = $("btn-workspace-open");
225
- if (!panel) return;
226
- const hasSession = !!_sessionId;
227
- panel.classList.toggle("collapsed", !(_open && hasSession));
228
- if (opener) opener.style.display = (!_open && hasSession) ? "" : "none";
229
- }
230
-
231
- function setOpen(open) {
232
- _open = open;
233
- try { localStorage.setItem(STORAGE_KEY, open ? "1" : "0"); } catch (_) {}
234
- applyOpenState();
235
- if (open) loadRoot();
236
- }
237
-
238
- return {
239
- init() {
240
- try { _open = localStorage.getItem(STORAGE_KEY) === "1"; } catch (_) { _open = false; }
241
-
242
- const close = $("btn-workspace-close");
243
- const opener = $("btn-workspace-open");
244
- const refresh = $("btn-workspace-refresh");
245
- if (close) close.addEventListener("click", () => setOpen(false));
246
- if (opener) opener.addEventListener("click", () => setOpen(true));
247
- if (refresh) refresh.addEventListener("click", () => loadRoot());
248
-
249
- applyOpenState();
250
- },
251
-
252
- // Called from Sessions.updateInfoBar whenever the active session changes.
253
- // On a real session switch (from one session to another) we always collapse
254
- // the panel: the file list is only ever loaded when the user explicitly
255
- // expands it (which triggers a single refresh via setOpen), so the list is
256
- // never shown stale across sessions. The first attach (no previous session)
257
- // is not a switch and keeps the restored open state.
258
- onSession(session) {
259
- const newId = session ? session.id : null;
260
- const newDir = session ? session.working_dir : null;
261
- const hadSession = _sessionId != null;
262
- const changed = newId !== _sessionId || newDir !== _workingDir;
263
- _sessionId = newId;
264
- _workingDir = newDir;
265
- if (changed && hadSession && _open) setOpen(false);
266
- applyOpenState();
267
- // First attach with the panel restored open: load once.
268
- if (!hadSession && _open && _sessionId) loadRoot();
269
- }
270
- };
271
- })();
272
-
273
- (function _initWorkspaceResize() {
274
- const panel = document.getElementById("workspace-panel");
275
- const handle = document.getElementById("workspace-resize-handle");
276
- if (!panel || !handle) return;
277
-
278
- const MIN_W = 160;
279
- const MAX_W = 600;
280
-
281
- const saved = localStorage.getItem("workspace-width");
282
- if (saved) {
283
- const w = parseFloat(saved);
284
- if (w >= MIN_W && w <= MAX_W) panel.style.setProperty("--workspace-width", w + "px");
285
- }
286
-
287
- let startX = 0;
288
- let startW = 0;
289
-
290
- handle.addEventListener("mousedown", (e) => {
291
- e.preventDefault();
292
- startX = e.clientX;
293
- startW = parseFloat(getComputedStyle(panel).getPropertyValue("--workspace-width"));
294
- handle.classList.add("active");
295
- document.body.style.cursor = "col-resize";
296
- document.body.style.userSelect = "none";
297
- });
298
-
299
- document.addEventListener("mousemove", (e) => {
300
- if (!handle.classList.contains("active")) return;
301
- const dx = startX - e.clientX;
302
- const newW = Math.min(MAX_W, Math.max(MIN_W, startW + dx));
303
- panel.style.setProperty("--workspace-width", newW + "px");
304
- });
305
-
306
- document.addEventListener("mouseup", () => {
307
- if (!handle.classList.contains("active")) return;
308
- handle.classList.remove("active");
309
- document.body.style.cursor = "";
310
- document.body.style.userSelect = "";
311
- localStorage.setItem("workspace-width", parseFloat(getComputedStyle(panel).getPropertyValue("--workspace-width")));
312
- });
313
- })();
314
-
315
- document.addEventListener("DOMContentLoaded", () => Workspace.init());
316
- window.Workspace = Workspace;
File without changes