tycono-server 0.1.0-beta.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/bin/cli.js +35 -0
- package/bin/server.ts +160 -0
- package/package.json +50 -0
- package/src/api/package.json +31 -0
- package/src/api/src/create-app.ts +90 -0
- package/src/api/src/create-server.ts +251 -0
- package/src/api/src/engine/agent-loop.ts +738 -0
- package/src/api/src/engine/authority-validator.ts +149 -0
- package/src/api/src/engine/context-assembler.ts +912 -0
- package/src/api/src/engine/index.ts +27 -0
- package/src/api/src/engine/knowledge-gate.ts +365 -0
- package/src/api/src/engine/llm-adapter.ts +304 -0
- package/src/api/src/engine/org-tree.ts +270 -0
- package/src/api/src/engine/role-lifecycle.ts +369 -0
- package/src/api/src/engine/runners/claude-cli.ts +796 -0
- package/src/api/src/engine/runners/direct-api.ts +66 -0
- package/src/api/src/engine/runners/index.ts +30 -0
- package/src/api/src/engine/runners/types.ts +95 -0
- package/src/api/src/engine/skill-template.ts +134 -0
- package/src/api/src/engine/tools/definitions.ts +201 -0
- package/src/api/src/engine/tools/executor.ts +611 -0
- package/src/api/src/routes/active-sessions.ts +134 -0
- package/src/api/src/routes/coins.ts +153 -0
- package/src/api/src/routes/company.ts +57 -0
- package/src/api/src/routes/cost.ts +141 -0
- package/src/api/src/routes/engine.ts +220 -0
- package/src/api/src/routes/execute.ts +1075 -0
- package/src/api/src/routes/git.ts +211 -0
- package/src/api/src/routes/knowledge.ts +378 -0
- package/src/api/src/routes/operations.ts +309 -0
- package/src/api/src/routes/preferences.ts +63 -0
- package/src/api/src/routes/presets.ts +123 -0
- package/src/api/src/routes/projects.ts +82 -0
- package/src/api/src/routes/quests.ts +41 -0
- package/src/api/src/routes/roles.ts +112 -0
- package/src/api/src/routes/save.ts +152 -0
- package/src/api/src/routes/sessions.ts +288 -0
- package/src/api/src/routes/setup.ts +437 -0
- package/src/api/src/routes/skills.ts +357 -0
- package/src/api/src/routes/speech.ts +959 -0
- package/src/api/src/routes/supervision.ts +136 -0
- package/src/api/src/routes/sync.ts +165 -0
- package/src/api/src/server.ts +59 -0
- package/src/api/src/services/activity-stream.ts +184 -0
- package/src/api/src/services/activity-tracker.ts +115 -0
- package/src/api/src/services/claude-md-manager.ts +94 -0
- package/src/api/src/services/company-config.ts +115 -0
- package/src/api/src/services/database.ts +77 -0
- package/src/api/src/services/digest-engine.ts +313 -0
- package/src/api/src/services/execution-manager.ts +1036 -0
- package/src/api/src/services/file-reader.ts +77 -0
- package/src/api/src/services/git-save.ts +614 -0
- package/src/api/src/services/job-manager.ts +16 -0
- package/src/api/src/services/knowledge-importer.ts +466 -0
- package/src/api/src/services/markdown-parser.ts +173 -0
- package/src/api/src/services/port-registry.ts +222 -0
- package/src/api/src/services/preferences.ts +150 -0
- package/src/api/src/services/preset-loader.ts +149 -0
- package/src/api/src/services/pricing.ts +34 -0
- package/src/api/src/services/scaffold.ts +546 -0
- package/src/api/src/services/session-store.ts +340 -0
- package/src/api/src/services/supervisor-heartbeat.ts +897 -0
- package/src/api/src/services/team-recommender.ts +382 -0
- package/src/api/src/services/token-ledger.ts +127 -0
- package/src/api/src/services/wave-messages.ts +194 -0
- package/src/api/src/services/wave-multiplexer.ts +356 -0
- package/src/api/src/services/wave-tracker.ts +359 -0
- package/src/api/src/utils/role-level.ts +31 -0
- package/src/core/scaffolder.ts +620 -0
- package/src/shared/types.ts +224 -0
- package/templates/CLAUDE.md.tmpl +239 -0
- package/templates/company.md.tmpl +17 -0
- package/templates/gitignore.tmpl +28 -0
- package/templates/roles.md.tmpl +8 -0
- package/templates/skills/_manifest.json +23 -0
- package/templates/skills/agent-browser/SKILL.md +159 -0
- package/templates/skills/agent-browser/meta.json +19 -0
- package/templates/skills/akb-linter/SKILL.md +125 -0
- package/templates/skills/akb-linter/meta.json +12 -0
- package/templates/skills/knowledge-gate/SKILL.md +120 -0
- package/templates/skills/knowledge-gate/meta.json +12 -0
- package/templates/teams/agency.json +58 -0
- package/templates/teams/research.json +58 -0
- package/templates/teams/startup.json +58 -0
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* skills.ts — Skill registry + Role-Skill management API
|
|
3
|
+
*/
|
|
4
|
+
import { Router, Request, Response, NextFunction } from 'express';
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import { fileURLToPath } from 'node:url';
|
|
8
|
+
import YAML from 'yaml';
|
|
9
|
+
import { COMPANY_ROOT } from '../services/file-reader.js';
|
|
10
|
+
import { getAvailableSkills } from '../services/scaffold.js';
|
|
11
|
+
|
|
12
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
13
|
+
const __dirname = path.dirname(__filename);
|
|
14
|
+
|
|
15
|
+
export const skillsRouter = Router();
|
|
16
|
+
|
|
17
|
+
/* ─── Skill Registry ─────────────────────── */
|
|
18
|
+
|
|
19
|
+
// GET /api/skills — Available skills (from templates + installed)
|
|
20
|
+
skillsRouter.get('/', (_req: Request, res: Response, next: NextFunction) => {
|
|
21
|
+
try {
|
|
22
|
+
// 1. Template skills (bundled with the product)
|
|
23
|
+
const templateSkills = getAvailableSkills().map(s => ({
|
|
24
|
+
...s,
|
|
25
|
+
source: 'template' as const,
|
|
26
|
+
installed: isSkillInstalled(s.id),
|
|
27
|
+
}));
|
|
28
|
+
|
|
29
|
+
// 2. Installed skills not in templates (user-added)
|
|
30
|
+
const sharedDir = path.join(COMPANY_ROOT, '.claude', 'skills', '_shared');
|
|
31
|
+
const installedIds = new Set(templateSkills.map(s => s.id));
|
|
32
|
+
const userSkills: Array<Record<string, unknown>> = [];
|
|
33
|
+
|
|
34
|
+
if (fs.existsSync(sharedDir)) {
|
|
35
|
+
for (const entry of fs.readdirSync(sharedDir, { withFileTypes: true })) {
|
|
36
|
+
if (!entry.isDirectory() || installedIds.has(entry.name)) continue;
|
|
37
|
+
const skillMdPath = path.join(sharedDir, entry.name, 'SKILL.md');
|
|
38
|
+
if (!fs.existsSync(skillMdPath)) continue;
|
|
39
|
+
|
|
40
|
+
const content = fs.readFileSync(skillMdPath, 'utf-8');
|
|
41
|
+
const meta = extractSkillMeta(content, entry.name);
|
|
42
|
+
userSkills.push({ ...meta, source: 'user', installed: true });
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
res.json([...templateSkills, ...userSkills]);
|
|
47
|
+
} catch (err) {
|
|
48
|
+
next(err);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// GET /api/skills/registry — Browse external skill registries
|
|
53
|
+
skillsRouter.get('/registry', async (_req: Request, res: Response, next: NextFunction) => {
|
|
54
|
+
try {
|
|
55
|
+
// Known skill registries (curated list of quality skills)
|
|
56
|
+
const REGISTRIES = [
|
|
57
|
+
{
|
|
58
|
+
source: 'anthropics/skills',
|
|
59
|
+
label: 'Anthropic Official',
|
|
60
|
+
skills: [
|
|
61
|
+
{ id: 'frontend-design', name: 'Frontend Design', description: 'Create distinctive, production-grade frontend interfaces with high design quality', category: 'design', url: 'https://raw.githubusercontent.com/anthropics/skills/main/skills/frontend-design/SKILL.md' },
|
|
62
|
+
{ id: 'webapp-testing', name: 'Web App Testing', description: 'Playwright-based toolkit for testing local web applications', category: 'testing', url: 'https://raw.githubusercontent.com/anthropics/skills/main/skills/webapp-testing/SKILL.md' },
|
|
63
|
+
{ id: 'mcp-builder', name: 'MCP Builder', description: 'Guide for creating high-quality MCP servers for LLM tool integration', category: 'development', url: 'https://raw.githubusercontent.com/anthropics/skills/main/skills/mcp-builder/SKILL.md' },
|
|
64
|
+
{ id: 'internal-comms', name: 'Internal Comms', description: 'Write internal communications: status reports, newsletters, 3P updates', category: 'operations', url: 'https://raw.githubusercontent.com/anthropics/skills/main/skills/internal-comms/SKILL.md' },
|
|
65
|
+
{ id: 'web-artifacts-builder', name: 'Web Artifacts Builder', description: 'React + Tailwind + shadcn/ui component development and bundling', category: 'development', url: 'https://raw.githubusercontent.com/anthropics/skills/main/skills/web-artifacts-builder/SKILL.md' },
|
|
66
|
+
{ id: 'skill-creator', name: 'Skill Creator', description: 'Interactive guide for building new Claude Code skills', category: 'meta', url: 'https://raw.githubusercontent.com/anthropics/skills/main/skills/skill-creator/SKILL.md' },
|
|
67
|
+
{ id: 'algorithmic-art', name: 'Algorithmic Art', description: 'Generative art using p5.js with flow fields and particle systems', category: 'creative', url: 'https://raw.githubusercontent.com/anthropics/skills/main/skills/algorithmic-art/SKILL.md' },
|
|
68
|
+
{ id: 'canvas-design', name: 'Canvas Design', description: 'Visual art creation in PNG and PDF formats', category: 'creative', url: 'https://raw.githubusercontent.com/anthropics/skills/main/skills/canvas-design/SKILL.md' },
|
|
69
|
+
],
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
source: 'community',
|
|
73
|
+
label: 'Community',
|
|
74
|
+
skills: [
|
|
75
|
+
{ id: 'tdd-superpowers', name: 'TDD (Test-Driven Dev)', description: 'Test-first development with Red-Green-Refactor cycle', category: 'development', url: 'https://raw.githubusercontent.com/obra/superpowers/main/skills/tdd/SKILL.md' },
|
|
76
|
+
],
|
|
77
|
+
},
|
|
78
|
+
];
|
|
79
|
+
|
|
80
|
+
// Mark which ones are already installed
|
|
81
|
+
const result = REGISTRIES.map(registry => ({
|
|
82
|
+
...registry,
|
|
83
|
+
skills: registry.skills.map(skill => ({
|
|
84
|
+
...skill,
|
|
85
|
+
installed: isSkillInstalled(skill.id),
|
|
86
|
+
})),
|
|
87
|
+
}));
|
|
88
|
+
|
|
89
|
+
res.json(result);
|
|
90
|
+
} catch (err) {
|
|
91
|
+
next(err);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// POST /api/skills/registry/install — Install a skill from external registry
|
|
96
|
+
skillsRouter.post('/registry/install', async (req: Request, res: Response, next: NextFunction) => {
|
|
97
|
+
try {
|
|
98
|
+
const { skillId, url } = req.body;
|
|
99
|
+
|
|
100
|
+
if (!skillId || !url) {
|
|
101
|
+
res.status(400).json({ error: 'skillId and url are required' });
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Already installed?
|
|
106
|
+
if (isSkillInstalled(skillId)) {
|
|
107
|
+
res.json({ ok: true, message: 'Already installed', skillId });
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Fetch SKILL.md from URL
|
|
112
|
+
const response = await fetch(url);
|
|
113
|
+
if (!response.ok) {
|
|
114
|
+
res.status(502).json({ error: `Failed to fetch skill: ${response.status}` });
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const content = await response.text();
|
|
118
|
+
|
|
119
|
+
// Install to .claude/skills/_shared/{skillId}/SKILL.md
|
|
120
|
+
const destDir = path.join(COMPANY_ROOT, '.claude', 'skills', '_shared', skillId);
|
|
121
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
122
|
+
fs.writeFileSync(path.join(destDir, 'SKILL.md'), content);
|
|
123
|
+
|
|
124
|
+
res.json({ ok: true, skillId, installed: true });
|
|
125
|
+
} catch (err) {
|
|
126
|
+
next(err);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
/* ─── Skill Export (for Store publish) ──── */
|
|
131
|
+
|
|
132
|
+
// GET /api/skills/export/:roleId — Export full SKILL.md content for publishing
|
|
133
|
+
// NOTE: Must be registered BEFORE /:id to avoid "export" matching as an id
|
|
134
|
+
skillsRouter.get('/export/:roleId', (req: Request, res: Response, next: NextFunction) => {
|
|
135
|
+
try {
|
|
136
|
+
const roleId = req.params.roleId as string;
|
|
137
|
+
|
|
138
|
+
// 1. Primary skill: .claude/skills/{roleId}/SKILL.md
|
|
139
|
+
let primary: { frontmatter: Record<string, unknown>; body: string } | null = null;
|
|
140
|
+
const primaryPath = path.join(COMPANY_ROOT, '.claude', 'skills', roleId, 'SKILL.md');
|
|
141
|
+
if (fs.existsSync(primaryPath)) {
|
|
142
|
+
const content = fs.readFileSync(primaryPath, 'utf-8');
|
|
143
|
+
primary = parseSkillContent(content, roleId);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// 2. Shared skills from role.yaml skills[] array
|
|
147
|
+
const sharedIds = getRoleSkills(roleId);
|
|
148
|
+
const shared: Array<{ id: string; frontmatter: Record<string, unknown>; body: string }> = [];
|
|
149
|
+
|
|
150
|
+
for (const sharedId of sharedIds) {
|
|
151
|
+
const sharedPath = path.join(COMPANY_ROOT, '.claude', 'skills', '_shared', sharedId, 'SKILL.md');
|
|
152
|
+
if (fs.existsSync(sharedPath)) {
|
|
153
|
+
const content = fs.readFileSync(sharedPath, 'utf-8');
|
|
154
|
+
const parsed = parseSkillContent(content, sharedId);
|
|
155
|
+
shared.push({ id: sharedId, ...parsed });
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
res.json({ primary, shared });
|
|
160
|
+
} catch (err) {
|
|
161
|
+
next(err);
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// GET /api/skills/:id — Skill detail (wildcard — must be LAST among GET routes)
|
|
166
|
+
skillsRouter.get('/:id', (req: Request, res: Response, next: NextFunction) => {
|
|
167
|
+
try {
|
|
168
|
+
const id = req.params.id as string;
|
|
169
|
+
|
|
170
|
+
// Check installed first
|
|
171
|
+
const installedPath = path.join(COMPANY_ROOT, '.claude', 'skills', '_shared', id, 'SKILL.md');
|
|
172
|
+
if (fs.existsSync(installedPath)) {
|
|
173
|
+
const content = fs.readFileSync(installedPath, 'utf-8');
|
|
174
|
+
const meta = extractSkillMeta(content, id);
|
|
175
|
+
res.json({ ...meta, installed: true, content });
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Check template
|
|
180
|
+
const templateSkills = getAvailableSkills();
|
|
181
|
+
const templateSkill = templateSkills.find(s => s.id === id);
|
|
182
|
+
if (templateSkill) {
|
|
183
|
+
res.json({ ...templateSkill, installed: false });
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
res.status(404).json({ error: `Skill not found: ${id}` });
|
|
188
|
+
} catch (err) {
|
|
189
|
+
next(err);
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
/* ─── Role-Skill Management ─────────────── */
|
|
194
|
+
|
|
195
|
+
// GET /api/roles/:id/skills — Skills equipped to a role
|
|
196
|
+
skillsRouter.get('/role/:roleId', (req: Request, res: Response, next: NextFunction) => {
|
|
197
|
+
try {
|
|
198
|
+
const roleId = req.params.roleId as string;
|
|
199
|
+
const skills = getRoleSkills(roleId);
|
|
200
|
+
res.json(skills);
|
|
201
|
+
} catch (err) {
|
|
202
|
+
next(err);
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
// POST /api/roles/:id/skills — Equip a skill to a role
|
|
207
|
+
skillsRouter.post('/role/:roleId', (req: Request, res: Response, next: NextFunction) => {
|
|
208
|
+
try {
|
|
209
|
+
const roleId = req.params.roleId as string;
|
|
210
|
+
const { skillId } = req.body;
|
|
211
|
+
|
|
212
|
+
if (!skillId || typeof skillId !== 'string') {
|
|
213
|
+
res.status(400).json({ error: 'skillId is required' });
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Verify skill exists (installed or template)
|
|
218
|
+
if (!isSkillInstalled(skillId) && !isSkillInTemplate(skillId)) {
|
|
219
|
+
res.status(404).json({ error: `Skill not found: ${skillId}` });
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Install skill if not already installed
|
|
224
|
+
if (!isSkillInstalled(skillId)) {
|
|
225
|
+
installSkillFromTemplate(skillId);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Add to role.yaml skills array
|
|
229
|
+
const yamlPath = path.join(COMPANY_ROOT, 'knowledge', 'roles', roleId, 'role.yaml');
|
|
230
|
+
if (!fs.existsSync(yamlPath)) {
|
|
231
|
+
res.status(404).json({ error: `Role not found: ${roleId}` });
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const raw = YAML.parse(fs.readFileSync(yamlPath, 'utf-8')) as Record<string, unknown>;
|
|
236
|
+
const skills = (raw.skills as string[]) || [];
|
|
237
|
+
|
|
238
|
+
if (skills.includes(skillId)) {
|
|
239
|
+
res.json({ ok: true, message: 'Skill already equipped' });
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
skills.push(skillId);
|
|
244
|
+
raw.skills = skills;
|
|
245
|
+
fs.writeFileSync(yamlPath, YAML.stringify(raw));
|
|
246
|
+
|
|
247
|
+
res.json({ ok: true, roleId, skillId, skills });
|
|
248
|
+
} catch (err) {
|
|
249
|
+
next(err);
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
// DELETE /api/roles/:roleId/skills/:skillId — Unequip a skill
|
|
254
|
+
skillsRouter.delete('/role/:roleId/:skillId', (req: Request, res: Response, next: NextFunction) => {
|
|
255
|
+
try {
|
|
256
|
+
const roleId = req.params.roleId as string;
|
|
257
|
+
const skillId = req.params.skillId as string;
|
|
258
|
+
|
|
259
|
+
const yamlPath = path.join(COMPANY_ROOT, 'knowledge', 'roles', roleId, 'role.yaml');
|
|
260
|
+
if (!fs.existsSync(yamlPath)) {
|
|
261
|
+
res.status(404).json({ error: `Role not found: ${roleId}` });
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const raw = YAML.parse(fs.readFileSync(yamlPath, 'utf-8')) as Record<string, unknown>;
|
|
266
|
+
const skills = (raw.skills as string[]) || [];
|
|
267
|
+
const idx = skills.indexOf(skillId);
|
|
268
|
+
|
|
269
|
+
if (idx === -1) {
|
|
270
|
+
res.json({ ok: true, message: 'Skill not equipped' });
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
skills.splice(idx, 1);
|
|
275
|
+
raw.skills = skills.length > 0 ? skills : undefined;
|
|
276
|
+
fs.writeFileSync(yamlPath, YAML.stringify(raw));
|
|
277
|
+
|
|
278
|
+
res.json({ ok: true, roleId, skillId, skills });
|
|
279
|
+
} catch (err) {
|
|
280
|
+
next(err);
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
/* ─── Helpers ────────────────────────────── */
|
|
285
|
+
|
|
286
|
+
function isSkillInstalled(skillId: string): boolean {
|
|
287
|
+
return fs.existsSync(
|
|
288
|
+
path.join(COMPANY_ROOT, '.claude', 'skills', '_shared', skillId, 'SKILL.md')
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function isSkillInTemplate(skillId: string): boolean {
|
|
293
|
+
return getAvailableSkills().some(s => s.id === skillId);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function installSkillFromTemplate(skillId: string): void {
|
|
297
|
+
const srcDir = path.resolve(__dirname, '../../../../../templates/skills', skillId);
|
|
298
|
+
const destDir = path.join(COMPANY_ROOT, '.claude', 'skills', '_shared', skillId);
|
|
299
|
+
|
|
300
|
+
if (!fs.existsSync(srcDir)) return;
|
|
301
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
302
|
+
|
|
303
|
+
const skillMdSrc = path.join(srcDir, 'SKILL.md');
|
|
304
|
+
if (fs.existsSync(skillMdSrc)) {
|
|
305
|
+
fs.copyFileSync(skillMdSrc, path.join(destDir, 'SKILL.md'));
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function getRoleSkills(roleId: string): string[] {
|
|
310
|
+
const yamlPath = path.join(COMPANY_ROOT, 'knowledge', 'roles', roleId, 'role.yaml');
|
|
311
|
+
if (!fs.existsSync(yamlPath)) return [];
|
|
312
|
+
|
|
313
|
+
const raw = YAML.parse(fs.readFileSync(yamlPath, 'utf-8')) as Record<string, unknown>;
|
|
314
|
+
return (raw.skills as string[]) || [];
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function extractSkillMeta(content: string, id: string): Record<string, unknown> {
|
|
318
|
+
const frontmatter = content.match(/^---\n([\s\S]*?)\n---/);
|
|
319
|
+
if (!frontmatter) {
|
|
320
|
+
return { id, name: id, description: '' };
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
try {
|
|
324
|
+
const meta = YAML.parse(frontmatter[1]) as Record<string, unknown>;
|
|
325
|
+
return {
|
|
326
|
+
id,
|
|
327
|
+
name: meta.name || id,
|
|
328
|
+
description: meta.description || '',
|
|
329
|
+
...(meta.tags ? { tags: meta.tags } : {}),
|
|
330
|
+
};
|
|
331
|
+
} catch {
|
|
332
|
+
return { id, name: id, description: '' };
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function parseSkillContent(content: string, id: string): { frontmatter: Record<string, unknown>; body: string } {
|
|
337
|
+
const fmMatch = content.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)/);
|
|
338
|
+
if (!fmMatch) {
|
|
339
|
+
return { frontmatter: { name: id, description: '' }, body: content };
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
try {
|
|
343
|
+
const meta = YAML.parse(fmMatch[1]) as Record<string, unknown>;
|
|
344
|
+
return {
|
|
345
|
+
frontmatter: {
|
|
346
|
+
name: meta.name || id,
|
|
347
|
+
description: (meta.description as string) || '',
|
|
348
|
+
...(meta.allowedTools ? { allowedTools: meta.allowedTools } : {}),
|
|
349
|
+
...(meta.model ? { model: meta.model } : {}),
|
|
350
|
+
...(meta.tags ? { tags: meta.tags } : {}),
|
|
351
|
+
},
|
|
352
|
+
body: fmMatch[2].trim(),
|
|
353
|
+
};
|
|
354
|
+
} catch {
|
|
355
|
+
return { frontmatter: { name: id, description: '' }, body: content };
|
|
356
|
+
}
|
|
357
|
+
}
|