mulmoclaude 0.3.0 → 0.5.0

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 (312) hide show
  1. package/bin/mulmoclaude.js +7 -24
  2. package/client/assets/html2canvas-Cx501zZr-DiKaqnKs.js +5 -0
  3. package/client/assets/{index-eHWB79u5.js → index-C94GcmNa.js} +203 -198
  4. package/client/assets/index-CY-WpQUm.css +2 -0
  5. package/client/assets/{index.es-D4YyL_Dg-BfRHLTZV.js → index.es-D4YyL_Dg-5ipqh8Pe.js} +5 -5
  6. package/client/assets/material-symbols-outlined-NzYEeyps.woff2 +0 -0
  7. package/client/index.html +2 -4
  8. package/package.json +17 -15
  9. package/server/agent/attachmentConverter.ts +2 -2
  10. package/server/agent/backend/claude-code.ts +170 -0
  11. package/server/agent/backend/index.ts +14 -0
  12. package/server/agent/backend/types.ts +65 -0
  13. package/server/agent/index.ts +31 -159
  14. package/server/agent/mcp-server.ts +88 -10
  15. package/server/agent/mcp-tools/index.ts +8 -7
  16. package/server/agent/mcp-tools/notify.ts +76 -0
  17. package/server/agent/mcp-tools/x.ts +12 -2
  18. package/server/agent/plugin-names.ts +10 -4
  19. package/server/agent/prompt.ts +187 -26
  20. package/server/agent/resumeFailover.ts +5 -5
  21. package/server/agent/sandboxMounts.ts +3 -3
  22. package/server/api/auth/bearerAuth.ts +3 -3
  23. package/server/api/auth/token.ts +2 -2
  24. package/server/api/routes/agent.ts +99 -4
  25. package/server/api/routes/chart.ts +13 -0
  26. package/server/api/routes/chat-index.ts +2 -1
  27. package/server/api/routes/config.ts +35 -8
  28. package/server/api/routes/files.ts +75 -24
  29. package/server/api/routes/html.ts +15 -2
  30. package/server/api/routes/image.ts +75 -20
  31. package/server/api/routes/mulmo-script.ts +33 -31
  32. package/server/api/routes/news.ts +146 -0
  33. package/server/api/routes/notifications.ts +58 -2
  34. package/server/api/routes/pdf.ts +2 -2
  35. package/server/api/routes/plugins.ts +73 -91
  36. package/server/api/routes/presentHtml.ts +9 -0
  37. package/server/api/routes/roles.ts +12 -2
  38. package/server/api/routes/scheduler.ts +20 -11
  39. package/server/api/routes/schedulerTasks.ts +58 -21
  40. package/server/api/routes/sessions.ts +15 -4
  41. package/server/api/routes/sessionsCursor.ts +4 -4
  42. package/server/api/routes/skills.ts +26 -5
  43. package/server/api/routes/sources.ts +8 -7
  44. package/server/api/routes/todos.ts +30 -0
  45. package/server/api/routes/todosColumnsHandlers.ts +13 -27
  46. package/server/api/routes/todosHandlers.ts +1 -1
  47. package/server/api/routes/todosItemsHandlers.ts +14 -14
  48. package/server/api/routes/wiki/frontmatter.ts +86 -0
  49. package/server/api/routes/wiki.ts +335 -75
  50. package/server/api/sandboxStatus.ts +1 -1
  51. package/server/events/notifications.ts +32 -8
  52. package/server/events/pub-sub/index.ts +3 -3
  53. package/server/events/relay-client.ts +26 -16
  54. package/server/events/resolveRelayBridgeOptions.ts +125 -0
  55. package/server/index.ts +72 -49
  56. package/server/system/config.ts +5 -5
  57. package/server/system/credentials.ts +7 -5
  58. package/server/system/env.ts +15 -5
  59. package/server/system/macosNotify.ts +152 -0
  60. package/server/utils/errors.ts +11 -2
  61. package/server/utils/fetch.ts +54 -0
  62. package/server/utils/files/atomic.ts +18 -17
  63. package/server/utils/files/image-store.ts +19 -13
  64. package/server/utils/files/journal-io.ts +2 -2
  65. package/server/utils/files/json.ts +5 -5
  66. package/server/utils/files/markdown-image-fill.ts +131 -0
  67. package/server/utils/files/markdown-store.ts +22 -6
  68. package/server/utils/files/naming.ts +20 -10
  69. package/server/utils/files/reference-dirs-io.ts +3 -3
  70. package/server/utils/files/roles-io.ts +4 -4
  71. package/server/utils/files/safe.ts +14 -14
  72. package/server/utils/files/scheduler-overrides-io.ts +2 -2
  73. package/server/utils/files/spreadsheet-store.ts +15 -10
  74. package/server/utils/files/workspace-io.ts +12 -12
  75. package/server/utils/gemini.ts +30 -4
  76. package/server/utils/gitignore.ts +9 -9
  77. package/server/utils/id.ts +40 -8
  78. package/server/utils/json.ts +5 -5
  79. package/server/utils/logBackgroundError.ts +12 -3
  80. package/server/utils/logPreview.ts +24 -0
  81. package/server/utils/markdown.ts +5 -5
  82. package/server/utils/port.d.mts +6 -0
  83. package/server/utils/port.mjs +48 -0
  84. package/server/utils/promptMeta.ts +32 -0
  85. package/server/utils/request.ts +12 -6
  86. package/server/utils/slug.ts +65 -4
  87. package/server/utils/spawn.ts +1 -1
  88. package/server/utils/types.ts +2 -2
  89. package/server/workspace/chat-index/index.ts +1 -1
  90. package/server/workspace/chat-index/summarizer.ts +5 -5
  91. package/server/workspace/custom-dirs.ts +5 -5
  92. package/server/workspace/helps/gemini.md +57 -0
  93. package/server/workspace/helps/index.md +2 -1
  94. package/server/workspace/helps/sources.md +42 -0
  95. package/server/workspace/helps/wiki.md +40 -5
  96. package/server/workspace/journal/archivist-cli.ts +121 -0
  97. package/server/workspace/journal/{archivist.ts → archivist-schemas.ts} +12 -120
  98. package/server/workspace/journal/dailyPass.ts +78 -38
  99. package/server/workspace/journal/diff.ts +2 -2
  100. package/server/workspace/journal/index.ts +56 -5
  101. package/server/workspace/journal/memoryExtractor.ts +1 -1
  102. package/server/workspace/journal/optimizationPass.ts +4 -5
  103. package/server/workspace/journal/paths.ts +8 -24
  104. package/server/workspace/journal/state.ts +18 -8
  105. package/server/workspace/news/reader.ts +248 -0
  106. package/server/workspace/paths.ts +4 -3
  107. package/server/workspace/reference-dirs.ts +3 -3
  108. package/server/workspace/skills/parser.ts +6 -6
  109. package/server/workspace/skills/scheduler.ts +5 -4
  110. package/server/workspace/skills/user-tasks.ts +3 -2
  111. package/server/workspace/skills/writer.ts +3 -3
  112. package/server/workspace/sources/arxivDiscovery.ts +2 -2
  113. package/server/workspace/sources/classifier.ts +1 -1
  114. package/server/workspace/sources/fetchers/rss.ts +5 -5
  115. package/server/workspace/sources/fetchers/rssParser.ts +4 -4
  116. package/server/workspace/sources/interests.ts +3 -3
  117. package/server/workspace/sources/paths.ts +6 -6
  118. package/server/workspace/sources/pipeline/fetch.ts +59 -13
  119. package/server/workspace/sources/pipeline/index.ts +59 -7
  120. package/server/workspace/sources/pipeline/notify.ts +13 -5
  121. package/server/workspace/sources/pipeline/plan.ts +11 -9
  122. package/server/workspace/sources/pipeline/summarize.ts +1 -1
  123. package/server/workspace/sources/pipeline/write.ts +5 -5
  124. package/server/workspace/sources/rateLimiter.ts +1 -1
  125. package/server/workspace/sources/sourceState.ts +9 -4
  126. package/server/workspace/sources/types.ts +9 -0
  127. package/server/workspace/sources/urls.ts +1 -1
  128. package/server/workspace/tool-trace/classify.ts +4 -4
  129. package/server/workspace/workspace.ts +7 -7
  130. package/src/App.vue +477 -251
  131. package/src/components/CanvasViewToggle.vue +12 -10
  132. package/src/components/ChatInput.vue +112 -105
  133. package/src/components/FileContentHeader.vue +10 -7
  134. package/src/components/FileContentRenderer.vue +37 -10
  135. package/src/components/FileTree.vue +34 -4
  136. package/src/components/FileTreePane.vue +32 -27
  137. package/src/components/FilesView.vue +5 -3
  138. package/src/components/FilterChip.vue +22 -0
  139. package/src/components/LockStatusPopup.vue +19 -13
  140. package/src/components/NewsView.vue +252 -0
  141. package/src/components/NotificationBell.vue +35 -9
  142. package/src/components/NotificationToast.vue +4 -1
  143. package/src/components/PageChatComposer.vue +101 -0
  144. package/src/components/PluginLauncher.vue +36 -62
  145. package/src/components/RightSidebar.vue +13 -10
  146. package/src/components/RoleSelector.vue +3 -2
  147. package/src/components/SessionHeaderControls.vue +63 -0
  148. package/src/components/SessionHistoryExpandButton.vue +30 -0
  149. package/src/components/SessionHistoryPanel.vue +64 -93
  150. package/src/components/SessionHistoryToggleButton.vue +40 -0
  151. package/src/components/SessionRoleIcon.vue +72 -0
  152. package/src/components/SessionSidebar.vue +96 -0
  153. package/src/components/SessionTabBar.vue +44 -51
  154. package/src/components/SettingsMcpTab.vue +361 -52
  155. package/src/components/SettingsModal.vue +203 -72
  156. package/src/components/SettingsReferenceDirsTab.vue +72 -51
  157. package/src/components/SettingsWorkspaceDirsTab.vue +74 -51
  158. package/src/components/SidebarHeader.vue +50 -16
  159. package/src/components/SourcesManager.vue +900 -0
  160. package/src/components/SourcesView.vue +45 -0
  161. package/src/components/StackView.vue +84 -48
  162. package/src/components/SuggestionsPanel.vue +25 -36
  163. package/src/components/SystemFileBanner.vue +106 -0
  164. package/src/components/ThinkingIndicator.vue +41 -0
  165. package/src/components/TodoExplorer.vue +72 -22
  166. package/src/components/todo/TodoAddDialog.vue +17 -12
  167. package/src/components/todo/TodoEditDialog.vue +7 -2
  168. package/src/components/todo/TodoEditPanel.vue +15 -10
  169. package/src/components/todo/TodoKanbanView.vue +16 -6
  170. package/src/components/todo/TodoListView.vue +14 -3
  171. package/src/components/todo/TodoTableView.vue +36 -5
  172. package/src/composables/favicon/conditions.ts +76 -0
  173. package/src/composables/favicon/resolveColor.ts +93 -0
  174. package/src/composables/favicon/types.ts +61 -0
  175. package/src/composables/useAppApi.ts +23 -0
  176. package/src/composables/useChatScroll.ts +5 -5
  177. package/src/composables/useCurrentRole.ts +32 -0
  178. package/src/composables/useDynamicFavicon.ts +174 -58
  179. package/src/composables/useEventListeners.ts +7 -12
  180. package/src/composables/useFaviconState.ts +93 -12
  181. package/src/composables/useFileSelection.ts +25 -6
  182. package/src/composables/useHealth.ts +76 -7
  183. package/src/composables/useLayoutMode.ts +27 -0
  184. package/src/composables/useNewsItems.ts +38 -0
  185. package/src/composables/useNewsReadState.ts +75 -0
  186. package/src/composables/useNotifications.ts +76 -13
  187. package/src/composables/usePendingCalls.ts +11 -1
  188. package/src/composables/useRoles.ts +6 -10
  189. package/src/composables/useRunElapsed.ts +80 -0
  190. package/src/composables/useSessionDerived.ts +21 -5
  191. package/src/composables/useSessionHistory.ts +7 -17
  192. package/src/composables/useSidePanelVisible.ts +25 -0
  193. package/src/composables/useViewLayout.ts +16 -37
  194. package/src/config/apiRoutes.ts +19 -6
  195. package/src/config/historyFilters.ts +30 -0
  196. package/src/config/mcpCatalog.ts +285 -0
  197. package/src/config/mcpTypes.ts +26 -0
  198. package/src/config/roles.ts +19 -51
  199. package/src/config/systemFileDescriptors.ts +170 -0
  200. package/src/config/toolNames.ts +6 -1
  201. package/src/config/workspacePaths.ts +1 -0
  202. package/src/index.css +14 -0
  203. package/src/lang/de.ts +706 -0
  204. package/src/lang/en.ts +726 -0
  205. package/src/lang/es.ts +712 -0
  206. package/src/lang/fr.ts +704 -0
  207. package/src/lang/ja.ts +707 -0
  208. package/src/lang/ko.ts +709 -0
  209. package/src/lang/pt-BR.ts +702 -0
  210. package/src/lang/zh.ts +705 -0
  211. package/src/lib/vue-i18n.ts +97 -0
  212. package/src/main.ts +3 -0
  213. package/src/plugins/canvas/View.vue +104 -186
  214. package/src/plugins/canvas/definition.ts +0 -8
  215. package/src/plugins/canvas/index.ts +3 -2
  216. package/src/plugins/chart/Preview.vue +1 -1
  217. package/src/plugins/chart/View.vue +9 -4
  218. package/src/plugins/chart/index.ts +3 -2
  219. package/src/plugins/editImage/index.ts +3 -2
  220. package/src/plugins/generateImage/index.ts +3 -2
  221. package/src/plugins/manageRoles/Preview.vue +4 -1
  222. package/src/plugins/manageRoles/View.vue +67 -46
  223. package/src/plugins/manageRoles/index.ts +3 -2
  224. package/src/plugins/manageSkills/Preview.vue +8 -3
  225. package/src/plugins/manageSkills/View.vue +39 -34
  226. package/src/plugins/manageSkills/index.ts +3 -2
  227. package/src/plugins/manageSource/Preview.vue +1 -1
  228. package/src/plugins/manageSource/View.vue +3 -687
  229. package/src/plugins/manageSource/index.ts +3 -2
  230. package/src/plugins/markdown/Preview.vue +1 -1
  231. package/src/plugins/markdown/View.vue +164 -73
  232. package/src/plugins/markdown/definition.ts +6 -4
  233. package/src/plugins/markdown/index.ts +3 -2
  234. package/src/plugins/presentForm/Preview.vue +99 -0
  235. package/src/plugins/presentForm/View.vue +675 -0
  236. package/src/plugins/presentForm/definition.ts +127 -0
  237. package/src/plugins/presentForm/index.ts +18 -0
  238. package/src/plugins/presentForm/plugin.ts +94 -0
  239. package/src/plugins/presentForm/types.ts +109 -0
  240. package/src/plugins/presentHtml/Preview.vue +1 -1
  241. package/src/plugins/presentHtml/View.vue +7 -4
  242. package/src/plugins/presentHtml/index.ts +3 -2
  243. package/src/plugins/presentMulmoScript/Preview.vue +1 -1
  244. package/src/plugins/presentMulmoScript/View.vue +36 -26
  245. package/src/plugins/presentMulmoScript/index.ts +3 -2
  246. package/src/plugins/scheduler/AutomationsPreview.vue +37 -0
  247. package/src/plugins/scheduler/AutomationsView.vue +23 -0
  248. package/src/plugins/scheduler/CalendarView.vue +23 -0
  249. package/src/plugins/scheduler/LegacySchedulerView.vue +32 -0
  250. package/src/plugins/scheduler/Preview.vue +7 -4
  251. package/src/plugins/scheduler/TasksTab.vue +119 -28
  252. package/src/plugins/scheduler/View.vue +75 -32
  253. package/src/plugins/scheduler/automationsDefinition.ts +58 -0
  254. package/src/plugins/scheduler/calendarDefinition.ts +46 -0
  255. package/src/plugins/scheduler/formatSchedule.ts +93 -0
  256. package/src/plugins/scheduler/index.ts +68 -14
  257. package/src/plugins/scheduler/legacyShape.ts +34 -0
  258. package/src/plugins/spreadsheet/Preview.vue +9 -5
  259. package/src/plugins/spreadsheet/View.vue +43 -57
  260. package/src/plugins/spreadsheet/engine/responseDecoder.ts +2 -1
  261. package/src/plugins/spreadsheet/index.ts +3 -2
  262. package/src/plugins/textResponse/Preview.vue +15 -58
  263. package/src/plugins/textResponse/View.vue +42 -45
  264. package/src/plugins/textResponse/utils.ts +25 -0
  265. package/src/plugins/todo/Preview.vue +11 -6
  266. package/src/plugins/todo/View.vue +27 -13
  267. package/src/plugins/todo/composables/useTodos.ts +3 -1
  268. package/src/plugins/todo/index.ts +3 -2
  269. package/src/plugins/ui-image/ImagePreview.vue +6 -3
  270. package/src/plugins/ui-image/ImageView.vue +7 -4
  271. package/src/plugins/wiki/Preview.vue +5 -2
  272. package/src/plugins/wiki/View.vue +539 -92
  273. package/src/plugins/wiki/index.ts +5 -2
  274. package/src/plugins/wiki/route.ts +121 -0
  275. package/src/router/guards.ts +43 -24
  276. package/src/router/index.ts +53 -26
  277. package/src/router/pageRoutes.ts +23 -0
  278. package/src/tools/index.ts +12 -5
  279. package/src/tools/legacyPluginNames.ts +13 -0
  280. package/src/types/notification.ts +31 -6
  281. package/src/types/vue-i18n.d.ts +20 -0
  282. package/src/utils/agent/eventDispatch.ts +3 -6
  283. package/src/utils/agent/formatElapsed.ts +37 -0
  284. package/src/utils/agent/request.ts +22 -1
  285. package/src/utils/canvas/layoutMode.ts +26 -0
  286. package/src/utils/canvas/sidePanelVisible.ts +19 -0
  287. package/src/utils/dom/scrollIntoViewByTestId.ts +38 -0
  288. package/src/utils/errors.ts +9 -2
  289. package/src/utils/files/filename.ts +24 -0
  290. package/src/utils/filesPreview/schedulerPreview.ts +9 -3
  291. package/src/utils/id.ts +18 -0
  292. package/src/utils/image/cacheBust.ts +16 -0
  293. package/src/utils/image/resolve.ts +16 -0
  294. package/src/utils/markdown/taskList.ts +175 -0
  295. package/src/utils/mcp/interpolateSpec.ts +97 -0
  296. package/src/utils/notification/dispatch.ts +51 -15
  297. package/src/utils/path/workspaceLinkRouter.ts +99 -0
  298. package/src/utils/session/mergeSessions.ts +5 -0
  299. package/src/utils/sources/filter.ts +69 -0
  300. package/src/vite-env.d.ts +9 -0
  301. package/client/assets/chunk-vKJrgz-R-C_I3GbVV.js +0 -1
  302. package/client/assets/html2canvas-Cx501zZr-BF5dYYkY.js +0 -5
  303. package/client/assets/index-Bm70FDU2.css +0 -1
  304. package/client/assets/typeof-DBp4T-Ny-BC0P-2DM.js +0 -1
  305. package/server/workspace/journal/linkRewrite.ts +0 -4
  306. package/src/components/ToolResultsPanel.vue +0 -77
  307. package/src/composables/useCanvasViewMode.ts +0 -121
  308. package/src/plugins/scheduler/definition.ts +0 -57
  309. package/src/utils/canvas/viewMode.ts +0 -46
  310. package/src/utils/role/plugins.ts +0 -12
  311. package/src/utils/session/seedRoleDefault.ts +0 -35
  312. /package/client/assets/{purify.es-Fx1Nqyry-PeS5RUhs.js → purify.es-Fx1Nqyry-BwJECkqS.js} +0 -0
@@ -6,6 +6,7 @@ import View from "./View.vue";
6
6
  import Preview from "./Preview.vue";
7
7
  import { apiPost } from "../../utils/api";
8
8
  import { API_ROUTES } from "../../config/apiRoutes";
9
+ import { makeUuid } from "../../utils/id";
9
10
 
10
11
  function createUploadedImageResult(imageData: string, fileName: string, prompt: string): ToolResult<ImageToolData, never> {
11
12
  return {
@@ -24,14 +25,14 @@ const generateImagePlugin: ToolPlugin<ImageToolData> = {
24
25
  if (!result.ok) {
25
26
  return {
26
27
  toolName: TOOL_NAME,
27
- uuid: crypto.randomUUID(),
28
+ uuid: makeUuid(),
28
29
  message: result.error,
29
30
  };
30
31
  }
31
32
  return {
32
33
  ...result.data,
33
34
  toolName: TOOL_NAME,
34
- uuid: crypto.randomUUID(),
35
+ uuid: makeUuid(),
35
36
  };
36
37
  },
37
38
 
@@ -2,7 +2,7 @@
2
2
  <div class="text-sm">
3
3
  <div class="flex items-center gap-1 font-medium text-gray-700 mb-1">
4
4
  <span class="material-icons" style="font-size: 14px">manage_accounts</span>
5
- <span>{{ customRoles.length }} custom role{{ customRoles.length !== 1 ? "s" : "" }}</span>
5
+ <span>{{ t("pluginManageRoles.previewCount", customRoles.length, { named: { count: customRoles.length } }) }}</span>
6
6
  </div>
7
7
  <div v-for="role in customRoles" :key="role.id" class="text-xs text-gray-600 flex items-center gap-1">
8
8
  <span class="material-icons" style="font-size: 12px">{{ role.icon }}</span>
@@ -13,7 +13,10 @@
13
13
 
14
14
  <script setup lang="ts">
15
15
  import { ref, watch } from "vue";
16
+ import { useI18n } from "vue-i18n";
16
17
  import type { ToolResultComplete } from "gui-chat-protocol/vue";
18
+
19
+ const { t } = useI18n();
17
20
  import type { ManageRolesData, CustomRole } from "./index";
18
21
  import { useFreshPluginData } from "../../composables/useFreshPluginData";
19
22
  import { API_ROUTES } from "../../config/apiRoutes";
@@ -1,14 +1,16 @@
1
1
  <template>
2
2
  <div class="h-full bg-white flex flex-col">
3
- <div class="flex items-center justify-between px-6 py-4 border-b border-gray-100">
4
- <h2 class="text-lg font-semibold text-gray-800">Custom Roles</h2>
5
- <div class="flex items-center gap-3">
6
- <span class="text-sm text-gray-500">
7
- {{ customRoles.length }}
8
- role{{ customRoles.length !== 1 ? "s" : "" }}
9
- </span>
10
- <button v-if="!creating" data-testid="role-add-btn" class="px-2 py-1 text-xs rounded bg-blue-500 text-white hover:bg-blue-600" @click="startCreate">
11
- + Add
3
+ <div class="flex items-center justify-between gap-2 px-3 py-2 border-b border-gray-100">
4
+ <h2 class="text-lg font-semibold text-gray-800">{{ t("pluginManageRoles.heading") }}</h2>
5
+ <div class="flex items-center gap-2">
6
+ <span class="text-sm text-gray-500">{{ t("pluginManageRoles.roleCount", customRoles.length, { named: { count: customRoles.length } }) }}</span>
7
+ <button
8
+ v-if="!creating"
9
+ data-testid="role-add-btn"
10
+ class="h-8 px-2.5 flex items-center gap-1 text-sm rounded bg-blue-500 text-white hover:bg-blue-600"
11
+ @click="startCreate"
12
+ >
13
+ {{ t("pluginManageRoles.addButton") }}
12
14
  </button>
13
15
  </div>
14
16
  </div>
@@ -16,12 +18,12 @@
16
18
  <div class="flex-1 overflow-y-auto">
17
19
  <!-- New role creation panel -->
18
20
  <div v-if="creating" class="m-4 border border-blue-300 bg-blue-50 rounded-lg p-4 space-y-3">
19
- <div class="text-sm font-semibold text-gray-700">Create new role</div>
21
+ <div class="text-sm font-semibold text-gray-700">{{ t("pluginManageRoles.createPanel") }}</div>
20
22
 
21
23
  <!-- ID + Name + Icon row -->
22
24
  <div class="flex gap-3">
23
25
  <div class="w-40">
24
- <label class="block text-xs font-medium text-gray-600 mb-1">ID</label>
26
+ <label class="block text-xs font-medium text-gray-600 mb-1">{{ t("pluginManageRoles.fieldId") }}</label>
25
27
  <input
26
28
  v-model="newForm.id"
27
29
  type="text"
@@ -30,7 +32,7 @@
30
32
  />
31
33
  </div>
32
34
  <div class="flex-1">
33
- <label class="block text-xs font-medium text-gray-600 mb-1">Name</label>
35
+ <label class="block text-xs font-medium text-gray-600 mb-1">{{ t("pluginManageRoles.fieldName") }}</label>
34
36
  <input
35
37
  v-model="newForm.name"
36
38
  type="text"
@@ -39,8 +41,10 @@
39
41
  </div>
40
42
  <div class="w-32">
41
43
  <label class="block text-xs font-medium text-gray-600 mb-1">
42
- Icon
43
- <a class="text-blue-400 font-normal ml-1" href="https://fonts.google.com/icons" target="_blank" rel="noopener">?</a>
44
+ {{ t("pluginManageRoles.fieldIcon") }}
45
+ <a class="text-blue-400 font-normal ml-1" href="https://fonts.google.com/icons" target="_blank" rel="noopener">{{
46
+ t("pluginManageRoles.helpLink")
47
+ }}</a>
44
48
  </label>
45
49
  <input
46
50
  v-model="newForm.icon"
@@ -52,7 +56,7 @@
52
56
 
53
57
  <!-- Prompt -->
54
58
  <div>
55
- <label class="block text-xs font-medium text-gray-600 mb-1">Prompt</label>
59
+ <label class="block text-xs font-medium text-gray-600 mb-1">{{ t("pluginManageRoles.fieldPrompt") }}</label>
56
60
  <textarea
57
61
  v-model="newForm.prompt"
58
62
  rows="6"
@@ -63,14 +67,14 @@
63
67
 
64
68
  <!-- Plugins -->
65
69
  <div>
66
- <label class="block text-xs font-medium text-gray-600 mb-2">Plugins</label>
70
+ <label class="block text-xs font-medium text-gray-600 mb-2">{{ t("pluginManageRoles.fieldPlugins") }}</label>
67
71
  <div class="grid gap-x-4 gap-y-1 [grid-template-columns:repeat(auto-fit,minmax(180px,1fr))]">
68
72
  <label
69
73
  v-for="plugin in availablePlugins"
70
74
  :key="plugin.name"
71
75
  class="flex items-center gap-2 text-sm cursor-pointer"
72
76
  :class="plugin.enabled ? 'text-gray-700' : 'text-gray-400 cursor-not-allowed'"
73
- :title="plugin.enabled ? '' : `Requires ${plugin.requiredEnv.join(', ')} in .env`"
77
+ :title="plugin.enabled ? '' : t('pluginManageRoles.requiresEnv', { env: plugin.requiredEnv.join(', ') })"
74
78
  >
75
79
  <input
76
80
  v-model="newForm.selectedPlugins"
@@ -80,7 +84,7 @@
80
84
  class="cursor-pointer disabled:cursor-not-allowed"
81
85
  />
82
86
  {{ plugin.name }}
83
- <span v-if="!plugin.enabled" class="text-xs text-gray-400">(missing {{ plugin.requiredEnv.join(", ") }})</span>
87
+ <span v-if="!plugin.enabled" class="text-xs text-gray-400">{{ t("pluginManageRoles.missingEnv", { env: plugin.requiredEnv.join(", ") }) }}</span>
84
88
  </label>
85
89
  </div>
86
90
  </div>
@@ -88,8 +92,8 @@
88
92
  <!-- Starter queries -->
89
93
  <div>
90
94
  <label class="block text-xs font-medium text-gray-600 mb-1">
91
- Starter queries
92
- <span class="text-gray-400 font-normal">(one per line)</span>
95
+ {{ t("pluginManageRoles.fieldStarterQueries") }}
96
+ <span class="text-gray-400 font-normal">{{ t("pluginManageRoles.onePerLine") }}</span>
93
97
  </label>
94
98
  <textarea
95
99
  v-model="newForm.queriesText"
@@ -106,9 +110,11 @@
106
110
  :title="newFormError ?? ''"
107
111
  @click="saveNew"
108
112
  >
109
- {{ saving ? "Creating…" : "Create" }}
113
+ {{ saving ? t("pluginManageRoles.creating") : t("pluginManageRoles.create") }}
114
+ </button>
115
+ <button class="px-3 py-1.5 text-sm rounded border border-gray-300 text-gray-600 hover:bg-gray-50" @click="cancelCreate">
116
+ {{ t("common.cancel") }}
110
117
  </button>
111
- <button class="px-3 py-1.5 text-sm rounded border border-gray-300 text-gray-600 hover:bg-gray-50" @click="cancelCreate">Cancel</button>
112
118
  </div>
113
119
  <div v-if="newFormError" class="text-xs text-gray-500" data-testid="role-form-hint">
114
120
  {{ newFormError }}
@@ -119,7 +125,7 @@
119
125
  </div>
120
126
 
121
127
  <div v-if="!creating && customRoles.length === 0" class="h-full flex items-center justify-center text-gray-400 text-sm">
122
- No custom roles yet. Click "+ Add" or ask Claude to create one.
128
+ {{ t("pluginManageRoles.emptyHint") }}
123
129
  </div>
124
130
 
125
131
  <ul v-if="customRoles.length > 0" class="p-4 space-y-2">
@@ -134,13 +140,16 @@
134
140
  <div class="flex-1 min-w-0">
135
141
  <div class="font-medium text-sm text-gray-800">
136
142
  {{ role.name }}
137
- <span class="ml-1 text-xs font-mono text-gray-400">({{ role.id }})</span>
143
+ <span class="ml-1 text-xs font-mono text-gray-400">{{ t("pluginManageRoles.idFormatted", { id: role.id }) }}</span>
138
144
  </div>
139
145
  <div class="text-xs text-gray-400 truncate">
140
146
  {{ role.availablePlugins.join(", ") }}
141
147
  </div>
142
148
  </div>
143
- <span class="material-icons text-gray-400 text-sm" :title="selectedId === role.id ? 'Collapse' : 'Expand'">
149
+ <span
150
+ class="material-icons text-gray-400 text-sm"
151
+ :title="selectedId === role.id ? t('pluginManageRoles.collapse') : t('pluginManageRoles.expand')"
152
+ >
144
153
  {{ selectedId === role.id ? "expand_less" : "expand_more" }}
145
154
  </span>
146
155
  </div>
@@ -150,7 +159,7 @@
150
159
  <!-- ID + Name + Icon row -->
151
160
  <div class="flex gap-3">
152
161
  <div class="w-40">
153
- <label class="block text-xs font-medium text-gray-600 mb-1">ID</label>
162
+ <label class="block text-xs font-medium text-gray-600 mb-1">{{ t("pluginManageRoles.fieldId") }}</label>
154
163
  <input
155
164
  v-model="editForm.id"
156
165
  type="text"
@@ -158,7 +167,7 @@
158
167
  />
159
168
  </div>
160
169
  <div class="flex-1">
161
- <label class="block text-xs font-medium text-gray-600 mb-1">Name</label>
170
+ <label class="block text-xs font-medium text-gray-600 mb-1">{{ t("pluginManageRoles.fieldName") }}</label>
162
171
  <input
163
172
  v-model="editForm.name"
164
173
  type="text"
@@ -168,8 +177,10 @@
168
177
  </div>
169
178
  <div class="w-32">
170
179
  <label class="block text-xs font-medium text-gray-600 mb-1">
171
- Icon
172
- <a class="text-blue-400 font-normal ml-1" href="https://fonts.google.com/icons" target="_blank" rel="noopener">?</a>
180
+ {{ t("pluginManageRoles.fieldIcon") }}
181
+ <a class="text-blue-400 font-normal ml-1" href="https://fonts.google.com/icons" target="_blank" rel="noopener">{{
182
+ t("pluginManageRoles.helpLink")
183
+ }}</a>
173
184
  </label>
174
185
  <input
175
186
  v-model="editForm.icon"
@@ -181,7 +192,7 @@
181
192
 
182
193
  <!-- Prompt -->
183
194
  <div>
184
- <label class="block text-xs font-medium text-gray-600 mb-1">Prompt</label>
195
+ <label class="block text-xs font-medium text-gray-600 mb-1">{{ t("pluginManageRoles.fieldPrompt") }}</label>
185
196
  <textarea
186
197
  v-model="editForm.prompt"
187
198
  rows="6"
@@ -192,14 +203,14 @@
192
203
 
193
204
  <!-- Plugins -->
194
205
  <div>
195
- <label class="block text-xs font-medium text-gray-600 mb-2">Plugins</label>
206
+ <label class="block text-xs font-medium text-gray-600 mb-2">{{ t("pluginManageRoles.fieldPlugins") }}</label>
196
207
  <div class="grid gap-x-4 gap-y-1 [grid-template-columns:repeat(auto-fit,minmax(180px,1fr))]">
197
208
  <label
198
209
  v-for="plugin in availablePlugins"
199
210
  :key="plugin.name"
200
211
  class="flex items-center gap-2 text-sm cursor-pointer"
201
212
  :class="plugin.enabled ? 'text-gray-700' : 'text-gray-400 cursor-not-allowed'"
202
- :title="plugin.enabled ? '' : `Requires ${plugin.requiredEnv.join(', ')} in .env`"
213
+ :title="plugin.enabled ? '' : t('pluginManageRoles.requiresEnv', { env: plugin.requiredEnv.join(', ') })"
203
214
  >
204
215
  <input
205
216
  v-model="editForm.selectedPlugins"
@@ -209,7 +220,9 @@
209
220
  class="cursor-pointer disabled:cursor-not-allowed"
210
221
  />
211
222
  {{ plugin.name }}
212
- <span v-if="!plugin.enabled" class="text-xs text-gray-400">(missing {{ plugin.requiredEnv.join(", ") }})</span>
223
+ <span v-if="!plugin.enabled" class="text-xs text-gray-400">{{
224
+ t("pluginManageRoles.missingEnv", { env: plugin.requiredEnv.join(", ") })
225
+ }}</span>
213
226
  </label>
214
227
  </div>
215
228
  </div>
@@ -217,8 +230,8 @@
217
230
  <!-- Starter queries -->
218
231
  <div>
219
232
  <label class="block text-xs font-medium text-gray-600 mb-1">
220
- Starter queries
221
- <span class="text-gray-400 font-normal">(one per line)</span>
233
+ {{ t("pluginManageRoles.fieldStarterQueries") }}
234
+ <span class="text-gray-400 font-normal">{{ t("pluginManageRoles.onePerLine") }}</span>
222
235
  </label>
223
236
  <textarea
224
237
  v-model="editForm.queriesText"
@@ -236,16 +249,18 @@
236
249
  :title="editFormError ?? ''"
237
250
  @click="saveEdit(role.id)"
238
251
  >
239
- {{ saving ? "Updating…" : "Update" }}
252
+ {{ saving ? t("pluginManageRoles.updating") : t("pluginManageRoles.update") }}
253
+ </button>
254
+ <button class="px-3 py-1.5 text-sm rounded border border-gray-300 text-gray-600 hover:bg-gray-50" @click="selectedId = null">
255
+ {{ t("common.cancel") }}
240
256
  </button>
241
- <button class="px-3 py-1.5 text-sm rounded border border-gray-300 text-gray-600 hover:bg-gray-50" @click="selectedId = null">Cancel</button>
242
257
  </div>
243
258
  <button
244
259
  class="px-3 py-1.5 text-sm rounded border border-red-200 text-red-500 hover:bg-red-50 disabled:opacity-50"
245
260
  :disabled="saving"
246
261
  @click="deleteRole(role.id)"
247
262
  >
248
- Delete
263
+ {{ t("pluginManageRoles.delete") }}
249
264
  </button>
250
265
  </div>
251
266
  <div v-if="editFormError" class="text-xs text-gray-500">
@@ -263,6 +278,7 @@
263
278
 
264
279
  <script setup lang="ts">
265
280
  import { ref, computed, watch, onMounted } from "vue";
281
+ import { useI18n } from "vue-i18n";
266
282
  import { useFreshPluginData } from "../../composables/useFreshPluginData";
267
283
  import { useAppApi } from "../../composables/useAppApi";
268
284
  import type { ToolResultComplete } from "gui-chat-protocol/vue";
@@ -271,6 +287,8 @@ import { getAllPluginNames } from "../../tools/index";
271
287
  import { apiGet, apiPost } from "../../utils/api";
272
288
  import { API_ROUTES } from "../../config/apiRoutes";
273
289
 
290
+ const { t } = useI18n();
291
+
274
292
  interface PluginEntry {
275
293
  name: string;
276
294
  enabled: boolean;
@@ -406,7 +424,10 @@ async function callManage(body: Record<string, unknown>): Promise<ManageResult>
406
424
  // give us anything useful.
407
425
  return {
408
426
  success: false,
409
- error: result.status === 0 ? result.error || "Network error" : result.error || `Server error: ${result.status}`,
427
+ error:
428
+ result.status === 0
429
+ ? result.error || t("pluginManageRoles.errNetworkError")
430
+ : result.error || t("pluginManageRoles.errServerError", { status: result.status }),
410
431
  };
411
432
  }
412
433
  return result.data;
@@ -432,13 +453,13 @@ async function refreshList() {
432
453
  function validateRoleForm(form: EditForm, excludeId: string | null): string | null {
433
454
  const trimmedId = form.id.trim();
434
455
  const trimmedName = form.name.trim();
435
- if (!trimmedId) return "ID is required.";
456
+ if (!trimmedId) return t("pluginManageRoles.errIdRequired");
436
457
  if (!/^[a-zA-Z0-9_-]+$/.test(trimmedId)) {
437
- return "ID may only contain letters, numbers, '-' and '_'.";
458
+ return t("pluginManageRoles.errIdInvalid");
438
459
  }
439
- if (!trimmedName) return "Name is required.";
460
+ if (!trimmedName) return t("pluginManageRoles.errNameRequired");
440
461
  if (customRoles.value.some((existing) => existing.id === trimmedId && existing.id !== excludeId)) {
441
- return `A role with ID '${trimmedId}' already exists.`;
462
+ return t("pluginManageRoles.errIdDuplicate", { id: trimmedId });
442
463
  }
443
464
  return null;
444
465
  }
@@ -473,7 +494,7 @@ async function saveNew() {
473
494
  creating.value = false;
474
495
  await refreshList();
475
496
  } else {
476
- createError.value = result.error ?? "Create failed";
497
+ createError.value = result.error ?? t("pluginManageRoles.errCreateFailed");
477
498
  }
478
499
  saving.value = false;
479
500
  }
@@ -505,7 +526,7 @@ async function saveEdit(originalId: string) {
505
526
  selectedId.value = null;
506
527
  await refreshList();
507
528
  } else {
508
- saveError.value = result.error ?? "Save failed";
529
+ saveError.value = result.error ?? t("pluginManageRoles.errSaveFailed");
509
530
  }
510
531
  saving.value = false;
511
532
  }
@@ -518,7 +539,7 @@ async function deleteRole(roleId: string) {
518
539
  selectedId.value = null;
519
540
  await refreshList();
520
541
  } else {
521
- saveError.value = result.error ?? "Delete failed";
542
+ saveError.value = result.error ?? t("pluginManageRoles.errDeleteFailed");
522
543
  }
523
544
  saving.value = false;
524
545
  }
@@ -6,6 +6,7 @@ import View from "./View.vue";
6
6
  import Preview from "./Preview.vue";
7
7
  import { apiPost } from "../../utils/api";
8
8
  import { API_ROUTES } from "../../config/apiRoutes";
9
+ import { makeUuid } from "../../utils/id";
9
10
 
10
11
  export interface CustomRole {
11
12
  id: string;
@@ -27,14 +28,14 @@ const manageRolesPlugin: ToolPlugin = {
27
28
  if (!result.ok) {
28
29
  return {
29
30
  toolName: TOOL_NAME,
30
- uuid: crypto.randomUUID(),
31
+ uuid: makeUuid(),
31
32
  message: result.error,
32
33
  };
33
34
  }
34
35
  return {
35
36
  ...result.data,
36
37
  toolName: TOOL_NAME,
37
- uuid: crypto.randomUUID(),
38
+ uuid: makeUuid(),
38
39
  };
39
40
  },
40
41
  isEnabled: () => true,
@@ -1,21 +1,26 @@
1
1
  <template>
2
- <div class="text-sm">
2
+ <div class="p-2 text-sm">
3
3
  <div class="flex items-center gap-1 font-medium text-gray-700 mb-1">
4
4
  <span class="material-icons" style="font-size: 14px">auto_awesome</span>
5
- <span>{{ skills.length }} skill{{ skills.length !== 1 ? "s" : "" }}</span>
5
+ <span>{{ t("pluginManageSkills.previewCount", skills.length, { named: { count: skills.length } }) }}</span>
6
6
  </div>
7
7
  <div v-for="skill in skills.slice(0, 6)" :key="skill.name" class="text-xs text-gray-600 truncate">
8
8
  {{ skill.name }}
9
9
  </div>
10
- <div v-if="skills.length > 6" class="text-xs text-gray-400 italic">+{{ skills.length - 6 }} more</div>
10
+ <div v-if="skills.length > 6" class="text-xs text-gray-400 italic">
11
+ {{ t("pluginManageSkills.previewMore", { count: skills.length - 6 }) }}
12
+ </div>
11
13
  </div>
12
14
  </template>
13
15
 
14
16
  <script setup lang="ts">
15
17
  import { computed } from "vue";
18
+ import { useI18n } from "vue-i18n";
16
19
  import type { ToolResultComplete } from "gui-chat-protocol/vue";
17
20
  import type { ManageSkillsData } from "./index";
18
21
 
22
+ const { t } = useI18n();
23
+
19
24
  const props = defineProps<{ result: ToolResultComplete<ManageSkillsData> }>();
20
25
  const skills = computed(() => props.result.data?.skills ?? []);
21
26
  </script>
@@ -1,10 +1,10 @@
1
1
  <template>
2
2
  <div class="h-full bg-white flex flex-col overflow-hidden">
3
3
  <!-- Header -->
4
- <div class="flex items-center justify-between px-6 py-4 border-b border-gray-100 shrink-0">
4
+ <div class="flex items-center justify-between gap-2 px-3 py-2 border-b border-gray-100 shrink-0">
5
5
  <div>
6
- <h2 class="text-lg font-semibold text-gray-800">Skills</h2>
7
- <p class="text-xs text-gray-400 mt-0.5">{{ skills.length }} available · click one to view · "Run" invokes it as /&lt;name&gt;</p>
6
+ <h2 class="text-lg font-semibold text-gray-800">{{ t("pluginManageSkills.heading") }}</h2>
7
+ <p class="text-xs text-gray-400 mt-0.5">{{ t("pluginManageSkills.subheading", { count: skills.length }) }}</p>
8
8
  </div>
9
9
  </div>
10
10
 
@@ -32,15 +32,16 @@
32
32
  {{ skill.source }}
33
33
  </div>
34
34
  </div>
35
- <p v-if="skills.length === 0" class="p-4 text-sm text-gray-400 italic">
36
- No skills found. Add skill folders under
37
- <code class="text-[11px]">~/.claude/skills/</code>.
38
- </p>
35
+ <i18n-t v-if="skills.length === 0" keypath="pluginManageSkills.emptyWithPath" tag="p" class="p-4 text-sm text-gray-400 italic">
36
+ <template #path>
37
+ <code class="text-[11px]">{{ t("pluginManageSkills.emptySkillPath") }}</code>
38
+ </template>
39
+ </i18n-t>
39
40
  </div>
40
41
 
41
42
  <!-- Right: detail pane -->
42
43
  <div class="flex-1 min-w-0 overflow-y-auto">
43
- <div v-if="!selected" class="p-6 text-sm text-gray-400 italic">Select a skill on the left to view its SKILL.md.</div>
44
+ <div v-if="!selected" class="p-6 text-sm text-gray-400 italic">{{ t("pluginManageSkills.selectHint") }}</div>
44
45
  <div v-else class="p-6">
45
46
  <div class="flex items-start justify-between gap-4 mb-4">
46
47
  <div class="min-w-0">
@@ -54,64 +55,64 @@
54
55
  <div class="flex items-center gap-2 shrink-0">
55
56
  <template v-if="editing">
56
57
  <button
57
- class="px-3 py-1.5 text-sm rounded border border-gray-300 text-gray-600 hover:bg-gray-50 flex items-center gap-1"
58
+ class="h-8 px-2.5 flex items-center gap-1 text-sm rounded border border-gray-300 text-gray-600 hover:bg-gray-50"
58
59
  data-testid="skill-cancel-btn"
59
60
  @click="cancelEdit"
60
61
  >
61
- Cancel
62
+ {{ t("common.cancel") }}
62
63
  </button>
63
64
  <button
64
- class="px-3 py-1.5 text-sm rounded bg-green-600 hover:bg-green-700 text-white disabled:opacity-40 flex items-center gap-1"
65
+ class="h-8 px-2.5 flex items-center gap-1 text-sm rounded bg-green-600 hover:bg-green-700 text-white disabled:opacity-40"
65
66
  :disabled="saving"
66
67
  data-testid="skill-save-btn"
67
68
  @click="saveEdit"
68
69
  >
69
- <span class="material-icons text-base">save</span>
70
- Save
70
+ <span class="material-icons text-sm">save</span>
71
+ {{ t("common.save") }}
71
72
  </button>
72
73
  </template>
73
74
  <template v-else>
74
75
  <button
75
76
  v-if="detail && detail.source === 'project'"
76
- class="px-3 py-1.5 text-sm rounded border border-gray-300 text-gray-600 hover:bg-gray-50 disabled:opacity-40 flex items-center gap-1"
77
+ class="h-8 px-2.5 flex items-center gap-1 text-sm rounded border border-gray-300 text-gray-600 hover:bg-gray-50 disabled:opacity-40"
77
78
  :disabled="detailLoading"
78
79
  data-testid="skill-edit-btn"
79
80
  @click="startEdit"
80
81
  >
81
- <span class="material-icons text-base">edit</span>
82
- Edit
82
+ <span class="material-icons text-sm">edit</span>
83
+ {{ t("pluginManageSkills.btnEdit") }}
83
84
  </button>
84
85
  <button
85
86
  v-if="detail && detail.source === 'project'"
86
- class="px-3 py-1.5 text-sm rounded border border-red-300 text-red-600 hover:bg-red-50 disabled:opacity-40 flex items-center gap-1"
87
+ class="h-8 px-2.5 flex items-center gap-1 text-sm rounded border border-red-300 text-red-600 hover:bg-red-50 disabled:opacity-40"
87
88
  :disabled="detailLoading || deleting"
88
89
  data-testid="skill-delete-btn"
89
- title="Delete this project-scope skill"
90
+ :title="t('pluginManageSkills.deleteProjectSkill')"
90
91
  @click="deleteSkill"
91
92
  >
92
- <span class="material-icons text-base">delete</span>
93
- Delete
93
+ <span class="material-icons text-sm">delete</span>
94
+ {{ t("pluginManageSkills.btnDelete") }}
94
95
  </button>
95
96
  <button
96
- class="px-3 py-1.5 text-sm rounded bg-blue-600 hover:bg-blue-700 text-white disabled:opacity-40 flex items-center gap-1"
97
+ class="h-8 px-2.5 flex items-center gap-1 text-sm rounded bg-blue-600 hover:bg-blue-700 text-white disabled:opacity-40"
97
98
  :disabled="detailLoading || !detail"
98
99
  data-testid="skill-run-btn"
99
100
  @click="runSkill"
100
101
  >
101
- <span class="material-icons text-base">play_arrow</span>
102
- Run
102
+ <span class="material-icons text-sm">play_arrow</span>
103
+ {{ t("pluginManageSkills.btnRun") }}
103
104
  </button>
104
105
  </template>
105
106
  </div>
106
107
  </div>
107
- <div v-if="detailLoading" class="text-sm text-gray-400 italic">Loading…</div>
108
+ <div v-if="detailLoading" class="text-sm text-gray-400 italic">{{ t("pluginManageSkills.loading") }}</div>
108
109
  <div v-else-if="detailError" class="text-sm text-red-600">
109
110
  {{ detailError }}
110
111
  </div>
111
112
  <!-- Edit mode -->
112
113
  <div v-else-if="editing && detail" class="space-y-4">
113
114
  <div>
114
- <label class="block text-xs font-medium text-gray-500 mb-1"> Description </label>
115
+ <label class="block text-xs font-medium text-gray-500 mb-1"> {{ t("pluginManageSkills.fieldDescription") }} </label>
115
116
  <input
116
117
  v-model="editDescription"
117
118
  data-testid="skill-edit-description"
@@ -119,7 +120,7 @@
119
120
  />
120
121
  </div>
121
122
  <div class="flex-1">
122
- <label class="block text-xs font-medium text-gray-500 mb-1"> Body (Markdown) </label>
123
+ <label class="block text-xs font-medium text-gray-500 mb-1"> {{ t("pluginManageSkills.fieldBody") }} </label>
123
124
  <textarea
124
125
  v-model="editBody"
125
126
  data-testid="skill-edit-body"
@@ -130,7 +131,7 @@
130
131
  <!-- View mode -->
131
132
  <!-- eslint-disable-next-line vue/no-v-html -- sanitized via DOMPurify -->
132
133
  <div v-else-if="detail && renderedBody" class="markdown-content text-gray-700" data-testid="skill-body-rendered" v-html="renderedBody"></div>
133
- <p v-else-if="detail" class="text-sm text-gray-400 italic">(empty body)</p>
134
+ <p v-else-if="detail" class="text-sm text-gray-400 italic">{{ t("pluginManageSkills.emptyBody") }}</p>
134
135
  </div>
135
136
  </div>
136
137
  </div>
@@ -139,6 +140,7 @@
139
140
 
140
141
  <script setup lang="ts">
141
142
  import { computed, onMounted, ref, watch } from "vue";
143
+ import { useI18n } from "vue-i18n";
142
144
  import { marked } from "marked";
143
145
  import DOMPurify from "dompurify";
144
146
  import type { ToolResultComplete } from "gui-chat-protocol/vue";
@@ -147,6 +149,8 @@ import { useAppApi } from "../../composables/useAppApi";
147
149
  import { apiGet, apiPut, apiDelete } from "../../utils/api";
148
150
  import { API_ROUTES } from "../../config/apiRoutes";
149
151
 
152
+ const { t } = useI18n();
153
+
150
154
  interface SkillDetail {
151
155
  name: string;
152
156
  description: string;
@@ -199,7 +203,7 @@ onMounted(async () => {
199
203
  if (props.selectedResult || skills.value.length > 0) return;
200
204
  const response = await apiGet<{ skills: SkillSummary[] }>(API_ROUTES.skills.list);
201
205
  if (!response.ok) {
202
- listError.value = `Failed to load skills: ${response.error}`;
206
+ listError.value = t("pluginManageSkills.errListFailed", { error: response.error });
203
207
  return;
204
208
  }
205
209
  if (Array.isArray(response.data.skills)) {
@@ -231,7 +235,7 @@ watch(
231
235
  return;
232
236
  }
233
237
  if (!response.ok) {
234
- detailError.value = `Failed to load skill: ${response.error}`;
238
+ detailError.value = t("pluginManageSkills.errDetailFailed", { error: response.error });
235
239
  detail.value = null;
236
240
  } else {
237
241
  detail.value = response.data.skill;
@@ -263,7 +267,7 @@ async function saveEdit(): Promise<void> {
263
267
  });
264
268
  saving.value = false;
265
269
  if (!result.ok) {
266
- detailError.value = `Save failed: ${result.error}`;
270
+ detailError.value = t("pluginManageSkills.errSaveFailed", { error: result.error });
267
271
  return;
268
272
  }
269
273
  detail.value = {
@@ -285,12 +289,13 @@ async function saveEdit(): Promise<void> {
285
289
  // Run = send the skill invocation as a Claude Code slash command.
286
290
  // Claude CLI already knows about every ~/.claude/skills/<name>/SKILL.md
287
291
  // at spawn, so sending `/<name>` is enough — no need to ship the body.
288
- // Routes through App.vue's sendMessage via provide/inject (#227).
292
+ // Uses startNewChat (not sendMessage) so the user is routed to /chat
293
+ // to see the response — Skills view is only rendered on /skills.
289
294
  const appApi = useAppApi();
290
295
 
291
296
  function runSkill(): void {
292
297
  if (!selectedName.value) return;
293
- appApi.sendMessage(`/${selectedName.value}`);
298
+ appApi.startNewChat(`/${selectedName.value}`);
294
299
  }
295
300
 
296
301
  // Delete is project-scope only — see saveProjectSkill / deleteProjectSkill
@@ -300,14 +305,14 @@ function runSkill(): void {
300
305
  async function deleteSkill(): Promise<void> {
301
306
  if (!detail.value || detail.value.source !== "project") return;
302
307
  const name = detail.value.name;
303
- if (!window.confirm(`Delete skill "${name}"? This removes ~/mulmoclaude/.claude/skills/${name}/SKILL.md.`)) {
308
+ if (!window.confirm(t("pluginManageSkills.confirmDelete", { name }))) {
304
309
  return;
305
310
  }
306
311
  deleting.value = true;
307
312
  const result = await apiDelete<unknown>(API_ROUTES.skills.remove.replace(":name", encodeURIComponent(name)));
308
313
  deleting.value = false;
309
314
  if (!result.ok) {
310
- detailError.value = result.error || "Failed to delete";
315
+ detailError.value = result.error || t("pluginManageSkills.errDeleteFailed");
311
316
  return;
312
317
  }
313
318
  // Remove from the local list, advance selection, clear detail.