openclacky 1.3.6 → 1.3.7

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 (103) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +30 -0
  3. data/lib/clacky/agent.rb +8 -1
  4. data/lib/clacky/agent_profile.rb +121 -53
  5. data/lib/clacky/brand_config.rb +309 -6
  6. data/lib/clacky/cli.rb +4 -57
  7. data/lib/clacky/default_extensions/coding/ext.yml +18 -0
  8. data/lib/clacky/default_extensions/ext-studio/agents/ext-developer/system_prompt.md +95 -0
  9. data/lib/clacky/default_extensions/ext-studio/api/handler.rb +254 -0
  10. data/lib/clacky/default_extensions/ext-studio/ext.yml +32 -0
  11. data/lib/clacky/default_extensions/ext-studio/panels/studio/view.js +1269 -0
  12. data/lib/clacky/default_extensions/ext-studio/skills/ext-debug/SKILL.md +71 -0
  13. data/lib/clacky/default_extensions/ext-studio/skills/ext-publish/SKILL.md +74 -0
  14. data/lib/clacky/default_extensions/ext-studio/skills/ext-scaffold/SKILL.md +65 -0
  15. data/lib/clacky/default_extensions/general/ext.yml +18 -0
  16. data/lib/clacky/default_extensions/git/ext.yml +12 -0
  17. data/lib/clacky/{default_agents/_panels/git/panel.js → default_extensions/git/panels/git/view.js} +1 -2
  18. data/lib/clacky/default_extensions/meeting/{handler.rb → api/handler.rb} +78 -26
  19. data/lib/clacky/default_extensions/meeting/ext.yml +15 -0
  20. data/lib/clacky/default_extensions/meeting/{meeting.js → panels/meeting/view.js} +266 -89
  21. data/lib/clacky/default_extensions/meeting/skills/meeting-summarizer/SKILL.md +2 -1
  22. data/lib/clacky/default_extensions/time_machine/ext.yml +12 -0
  23. data/lib/clacky/{default_agents/_panels/time_machine/panel.js → default_extensions/time_machine/panels/time_machine/view.js} +58 -47
  24. data/lib/clacky/default_skills/channel-manager/discord_setup.rb +1 -1
  25. data/lib/clacky/default_skills/extend-openclacky/SKILL.md +83 -18
  26. data/lib/clacky/{api_extension.rb → extension/api_extension.rb} +27 -11
  27. data/lib/clacky/extension/api_loader.rb +136 -0
  28. data/lib/clacky/extension/cli_commands.rb +226 -0
  29. data/lib/clacky/{server/api_extension_dispatcher.rb → extension/dispatcher.rb} +22 -9
  30. data/lib/clacky/extension/hook_loader.rb +77 -0
  31. data/lib/clacky/extension/loader.rb +483 -0
  32. data/lib/clacky/extension/packager.rb +226 -0
  33. data/lib/clacky/{patch_loader.rb → extension/patch_loader.rb} +45 -0
  34. data/lib/clacky/extension/scaffold/template_renderer.rb +64 -0
  35. data/lib/clacky/extension/scaffold/templates/full/README.md.erb +48 -0
  36. data/lib/clacky/extension/scaffold/templates/full/agents/designer.md +5 -0
  37. data/lib/clacky/extension/scaffold/templates/full/api/handler.rb.erb +16 -0
  38. data/lib/clacky/extension/scaffold/templates/full/channels/noop.rb.erb +40 -0
  39. data/lib/clacky/extension/scaffold/templates/full/ext.yml.erb +41 -0
  40. data/lib/clacky/extension/scaffold/templates/full/hooks/audit.rb +9 -0
  41. data/lib/clacky/extension/scaffold/templates/full/panels/dashboard/view.js.erb +194 -0
  42. data/lib/clacky/extension/scaffold/templates/full/patches/audit.rb +15 -0
  43. data/lib/clacky/extension/scaffold/templates/full/skills/__slug__-skill/SKILL.md.erb +8 -0
  44. data/lib/clacky/extension/scaffold/templates/hello/api/handler.rb.erb +9 -0
  45. data/lib/clacky/extension/scaffold/templates/hello/ext.yml.erb +12 -0
  46. data/lib/clacky/extension/scaffold/templates/hello/panels/hello/view.js.erb +31 -0
  47. data/lib/clacky/extension/scaffold.rb +55 -0
  48. data/lib/clacky/extension/verifier.rb +196 -0
  49. data/lib/clacky/identity.rb +70 -0
  50. data/lib/clacky/locales/en.rb +19 -1
  51. data/lib/clacky/locales/zh.rb +19 -1
  52. data/lib/clacky/platform_http_client.rb +18 -15
  53. data/lib/clacky/server/channel/extension_adapter_loader.rb +32 -0
  54. data/lib/clacky/server/channel.rb +6 -0
  55. data/lib/clacky/server/http_server.rb +313 -144
  56. data/lib/clacky/server/session_registry.rb +9 -2
  57. data/lib/clacky/skill_loader.rb +28 -1
  58. data/lib/clacky/ui2/ui_controller.rb +9 -6
  59. data/lib/clacky/version.rb +1 -1
  60. data/lib/clacky/web/app.css +748 -573
  61. data/lib/clacky/web/app.js +63 -15
  62. data/lib/clacky/web/auth.js +2 -0
  63. data/lib/clacky/web/components/notify.js +2 -0
  64. data/lib/clacky/web/components/sidebar.js +1 -5
  65. data/lib/clacky/web/core/ext.js +316 -79
  66. data/lib/clacky/web/features/backup/store.js +1 -0
  67. data/lib/clacky/web/features/backup/view.js +0 -1
  68. data/lib/clacky/web/features/brand/view.js +13 -5
  69. data/lib/clacky/web/features/extensions/store.js +184 -0
  70. data/lib/clacky/web/features/extensions/view.js +389 -0
  71. data/lib/clacky/web/features/new-session/store.js +186 -0
  72. data/lib/clacky/web/features/new-session/view.js +319 -0
  73. data/lib/clacky/web/features/share/view.js +63 -14
  74. data/lib/clacky/web/features/skills/store.js +3 -1
  75. data/lib/clacky/web/features/skills/view.js +1 -1
  76. data/lib/clacky/web/features/tasks/view.js +12 -0
  77. data/lib/clacky/web/features/trash/store.js +24 -6
  78. data/lib/clacky/web/features/trash/view.js +46 -9
  79. data/lib/clacky/web/features/workspace/store.js +2 -0
  80. data/lib/clacky/web/features/workspace/view.js +0 -1
  81. data/lib/clacky/web/i18n.js +118 -106
  82. data/lib/clacky/web/index.html +138 -188
  83. data/lib/clacky/web/sessions.js +64 -269
  84. data/lib/clacky/web/settings.js +1 -1
  85. data/lib/clacky/web/skills.js +2 -0
  86. data/lib/clacky/web/theme.js +2 -0
  87. data/lib/clacky/web/ws-dispatcher.js +1 -0
  88. data/lib/clacky/web/ws.js +2 -0
  89. data/lib/clacky.rb +21 -10
  90. metadata +55 -24
  91. data/benchmark/runner.rb +0 -441
  92. data/lib/clacky/api_extension_loader.rb +0 -168
  93. data/lib/clacky/default_agents/SOUL.md +0 -3
  94. data/lib/clacky/default_agents/USER.md +0 -1
  95. data/lib/clacky/default_agents/coding/profile.yml +0 -5
  96. data/lib/clacky/default_agents/coding/webui/.gitkeep +0 -0
  97. data/lib/clacky/default_agents/general/profile.yml +0 -2
  98. data/lib/clacky/default_extensions/meeting/meta.yml +0 -3
  99. data/lib/clacky/web/features/creator/store.js +0 -81
  100. data/lib/clacky/web/features/creator/view.js +0 -380
  101. /data/lib/clacky/{default_agents → default_extensions/coding/agents}/coding/system_prompt.md +0 -0
  102. /data/lib/clacky/{default_agents → default_extensions/general/agents}/general/system_prompt.md +0 -0
  103. /data/lib/clacky/{default_agents/base_prompt.md → prompts/base.md} +0 -0
@@ -76,6 +76,12 @@ const TasksView = (() => {
76
76
  return isZh ? `每 ${hour.slice(2)} 小时` : `Every ${hour.slice(2)} hr`;
77
77
  }
78
78
  }
79
+ // ── every-N-hours on specific days e.g. 0 */3 * * 1-5 ────────────
80
+ if (isAny(dom) && isAny(month) && !isAny(dow) && hour.startsWith("*/")) {
81
+ const dl = dowLabel(dow);
82
+ const ev = isZh ? `每 ${hour.slice(2)} 小时` : `Every ${hour.slice(2)} hr`;
83
+ if (dl) return `${dl} ${ev}`;
84
+ }
79
85
  // ── every minute ──────────────────────────────────────────────────────
80
86
  if (isAny(min) && isAny(hour) && isAny(dom) && isAny(month) && isAny(dow)) {
81
87
  return isZh ? "每分钟" : "Every minute";
@@ -84,6 +90,12 @@ const TasksView = (() => {
84
90
  if (isInt(min) && isAny(hour) && isAny(dom) && isAny(month) && isAny(dow)) {
85
91
  return isZh ? `每小时 :${pad(min)}` : `Hourly at :${pad(min)}`;
86
92
  }
93
+ // ── hourly at :MM on specific days e.g. 30 * * * 1-5 ─────────────
94
+ if (isInt(min) && isAny(hour) && isAny(dom) && isAny(month) && !isAny(dow)) {
95
+ const dl = dowLabel(dow);
96
+ const tm = isZh ? `每小时 :${pad(min)}` : `Hourly at :${pad(min)}`;
97
+ if (dl) return `${dl} ${tm}`;
98
+ }
87
99
 
88
100
  // ── every-N-min within hour range on certain days e.g. */1 9-14 * * 1-5
89
101
  if (min.startsWith("*/") && /^\d+-\d+$/.test(hour) && isAny(dom) && isAny(month)) {
@@ -13,10 +13,13 @@
13
13
  // ───────────────────────────────────────────────────────────────────────────
14
14
 
15
15
  const TrashStore = (() => {
16
+ const SOURCES = Object.freeze({ ALL: "all", CRON: "cron", MANUAL: "manual" });
17
+
16
18
  let _files = [];
17
19
  let _totals = { count: 0, size: 0 };
18
20
  let _sessions = [];
19
21
  let _sessionTotals = { count: 0, size: 0 };
22
+ let _filterSource = SOURCES.ALL;
20
23
  let _loading = false;
21
24
 
22
25
  const _listeners = {};
@@ -44,8 +47,20 @@ const TrashStore = (() => {
44
47
  const state = {
45
48
  get files() { return _files; },
46
49
  get totals() { return _totals; },
47
- get sessions() { return _sessions; },
48
- get sessionTotals() { return _sessionTotals; },
50
+ get sessions() {
51
+ if (_filterSource === SOURCES.ALL) return _sessions;
52
+ return _filterSource === SOURCES.CRON
53
+ ? _sessions.filter(s => s.source === SOURCES.CRON)
54
+ : _sessions.filter(s => s.source !== SOURCES.CRON);
55
+ },
56
+ get sessionTotals() {
57
+ const filtered = _filterSource === SOURCES.ALL ? _sessions
58
+ : _filterSource === SOURCES.CRON
59
+ ? _sessions.filter(s => s.source === SOURCES.CRON)
60
+ : _sessions.filter(s => s.source !== SOURCES.CRON);
61
+ return { count: filtered.length, size: filtered.reduce((sum, s) => sum + (s.file_size || 0), 0) };
62
+ },
63
+ get filterSource() { return _filterSource; },
49
64
  orphanCount() { return _files.filter(f => _isOrphanRoot(f.project_root)).length; },
50
65
  isOrphan(file) { return _isOrphanRoot(file.project_root); },
51
66
  };
@@ -53,6 +68,13 @@ const TrashStore = (() => {
53
68
  const Trash = {
54
69
  on: _on,
55
70
  state,
71
+ SOURCES,
72
+
73
+ setFilterSource(source) {
74
+ if (source === _filterSource) return;
75
+ _filterSource = source;
76
+ _emit("trash:sessionsChanged");
77
+ },
56
78
 
57
79
  async loadFiles() {
58
80
  if (_loading) return;
@@ -230,10 +252,6 @@ const TrashStore = (() => {
230
252
 
231
253
  function _removeSessionLocal(session) {
232
254
  _sessions = _sessions.filter(s => s.session_id !== session.session_id);
233
- _sessionTotals = {
234
- count: Math.max(0, _sessionTotals.count - 1),
235
- size: Math.max(0, _sessionTotals.size - (session.file_size || 0))
236
- };
237
255
  }
238
256
 
239
257
  return Trash;
@@ -61,8 +61,13 @@ const TrashView = (() => {
61
61
 
62
62
  const filePane = $("trash-tab-file");
63
63
  const sessionPane = $("trash-tab-session");
64
+ const filterBar = $("trash-filter-bar");
64
65
  if (filePane) filePane.style.display = tab === "file-trash" ? "" : "none";
65
66
  if (sessionPane) sessionPane.style.display = tab === "session-trash" ? "" : "none";
67
+ if (filterBar) filterBar.style.display = tab === "session-trash" ? "" : "none";
68
+
69
+ const hint = $("trash-session-hint");
70
+ if (hint) hint.style.display = tab === "session-trash" ? "" : "none";
66
71
 
67
72
  const btnOrphans = $("btn-trash-empty-orphans");
68
73
  if (btnOrphans) btnOrphans.style.display = tab === "file-trash" ? "" : "none";
@@ -73,7 +78,7 @@ const TrashView = (() => {
73
78
  function _load() {
74
79
  if (_activeTab === "file-trash") {
75
80
  const list = $("trash-list");
76
- if (list) list.innerHTML = `<div class="creator-loading">${_t("trash.loading")}</div>`;
81
+ if (list) list.innerHTML = `<div class="trash-loading">${_t("trash.loading")}</div>`;
77
82
  Trash.loadFiles();
78
83
  } else {
79
84
  const list = $("trash-session-list");
@@ -109,7 +114,7 @@ const TrashView = (() => {
109
114
  if (btnAll) btnAll.disabled = files.length === 0;
110
115
 
111
116
  if (files.length === 0) {
112
- list.innerHTML = `<div class="creator-empty">${_t("trash.empty")}</div>`;
117
+ list.innerHTML = `<div class="trash-empty">${_t("trash.empty")}</div>`;
113
118
  return;
114
119
  }
115
120
 
@@ -176,11 +181,33 @@ const TrashView = (() => {
176
181
 
177
182
  // ── Session trash rendering ──────────────────────────────────────────
178
183
 
184
+ function _renderSkeleton() {
185
+ const list = $("trash-session-list");
186
+ if (!list) return;
187
+ let html = "";
188
+ for (let i = 0; i < 3; i++) {
189
+ html += `
190
+ <div class="trash-session-skeleton">
191
+ <div class="trash-session-skeleton-info">
192
+ <div class="skel skel-title"></div>
193
+ <div class="skel skel-subtitle"></div>
194
+ <div class="skel skel-text-sm"></div>
195
+ </div>
196
+ <div class="trash-skeleton-actions">
197
+ <div class="skel trash-skeleton-btn"></div>
198
+ <div class="skel trash-skeleton-btn"></div>
199
+ </div>
200
+ </div>`;
201
+ }
202
+ list.innerHTML = html;
203
+ }
204
+
179
205
  function _renderSessions() {
180
- const list = $("trash-session-list");
181
- const summary = $("trash-summary");
182
- const btnOld = $("btn-trash-empty-old");
183
- const btnAll = $("btn-trash-empty-all");
206
+ const list = $("trash-session-list");
207
+ const summary = $("trash-summary");
208
+ const btnOld = $("btn-trash-empty-old");
209
+ const btnAll = $("btn-trash-empty-all");
210
+ const filterBar = $("trash-filter-bar");
184
211
  if (!list) return;
185
212
 
186
213
  const sessions = Trash.state.sessions;
@@ -196,9 +223,10 @@ const TrashView = (() => {
196
223
  }
197
224
  if (btnOld) btnOld.disabled = sessions.length === 0;
198
225
  if (btnAll) btnAll.disabled = sessions.length === 0;
226
+ if (filterBar) filterBar.style.display = "";
199
227
 
200
228
  if (sessions.length === 0) {
201
- list.innerHTML = `<div class="creator-empty">${_t("trash.noSessionTrash")}</div>`;
229
+ list.innerHTML = `<div class="trash-empty">${_t("trash.noSessionTrash")}</div>`;
202
230
  return;
203
231
  }
204
232
 
@@ -385,6 +413,15 @@ const TrashView = (() => {
385
413
  if (btnAll) btnAll.addEventListener("click",
386
414
  () => _emptyBulk(0, _activeTab === "session-trash"
387
415
  ? "trash.confirmEmptySessionAll" : "trash.confirmEmptyAll"));
416
+
417
+ // Filter chips
418
+ document.querySelectorAll(".trash-filter-chip").forEach(chip => {
419
+ chip.addEventListener("click", () => {
420
+ const val = chip.dataset.filter;
421
+ Trash.setFilterSource(val);
422
+ document.querySelectorAll(".trash-filter-chip").forEach(c => c.classList.toggle("active", c.dataset.filter === val));
423
+ });
424
+ });
388
425
  }
389
426
 
390
427
  function _subscribe() {
@@ -392,11 +429,11 @@ const TrashView = (() => {
392
429
  Trash.on("trash:sessionsChanged", _renderSessions);
393
430
  Trash.on("trash:filesError", (e) => {
394
431
  const list = $("trash-list");
395
- if (list) list.innerHTML = `<div class="creator-empty creator-error">${escapeHtml(e.message)}</div>`;
432
+ if (list) list.innerHTML = `<div class="trash-empty trash-error">${escapeHtml(e.message)}</div>`;
396
433
  });
397
434
  Trash.on("trash:sessionsError", (e) => {
398
435
  const list = $("trash-session-list");
399
- if (list) list.innerHTML = `<div class="creator-empty creator-error">${escapeHtml(e.message)}</div>`;
436
+ if (list) list.innerHTML = `<div class="trash-empty trash-error">${escapeHtml(e.message)}</div>`;
400
437
  });
401
438
  }
402
439
 
@@ -97,3 +97,5 @@ const WorkspaceStore = (() => {
97
97
  })();
98
98
 
99
99
  const Workspace = WorkspaceStore;
100
+ Clacky.Workspace = Workspace;
101
+ Clacky.WorkspaceStore = WorkspaceStore;
@@ -302,4 +302,3 @@ if (window.Clacky && Clacky.ext) {
302
302
  // Workspace.onSession on every session switch). Rendering is driven by the
303
303
  // slot re-render, so this only updates state.
304
304
  Workspace.onSession = (session) => { Workspace.setSession(session); };
305
- window.Workspace = Workspace;
@@ -20,28 +20,20 @@ const I18n = (() => {
20
20
  "sidebar.skills": "Skill Management",
21
21
  "sidebar.channels": "Channel Management",
22
22
  "sidebar.mcp": "MCP Servers",
23
+ "sidebar.extensions": "Extension Market",
23
24
  "sidebar.trash": "File Recall",
24
25
  "sidebar.profile": "Assistant Memory",
25
26
  "sidebar.memories": "Memories",
26
27
  "sidebar.dataSection": "My Data",
27
- "sidebar.creator": "Creator Hub",
28
- "sidebar.creatorSection": "Creator",
29
28
  "sidebar.settings": "Settings",
30
29
  "sidebar.billing": "Usage",
31
30
 
32
31
  // ── Welcome screen ──
33
- "welcome.title": "What should we build?",
34
- "welcome.body": "Your agent is standing by. Pick a starting point or just type.",
35
- "welcome.btn": "Start a blank session",
36
- "welcome.chip.code.title": "Write code",
37
- "welcome.chip.code.desc": "build, refactor, debug",
38
- "welcome.chip.code.prompt": "Help me write some code. ",
39
- "welcome.chip.task.title": "Automate a task",
40
- "welcome.chip.task.desc": "schedule, run, repeat",
41
- "welcome.chip.task.prompt": "I want to automate a recurring task. ",
42
- "welcome.chip.research.title": "Research",
43
- "welcome.chip.research.desc": "search, read, summarize",
44
- "welcome.chip.research.prompt":"Research this for me: ",
32
+ "sessions.new.title": "Start a new session",
33
+ "sessions.new.subtitle": "Pick who you want to chat with, then type your first message.",
34
+ "sessions.new.placeholder": "Type your first message to {{agent}}…",
35
+ "sessions.new.moreOptions": "More options",
36
+ "sessions.new.send": "Send",
45
37
 
46
38
  // ── Chat panel ──
47
39
  "chat.status.idle": "idle",
@@ -58,6 +50,11 @@ const I18n = (() => {
58
50
  "chat.history_load_failed": "Could not load history",
59
51
  "chat.history_start": "No more history",
60
52
  "chat.image_expired": "Expired",
53
+ "chat.file.tooLarge": "File too large: {{name}} (max {{max}})",
54
+ "chat.file.imageTooLarge": "Image too large: {{name}} (max {{max}})",
55
+ "chat.file.unsupportedType": "Unsupported image type: {{type}}\nSupported: PNG, JPEG, GIF, WEBP",
56
+ "chat.file.processFailed": "Image processing failed: {{msg}}",
57
+ "chat.file.uploadFailed": "Upload failed: {{msg}}",
61
58
  "chat.done": "Done — {{n}} iteration(s), {{cost}}",
62
59
  "chat.done.duration": " · {{duration}}s",
63
60
  "chat.done.cache": "Cache hit {{rate}}% ({{hits}}/{{total}}) · {{tokens}} tokens reused",
@@ -153,6 +150,7 @@ const I18n = (() => {
153
150
  "sib.reasoning.low": "Low",
154
151
  "sib.reasoning.medium": "Medium",
155
152
  "sib.reasoning.high": "High",
153
+ "sib.reasoning.xhigh": "XHigh",
156
154
  "sessions.thinking": "Thinking…",
157
155
  "sessions.default_name": "Session {{n}}",
158
156
  "sessions.cronGroup": "Scheduled Tasks",
@@ -168,7 +166,6 @@ const I18n = (() => {
168
166
  "sessions.actions.fork": "Fork",
169
167
  "sessions.actions.rename": "Rename",
170
168
  "sessions.actions.delete": "Delete",
171
- "sessions.newSessionAdvanced": "More Options",
172
169
  "sessions.loadMore": "Load more sessions",
173
170
  "sessions.loadingMore": "Loading…",
174
171
  "sessions.search.placeholder": "Search sessions…",
@@ -194,7 +191,6 @@ const I18n = (() => {
194
191
  "auth.rateLimited": "Too many failed attempts. Please try again in {{seconds}} seconds.",
195
192
 
196
193
  // ── New Session Modal ──
197
- "sessions.modal.title": "Create New Session",
198
194
  "sessions.modal.agent": "Agent",
199
195
  "sessions.modal.agent.general": "General",
200
196
  "sessions.modal.agent.coding": "Coding",
@@ -206,7 +202,6 @@ const I18n = (() => {
206
202
  "sessions.modal.directory.browse": "Browse folders",
207
203
  "sessions.modal.dirpicker.title": "Select Working Directory",
208
204
  "sessions.modal.initProject": "Initialize full-stack project (/new)",
209
- "sessions.modal.create": "Create Session",
210
205
 
211
206
  // ── Offline banner ──
212
207
  "offline.banner": "Server disconnected — reconnecting…",
@@ -264,48 +259,17 @@ const I18n = (() => {
264
259
  "skills.title": "Skills",
265
260
  "skills.subtitle": "Extend your assistant's capabilities with custom skills",
266
261
 
267
- // ── Creator panel ──
268
- "creator.title": "Creator Hub",
269
- "creator.subtitle": "Manage and publish your skills to the platform",
270
- "creator.loading": "Loading…",
271
-
272
- "creator.section.cloud": "Cloud Skills",
273
- "creator.section.cloudHint": "Published to the platform",
274
- "creator.section.local": "Local Skills",
275
- "creator.section.localHint": "Ready to publish",
276
- "creator.section.new": "Create New Skill",
277
-
278
- "creator.cloud.empty": "No skills published yet.",
279
- "creator.local.empty": "All local skills are already published.",
280
-
281
- "creator.badge.published": "Published",
282
- "creator.badge.unpublished": "Not published",
283
-
284
- "creator.changed": "Has local changes",
285
- "creator.hasLocalChanges": "Local SKILL.md is newer than the last upload",
286
- "creator.downloads": "Downloads",
287
-
288
- "creator.btn.publish": "Publish",
289
- "creator.btn.update": "Update",
290
- "creator.btn.upToDate": "Up to date",
291
- "creator.btn.iterate": "Iterate",
292
- "creator.iterate.prompt": "Update skill:",
293
-
294
- "creator.shadow.label": "Local override",
295
- "creator.shadow.tooltip": "Local copy shadows a same-named brand skill",
296
-
297
- "creator.newSkill.label": "Create a new skill with /skill-creator",
298
- "creator.promo.text": "Publish your skills & build your own brand on OpenClacky.",
299
- "creator.promo.link": "Learn more →",
300
- "creator.cloud.locked": "Brand license required to publish cloud skills.",
301
-
302
262
  // ── Creator trash ──
303
263
  "trash.title": "File Recall",
304
264
  "trash.subtitle": "Files the agent moved to trash across all projects. Recall them back to where they were, or clear the ones you don't need.",
305
265
  "trash.refresh": "Refresh",
266
+ "trash.refresh.tooltip": "Reload the trash list",
306
267
  "trash.emptyOld": "Empty >7 days",
268
+ "trash.emptyOld.tooltip": "Permanently delete files older than 7 days",
307
269
  "trash.emptyOrphans":"Clean orphans",
270
+ "trash.emptyOrphans.tooltip": "Permanently delete entries whose original project no longer exists",
308
271
  "trash.emptyAll": "Empty all",
272
+ "trash.emptyAll.tooltip": "Permanently delete all trashed files",
309
273
  "trash.empty": "Nothing in the trash.",
310
274
  "trash.loading": "Loading trash…",
311
275
  "trash.summary": "{{count}} file(s), {{size}}",
@@ -345,6 +309,12 @@ const I18n = (() => {
345
309
  "trash.deleteFail": "Delete failed: {{msg}}",
346
310
  "trash.cleanFail": "Operation failed: {{msg}}",
347
311
 
312
+ // ── Session type filter ──
313
+ "trash.filterAll": "All",
314
+ "trash.filterCron": "Scheduled",
315
+ "trash.filterManual": "Regular",
316
+ "trash.sessionAutoCleanHint": "The system keeps the most recent 200 sessions; older ones are moved here.",
317
+
348
318
  // ── Relative time (used by _humanTime helper) ──
349
319
  "time.justNow": "just now",
350
320
  "time.minsAgo": "{{n}}m ago",
@@ -520,6 +490,37 @@ const I18n = (() => {
520
490
  // ── MCP panel ──
521
491
  "mcp.title": "MCP Servers",
522
492
  "mcp.subtitle": "Standard Model Context Protocol servers (Claude Desktop / Cursor compatible). Edit ~/.clacky/mcp.json to add or remove servers.",
493
+ "extensions.title": "Extension Market",
494
+ "extensions.subtitle": "Browse and manage your extensions to enhance OpenClacky with agents, skills, and panels.",
495
+ "extensions.searchPlaceholder": "Search extensions…",
496
+ "extensions.sort.newest": "Newest",
497
+ "extensions.sort.updated": "Recently updated",
498
+ "extensions.sort.downloads": "Most downloaded",
499
+ "extensions.empty": "No extensions available yet.",
500
+ "extensions.noResults": "No extensions match your search.",
501
+ "extensions.loadFailed": "Could not reach the extension store.",
502
+ "extensions.homepage": "Homepage",
503
+ "extensions.installed": "Installed",
504
+ "extensions.unit.skill": "skill",
505
+ "extensions.unit.skills": "skills",
506
+ "extensions.unit.agent": "agent",
507
+ "extensions.unit.agents": "agents",
508
+ "extensions.unit.panel": "panel",
509
+ "extensions.unit.panels": "panels",
510
+ "extensions.unit.api": "API",
511
+ "extensions.unit.apis": "APIs",
512
+ "extensions.detail.back": "Back",
513
+ "extensions.detail.contributes": "What's inside",
514
+ "extensions.detail.versions": "Version history",
515
+ "extensions.section.agents": "Agents",
516
+ "extensions.section.skills": "Skills",
517
+ "extensions.section.panels": "Panels",
518
+ "extensions.section.api": "APIs",
519
+ "extensions.disabled": "Disabled",
520
+ "extensions.action.disable": "Disable",
521
+ "extensions.action.enable": "Enable",
522
+ "extensions.action.remove": "Remove",
523
+ "extensions.action.removeConfirm": "Remove this extension? Its files will be deleted. You can reinstall it from your license package later.",
523
524
  "mcp.loading": "Loading…",
524
525
  "mcp.empty.title": "No MCP servers configured",
525
526
  "mcp.empty.body": "Create ~/.clacky/mcp.json to plug in MCP servers. The format matches Claude Desktop and Cursor, so existing configs work as-is.",
@@ -800,6 +801,7 @@ const I18n = (() => {
800
801
  "brand.activate.subtitle": "Enter your serial number to get started.",
801
802
  "brand.activate.success": "License activated successfully!",
802
803
  "brand.skip.warning": "Brand license not activated — brand-exclusive skills are unavailable. You can activate your license anytime in Settings → Brand & License.",
804
+ "brand.warning.renewBtn": "Renew License",
803
805
 
804
806
  // ── Brand activation banner ──
805
807
  "brand.banner.prompt": "{{name}} is not activated yet — some features are unavailable.",
@@ -811,11 +813,14 @@ const I18n = (() => {
811
813
  "brand.banner.freePromptOnlyPaid": "Welcome to {{name}} — {{paid}} premium skill{{paidPlural}} can be unlocked with a serial number.",
812
814
  "brand.banner.freePromptBoth": "Welcome to {{name}} — {{free}} free skill{{freePlural}} ready to use, plus {{paid}} premium skill{{paidPlural}} unlockable with a serial number.",
813
815
 
814
- "header.owner.tooltip": "Creator — click to open Creator Hub",
816
+ "header.owner.tooltip": "Creator — click to open Extension & Creation",
815
817
  "header.cmdbar.placeholder": "Search sessions…",
816
818
  "notify.tooltip.on": "Sound on task complete: ON (click to mute)",
817
819
  "notify.tooltip.off": "Sound on task complete: OFF (click to enable)",
818
820
 
821
+ "header.refresh": "Refresh",
822
+ "header.theme.toDark": "Switch to dark mode",
823
+ "header.theme.toLight": "Switch to light mode",
819
824
  "share.tooltip": "Share",
820
825
  "share.copy.1": "Been running {{brand}} with domestic models (Kimi, DeepSeek, GLM…) — first-class support, no proxy hacks. Solid.",
821
826
  "share.copy.2": "{{brand}} is stingy with tokens in the best way — prompt caching keeps my AI bill tiny. Recommend.",
@@ -958,28 +963,20 @@ const I18n = (() => {
958
963
  "sidebar.skills": "技能管理",
959
964
  "sidebar.channels": "频道管理",
960
965
  "sidebar.mcp": "MCP 工具",
966
+ "sidebar.extensions": "扩展市场",
961
967
  "sidebar.trash": "文件召回",
962
968
  "sidebar.profile": "助理记忆",
963
969
  "sidebar.memories": "记忆",
964
970
  "sidebar.dataSection": "我的数据",
965
- "sidebar.creator": "创作者中心",
966
- "sidebar.creatorSection": "创作者",
967
971
  "sidebar.settings": "设置",
968
972
  "sidebar.billing": "用量信息",
969
973
 
970
974
  // ── Welcome screen ──
971
- "welcome.title": "今天想做点什么?",
972
- "welcome.body": "Agent 已就绪。选一个起点,或者直接开始输入。",
973
- "welcome.btn": "新建空白会话",
974
- "welcome.chip.code.title": "写代码",
975
- "welcome.chip.code.desc": "开发、重构、调试",
976
- "welcome.chip.code.prompt": "帮我写一段代码:",
977
- "welcome.chip.task.title": "自动化任务",
978
- "welcome.chip.task.desc": "定时、执行、重复",
979
- "welcome.chip.task.prompt": "我想把一个重复性任务自动化:",
980
- "welcome.chip.research.title": "做调研",
981
- "welcome.chip.research.desc": "搜索、阅读、汇总",
982
- "welcome.chip.research.prompt":"帮我调研一下:",
975
+ "sessions.new.title": "开始新会话",
976
+ "sessions.new.subtitle": "选一个 Agent,然后输入第一条消息。",
977
+ "sessions.new.placeholder": "输入第一条消息发给 {{agent}}…",
978
+ "sessions.new.moreOptions": "更多选项",
979
+ "sessions.new.send": "发送",
983
980
 
984
981
  // ── Chat panel ──
985
982
  "chat.status.idle": "空闲",
@@ -996,6 +993,11 @@ const I18n = (() => {
996
993
  "chat.history_load_failed": "历史记录加载失败",
997
994
  "chat.history_start": "没有更多历史了",
998
995
  "chat.image_expired": "已过期",
996
+ "chat.file.tooLarge": "文件过大:{{name}}(最大 {{max}})",
997
+ "chat.file.imageTooLarge": "图片过大:{{name}}(最大 {{max}})",
998
+ "chat.file.unsupportedType": "不支持的图片格式:{{type}}\n支持:PNG、JPEG、GIF、WEBP",
999
+ "chat.file.processFailed": "图片处理失败:{{msg}}",
1000
+ "chat.file.uploadFailed": "上传失败:{{msg}}",
999
1001
  "chat.done": "完成 — {{n}} 步,{{cost}}",
1000
1002
  "chat.done.duration": " · {{duration}}s",
1001
1003
  "chat.done.cache": "缓存命中 {{rate}}% ({{hits}}/{{total}}) · 复用 {{tokens}} tokens",
@@ -1089,6 +1091,7 @@ const I18n = (() => {
1089
1091
  "sib.reasoning.low": "低",
1090
1092
  "sib.reasoning.medium": "中",
1091
1093
  "sib.reasoning.high": "高",
1094
+ "sib.reasoning.xhigh": "超高",
1092
1095
  "sessions.thinking": "思考中…",
1093
1096
  "sessions.default_name": "会话 {{n}}",
1094
1097
  "sessions.cronGroup": "定时任务",
@@ -1099,7 +1102,6 @@ const I18n = (() => {
1099
1102
  "sessions.badge.coding": "Coding",
1100
1103
  "sessions.badge.setup": "配置",
1101
1104
  "sessions.newSession": "+ 新会话",
1102
- "sessions.newSessionAdvanced": "更多选项",
1103
1105
  "sessions.loadMore": "加载更多会话",
1104
1106
  "sessions.actions.pin": "置顶",
1105
1107
  "sessions.actions.unpin": "取消置顶",
@@ -1128,7 +1130,6 @@ const I18n = (() => {
1128
1130
  "auth.rateLimited": "尝试次数过多,请 {{seconds}} 秒后再试。",
1129
1131
 
1130
1132
  // ── New Session Modal ──
1131
- "sessions.modal.title": "创建新会话",
1132
1133
  "sessions.modal.agent": "助手类型",
1133
1134
  "sessions.modal.agent.general": "通用",
1134
1135
  "sessions.modal.agent.coding": "编程",
@@ -1140,7 +1141,6 @@ const I18n = (() => {
1140
1141
  "sessions.modal.directory.browse": "选择文件夹",
1141
1142
  "sessions.modal.dirpicker.title": "选择工作目录",
1142
1143
  "sessions.modal.initProject": "初始化全栈项目 (/new)",
1143
- "sessions.modal.create": "创建会话",
1144
1144
 
1145
1145
  // ── Offline banner ──
1146
1146
  "offline.banner": "服务已断开,正在重连…",
@@ -1198,48 +1198,17 @@ const I18n = (() => {
1198
1198
  "skills.title": "技能",
1199
1199
  "skills.subtitle": "为助手添加自定义能力",
1200
1200
 
1201
- // ── Creator panel ──
1202
- "creator.title": "创作者中心",
1203
- "creator.subtitle": "管理并发布你的技能到平台",
1204
- "creator.loading": "加载中…",
1205
-
1206
- "creator.section.cloud": "云端技能",
1207
- "creator.section.cloudHint": "已发布到平台",
1208
- "creator.section.local": "本地技能",
1209
- "creator.section.localHint": "准备好可以上传",
1210
- "creator.section.new": "创建新技能",
1211
-
1212
- "creator.cloud.empty": "还没有发布任何技能。",
1213
- "creator.local.empty": "所有本地技能均已发布。",
1214
-
1215
- "creator.badge.published": "已发布",
1216
- "creator.badge.unpublished": "未发布",
1217
-
1218
- "creator.changed": "有本地改动",
1219
- "creator.hasLocalChanges": "本地 SKILL.md 比上次上传更新",
1220
- "creator.downloads": "下载量",
1221
-
1222
- "creator.btn.publish": "发布",
1223
- "creator.btn.update": "更新",
1224
- "creator.btn.upToDate": "已是最新",
1225
- "creator.btn.iterate": "迭代",
1226
- "creator.iterate.prompt": "更新技能:",
1227
-
1228
- "creator.shadow.label": "本地优先",
1229
- "creator.shadow.tooltip": "本地版本覆盖了同名的品牌加密技能",
1230
-
1231
- "creator.newSkill.label": "用 /skill-creator 创建新技能",
1232
- "creator.promo.text": "发布你的 Skill,在 OpenClacky 上打造自己的品牌。",
1233
- "creator.promo.link": "了解更多 →",
1234
- "creator.cloud.locked": "需要品牌授权才能发布云端 Skill。",
1235
-
1236
1201
  // ── Creator trash ──
1237
1202
  "trash.title": "文件召回",
1238
1203
  "trash.subtitle": "AI 代理在各个项目里丢进回收站的文件。可以把它们召回到原位置,或彻底清理不再需要的。",
1239
1204
  "trash.refresh": "刷新",
1205
+ "trash.refresh.tooltip": "重新加载回收站列表",
1240
1206
  "trash.emptyOld": "清理 >7 天",
1207
+ "trash.emptyOld.tooltip": "永久删除 7 天前的文件",
1241
1208
  "trash.emptyOrphans":"清理孤儿",
1209
+ "trash.emptyOrphans.tooltip": "永久删除原始项目目录已不存在的条目",
1242
1210
  "trash.emptyAll": "全部清空",
1211
+ "trash.emptyAll.tooltip": "永久删除所有回收站文件",
1243
1212
  "trash.empty": "回收站空空如也。",
1244
1213
  "trash.loading": "加载回收站…",
1245
1214
  "trash.summary": "共 {{count}} 个文件,{{size}}",
@@ -1281,6 +1250,12 @@ const I18n = (() => {
1281
1250
  "trash.deleteFail": "删除失败:{{msg}}",
1282
1251
  "trash.cleanFail": "操作失败:{{msg}}",
1283
1252
 
1253
+ // ── Session type filter ──
1254
+ "trash.filterAll": "全部",
1255
+ "trash.filterCron": "定时任务",
1256
+ "trash.filterManual": "普通会话",
1257
+ "trash.sessionAutoCleanHint": "系统自动保留最近 200 个会话,超出后自动移入此处。",
1258
+
1284
1259
  // ── Relative time (used by _humanTime helper) ──
1285
1260
  "time.justNow": "刚刚",
1286
1261
  "time.minsAgo": "{{n}} 分钟前",
@@ -1455,6 +1430,37 @@ const I18n = (() => {
1455
1430
  // ── MCP panel ──
1456
1431
  "mcp.title": "MCP 工具",
1457
1432
  "mcp.subtitle": "标准 Model Context Protocol 服务(与 Claude Desktop / Cursor 兼容)。编辑 ~/.clacky/mcp.json 添加或移除服务。",
1433
+ "extensions.title": "扩展市场",
1434
+ "extensions.subtitle": "浏览并管理你的扩展,用 Agent、技能与面板增强 OpenClacky。",
1435
+ "extensions.searchPlaceholder": "搜索扩展…",
1436
+ "extensions.sort.newest": "最新",
1437
+ "extensions.sort.updated": "最近更新",
1438
+ "extensions.sort.downloads": "下载最多",
1439
+ "extensions.empty": "暂无可用扩展。",
1440
+ "extensions.noResults": "没有匹配的扩展。",
1441
+ "extensions.loadFailed": "无法连接扩展市场。",
1442
+ "extensions.homepage": "主页",
1443
+ "extensions.installed": "已安装",
1444
+ "extensions.unit.skill": "个技能",
1445
+ "extensions.unit.skills": "个技能",
1446
+ "extensions.unit.agent": "个 Agent",
1447
+ "extensions.unit.agents": "个 Agent",
1448
+ "extensions.unit.panel": "个面板",
1449
+ "extensions.unit.panels": "个面板",
1450
+ "extensions.unit.api": "个 API",
1451
+ "extensions.unit.apis": "个 API",
1452
+ "extensions.detail.back": "返回",
1453
+ "extensions.detail.contributes": "包含内容",
1454
+ "extensions.detail.versions": "版本历史",
1455
+ "extensions.section.agents": "Agent",
1456
+ "extensions.section.skills": "技能",
1457
+ "extensions.section.panels": "面板",
1458
+ "extensions.section.api": "API",
1459
+ "extensions.disabled": "已禁用",
1460
+ "extensions.action.disable": "禁用",
1461
+ "extensions.action.enable": "启用",
1462
+ "extensions.action.remove": "移除",
1463
+ "extensions.action.removeConfirm": "确定移除此扩展?相关文件会被删除,之后可从授权包重新安装。",
1458
1464
  "mcp.loading": "加载中…",
1459
1465
  "mcp.empty.title": "尚未配置 MCP 服务",
1460
1466
  "mcp.empty.body": "新建 ~/.clacky/mcp.json 即可接入 MCP 服务。格式与 Claude Desktop、Cursor 一致,现有配置可直接复用。",
@@ -1742,6 +1748,7 @@ const I18n = (() => {
1742
1748
  "brand.activate.subtitle": "输入序列号以开始使用。",
1743
1749
  "brand.activate.success": "授权激活成功!",
1744
1750
  "brand.skip.warning": "品牌授权未激活 — 品牌专属技能暂不可用。可随时在「设置 → 品牌 & 授权」中激活。",
1751
+ "brand.warning.renewBtn": "续期授权",
1745
1752
 
1746
1753
  // ── Brand activation banner ──
1747
1754
  "brand.banner.prompt": "{{name}} 尚未激活授权 — 部分功能暂不可用。",
@@ -1753,11 +1760,14 @@ const I18n = (() => {
1753
1760
  "brand.banner.freePromptOnlyPaid": "欢迎使用 {{name}} — 还有 {{paid}} 个增值技能,输入序列号后即可解锁。",
1754
1761
  "brand.banner.freePromptBoth": "欢迎使用 {{name}} — 已自动安装 {{free}} 个免费技能,还有 {{paid}} 个增值技能可输入序列号解锁。",
1755
1762
 
1756
- "header.owner.tooltip": "创作者 — 点击进入创作者中心",
1763
+ "header.owner.tooltip": "创作者 — 点击进入扩展与创作",
1757
1764
  "header.cmdbar.placeholder": "搜索会话…",
1758
1765
  "notify.tooltip.on": "任务完成提示音:已开启(点击关闭)",
1759
1766
  "notify.tooltip.off": "任务完成提示音:已关闭(点击开启)",
1760
1767
 
1768
+ "header.refresh": "刷新",
1769
+ "header.theme.toDark": "切换到深色模式",
1770
+ "header.theme.toLight": "切换到浅色模式",
1761
1771
  "share.tooltip": "分享",
1762
1772
  "share.copy.1": "{{brand}} 对国产模型(Kimi、DeepSeek、智谱…)适配很到位,原生支持不用折腾代理,好用。",
1763
1773
  "share.copy.2": "{{brand}} 是真省 Token —— 靠 Prompt 缓存把我的 AI 账单压得很低,推荐。",
@@ -1980,6 +1990,8 @@ const I18n = (() => {
1980
1990
  return { lang, setLang, t, tList, applyAll };
1981
1991
  })();
1982
1992
 
1993
+ Clacky.I18n = I18n;
1994
+
1983
1995
  // ── Thinking Verbs for Progress Animation ──────────────────────────────────
1984
1996
  //
1985
1997
  // The primary verb ("Thinking" / "思考中") is chosen 90% of the time inside