mulmoclaude 0.1.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.
- package/README.md +44 -0
- package/bin/mulmoclaude.js +202 -0
- package/bin/prepare-dist.js +93 -0
- package/client/assets/chunk-vKJrgz-R-C_I3GbVV.js +1 -0
- package/client/assets/html2canvas-Cx501zZr-BF5dYYkY.js +5 -0
- package/client/assets/index-D8rhwXLq.js +4906 -0
- package/client/assets/index-KNLBjwuh.css +1 -0
- package/client/assets/index.es-D4YyL_Dg-BfRHLTZV.js +5 -0
- package/client/assets/material-icons-Dr0goTwe.woff +0 -0
- package/client/assets/material-icons-kAwBdRge.woff2 +0 -0
- package/client/assets/material-icons-outlined-BpWbwl2n.woff +0 -0
- package/client/assets/material-icons-outlined-DZhiGvEA.woff2 +0 -0
- package/client/assets/material-icons-round-BDlwx-sv.woff +0 -0
- package/client/assets/material-icons-round-DrirKXBx.woff2 +0 -0
- package/client/assets/material-icons-sharp-CH1KkVu7.woff +0 -0
- package/client/assets/material-icons-sharp-gidztirS.woff2 +0 -0
- package/client/assets/material-icons-two-tone-B7wz7mED.woff +0 -0
- package/client/assets/material-icons-two-tone-DuNIpaEj.woff2 +0 -0
- package/client/assets/mulmo_bw-ERmkSv0a.png +0 -0
- package/client/assets/purify.es-Fx1Nqyry-PeS5RUhs.js +2 -0
- package/client/assets/typeof-DBp4T-Ny-BC0P-2DM.js +1 -0
- package/client/index.html +28 -0
- package/package.json +66 -0
- package/server/agent/attachmentConverter.ts +270 -0
- package/server/agent/config.ts +414 -0
- package/server/agent/index.ts +260 -0
- package/server/agent/mcp-server.ts +412 -0
- package/server/agent/mcp-tools/index.ts +63 -0
- package/server/agent/mcp-tools/x.ts +188 -0
- package/server/agent/plugin-names.ts +75 -0
- package/server/agent/prompt.ts +349 -0
- package/server/agent/resumeFailover.ts +129 -0
- package/server/agent/sandboxMounts.ts +329 -0
- package/server/agent/stream.ts +194 -0
- package/server/api/auth/bearerAuth.ts +61 -0
- package/server/api/auth/token.ts +98 -0
- package/server/api/csrfGuard.ts +85 -0
- package/server/api/routes/agent.ts +478 -0
- package/server/api/routes/chart.ts +98 -0
- package/server/api/routes/chat-index.ts +46 -0
- package/server/api/routes/config.ts +258 -0
- package/server/api/routes/dispatchResponse.ts +79 -0
- package/server/api/routes/files.ts +812 -0
- package/server/api/routes/html.ts +101 -0
- package/server/api/routes/image.ts +169 -0
- package/server/api/routes/mulmo-script.ts +712 -0
- package/server/api/routes/mulmoScriptValidate.ts +101 -0
- package/server/api/routes/notifications.ts +69 -0
- package/server/api/routes/pdf.ts +163 -0
- package/server/api/routes/plugins.ts +276 -0
- package/server/api/routes/presentHtml.ts +48 -0
- package/server/api/routes/roles.ts +125 -0
- package/server/api/routes/scheduler.ts +153 -0
- package/server/api/routes/schedulerHandlers.ts +151 -0
- package/server/api/routes/schedulerTasks.ts +163 -0
- package/server/api/routes/sessions.ts +294 -0
- package/server/api/routes/sessionsCursor.ts +59 -0
- package/server/api/routes/skills.ts +195 -0
- package/server/api/routes/sources.ts +540 -0
- package/server/api/routes/todos.ts +263 -0
- package/server/api/routes/todosColumnsHandlers.ts +347 -0
- package/server/api/routes/todosHandlers.ts +274 -0
- package/server/api/routes/todosItemsHandlers.ts +386 -0
- package/server/api/routes/wiki/pageIndex.ts +53 -0
- package/server/api/routes/wiki.ts +363 -0
- package/server/api/sandboxStatus.ts +64 -0
- package/server/events/notifications.ts +160 -0
- package/server/events/pub-sub/index.ts +45 -0
- package/server/events/relay-client.ts +288 -0
- package/server/events/scheduler-adapter.ts +302 -0
- package/server/events/session-store/index.ts +492 -0
- package/server/events/task-manager/index.ts +181 -0
- package/server/index.ts +572 -0
- package/server/system/config.ts +243 -0
- package/server/system/credentials.ts +220 -0
- package/server/system/docker.ts +97 -0
- package/server/system/env.ts +109 -0
- package/server/system/logger/config.ts +112 -0
- package/server/system/logger/formatters.ts +40 -0
- package/server/system/logger/index.ts +53 -0
- package/server/system/logger/rotation.ts +37 -0
- package/server/system/logger/sinks.ts +101 -0
- package/server/system/logger/types.ts +29 -0
- package/server/utils/date.ts +57 -0
- package/server/utils/errors.ts +7 -0
- package/server/utils/fetch.ts +27 -0
- package/server/utils/files/atomic.ts +125 -0
- package/server/utils/files/html-io.ts +20 -0
- package/server/utils/files/image-store.ts +66 -0
- package/server/utils/files/index.ts +45 -0
- package/server/utils/files/journal-io.ts +213 -0
- package/server/utils/files/json.ts +69 -0
- package/server/utils/files/markdown-store.ts +33 -0
- package/server/utils/files/naming.ts +50 -0
- package/server/utils/files/reference-dirs-io.ts +45 -0
- package/server/utils/files/roles-io.ts +45 -0
- package/server/utils/files/safe.ts +106 -0
- package/server/utils/files/scheduler-io.ts +20 -0
- package/server/utils/files/scheduler-overrides-io.ts +64 -0
- package/server/utils/files/session-io.ts +136 -0
- package/server/utils/files/spreadsheet-store.ts +63 -0
- package/server/utils/files/todos-io.ts +29 -0
- package/server/utils/files/user-tasks-io.ts +25 -0
- package/server/utils/files/workspace-io.ts +221 -0
- package/server/utils/gemini.ts +59 -0
- package/server/utils/gitignore.ts +69 -0
- package/server/utils/http.ts +15 -0
- package/server/utils/httpError.ts +61 -0
- package/server/utils/id.ts +16 -0
- package/server/utils/json.ts +83 -0
- package/server/utils/logBackgroundError.ts +22 -0
- package/server/utils/markdown.ts +82 -0
- package/server/utils/request.ts +29 -0
- package/server/utils/slug.ts +50 -0
- package/server/utils/spawn.ts +62 -0
- package/server/utils/time.ts +34 -0
- package/server/utils/types.ts +47 -0
- package/server/workspace/chat-index/index.ts +153 -0
- package/server/workspace/chat-index/indexer.ts +209 -0
- package/server/workspace/chat-index/paths.ts +34 -0
- package/server/workspace/chat-index/summarizer.ts +247 -0
- package/server/workspace/chat-index/types.ts +38 -0
- package/server/workspace/custom-dirs.ts +220 -0
- package/server/workspace/helps/business.md +104 -0
- package/server/workspace/helps/github.md +23 -0
- package/server/workspace/helps/index.md +60 -0
- package/server/workspace/helps/mulmoscript.md +249 -0
- package/server/workspace/helps/sandbox.md +90 -0
- package/server/workspace/helps/spreadsheet.md +43 -0
- package/server/workspace/helps/telegram.md +135 -0
- package/server/workspace/helps/wiki.md +131 -0
- package/server/workspace/journal/archivist.ts +386 -0
- package/server/workspace/journal/dailyPass.ts +743 -0
- package/server/workspace/journal/diff.ts +71 -0
- package/server/workspace/journal/index.ts +185 -0
- package/server/workspace/journal/indexFile.ts +136 -0
- package/server/workspace/journal/linkRewrite.ts +4 -0
- package/server/workspace/journal/memoryExtractor.ts +130 -0
- package/server/workspace/journal/optimizationPass.ts +160 -0
- package/server/workspace/journal/paths.ts +76 -0
- package/server/workspace/journal/state.ts +125 -0
- package/server/workspace/paths.ts +158 -0
- package/server/workspace/reference-dirs.ts +252 -0
- package/server/workspace/roles.ts +37 -0
- package/server/workspace/skills/discovery.ts +125 -0
- package/server/workspace/skills/index.ts +10 -0
- package/server/workspace/skills/parser.ts +144 -0
- package/server/workspace/skills/paths.ts +41 -0
- package/server/workspace/skills/scheduler.ts +149 -0
- package/server/workspace/skills/types.ts +30 -0
- package/server/workspace/skills/user-tasks.ts +257 -0
- package/server/workspace/skills/writer.ts +189 -0
- package/server/workspace/sources/arxivDiscovery.ts +182 -0
- package/server/workspace/sources/classifier.ts +268 -0
- package/server/workspace/sources/fetchers/arxiv.ts +170 -0
- package/server/workspace/sources/fetchers/github.ts +106 -0
- package/server/workspace/sources/fetchers/githubIssues.ts +208 -0
- package/server/workspace/sources/fetchers/githubReleases.ts +186 -0
- package/server/workspace/sources/fetchers/index.ts +71 -0
- package/server/workspace/sources/fetchers/registerAll.ts +15 -0
- package/server/workspace/sources/fetchers/rss.ts +141 -0
- package/server/workspace/sources/fetchers/rssParser.ts +295 -0
- package/server/workspace/sources/httpFetcher.ts +230 -0
- package/server/workspace/sources/interests.ts +120 -0
- package/server/workspace/sources/paths.ts +110 -0
- package/server/workspace/sources/pipeline/dedup.ts +60 -0
- package/server/workspace/sources/pipeline/fetch.ts +136 -0
- package/server/workspace/sources/pipeline/index.ts +249 -0
- package/server/workspace/sources/pipeline/notify.ts +72 -0
- package/server/workspace/sources/pipeline/plan.ts +66 -0
- package/server/workspace/sources/pipeline/summarize.ts +189 -0
- package/server/workspace/sources/pipeline/write.ts +185 -0
- package/server/workspace/sources/rateLimiter.ts +148 -0
- package/server/workspace/sources/registry.ts +326 -0
- package/server/workspace/sources/robots.ts +271 -0
- package/server/workspace/sources/sourceState.ts +135 -0
- package/server/workspace/sources/taxonomy.ts +74 -0
- package/server/workspace/sources/types.ts +144 -0
- package/server/workspace/sources/urls.ts +112 -0
- package/server/workspace/tool-trace/classify.ts +114 -0
- package/server/workspace/tool-trace/index.ts +250 -0
- package/server/workspace/tool-trace/writeSearch.ts +98 -0
- package/server/workspace/wiki-backlinks/index.ts +107 -0
- package/server/workspace/wiki-backlinks/sessionBacklinks.ts +144 -0
- package/server/workspace/workspace.ts +66 -0
- package/src/App.vue +720 -0
- package/src/assets/mulmo_bw.png +0 -0
- package/src/components/CanvasViewToggle.vue +27 -0
- package/src/components/ChatAttachmentPreview.vue +45 -0
- package/src/components/ChatImagePreview.vue +17 -0
- package/src/components/ChatInput.vue +208 -0
- package/src/components/FileContentHeader.vue +49 -0
- package/src/components/FileContentRenderer.vue +162 -0
- package/src/components/FileTree.vue +115 -0
- package/src/components/FileTreePane.vue +85 -0
- package/src/components/FilesView.vue +206 -0
- package/src/components/LockStatusPopup.vue +111 -0
- package/src/components/NotificationBell.vue +131 -0
- package/src/components/NotificationToast.vue +72 -0
- package/src/components/PluginLauncher.vue +138 -0
- package/src/components/RightSidebar.vue +113 -0
- package/src/components/RoleSelector.vue +64 -0
- package/src/components/SessionHistoryPanel.vue +176 -0
- package/src/components/SessionTabBar.vue +81 -0
- package/src/components/SettingsMcpTab.vue +350 -0
- package/src/components/SettingsModal.vue +275 -0
- package/src/components/SettingsReferenceDirsTab.vue +173 -0
- package/src/components/SettingsWorkspaceDirsTab.vue +174 -0
- package/src/components/SidebarHeader.vue +69 -0
- package/src/components/StackView.vue +360 -0
- package/src/components/SuggestionsPanel.vue +65 -0
- package/src/components/TodoExplorer.vue +358 -0
- package/src/components/ToolResultsPanel.vue +77 -0
- package/src/components/todo/TodoAddDialog.vue +131 -0
- package/src/components/todo/TodoEditDialog.vue +47 -0
- package/src/components/todo/TodoEditPanel.vue +113 -0
- package/src/components/todo/TodoKanbanView.vue +249 -0
- package/src/components/todo/TodoListView.vue +79 -0
- package/src/components/todo/TodoTableView.vue +177 -0
- package/src/composables/useActiveSession.ts +40 -0
- package/src/composables/useAppApi.ts +45 -0
- package/src/composables/useCanvasViewMode.ts +121 -0
- package/src/composables/useChatScroll.ts +47 -0
- package/src/composables/useClickOutside.ts +26 -0
- package/src/composables/useClipboardCopy.ts +44 -0
- package/src/composables/useContentDisplay.ts +52 -0
- package/src/composables/useDebugBeat.ts +23 -0
- package/src/composables/useDynamicFavicon.ts +115 -0
- package/src/composables/useEventListeners.ts +42 -0
- package/src/composables/useExpandedDirs.ts +64 -0
- package/src/composables/useFaviconState.ts +30 -0
- package/src/composables/useFileSelection.ts +115 -0
- package/src/composables/useFileSortMode.ts +24 -0
- package/src/composables/useFileTree.ts +85 -0
- package/src/composables/useFreshPluginData.ts +89 -0
- package/src/composables/useHealth.ts +38 -0
- package/src/composables/useImeAwareEnter.ts +57 -0
- package/src/composables/useKeyNavigation.ts +60 -0
- package/src/composables/useMarkdownLinkHandler.ts +46 -0
- package/src/composables/useMarkdownMode.ts +17 -0
- package/src/composables/useMcpTools.ts +71 -0
- package/src/composables/useMergedSessions.ts +27 -0
- package/src/composables/useNotifications.ts +90 -0
- package/src/composables/usePdfDownload.ts +60 -0
- package/src/composables/usePendingCalls.ts +77 -0
- package/src/composables/usePubSub.ts +85 -0
- package/src/composables/useRightSidebar.ts +23 -0
- package/src/composables/useRoles.ts +34 -0
- package/src/composables/useSandboxStatus.ts +67 -0
- package/src/composables/useSelectedResult.ts +49 -0
- package/src/composables/useSessionDerived.ts +51 -0
- package/src/composables/useSessionHistory.ts +81 -0
- package/src/composables/useSessionSync.ts +57 -0
- package/src/composables/useViewLayout.ts +55 -0
- package/src/config/apiRoutes.ts +173 -0
- package/src/config/pubsubChannels.ts +45 -0
- package/src/config/roles.ts +335 -0
- package/src/config/schedulerActions.ts +25 -0
- package/src/config/toolNames.ts +71 -0
- package/src/config/workspacePaths.ts +24 -0
- package/src/index.css +107 -0
- package/src/main.ts +25 -0
- package/src/plugins/canvas/Preview.vue +13 -0
- package/src/plugins/canvas/View.vue +333 -0
- package/src/plugins/canvas/definition.ts +38 -0
- package/src/plugins/canvas/index.ts +36 -0
- package/src/plugins/chart/Preview.vue +49 -0
- package/src/plugins/chart/View.vue +143 -0
- package/src/plugins/chart/definition.ts +58 -0
- package/src/plugins/chart/index.ts +52 -0
- package/src/plugins/editImage/Preview.vue +13 -0
- package/src/plugins/editImage/View.vue +13 -0
- package/src/plugins/editImage/definition.ts +27 -0
- package/src/plugins/editImage/index.ts +36 -0
- package/src/plugins/generateImage/Preview.vue +13 -0
- package/src/plugins/generateImage/View.vue +33 -0
- package/src/plugins/generateImage/definition.ts +32 -0
- package/src/plugins/generateImage/index.ts +56 -0
- package/src/plugins/manageRoles/Preview.vue +49 -0
- package/src/plugins/manageRoles/View.vue +525 -0
- package/src/plugins/manageRoles/definition.ts +43 -0
- package/src/plugins/manageRoles/index.ts +47 -0
- package/src/plugins/manageSkills/Preview.vue +21 -0
- package/src/plugins/manageSkills/View.vue +321 -0
- package/src/plugins/manageSkills/definition.ts +49 -0
- package/src/plugins/manageSkills/index.ts +49 -0
- package/src/plugins/manageSource/Preview.vue +33 -0
- package/src/plugins/manageSource/View.vue +697 -0
- package/src/plugins/manageSource/definition.ts +63 -0
- package/src/plugins/manageSource/index.ts +66 -0
- package/src/plugins/markdown/Preview.vue +77 -0
- package/src/plugins/markdown/View.vue +476 -0
- package/src/plugins/markdown/definition.ts +50 -0
- package/src/plugins/markdown/index.ts +36 -0
- package/src/plugins/presentHtml/Preview.vue +25 -0
- package/src/plugins/presentHtml/View.vue +52 -0
- package/src/plugins/presentHtml/definition.ts +27 -0
- package/src/plugins/presentHtml/helpers.ts +72 -0
- package/src/plugins/presentHtml/index.ts +41 -0
- package/src/plugins/presentMulmoScript/Preview.vue +23 -0
- package/src/plugins/presentMulmoScript/View.vue +1166 -0
- package/src/plugins/presentMulmoScript/definition.ts +95 -0
- package/src/plugins/presentMulmoScript/helpers.ts +162 -0
- package/src/plugins/presentMulmoScript/index.ts +40 -0
- package/src/plugins/scheduler/Preview.vue +67 -0
- package/src/plugins/scheduler/TasksTab.vue +205 -0
- package/src/plugins/scheduler/View.vue +565 -0
- package/src/plugins/scheduler/definition.ts +57 -0
- package/src/plugins/scheduler/index.ts +45 -0
- package/src/plugins/scheduler/viewModes.ts +26 -0
- package/src/plugins/spreadsheet/Preview.vue +29 -0
- package/src/plugins/spreadsheet/View.vue +997 -0
- package/src/plugins/spreadsheet/cellHighlights.ts +79 -0
- package/src/plugins/spreadsheet/definition.ts +121 -0
- package/src/plugins/spreadsheet/engine/calculator.ts +459 -0
- package/src/plugins/spreadsheet/engine/cellBuilder.ts +81 -0
- package/src/plugins/spreadsheet/engine/date-parser.ts +220 -0
- package/src/plugins/spreadsheet/engine/date-utils.ts +56 -0
- package/src/plugins/spreadsheet/engine/engine.ts +176 -0
- package/src/plugins/spreadsheet/engine/evaluator.ts +390 -0
- package/src/plugins/spreadsheet/engine/formatter.ts +172 -0
- package/src/plugins/spreadsheet/engine/formulaRefs.ts +101 -0
- package/src/plugins/spreadsheet/engine/functions/date.ts +299 -0
- package/src/plugins/spreadsheet/engine/functions/financial.ts +387 -0
- package/src/plugins/spreadsheet/engine/functions/index.ts +16 -0
- package/src/plugins/spreadsheet/engine/functions/logical.ts +262 -0
- package/src/plugins/spreadsheet/engine/functions/lookup.ts +400 -0
- package/src/plugins/spreadsheet/engine/functions/mathematical.ts +297 -0
- package/src/plugins/spreadsheet/engine/functions/statistical.ts +338 -0
- package/src/plugins/spreadsheet/engine/functions/text.ts +389 -0
- package/src/plugins/spreadsheet/engine/index.ts +27 -0
- package/src/plugins/spreadsheet/engine/jsonCellLocator.ts +111 -0
- package/src/plugins/spreadsheet/engine/parser.ts +143 -0
- package/src/plugins/spreadsheet/engine/registry.ts +150 -0
- package/src/plugins/spreadsheet/engine/responseDecoder.ts +67 -0
- package/src/plugins/spreadsheet/engine/types.ts +64 -0
- package/src/plugins/spreadsheet/index.ts +36 -0
- package/src/plugins/textResponse/Preview.vue +94 -0
- package/src/plugins/textResponse/View.vue +503 -0
- package/src/plugins/textResponse/definition.ts +34 -0
- package/src/plugins/textResponse/index.ts +27 -0
- package/src/plugins/textResponse/plugin.ts +29 -0
- package/src/plugins/textResponse/samples.ts +97 -0
- package/src/plugins/textResponse/types.ts +11 -0
- package/src/plugins/todo/Preview.vue +63 -0
- package/src/plugins/todo/View.vue +364 -0
- package/src/plugins/todo/composables/useTodos.ts +177 -0
- package/src/plugins/todo/definition.ts +45 -0
- package/src/plugins/todo/index.ts +61 -0
- package/src/plugins/todo/labels.ts +163 -0
- package/src/plugins/todo/priority.ts +98 -0
- package/src/plugins/todo/viewModes.ts +19 -0
- package/src/plugins/ui-image/ImagePreview.vue +23 -0
- package/src/plugins/ui-image/ImageView.vue +34 -0
- package/src/plugins/ui-image/index.ts +3 -0
- package/src/plugins/ui-image/types.ts +4 -0
- package/src/plugins/wiki/Preview.vue +65 -0
- package/src/plugins/wiki/View.vue +342 -0
- package/src/plugins/wiki/definition.ts +25 -0
- package/src/plugins/wiki/helpers.ts +59 -0
- package/src/plugins/wiki/index.ts +52 -0
- package/src/router/guards.ts +61 -0
- package/src/router/index.ts +50 -0
- package/src/tools/index.ts +52 -0
- package/src/tools/types.ts +27 -0
- package/src/types/events.ts +16 -0
- package/src/types/fileTree.ts +13 -0
- package/src/types/notification.ts +67 -0
- package/src/types/session.ts +116 -0
- package/src/types/sse.ts +90 -0
- package/src/types/toolCallHistory.ts +13 -0
- package/src/utils/agent/eventDispatch.ts +74 -0
- package/src/utils/agent/request.ts +55 -0
- package/src/utils/agent/toolCalls.ts +62 -0
- package/src/utils/api.ts +218 -0
- package/src/utils/canvas/viewMode.ts +46 -0
- package/src/utils/dom/authTokenMeta.ts +20 -0
- package/src/utils/dom/clickOutside.ts +11 -0
- package/src/utils/dom/externalLink.ts +57 -0
- package/src/utils/dom/scrollable.ts +24 -0
- package/src/utils/errors.ts +11 -0
- package/src/utils/files/expandedDirs.ts +25 -0
- package/src/utils/files/filename.ts +12 -0
- package/src/utils/files/sortChildren.ts +20 -0
- package/src/utils/filesPreview/schedulerPreview.ts +38 -0
- package/src/utils/filesPreview/todoPreview.ts +40 -0
- package/src/utils/format/date.ts +85 -0
- package/src/utils/format/frontmatter.ts +80 -0
- package/src/utils/format/jsonSyntax.ts +109 -0
- package/src/utils/html/previewCsp.ts +65 -0
- package/src/utils/image/resolve.ts +8 -0
- package/src/utils/image/rewriteMarkdownImageRefs.ts +182 -0
- package/src/utils/markdown/extractFirstH1.ts +39 -0
- package/src/utils/notification/dispatch.ts +22 -0
- package/src/utils/path/relativeLink.ts +130 -0
- package/src/utils/role/icon.ts +20 -0
- package/src/utils/role/merge.ts +10 -0
- package/src/utils/role/plugins.ts +12 -0
- package/src/utils/session/mergeSessions.ts +103 -0
- package/src/utils/session/seedRoleDefault.ts +35 -0
- package/src/utils/session/sessionEntries.ts +121 -0
- package/src/utils/session/sessionFactory.ts +22 -0
- package/src/utils/session/sessionHelpers.ts +99 -0
- package/src/utils/tools/dedup.ts +17 -0
- package/src/utils/tools/mcp.ts +33 -0
- package/src/utils/tools/pendingCalls.ts +16 -0
- package/src/utils/tools/result.ts +40 -0
- package/src/utils/types.ts +44 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
// Scan the user's ~/.claude/skills/ and the workspace-level
|
|
2
|
+
// <workspace>/.claude/skills/ for SKILL.md files, parse them, and
|
|
3
|
+
// produce a deduped list. Project-level skills override user-level
|
|
4
|
+
// skills with the same name (mirrors settings precedence in #197).
|
|
5
|
+
|
|
6
|
+
import { readdir, readFile, stat } from "node:fs/promises";
|
|
7
|
+
import { join, resolve } from "node:path";
|
|
8
|
+
import { log } from "../../system/logger/index.js";
|
|
9
|
+
import { parseSkillFrontmatter } from "./parser.js";
|
|
10
|
+
import { SKILL_FILE, USER_SKILLS_DIR, projectSkillsDir } from "./paths.js";
|
|
11
|
+
import type { Skill, SkillSource } from "./types.js";
|
|
12
|
+
|
|
13
|
+
// One directory entry → a parsed Skill, or null when the entry is
|
|
14
|
+
// not a valid skill (no SKILL.md, malformed frontmatter, I/O error).
|
|
15
|
+
// Errors are logged at warn, not thrown — a single broken skill
|
|
16
|
+
// shouldn't take down the whole list.
|
|
17
|
+
async function readSkillDir(skillDir: string, name: string, source: SkillSource): Promise<Skill | null> {
|
|
18
|
+
const skillPath = join(skillDir, SKILL_FILE);
|
|
19
|
+
try {
|
|
20
|
+
// Follow symlinks: stat rather than lstat so a user's
|
|
21
|
+
// `pptx@ → ~/ss/llm/skills/pptx/` reads through the link.
|
|
22
|
+
const fileStat = await stat(skillPath);
|
|
23
|
+
if (!fileStat.isFile()) return null;
|
|
24
|
+
const raw = await readFile(skillPath, "utf-8");
|
|
25
|
+
const parsed = parseSkillFrontmatter(raw);
|
|
26
|
+
if (!parsed) {
|
|
27
|
+
log.warn("skills", "SKILL.md has no usable frontmatter, skipping", {
|
|
28
|
+
name,
|
|
29
|
+
path: skillPath,
|
|
30
|
+
});
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
name,
|
|
35
|
+
description: parsed.description,
|
|
36
|
+
body: parsed.body,
|
|
37
|
+
source,
|
|
38
|
+
path: skillPath,
|
|
39
|
+
};
|
|
40
|
+
} catch (err) {
|
|
41
|
+
// ENOENT = SKILL.md missing. Anything else is logged so a
|
|
42
|
+
// permissions issue is findable; we still treat the slot as
|
|
43
|
+
// "not a skill" rather than failing the whole list.
|
|
44
|
+
const error = err as { code?: string };
|
|
45
|
+
if (error.code !== "ENOENT") {
|
|
46
|
+
log.warn("skills", "failed to read SKILL.md, skipping", {
|
|
47
|
+
name,
|
|
48
|
+
path: skillPath,
|
|
49
|
+
error: String(err),
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Scan one skills root (either the user or project location) and
|
|
58
|
+
* return every valid Skill. The root itself is allowed to not exist
|
|
59
|
+
* — we just return an empty list (a workspace with no .claude/skills/
|
|
60
|
+
* is the common case).
|
|
61
|
+
*/
|
|
62
|
+
export async function collectSkillsFromDir(root: string, source: SkillSource): Promise<Skill[]> {
|
|
63
|
+
let entries: string[];
|
|
64
|
+
try {
|
|
65
|
+
entries = await readdir(root);
|
|
66
|
+
} catch (err) {
|
|
67
|
+
const error = err as { code?: string };
|
|
68
|
+
if (error.code === "ENOENT") return [];
|
|
69
|
+
log.warn("skills", "failed to list skills dir, returning empty", {
|
|
70
|
+
root,
|
|
71
|
+
error: String(err),
|
|
72
|
+
});
|
|
73
|
+
return [];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const results: Skill[] = [];
|
|
77
|
+
for (const name of entries) {
|
|
78
|
+
// Skip hidden entries (.DS_Store, .gitkeep, etc.) up front.
|
|
79
|
+
if (name.startsWith(".")) continue;
|
|
80
|
+
const skillDir = resolve(root, name);
|
|
81
|
+
let dirStat;
|
|
82
|
+
try {
|
|
83
|
+
// stat follows symlinks — supports `ln -s target skills/name`.
|
|
84
|
+
dirStat = await stat(skillDir);
|
|
85
|
+
} catch {
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (!dirStat.isDirectory()) continue;
|
|
89
|
+
const skill = await readSkillDir(skillDir, name, source);
|
|
90
|
+
if (skill) results.push(skill);
|
|
91
|
+
}
|
|
92
|
+
// Stable alphabetical order for the UI.
|
|
93
|
+
results.sort((a, b) => a.name.localeCompare(b.name));
|
|
94
|
+
return results;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface DiscoverSkillsOptions {
|
|
98
|
+
/** Absolute path to the user's ~/.claude/skills/. Overridable for
|
|
99
|
+
* tests that point at mkdtempSync trees. */
|
|
100
|
+
userDir?: string;
|
|
101
|
+
/** Workspace root; project-level skills live at
|
|
102
|
+
* `<workspaceRoot>/.claude/skills/`. Passing undefined skips the
|
|
103
|
+
* project scope entirely. */
|
|
104
|
+
workspaceRoot?: string;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Discover every skill available to this workspace. Project-level
|
|
109
|
+
* skills (under `<workspace>/.claude/skills/`) override user-level
|
|
110
|
+
* skills of the same name.
|
|
111
|
+
*/
|
|
112
|
+
export async function discoverSkills(opts: DiscoverSkillsOptions = {}): Promise<Skill[]> {
|
|
113
|
+
const userDir = opts.userDir ?? USER_SKILLS_DIR;
|
|
114
|
+
const userSkills = await collectSkillsFromDir(userDir, "user");
|
|
115
|
+
|
|
116
|
+
const projectSkills = opts.workspaceRoot ? await collectSkillsFromDir(projectSkillsDir(opts.workspaceRoot), "project") : [];
|
|
117
|
+
|
|
118
|
+
// Project overrides user on name collision. Merge by building a
|
|
119
|
+
// map keyed by name, starting with user, overwriting with project.
|
|
120
|
+
const merged = new Map<string, Skill>();
|
|
121
|
+
for (const s of userSkills) merged.set(s.name, s);
|
|
122
|
+
for (const s of projectSkills) merged.set(s.name, s);
|
|
123
|
+
|
|
124
|
+
return [...merged.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
125
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Public API for the skills module. Discovery (read-only) is phase
|
|
2
|
+
// 0; save + delete (project scope only) is phase 1.
|
|
3
|
+
|
|
4
|
+
export { discoverSkills, collectSkillsFromDir } from "./discovery.js";
|
|
5
|
+
export { parseSkillFrontmatter } from "./parser.js";
|
|
6
|
+
export { saveProjectSkill, updateProjectSkill, deleteProjectSkill } from "./writer.js";
|
|
7
|
+
export type { SaveResult, UpdateResult, DeleteResult } from "./writer.js";
|
|
8
|
+
export { isValidSlug } from "../../utils/slug.js";
|
|
9
|
+
export { projectSkillsDir, projectSkillPath, projectSkillDir } from "./paths.js";
|
|
10
|
+
export type { Skill, SkillSource, SkillSummary } from "./types.js";
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
// Pure SKILL.md parser. Given the raw file content, return the
|
|
2
|
+
// `description` (from YAML frontmatter) + body, plus optional
|
|
3
|
+
// `schedule` and `roleId` for auto-scheduling (#357 Phase 2).
|
|
4
|
+
//
|
|
5
|
+
// Minimal YAML: we only care about a few keys, so rather than
|
|
6
|
+
// pulling in a YAML parser we do line-by-line extraction.
|
|
7
|
+
|
|
8
|
+
import { TIME_UNIT_MS, ONE_SECOND_MS } from "../../utils/time.js";
|
|
9
|
+
import { SCHEDULE_TYPES } from "@receptron/task-scheduler";
|
|
10
|
+
|
|
11
|
+
export interface SkillSchedule {
|
|
12
|
+
/** "daily HH:MM" or "interval Ns/Nm/Nh" */
|
|
13
|
+
raw: string;
|
|
14
|
+
/** Parsed into task-manager-compatible shape */
|
|
15
|
+
parsed: { type: typeof SCHEDULE_TYPES.daily; time: string } | { type: typeof SCHEDULE_TYPES.interval; intervalMs: number } | null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ParsedSkill {
|
|
19
|
+
description: string;
|
|
20
|
+
body: string;
|
|
21
|
+
/** If present, this skill should be auto-scheduled */
|
|
22
|
+
schedule?: SkillSchedule;
|
|
23
|
+
/** Role to use when running the scheduled skill (default: "general") */
|
|
24
|
+
roleId?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Match a YAML scalar value on a single line:
|
|
28
|
+
// description: Enable CI for a repository
|
|
29
|
+
// description: "Quoted with colons: inside"
|
|
30
|
+
// Leading/trailing whitespace trimmed. Quoted values have their
|
|
31
|
+
// outer quotes stripped but inner JSON-style escapes are NOT
|
|
32
|
+
// reversed — SKILL.md descriptions in the wild are plain text.
|
|
33
|
+
function parseScalar(raw: string): string {
|
|
34
|
+
const trimmed = raw.trim();
|
|
35
|
+
if (trimmed.length === 0) return "";
|
|
36
|
+
if ((trimmed.startsWith('"') && trimmed.endsWith('"')) || (trimmed.startsWith("'") && trimmed.endsWith("'"))) {
|
|
37
|
+
return trimmed.slice(1, -1);
|
|
38
|
+
}
|
|
39
|
+
return trimmed;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Parse schedule value from frontmatter.
|
|
44
|
+
* Supported formats:
|
|
45
|
+
* "daily HH:MM" → { type: "daily", time: "HH:MM" }
|
|
46
|
+
* "interval 30m" → { type: "interval", intervalMs: 1800000 }
|
|
47
|
+
* "interval 2h" → { type: "interval", intervalMs: 7200000 }
|
|
48
|
+
* "interval 300s" → { type: "interval", intervalMs: 300000 }
|
|
49
|
+
*/
|
|
50
|
+
// Minimum interval to prevent accidental runaway scheduling.
|
|
51
|
+
const MIN_INTERVAL_MS = 10 * ONE_SECOND_MS;
|
|
52
|
+
|
|
53
|
+
function parseScheduleValue(raw: string): SkillSchedule["parsed"] {
|
|
54
|
+
const trimmed = raw.trim();
|
|
55
|
+
|
|
56
|
+
// daily HH:MM — validate range: HH 00-23, MM 00-59
|
|
57
|
+
const dailyMatch = trimmed.match(/^daily\s+(\d{2}):(\d{2})$/);
|
|
58
|
+
if (dailyMatch) {
|
|
59
|
+
const hh = Number(dailyMatch[1]);
|
|
60
|
+
const mm = Number(dailyMatch[2]);
|
|
61
|
+
if (hh > 23 || mm > 59) return null;
|
|
62
|
+
return {
|
|
63
|
+
type: SCHEDULE_TYPES.daily,
|
|
64
|
+
time: `${dailyMatch[1]}:${dailyMatch[2]}`,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// interval Ns / Nm / Nh — must be >= MIN_INTERVAL_MS
|
|
69
|
+
const intervalMatch = trimmed.match(/^interval\s+(\d+)([smh])$/);
|
|
70
|
+
if (intervalMatch) {
|
|
71
|
+
const value = Number(intervalMatch[1]);
|
|
72
|
+
const unit = intervalMatch[2];
|
|
73
|
+
const ms = TIME_UNIT_MS[unit];
|
|
74
|
+
if (!ms) return null;
|
|
75
|
+
const intervalMs = value * ms;
|
|
76
|
+
if (intervalMs < MIN_INTERVAL_MS) return null;
|
|
77
|
+
return { type: SCHEDULE_TYPES.interval, intervalMs };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Parse a SKILL.md file. Returns null when:
|
|
85
|
+
* - the file has no frontmatter (no leading `---` fence)
|
|
86
|
+
* - the frontmatter is unterminated
|
|
87
|
+
* - there is no `description:` key
|
|
88
|
+
*
|
|
89
|
+
* An empty body is allowed (the skill may be just metadata for now).
|
|
90
|
+
*/
|
|
91
|
+
// Extract key-value pairs from YAML frontmatter lines. Returns a
|
|
92
|
+
// map of key → scalar value. Keeps parseSkillFrontmatter under the
|
|
93
|
+
// cognitive-complexity threshold.
|
|
94
|
+
function extractFrontmatterFields(lines: string[], startIdx: number, endIdx: number): Map<string, string> {
|
|
95
|
+
const fields = new Map<string, string>();
|
|
96
|
+
for (let i = startIdx; i < endIdx; i++) {
|
|
97
|
+
const line = lines[i];
|
|
98
|
+
const colonIdx = line.indexOf(":");
|
|
99
|
+
if (colonIdx === -1) continue;
|
|
100
|
+
const key = line.slice(0, colonIdx).trim();
|
|
101
|
+
const value = parseScalar(line.slice(colonIdx + 1));
|
|
102
|
+
fields.set(key, value);
|
|
103
|
+
}
|
|
104
|
+
return fields;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function parseSkillFrontmatter(raw: string): ParsedSkill | null {
|
|
108
|
+
const lines = raw.split(/\r?\n/);
|
|
109
|
+
if (lines.length === 0 || lines[0].trim() !== "---") return null;
|
|
110
|
+
|
|
111
|
+
let closeIdx = -1;
|
|
112
|
+
for (let i = 1; i < lines.length; i++) {
|
|
113
|
+
if (lines[i].trim() === "---") {
|
|
114
|
+
closeIdx = i;
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (closeIdx === -1) return null;
|
|
119
|
+
|
|
120
|
+
const fields = extractFrontmatterFields(lines, 1, closeIdx);
|
|
121
|
+
const description = fields.get("description") ?? null;
|
|
122
|
+
if (description === null) return null;
|
|
123
|
+
|
|
124
|
+
const scheduleRaw = fields.get("schedule") ?? null;
|
|
125
|
+
const roleId = fields.get("roleId") ?? null;
|
|
126
|
+
|
|
127
|
+
// Body starts after the closing fence. Trim leading blank lines so
|
|
128
|
+
// the UI doesn't render an awkward gap above the first heading.
|
|
129
|
+
const body = lines
|
|
130
|
+
.slice(closeIdx + 1)
|
|
131
|
+
.join("\n")
|
|
132
|
+
.replace(/^(?:\s*\n)+/, "")
|
|
133
|
+
.trimEnd();
|
|
134
|
+
|
|
135
|
+
const result: ParsedSkill = { description, body };
|
|
136
|
+
if (scheduleRaw) {
|
|
137
|
+
result.schedule = {
|
|
138
|
+
raw: scheduleRaw,
|
|
139
|
+
parsed: parseScheduleValue(scheduleRaw),
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
if (roleId) result.roleId = roleId;
|
|
143
|
+
return result;
|
|
144
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// Path helpers and slug validation for the skills module.
|
|
2
|
+
//
|
|
3
|
+
// Skills live in two scopes:
|
|
4
|
+
// - user: ~/.claude/skills/<slug>/SKILL.md (read-only from MulmoClaude)
|
|
5
|
+
// - project: <workspaceRoot>/.claude/skills/<slug>/SKILL.md (MulmoClaude can CRUD)
|
|
6
|
+
//
|
|
7
|
+
// The slug doubles as a filename and appears in Claude CLI slash
|
|
8
|
+
// commands (`/<slug>`), so it has to be strict: no uppercase, no
|
|
9
|
+
// spaces, no path separators, no leading/trailing hyphens, 1-64
|
|
10
|
+
// chars. Same rule as `server/sources/paths.ts#isValidSlug` — keep
|
|
11
|
+
// them in sync manually.
|
|
12
|
+
|
|
13
|
+
import { homedir } from "node:os";
|
|
14
|
+
import { join } from "node:path";
|
|
15
|
+
|
|
16
|
+
export const SKILL_FILE = "SKILL.md";
|
|
17
|
+
|
|
18
|
+
/** `~/.claude/skills/` — user scope, read-only from MulmoClaude. */
|
|
19
|
+
export const USER_SKILLS_DIR = join(homedir(), ".claude", "skills");
|
|
20
|
+
|
|
21
|
+
/** `<workspaceRoot>/.claude/skills/` — project scope, writable. */
|
|
22
|
+
export function projectSkillsDir(workspaceRoot: string): string {
|
|
23
|
+
return join(workspaceRoot, ".claude", "skills");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Full path to the project-scope SKILL.md for a given slug. */
|
|
27
|
+
export function projectSkillPath(workspaceRoot: string, slug: string): string {
|
|
28
|
+
return join(projectSkillsDir(workspaceRoot), slug, SKILL_FILE);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Directory holding a project skill (one level above SKILL.md). */
|
|
32
|
+
export function projectSkillDir(workspaceRoot: string, slug: string): string {
|
|
33
|
+
return join(projectSkillsDir(workspaceRoot), slug);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Strict slug validator. Rejects anything that could surprise the
|
|
38
|
+
* filesystem, the Claude CLI slash-command resolver, or the URL
|
|
39
|
+
* parser. Single source of truth for `save` / `delete` input.
|
|
40
|
+
*/
|
|
41
|
+
// isValidSlug moved to server/utils/slug.ts — import from there.
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
// Skill scheduling (#357 Phase 2). Scans all discovered skills for
|
|
2
|
+
// `schedule:` frontmatter and registers matching ones with the
|
|
3
|
+
// task-manager. Each scheduled skill fires `startChat()` with the
|
|
4
|
+
// skill body as the message and the skill's `roleId` (or "general").
|
|
5
|
+
//
|
|
6
|
+
// `refreshScheduledSkills()` can be called at any time to re-scan
|
|
7
|
+
// and update registrations (e.g. after a skill is saved/deleted
|
|
8
|
+
// via the API). It unregisters stale tasks and registers new ones.
|
|
9
|
+
|
|
10
|
+
import { discoverSkills } from "./discovery.js";
|
|
11
|
+
import type { Skill } from "./types.js";
|
|
12
|
+
import type { ITaskManager, TaskSchedule } from "../../events/task-manager/index.js";
|
|
13
|
+
import { parseSkillFrontmatter } from "./parser.js";
|
|
14
|
+
import { log } from "../../system/logger/index.js";
|
|
15
|
+
import { readFileSync } from "fs";
|
|
16
|
+
import { DEFAULT_ROLE_ID } from "../../../src/config/roles.js";
|
|
17
|
+
import { SESSION_ORIGINS, type SessionOrigin } from "../../../src/types/session.js";
|
|
18
|
+
|
|
19
|
+
interface SkillScheduleInfo {
|
|
20
|
+
schedule: TaskSchedule;
|
|
21
|
+
roleId: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface StartChatResult {
|
|
25
|
+
kind: string;
|
|
26
|
+
error?: string;
|
|
27
|
+
status?: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface SkillSchedulerDeps {
|
|
31
|
+
taskManager: ITaskManager;
|
|
32
|
+
workspaceRoot: string;
|
|
33
|
+
startChat: (params: { message: string; roleId: string; chatSessionId: string; origin?: SessionOrigin }) => Promise<StartChatResult>;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const SKILL_TASK_PREFIX = "skill.";
|
|
37
|
+
|
|
38
|
+
// Track registered skill task IDs so refresh can unregister stale ones.
|
|
39
|
+
let registeredTaskIds = new Set<string>();
|
|
40
|
+
let cachedDeps: SkillSchedulerDeps | null = null;
|
|
41
|
+
|
|
42
|
+
// Mutex: serialize refresh calls so concurrent save/update/delete
|
|
43
|
+
// API calls don't interleave register/unregister and corrupt state.
|
|
44
|
+
let refreshMutex: Promise<number> = Promise.resolve(0);
|
|
45
|
+
|
|
46
|
+
export async function registerScheduledSkills(deps: SkillSchedulerDeps): Promise<number> {
|
|
47
|
+
cachedDeps = deps;
|
|
48
|
+
return serializedRefresh(deps);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Re-scan skills and update task-manager registrations. Safe to call
|
|
53
|
+
* after a skill is saved, updated, or deleted — removes stale tasks
|
|
54
|
+
* and adds new ones without a server restart. Serialized: concurrent
|
|
55
|
+
* calls queue behind the in-flight one.
|
|
56
|
+
*/
|
|
57
|
+
export async function refreshScheduledSkills(): Promise<number> {
|
|
58
|
+
if (!cachedDeps) {
|
|
59
|
+
log.warn("skills", "refreshScheduledSkills called before initial register");
|
|
60
|
+
return 0;
|
|
61
|
+
}
|
|
62
|
+
return serializedRefresh(cachedDeps);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function serializedRefresh(deps: SkillSchedulerDeps): Promise<number> {
|
|
66
|
+
refreshMutex = refreshMutex.then(
|
|
67
|
+
() => doRegister(deps),
|
|
68
|
+
() => doRegister(deps),
|
|
69
|
+
);
|
|
70
|
+
return refreshMutex;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function doRegister(deps: SkillSchedulerDeps): Promise<number> {
|
|
74
|
+
const { taskManager, workspaceRoot, startChat } = deps;
|
|
75
|
+
|
|
76
|
+
// Unregister all previously registered skill tasks
|
|
77
|
+
for (const taskId of registeredTaskIds) {
|
|
78
|
+
taskManager.removeTask(taskId);
|
|
79
|
+
}
|
|
80
|
+
const previousCount = registeredTaskIds.size;
|
|
81
|
+
registeredTaskIds = new Set<string>();
|
|
82
|
+
|
|
83
|
+
const skills = await discoverSkills({ workspaceRoot });
|
|
84
|
+
let registered = 0;
|
|
85
|
+
|
|
86
|
+
for (const skill of skills) {
|
|
87
|
+
const info = readSkillScheduleInfo(skill);
|
|
88
|
+
if (!info) continue;
|
|
89
|
+
|
|
90
|
+
const { schedule, roleId } = info;
|
|
91
|
+
const taskId = `${SKILL_TASK_PREFIX}${skill.name}`;
|
|
92
|
+
|
|
93
|
+
taskManager.registerTask({
|
|
94
|
+
id: taskId,
|
|
95
|
+
description: `Scheduled skill: ${skill.name} — ${skill.description}`,
|
|
96
|
+
schedule,
|
|
97
|
+
run: async () => {
|
|
98
|
+
const chatSessionId = crypto.randomUUID();
|
|
99
|
+
log.info("skills", "running scheduled skill", {
|
|
100
|
+
name: skill.name,
|
|
101
|
+
roleId,
|
|
102
|
+
chatSessionId,
|
|
103
|
+
});
|
|
104
|
+
const result = await startChat({
|
|
105
|
+
message: `/${skill.name}`,
|
|
106
|
+
roleId,
|
|
107
|
+
chatSessionId,
|
|
108
|
+
origin: SESSION_ORIGINS.skill,
|
|
109
|
+
});
|
|
110
|
+
if (result.kind === "error") {
|
|
111
|
+
throw new Error(`scheduled skill failed: ${result.error ?? "unknown"}`);
|
|
112
|
+
}
|
|
113
|
+
log.info("skills", "scheduled skill completed", {
|
|
114
|
+
name: skill.name,
|
|
115
|
+
kind: result.kind,
|
|
116
|
+
});
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
registeredTaskIds.add(taskId);
|
|
121
|
+
registered++;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (previousCount > 0 || registered > 0) {
|
|
125
|
+
log.info("skills", "skill schedules refreshed", {
|
|
126
|
+
previous: previousCount,
|
|
127
|
+
current: registered,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return registered;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Read schedule + roleId in one file read (avoid reading the same
|
|
135
|
+
// SKILL.md twice). Returns null if no schedule is configured.
|
|
136
|
+
function readSkillScheduleInfo(skill: Skill): SkillScheduleInfo | null {
|
|
137
|
+
try {
|
|
138
|
+
const raw = readFileSync(skill.path, "utf-8");
|
|
139
|
+
const parsed = parseSkillFrontmatter(raw);
|
|
140
|
+
const s = parsed?.schedule?.parsed;
|
|
141
|
+
if (!s) return null;
|
|
142
|
+
return {
|
|
143
|
+
schedule: s,
|
|
144
|
+
roleId: parsed?.roleId ?? DEFAULT_ROLE_ID,
|
|
145
|
+
};
|
|
146
|
+
} catch {
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// Shared Skill type. A Skill is the parsed content of one
|
|
2
|
+
// ~/.claude/skills/<name>/SKILL.md file (or the project-level
|
|
3
|
+
// equivalent under <workspace>/.claude/skills/<name>/SKILL.md).
|
|
4
|
+
//
|
|
5
|
+
// Phase 0 is read-only: the server discovers and exposes skills
|
|
6
|
+
// but never writes to them. Edits happen through the user's file
|
|
7
|
+
// system or other tooling (e.g. their own skills repo).
|
|
8
|
+
|
|
9
|
+
export type SkillSource = "user" | "project";
|
|
10
|
+
|
|
11
|
+
export interface Skill {
|
|
12
|
+
/** Directory name under skills/, e.g. "ci_enable". */
|
|
13
|
+
name: string;
|
|
14
|
+
/** The `description` field from YAML frontmatter. Used as the
|
|
15
|
+
* one-line summary in the UI list. */
|
|
16
|
+
description: string;
|
|
17
|
+
/** Markdown body after the frontmatter. Passed to Claude when the
|
|
18
|
+
* user clicks "Run". */
|
|
19
|
+
body: string;
|
|
20
|
+
/** Which scope this skill was discovered in. Project overrides user
|
|
21
|
+
* when names collide. */
|
|
22
|
+
source: SkillSource;
|
|
23
|
+
/** Absolute path to the SKILL.md file (post-symlink resolve). Used
|
|
24
|
+
* to surface the origin in the detail view. */
|
|
25
|
+
path: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Lightweight summary for list endpoints — omits the full body so
|
|
29
|
+
* GET /api/skills stays small even with many skills. */
|
|
30
|
+
export type SkillSummary = Pick<Skill, "name" | "description" | "source">;
|