@yancyyu/openhermit 1.6.29 → 1.6.31

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 (157) hide show
  1. package/dist-renderer/assets/{ProjectEditorOverlay-CQm6jUR1.js → ProjectEditorOverlay-DkXfi2pg.js} +1 -1
  2. package/dist-renderer/assets/{TeamGraphOverlay-h0WDfifv.js → TeamGraphOverlay-CHNNVraw.js} +1 -1
  3. package/dist-renderer/assets/{_basePickBy-CgG_tjgX.js → _basePickBy-Do-Ff83V.js} +1 -1
  4. package/dist-renderer/assets/{_baseUniq-DwPTU9lP.js → _baseUniq-nDLhSuJI.js} +1 -1
  5. package/dist-renderer/assets/{arc-7nIrGRzY.js → arc-Bp7fA6sx.js} +1 -1
  6. package/dist-renderer/assets/{architectureDiagram-VXUJARFQ-BYhA6Ev2.js → architectureDiagram-VXUJARFQ-CPC1HdGy.js} +1 -1
  7. package/dist-renderer/assets/{blockDiagram-VD42YOAC-BVpZUGDg.js → blockDiagram-VD42YOAC-DTVKyNTO.js} +1 -1
  8. package/dist-renderer/assets/{c4Diagram-YG6GDRKO-DsdreMQ9.js → c4Diagram-YG6GDRKO-XVu-AN00.js} +1 -1
  9. package/dist-renderer/assets/channel-CIwbNcUO.js +1 -0
  10. package/dist-renderer/assets/{chunk-4BX2VUAB-CcoAs7Jd.js → chunk-4BX2VUAB-BcWmVyA-.js} +1 -1
  11. package/dist-renderer/assets/{chunk-55IACEB6-CGGAOoXd.js → chunk-55IACEB6-Co4Z2jsE.js} +1 -1
  12. package/dist-renderer/assets/{chunk-B4BG7PRW-FhpTEPvD.js → chunk-B4BG7PRW-C8q9gfDT.js} +1 -1
  13. package/dist-renderer/assets/{chunk-DI55MBZ5-DoYySbm1.js → chunk-DI55MBZ5-qDgb1gxO.js} +1 -1
  14. package/dist-renderer/assets/{chunk-FMBD7UC4-e9l2tGHG.js → chunk-FMBD7UC4-Cm8Gu2gu.js} +1 -1
  15. package/dist-renderer/assets/{chunk-QN33PNHL-DeiXVTCy.js → chunk-QN33PNHL-DYji1BRS.js} +1 -1
  16. package/dist-renderer/assets/{chunk-QZHKN3VN-DC2UJLJM.js → chunk-QZHKN3VN-DWAS568H.js} +1 -1
  17. package/dist-renderer/assets/{chunk-TZMSLE5B-BHFD9eZI.js → chunk-TZMSLE5B-CLFzXLA8.js} +1 -1
  18. package/dist-renderer/assets/classDiagram-2ON5EDUG-04A-pvql.js +1 -0
  19. package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-04A-pvql.js +1 -0
  20. package/dist-renderer/assets/clone-DQnvTIEM.js +1 -0
  21. package/dist-renderer/assets/{cose-bilkent-S5V4N54A-BdybQraU.js → cose-bilkent-S5V4N54A-CZdGhX_3.js} +1 -1
  22. package/dist-renderer/assets/{dagre-6UL2VRFP-DdF3pwM3.js → dagre-6UL2VRFP-BVY-G6nO.js} +1 -1
  23. package/dist-renderer/assets/{diagram-PSM6KHXK-B9Ldd3nh.js → diagram-PSM6KHXK-CUACvAwG.js} +1 -1
  24. package/dist-renderer/assets/{diagram-QEK2KX5R-XEqkrbpu.js → diagram-QEK2KX5R-3SfnesSG.js} +1 -1
  25. package/dist-renderer/assets/{diagram-S2PKOQOG-CipwtY59.js → diagram-S2PKOQOG-E3ksXClJ.js} +1 -1
  26. package/dist-renderer/assets/{erDiagram-Q2GNP2WA-BB-2ISGo.js → erDiagram-Q2GNP2WA-aYjGXss7.js} +1 -1
  27. package/dist-renderer/assets/{flowDiagram-NV44I4VS-B8XmJ0u2.js → flowDiagram-NV44I4VS-JMHrrTQs.js} +1 -1
  28. package/dist-renderer/assets/{ganttDiagram-JELNMOA3-D-8XglBb.js → ganttDiagram-JELNMOA3-CVQ-R5rN.js} +1 -1
  29. package/dist-renderer/assets/{gitGraphDiagram-V2S2FVAM-DL4ChakD.js → gitGraphDiagram-V2S2FVAM-OLn9jq61.js} +1 -1
  30. package/dist-renderer/assets/{graph-BiFNoBjP.js → graph-BAb2J0l8.js} +1 -1
  31. package/dist-renderer/assets/{index-qNBNjW4K.js → index-BSoCjBWn.js} +1 -1
  32. package/dist-renderer/assets/{index-6m1ZAymG.js → index-BtG3HbqP.js} +1 -1
  33. package/dist-renderer/assets/{index-BowUl0Jb.js → index-CH8e7g1f.js} +583 -573
  34. package/dist-renderer/assets/index-CSt8DTcn.css +1 -0
  35. package/dist-renderer/assets/{index-Dp3kJTEe.js → index-Ca4iNkRA.js} +1 -1
  36. package/dist-renderer/assets/{index-vAykq1H1.js → index-DU9PGgZJ.js} +1 -1
  37. package/dist-renderer/assets/{index-TOpt_T7A.js → index-DtMzIS9o.js} +1 -1
  38. package/dist-renderer/assets/{infoDiagram-HS3SLOUP-DRIBfHDi.js → infoDiagram-HS3SLOUP-CY_ptQNL.js} +1 -1
  39. package/dist-renderer/assets/{journeyDiagram-XKPGCS4Q-BOMiigU4.js → journeyDiagram-XKPGCS4Q-C2vuHEo_.js} +1 -1
  40. package/dist-renderer/assets/{kanban-definition-3W4ZIXB7-DDxeyjod.js → kanban-definition-3W4ZIXB7-mbdNfu8h.js} +1 -1
  41. package/dist-renderer/assets/{layout-DNANbrI4.js → layout-Do_ArEB1.js} +1 -1
  42. package/dist-renderer/assets/{linear-DxEJi1yT.js → linear-BMlMKyiq.js} +1 -1
  43. package/dist-renderer/assets/{mindmap-definition-VGOIOE7T-nBfGriW8.js → mindmap-definition-VGOIOE7T-Dfntn-o2.js} +1 -1
  44. package/dist-renderer/assets/{pieDiagram-ADFJNKIX-Din5j6sV.js → pieDiagram-ADFJNKIX-LiWHsGMV.js} +1 -1
  45. package/dist-renderer/assets/{quadrantDiagram-AYHSOK5B-DMVK2BEQ.js → quadrantDiagram-AYHSOK5B-D87St8AF.js} +1 -1
  46. package/dist-renderer/assets/{requirementDiagram-UZGBJVZJ-6SC94Gg_.js → requirementDiagram-UZGBJVZJ-DAa6lHBx.js} +1 -1
  47. package/dist-renderer/assets/{sankeyDiagram-TZEHDZUN-CD2gghhu.js → sankeyDiagram-TZEHDZUN-VOUngars.js} +1 -1
  48. package/dist-renderer/assets/{sequenceDiagram-WL72ISMW-BnhkN7nZ.js → sequenceDiagram-WL72ISMW-BzwzmFr2.js} +1 -1
  49. package/dist-renderer/assets/{stateDiagram-FKZM4ZOC-Bn8XdYX-.js → stateDiagram-FKZM4ZOC-BjAQEJ52.js} +1 -1
  50. package/dist-renderer/assets/{stateDiagram-v2-4FDKWEC3-1b6sI1_g.js → stateDiagram-v2-4FDKWEC3-BDwy4GJm.js} +1 -1
  51. package/dist-renderer/assets/{timeline-definition-IT6M3QCI-CNs3RPoa.js → timeline-definition-IT6M3QCI-Y5XBZt3W.js} +1 -1
  52. package/dist-renderer/assets/treemap-GDKQZRPO-DzkdUEow.js +162 -0
  53. package/dist-renderer/assets/{xychartDiagram-PRI3JC2R-B8o5J2f3.js → xychartDiagram-PRI3JC2R-D-zbvJOv.js} +1 -1
  54. package/dist-renderer/index.html +2 -2
  55. package/package.json +4 -1
  56. package/src/main/ipc/extensions.ts +353 -0
  57. package/src/main/server.ts +209 -6
  58. package/src/main/services/extensions/ExtensionFacadeService.ts +135 -0
  59. package/src/main/services/extensions/catalog/GlamaMcpEnrichmentService.ts +190 -0
  60. package/src/main/services/extensions/catalog/McpCatalogAggregator.ts +150 -0
  61. package/src/main/services/extensions/catalog/OfficialMcpRegistryService.ts +381 -0
  62. package/src/main/services/extensions/catalog/PluginCatalogService.ts +392 -0
  63. package/src/main/services/extensions/credentials/CredentialService.ts +343 -0
  64. package/src/main/services/extensions/install/McpInstallService.ts +407 -0
  65. package/src/main/services/extensions/install/PluginInstallService.ts +198 -0
  66. package/src/main/services/extensions/runtime/ClaudeCodeAdapter.ts +199 -0
  67. package/src/main/services/extensions/runtime/CodexAdapter.ts +100 -0
  68. package/src/main/services/extensions/runtime/CursorAdapter.ts +154 -0
  69. package/src/main/services/extensions/runtime/ExtensionsRuntimeAdapter.ts +172 -0
  70. package/src/main/services/extensions/runtime/GeminiAdapter.ts +91 -0
  71. package/src/main/services/extensions/runtime/HarnessInstallAdapter.ts +49 -0
  72. package/src/main/services/extensions/runtime/McpConfigStateReader.ts +209 -0
  73. package/src/main/services/extensions/runtime/OpenCodeAdapter.ts +91 -0
  74. package/src/main/services/extensions/runtime/adapterRegistry.ts +54 -0
  75. package/src/main/services/extensions/runtime/mcpDiagnosticsParser.ts +214 -0
  76. package/src/main/services/extensions/runtime/mcpRuntimeJson.ts +45 -0
  77. package/src/main/services/extensions/skills/SkillImportService.ts +155 -0
  78. package/src/main/services/extensions/skills/SkillMetadataParser.ts +323 -0
  79. package/src/main/services/extensions/skills/SkillPlanService.ts +411 -0
  80. package/src/main/services/extensions/skills/SkillReviewService.ts +73 -0
  81. package/src/main/services/extensions/skills/SkillRootsResolver.ts +49 -0
  82. package/src/main/services/extensions/skills/SkillScaffoldService.ts +89 -0
  83. package/src/main/services/extensions/skills/SkillScanner.ts +117 -0
  84. package/src/main/services/extensions/skills/SkillValidator.ts +69 -0
  85. package/src/main/services/extensions/skills/SkillsCatalogService.ts +92 -0
  86. package/src/main/services/extensions/skills/SkillsMutationService.ts +146 -0
  87. package/src/main/services/extensions/skills/SkillsWatcherService.ts +134 -0
  88. package/src/main/services/extensions/state/McpInstallationStateService.ts +42 -0
  89. package/src/main/services/extensions/state/PluginInstallationStateService.ts +281 -0
  90. package/src/main/services/identity/AgentTeamsIdentityStore.ts +218 -0
  91. package/src/main/services/runtime/providerAwareCliEnv.ts +60 -0
  92. package/src/main/services/team/ClaudeBinaryResolver.ts +469 -0
  93. package/src/main/services/team/ClaudeDoctorProbe.ts +0 -0
  94. package/src/main/services/team/cliFlavor.ts +54 -0
  95. package/src/main/services/teams-mvp/TaskDispatchService.ts +3 -0
  96. package/src/main/utils/atomicWrite.ts +72 -0
  97. package/src/main/utils/childProcess.ts +554 -0
  98. package/src/main/utils/cliEnv.ts +54 -0
  99. package/src/main/utils/cliPathMerge.ts +97 -0
  100. package/src/main/utils/pathDecoder.ts +664 -0
  101. package/src/main/utils/pathValidation.ts +432 -0
  102. package/src/main/utils/shellEnv.ts +331 -0
  103. package/src/renderer/api/httpClient.ts +61 -0
  104. package/src/renderer/components/extensions/ExtensionStoreView.tsx +63 -35
  105. package/src/renderer/components/extensions/ExtensionsSubTabTrigger.tsx +1 -1
  106. package/src/renderer/components/extensions/common/ExtensionToast.tsx +141 -0
  107. package/src/renderer/components/extensions/common/HarnessSelector.tsx +71 -0
  108. package/src/renderer/components/extensions/env/EnvVarPanel.tsx +335 -0
  109. package/src/renderer/components/extensions/env/ProjectEnvPanel.tsx +239 -0
  110. package/src/renderer/components/extensions/mcp/CustomMcpServerDialog.tsx +14 -223
  111. package/src/renderer/components/extensions/mcp/McpServerDetailDialog.tsx +111 -15
  112. package/src/renderer/components/extensions/mcp/McpServersPanel.tsx +51 -1
  113. package/src/renderer/components/extensions/skills/SkillsPanel.tsx +1 -126
  114. package/src/renderer/components/settings/sections/HarnessSection.tsx +2 -6
  115. package/src/renderer/components/settings/sections/TaskBusSection.tsx +17 -7
  116. package/src/renderer/components/sidebar/SidebarSessions.tsx +23 -0
  117. package/src/renderer/components/sidebar/WorkspaceBrowser.tsx +1 -7
  118. package/src/renderer/components/team/HarnessSelect.tsx +71 -0
  119. package/src/renderer/components/team/TeamDetailView.tsx +74 -123
  120. package/src/renderer/components/team/TeamListFilterPopover.tsx +0 -16
  121. package/src/renderer/components/team/TeamListView.tsx +7 -32
  122. package/src/renderer/components/team/dialogs/CreateTeamDialog.tsx +21 -12
  123. package/src/renderer/components/team/dialogs/EditTeamDialog.tsx +287 -418
  124. package/src/renderer/components/team/dialogs/useTeamEditForm.ts +283 -0
  125. package/src/renderer/components/team/kanban/KanbanBoard.tsx +26 -64
  126. package/src/renderer/components/team/messages/MessagesPanel.tsx +28 -24
  127. package/src/renderer/components/terminal/TerminalPanel.tsx +156 -0
  128. package/src/renderer/hooks/useExtensionsTabState.ts +2 -2
  129. package/src/renderer/store/slices/extensionsSlice.ts +42 -107
  130. package/src/renderer/store/slices/teamSlice.ts +8 -2
  131. package/src/renderer/utils/multimodelProviderVisibility.ts +17 -0
  132. package/src/renderer/utils/openCodeRuntimeDeliveryDiagnostics.ts +29 -9
  133. package/src/shared/types/api.ts +29 -0
  134. package/src/shared/types/extensions/index.ts +1 -0
  135. package/src/shared/types/extensions/mcp.ts +2 -0
  136. package/src/shared/types/extensions/plugin.ts +2 -1
  137. package/src/shared/types/extensions/skill.ts +7 -0
  138. package/src/shared/utils/providerExtensionCapabilities.ts +1 -1
  139. package/dist-renderer/assets/channel-C0SqeFU7.js +0 -1
  140. package/dist-renderer/assets/classDiagram-2ON5EDUG-DWew1HpM.js +0 -1
  141. package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-DWew1HpM.js +0 -1
  142. package/dist-renderer/assets/clone-Dm-k63Yr.js +0 -1
  143. package/dist-renderer/assets/index-BhellmRb.css +0 -1
  144. package/dist-renderer/assets/treemap-GDKQZRPO-DU_yr827.js +0 -162
  145. package/src/features/recent-projects/main/adapters/input/http/registerRecentProjectsHttp.ts +0 -30
  146. package/src/features/recent-projects/main/adapters/output/presenters/DashboardRecentProjectsPresenter.ts +0 -27
  147. package/src/features/recent-projects/main/adapters/output/sources/ClaudeRecentProjectsSourceAdapter.ts +0 -91
  148. package/src/features/recent-projects/main/adapters/output/sources/CodexRecentProjectsSourceAdapter.ts +0 -326
  149. package/src/features/recent-projects/main/composition/createRecentProjectsFeature.ts +0 -43
  150. package/src/features/recent-projects/main/index.ts +0 -3
  151. package/src/features/recent-projects/main/infrastructure/cache/InMemoryRecentProjectsCache.ts +0 -34
  152. package/src/features/recent-projects/main/infrastructure/codex/CodexAppServerClient.ts +0 -116
  153. package/src/features/recent-projects/main/infrastructure/identity/RecentProjectIdentityResolver.ts +0 -20
  154. package/src/features/recent-projects/main/infrastructure/identity/normalizeIdentityPath.ts +0 -10
  155. package/src/renderer/components/extensions/apikeys/ApiKeyCard.tsx +0 -143
  156. package/src/renderer/components/extensions/apikeys/ApiKeyFormDialog.tsx +0 -282
  157. package/src/renderer/components/extensions/apikeys/ApiKeysPanel.tsx +0 -280
@@ -0,0 +1,323 @@
1
+ import * as path from 'node:path';
2
+
3
+ import { createLogger } from '@shared/utils/logger';
4
+ import YAML from 'yaml';
5
+
6
+ import type { ResolvedSkillRoot } from './SkillRootsResolver';
7
+ import type {
8
+ SkillCatalogItem,
9
+ SkillDetail,
10
+ SkillDirectoryFlags,
11
+ SkillEnvVarDef,
12
+ SkillInvocationMode,
13
+ SkillValidationIssue,
14
+ } from '@shared/types/extensions';
15
+
16
+ const logger = createLogger('Extensions:SkillParser');
17
+
18
+ const ALLOWED_FRONTMATTER_KEYS = new Set([
19
+ 'name',
20
+ 'description',
21
+ // Third-party skills often include a semantic version in frontmatter.
22
+ 'version',
23
+ 'license',
24
+ 'compatibility',
25
+ 'metadata',
26
+ 'allowed-tools',
27
+ 'required-env',
28
+ 'disable-model-invocation',
29
+ ]);
30
+
31
+ const LARGE_SKILL_FILE_BYTES = 50_000;
32
+
33
+ interface ParsedFrontmatter {
34
+ rawFrontmatter: string | null;
35
+ body: string;
36
+ data: Record<string, unknown>;
37
+ issues: SkillValidationIssue[];
38
+ }
39
+
40
+ interface BuildSkillInput {
41
+ skillDir: string;
42
+ folderName: string;
43
+ skillFile: string;
44
+ rawContent: string;
45
+ modifiedAt: number;
46
+ flags: SkillDirectoryFlags;
47
+ root: ResolvedSkillRoot;
48
+ }
49
+
50
+ export interface SkillRelatedFiles {
51
+ referencesFiles: string[];
52
+ scriptFiles: string[];
53
+ assetFiles: string[];
54
+ }
55
+
56
+ export class SkillMetadataParser {
57
+ parseCatalogItem(input: BuildSkillInput): SkillCatalogItem {
58
+ const { folderName, flags, modifiedAt, rawContent, root, skillDir, skillFile } = input;
59
+ const parsed = this.parseFrontmatter(rawContent);
60
+ const metadata = this.normalizeMetadata(parsed.data.metadata);
61
+ const name = this.readString(parsed.data.name);
62
+ const description = this.readString(parsed.data.description);
63
+ const issues = [...parsed.issues];
64
+ const fileBaseName = path.basename(skillFile);
65
+
66
+ if (!name) {
67
+ issues.push({
68
+ code: 'missing-name',
69
+ message: 'Skill frontmatter is missing a valid `name` field.',
70
+ severity: 'error',
71
+ });
72
+ }
73
+
74
+ if (!description) {
75
+ issues.push({
76
+ code: 'missing-description',
77
+ message: 'Skill frontmatter is missing a valid `description` field.',
78
+ severity: 'error',
79
+ });
80
+ }
81
+
82
+ if (name && folderName !== name) {
83
+ issues.push({
84
+ code: 'folder-name-mismatch',
85
+ message: `Folder name "${folderName}" does not match skill name "${name}".`,
86
+ severity: 'error',
87
+ });
88
+ }
89
+
90
+ if (fileBaseName !== 'SKILL.md') {
91
+ issues.push({
92
+ code: 'nonstandard-file-name',
93
+ message: `Using "${fileBaseName}" instead of the standard "SKILL.md".`,
94
+ severity: 'warning',
95
+ });
96
+ }
97
+
98
+ const unknownKeys = Object.keys(parsed.data).filter(
99
+ (key) => !ALLOWED_FRONTMATTER_KEYS.has(key)
100
+ );
101
+ if (unknownKeys.length > 0) {
102
+ issues.push({
103
+ code: 'unknown-frontmatter-keys',
104
+ message: `Unknown frontmatter keys: ${unknownKeys.join(', ')}.`,
105
+ severity: 'warning',
106
+ });
107
+ }
108
+
109
+ if (Buffer.byteLength(rawContent, 'utf8') > LARGE_SKILL_FILE_BYTES) {
110
+ issues.push({
111
+ code: 'large-skill-file',
112
+ message: 'SKILL.md is large and may be expensive to load into context.',
113
+ severity: 'warning',
114
+ });
115
+ }
116
+
117
+ if (flags.hasScripts) {
118
+ issues.push({
119
+ code: 'has-scripts',
120
+ message:
121
+ 'This skill includes a scripts directory. Review bundled scripts before trusting it.',
122
+ severity: 'info',
123
+ });
124
+ }
125
+
126
+ const allowedTools = this.readAllowedTools(parsed.data['allowed-tools']);
127
+ const requiredEnv = this.readRequiredEnv(parsed.data['required-env']);
128
+ if (allowedTools) {
129
+ issues.push({
130
+ code: 'allowed-tools-advisory',
131
+ message:
132
+ '`allowed-tools` is present, but this app does not enforce or verify runtime compatibility.',
133
+ severity: 'warning',
134
+ });
135
+ }
136
+
137
+ const compatibility = this.readString(parsed.data.compatibility);
138
+ if (
139
+ compatibility &&
140
+ /(network|internet|online|env|environment|api key|credential)/iu.test(compatibility)
141
+ ) {
142
+ issues.push({
143
+ code: 'compatibility-advisory',
144
+ message:
145
+ '`compatibility` mentions environment or network requirements that this app cannot verify.',
146
+ severity: 'warning',
147
+ });
148
+ }
149
+
150
+ const isValid = !issues.some((issue) => issue.severity === 'error');
151
+
152
+ return {
153
+ id: skillDir,
154
+ sourceType: 'filesystem',
155
+ name: name ?? folderName,
156
+ description: description ?? 'Invalid skill metadata',
157
+ folderName,
158
+ scope: root.scope,
159
+ rootKind: root.rootKind,
160
+ projectRoot: root.projectRoot,
161
+ discoveryRoot: root.rootPath,
162
+ skillDir,
163
+ skillFile,
164
+ license: this.readString(parsed.data.license),
165
+ compatibility,
166
+ metadata,
167
+ allowedTools,
168
+ invocationMode: this.readInvocationMode(parsed.data['disable-model-invocation']),
169
+ flags,
170
+ isValid,
171
+ issues,
172
+ modifiedAt,
173
+ requiredEnv,
174
+ };
175
+ }
176
+
177
+ parseDetail(
178
+ item: SkillCatalogItem,
179
+ rawContent: string,
180
+ relatedFiles: SkillRelatedFiles
181
+ ): SkillDetail {
182
+ const parsed = this.parseFrontmatter(rawContent);
183
+
184
+ return {
185
+ item,
186
+ body: parsed.body,
187
+ rawContent,
188
+ rawFrontmatter: parsed.rawFrontmatter,
189
+ referencesFiles: relatedFiles.referencesFiles,
190
+ scriptFiles: relatedFiles.scriptFiles,
191
+ assetFiles: relatedFiles.assetFiles,
192
+ };
193
+ }
194
+
195
+ private parseFrontmatter(rawContent: string): ParsedFrontmatter {
196
+ const content = rawContent.replace(/^/, '');
197
+ if (!content.startsWith('---')) {
198
+ return {
199
+ rawFrontmatter: null,
200
+ body: content,
201
+ data: {},
202
+ issues: [
203
+ {
204
+ code: 'missing-frontmatter',
205
+ message: 'SKILL.md is missing YAML frontmatter.',
206
+ severity: 'error',
207
+ },
208
+ ],
209
+ };
210
+ }
211
+
212
+ const match = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/u.exec(content);
213
+ if (!match) {
214
+ return {
215
+ rawFrontmatter: null,
216
+ body: content,
217
+ data: {},
218
+ issues: [
219
+ {
220
+ code: 'invalid-frontmatter',
221
+ message: 'Unable to parse YAML frontmatter block.',
222
+ severity: 'error',
223
+ },
224
+ ],
225
+ };
226
+ }
227
+
228
+ const rawFrontmatter = match[1];
229
+ const body = match[2] ?? '';
230
+
231
+ try {
232
+ const parsed = YAML.parse(rawFrontmatter);
233
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
234
+ return {
235
+ rawFrontmatter,
236
+ body,
237
+ data: {},
238
+ issues: [
239
+ {
240
+ code: 'invalid-frontmatter',
241
+ message: 'YAML frontmatter must be a mapping/object.',
242
+ severity: 'error',
243
+ },
244
+ ],
245
+ };
246
+ }
247
+
248
+ return {
249
+ rawFrontmatter,
250
+ body,
251
+ data: parsed as Record<string, unknown>,
252
+ issues: [],
253
+ };
254
+ } catch (error) {
255
+ logger.warn('Failed to parse skill frontmatter', error);
256
+ return {
257
+ rawFrontmatter,
258
+ body,
259
+ data: {},
260
+ issues: [
261
+ {
262
+ code: 'invalid-frontmatter',
263
+ message: 'YAML frontmatter contains invalid syntax.',
264
+ severity: 'error',
265
+ },
266
+ ],
267
+ };
268
+ }
269
+ }
270
+
271
+ private normalizeMetadata(value: unknown): Record<string, string> {
272
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
273
+ return {};
274
+ }
275
+
276
+ return Object.fromEntries(
277
+ Object.entries(value).map(([key, entryValue]) => [key, String(entryValue)])
278
+ );
279
+ }
280
+
281
+ private readString(value: unknown): string | undefined {
282
+ if (typeof value !== 'string') return undefined;
283
+ const trimmed = value.trim();
284
+ return trimmed ? trimmed : undefined;
285
+ }
286
+
287
+ private readAllowedTools(value: unknown): string | undefined {
288
+ if (typeof value === 'string') {
289
+ return value.trim() || undefined;
290
+ }
291
+ if (Array.isArray(value)) {
292
+ const tools = value.map((entry) => String(entry).trim()).filter(Boolean);
293
+ return tools.length > 0 ? tools.join(' ') : undefined;
294
+ }
295
+ return undefined;
296
+ }
297
+
298
+ private readInvocationMode(value: unknown): SkillInvocationMode {
299
+ return value === true ? 'manual-only' : 'auto';
300
+ }
301
+
302
+ private readRequiredEnv(value: unknown): SkillEnvVarDef[] | undefined {
303
+ if (!Array.isArray(value)) return undefined;
304
+ const defs: SkillEnvVarDef[] = [];
305
+ for (const entry of value) {
306
+ if (typeof entry === 'string') {
307
+ const name = entry.trim();
308
+ if (name) defs.push({ name, isRequired: true });
309
+ } else if (entry && typeof entry === 'object') {
310
+ const obj = entry as Record<string, unknown>;
311
+ const name = typeof obj.name === 'string' ? obj.name.trim() : '';
312
+ if (name) {
313
+ defs.push({
314
+ name,
315
+ description: typeof obj.description === 'string' ? obj.description.trim() : undefined,
316
+ isRequired: obj['is-required'] !== false && obj.isRequired !== false,
317
+ });
318
+ }
319
+ }
320
+ }
321
+ return defs.length > 0 ? defs : undefined;
322
+ }
323
+ }