total-recall-brain 3.6.4 → 3.6.6

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.
@@ -83,6 +83,10 @@ import { memoryRouter } from './routes/memory.mjs';
83
83
  import { keysRouter } from './routes/keys.mjs';
84
84
  import { sessionsRouter } from './routes/sessions.mjs';
85
85
  import { shareRouter } from './routes/share.mjs';
86
+ import { authRouter } from './routes/auth.mjs';
87
+ import { sandboxRouter } from './routes/sandbox.mjs';
88
+ import { researchRouter } from './routes/research.mjs';
89
+ import { skillsRouter } from './routes/skills.mjs';
86
90
  // ollamaUrl removed — CLI agents replace Ollama
87
91
  import {
88
92
  AGENT_DIR,
@@ -159,6 +163,10 @@ router.use(memoryRouter);
159
163
  router.use(keysRouter);
160
164
  router.use(sessionsRouter);
161
165
  router.use(shareRouter);
166
+ router.use(authRouter);
167
+ router.use(sandboxRouter);
168
+ router.use(researchRouter);
169
+ router.use(skillsRouter);
162
170
 
163
171
  // ─── Helpers ──────────────────────────────────────────────────────────────────
164
172
 
@@ -490,36 +498,7 @@ router.post('/api/diagnostics/agents', requireAuth, requireScope('health:read'),
490
498
  // ─── Keys ─────────── (moved to ./routes/keys.mjs)
491
499
  // ─── Sessions ─────── (moved to ./routes/sessions.mjs)
492
500
  // ─── Sandbox ──────────────────────────────────────────────────────────────────
493
-
494
- /**
495
- * POST /api/sandbox
496
- * Body: { code }
497
- */
498
- // Sandbox is rate-limited *before* requireAuth on purpose — limiter keys on
499
- // the authenticated principal when present, so it's IP-bucketed for
500
- // pre-auth misuse and key-bucketed once a PAT is attached.
501
- router.post('/api/sandbox', sandboxRateLimiter(), requireAuth, requireScope('sandbox:run'), requireSandboxEnabled, async (req, res) => {
502
- try {
503
- const { code } = req.body || {};
504
- if (!code) return badRequest(res, 'code is required');
505
-
506
- const tmpDir = path.join(os.tmpdir(), 'total-recall-sandbox');
507
- fs.mkdirSync(tmpDir, { recursive: true });
508
- const script = path.join(tmpDir, `rest-${Date.now()}.mjs`);
509
- fs.writeFileSync(script, code);
510
-
511
- const result = await runInSandbox(script, 15000);
512
- try { fs.unlinkSync(script); } catch {}
513
-
514
- res.json({
515
- success: result.success,
516
- exit_code: result.code,
517
- output: result.output,
518
- });
519
- } catch (err) {
520
- serverError(res, err);
521
- }
522
- });
501
+ // Moved to routes/sandbox.mjs
523
502
 
524
503
  // ─── Config ───────────────────────────────────────────────────────────────────
525
504
 
@@ -707,41 +686,7 @@ router.get('/api', (req, res) => {
707
686
  });
708
687
 
709
688
  // ─── Research Queue ───────────────────────────────────────────────────────────
710
- // Thin REST wrappers over src/core/research-queue.mjs
711
-
712
- router.get('/api/research', requireAuth, requireScope('memory:read'), (req, res) => {
713
- try {
714
- const { status, query, limit, offset } = req.query;
715
- res.json(listQueue({ status, query, limit, offset }));
716
- } catch (err) { serverError(res, err); }
717
- });
718
-
719
- router.post('/api/research', requireAuth, requireScope('memory:write'), (req, res) => {
720
- try {
721
- const { topic, priority, notes } = req.body || {};
722
- if (!topic) return badRequest(res, 'topic is required');
723
- res.status(201).json(addToQueue({ topic, priority, notes }));
724
- } catch (err) { serverError(res, err); }
725
- });
726
-
727
- router.patch('/api/research/:id', requireAuth, requireScope('memory:write'), (req, res) => {
728
- try {
729
- res.json(updateQueueItem(req.params.id, req.body || {}));
730
- } catch (err) {
731
- if (err.status === 404) return notFound(res, err.message);
732
- serverError(res, err);
733
- }
734
- });
735
-
736
- router.delete('/api/research/:id', requireAuth, requireScope('memory:write'), (req, res) => {
737
- try {
738
- res.json(removeFromQueue(req.params.id));
739
- } catch (err) {
740
- if (err.status === 404) return notFound(res, err.message);
741
- serverError(res, err);
742
- }
743
- });
744
-
689
+ // Moved to routes/research.mjs
745
690
  // ─── Active Integrations ──────────────────────────────────────────────────────
746
691
 
747
692
  /**
@@ -1098,110 +1043,7 @@ router.get('/api/files', requireAuth, requireScope('files:read'), (req, res) =>
1098
1043
  } catch (err) { serverError(res, err); }
1099
1044
  });
1100
1045
 
1101
- router.get('/api/skills', requireAuth, requireScope('files:read', 'ssss:read'), (req, res) => {
1102
- try {
1103
- if (!fs.existsSync(SKILLS_DIR)) {
1104
- fs.mkdirSync(SKILLS_DIR, { recursive: true });
1105
- }
1106
- const skills = fs.readdirSync(SKILLS_DIR).map(dir => {
1107
- const dirPath = path.join(SKILLS_DIR, dir);
1108
- const stats = fs.statSync(dirPath);
1109
-
1110
- let subSkills = [];
1111
- const subSkillsPath = path.join(dirPath, 'skills');
1112
- if (fs.existsSync(subSkillsPath) && fs.statSync(subSkillsPath).isDirectory()) {
1113
- try {
1114
- subSkills = fs.readdirSync(subSkillsPath).filter(sd =>
1115
- fs.statSync(path.join(subSkillsPath, sd)).isDirectory()
1116
- );
1117
- } catch (e) {}
1118
- }
1119
-
1120
- return {
1121
- name: dir,
1122
- size: stats.size,
1123
- modified: stats.mtime,
1124
- isDirectory: stats.isDirectory(),
1125
- subSkills
1126
- };
1127
- });
1128
- res.json(skills);
1129
- } catch (err) { serverError(res, err); }
1130
- });
1131
-
1132
- router.get('/api/skills/search', requireAuth, requireScope('files:read', 'ssss:read'), async (req, res) => {
1133
- try {
1134
- const q = req.query.q || '';
1135
- if (!q) {
1136
- return res.status(400).json({ error: 'Missing search query parameter `q`.' });
1137
- }
1138
- const { searchAndSort } = await import('../../.agent/skills/total-recall/skills/skill/scripts/find-skills.mjs');
1139
- const results = searchAndSort(q);
1140
- res.json(results);
1141
- } catch (err) { serverError(res, err); }
1142
- });
1143
-
1144
- router.post('/api/skills/install', requireAuth, requireScope('files:write', 'ssss:write'), async (req, res) => {
1145
- try {
1146
- const { pkg } = req.body;
1147
- if (!pkg) {
1148
- return res.status(400).json({ error: 'Missing required `pkg` body parameter.' });
1149
- }
1150
- const { installSkill } = await import('../../.agent/skills/total-recall/skills/skill/scripts/install-skill.mjs');
1151
- const result = installSkill(pkg);
1152
- res.json(result);
1153
- } catch (err) { serverError(res, err); }
1154
- });
1155
-
1156
- router.get('/api/skills/:name', requireAuth, requireScope('files:read', 'ssss:read'), (req, res) => {
1157
- try {
1158
- const { name } = req.params;
1159
- if (name.includes('..') || name.includes('/') || name.includes('\\')) {
1160
- return res.status(400).json({ error: 'Invalid skill name' });
1161
- }
1162
- const skillPath = path.join(SKILLS_DIR, name, 'SKILL.md');
1163
- if (!fs.existsSync(skillPath)) {
1164
- return res.status(404).json({ error: `Skill "${name}" not found` });
1165
- }
1166
- const content = fs.readFileSync(skillPath, 'utf8');
1167
- res.json({ name, content });
1168
- } catch (err) { serverError(res, err); }
1169
- });
1170
-
1171
- router.put('/api/skills/:name', requireAuth, requireScope('files:write', 'ssss:write'), (req, res) => {
1172
- try {
1173
- const { name } = req.params;
1174
- const { content } = req.body;
1175
- if (name.includes('..') || name.includes('/') || name.includes('\\')) {
1176
- return res.status(400).json({ error: 'Invalid skill name' });
1177
- }
1178
- if (typeof content !== 'string') {
1179
- return res.status(400).json({ error: 'Missing or invalid `content` field.' });
1180
- }
1181
- const skillDir = path.join(SKILLS_DIR, name);
1182
- if (!fs.existsSync(skillDir)) {
1183
- fs.mkdirSync(skillDir, { recursive: true });
1184
- }
1185
- const skillPath = path.join(skillDir, 'SKILL.md');
1186
- fs.writeFileSync(skillPath, content, 'utf8');
1187
- res.json({ success: true, message: `Skill "${name}" updated successfully` });
1188
- } catch (err) { serverError(res, err); }
1189
- });
1190
-
1191
- router.delete('/api/skills/:name', requireAuth, requireScope('files:write', 'ssss:write'), (req, res) => {
1192
- try {
1193
- const { name } = req.params;
1194
- if (name.includes('..') || name.includes('/') || name.includes('\\')) {
1195
- return res.status(400).json({ error: 'Invalid skill name' });
1196
- }
1197
- const skillDir = path.join(SKILLS_DIR, name);
1198
- if (!fs.existsSync(skillDir)) {
1199
- return res.status(404).json({ error: `Skill "${name}" not found` });
1200
- }
1201
- fs.rmSync(skillDir, { recursive: true, force: true });
1202
- res.json({ success: true, message: `Skill "${name}" deleted successfully` });
1203
- } catch (err) { serverError(res, err); }
1204
- });
1046
+ // Moved to routes/skills.mjs
1205
1047
 
1206
1048
  // ─── Scripts Editor & Execution ───────────────────────────────────────────────
1207
1049
 
@@ -1345,6 +1187,12 @@ router.get('/api/tasks', requireAuth, requireScope('tasks:read'), (req, res) =>
1345
1187
  try {
1346
1188
  const raw = fs.readFileSync(path.join(TASKS_DIR, file), 'utf8');
1347
1189
  const { data, content } = matter(raw);
1190
+
1191
+ // By default, filter out completed tasks
1192
+ if (req.query.status !== 'all' && data.status === 'completed') {
1193
+ continue;
1194
+ }
1195
+
1348
1196
  tasks.push({ ...data, body: content.trim(), slug: file.replace('.md', '') });
1349
1197
  } catch (e) {
1350
1198
  // skip
@@ -1354,6 +1202,31 @@ router.get('/api/tasks', requireAuth, requireScope('tasks:read'), (req, res) =>
1354
1202
  } catch (err) { serverError(res, err); }
1355
1203
  });
1356
1204
 
1205
+ router.delete('/api/tasks/cleanup', requireAuth, requireScope('tasks:write'), (req, res) => {
1206
+ try {
1207
+ if (!fs.existsSync(TASKS_DIR)) {
1208
+ return res.json({ deleted: 0 });
1209
+ }
1210
+ let deleted = 0;
1211
+ const files = fs.readdirSync(TASKS_DIR);
1212
+ for (const file of files) {
1213
+ if (!file.endsWith('.md')) continue;
1214
+ const filePath = path.join(TASKS_DIR, file);
1215
+ try {
1216
+ const raw = fs.readFileSync(filePath, 'utf8');
1217
+ const { data } = matter(raw);
1218
+ if (data.status === 'completed') {
1219
+ fs.unlinkSync(filePath);
1220
+ deleted++;
1221
+ }
1222
+ } catch (e) {
1223
+ // skip
1224
+ }
1225
+ }
1226
+ res.json({ deleted });
1227
+ } catch (err) { serverError(res, err); }
1228
+ });
1229
+
1357
1230
  router.post('/api/tasks', requireAuth, requireScope('tasks:write'), (req, res) => {
1358
1231
  try {
1359
1232
  const { category, target, body, priority = 5 } = req.body || {};
@@ -1559,26 +1432,7 @@ router.post('/api/capture/:source', requireAuth, requireScope('memory:write'), a
1559
1432
  });
1560
1433
 
1561
1434
  // ─── Consolidated Auth Routes ──────────────────────────────────────────────────
1562
-
1563
- router.post('/auth/login', loginHandler);
1564
- router.post('/auth/logout', logoutHandler);
1565
- router.post('/auth/change-password', requireAuth, changePasswordHandler);
1566
- router.get('/auth/me', requireAuth, (req, res) => res.json({ authenticated: true }));
1567
-
1568
- router.get("/auth/status", (req, res) => {
1569
- const config = loadSecurityConfig();
1570
- const configured = !!config.dashboard?.password_hash;
1571
- res.json({ configured });
1572
- });
1573
-
1574
- router.post("/auth/setup", async (req, res) => {
1575
- const config = loadSecurityConfig();
1576
- const alreadyConfigured = !!config.dashboard?.password_hash;
1577
- if (alreadyConfigured) {
1578
- return res.status(400).json({ error: "Dashboard password is already configured" });
1579
- }
1580
- return changePasswordHandler(req, res);
1581
- });
1435
+ // Moved to routes/auth.mjs
1582
1436
 
1583
1437
  // ─── Voice / TTS (Kokoro / System) ───────────────────────────────────────────────
1584
1438
 
@@ -295,6 +295,10 @@
295
295
  "method": "POST",
296
296
  "path": "/api/tasks"
297
297
  },
298
+ {
299
+ "method": "DELETE",
300
+ "path": "/api/tasks/cleanup"
301
+ },
298
302
  {
299
303
  "method": "POST",
300
304
  "path": "/api/tts"
@@ -0,0 +1,32 @@
1
+ import express from 'express';
2
+ import {
3
+ requireAuth,
4
+ loadSecurityConfig,
5
+ loginHandler,
6
+ logoutHandler,
7
+ changePasswordHandler,
8
+ } from '../auth.mjs';
9
+
10
+ export const authRouter = express.Router();
11
+
12
+ // ─── Consolidated Auth Routes ──────────────────────────────────────────────────
13
+
14
+ authRouter.post('/auth/login', loginHandler);
15
+ authRouter.post('/auth/logout', logoutHandler);
16
+ authRouter.post('/auth/change-password', requireAuth, changePasswordHandler);
17
+ authRouter.get('/auth/me', requireAuth, (req, res) => res.json({ authenticated: true }));
18
+
19
+ authRouter.get("/auth/status", (req, res) => {
20
+ const config = loadSecurityConfig();
21
+ const configured = !!config.dashboard?.password_hash;
22
+ res.json({ configured });
23
+ });
24
+
25
+ authRouter.post("/auth/setup", async (req, res) => {
26
+ const config = loadSecurityConfig();
27
+ const alreadyConfigured = !!config.dashboard?.password_hash;
28
+ if (alreadyConfigured) {
29
+ return res.status(400).json({ error: "Dashboard password is already configured" });
30
+ }
31
+ return changePasswordHandler(req, res);
32
+ });
@@ -0,0 +1,55 @@
1
+ import express from 'express';
2
+ import { requireAuth, requireScope } from '../auth.mjs';
3
+ import { listQueue, addToQueue, updateQueueItem, removeFromQueue } from '../../core/research-queue.mjs';
4
+ import { logger } from '../../core/logger.mjs';
5
+
6
+ function badRequest(res, msg) {
7
+ return res.status(400).json({ error: msg });
8
+ }
9
+
10
+ function notFound(res, msg) {
11
+ return res.status(404).json({ error: msg || 'Not found' });
12
+ }
13
+
14
+ function serverError(res, err) {
15
+ logger.error('research', 'Internal server error', { error: err.message, stack: err.stack });
16
+ return res.status(500).json({ error: 'Internal server error' });
17
+ }
18
+
19
+ export const researchRouter = express.Router();
20
+
21
+ // ─── Research Queue ───────────────────────────────────────────────────────────
22
+ // Thin REST wrappers over src/core/research-queue.mjs
23
+
24
+ researchRouter.get('/api/research', requireAuth, requireScope('memory:read'), (req, res) => {
25
+ try {
26
+ const { status, query, limit, offset } = req.query;
27
+ res.json(listQueue({ status, query, limit, offset }));
28
+ } catch (err) { serverError(res, err); }
29
+ });
30
+
31
+ researchRouter.post('/api/research', requireAuth, requireScope('memory:write'), (req, res) => {
32
+ try {
33
+ const { topic, priority, notes } = req.body || {};
34
+ if (!topic) return badRequest(res, 'topic is required');
35
+ res.status(201).json(addToQueue({ topic, priority, notes }));
36
+ } catch (err) { serverError(res, err); }
37
+ });
38
+
39
+ researchRouter.patch('/api/research/:id', requireAuth, requireScope('memory:write'), (req, res) => {
40
+ try {
41
+ res.json(updateQueueItem(req.params.id, req.body || {}));
42
+ } catch (err) {
43
+ if (err.status === 404) return notFound(res, err.message);
44
+ serverError(res, err);
45
+ }
46
+ });
47
+
48
+ researchRouter.delete('/api/research/:id', requireAuth, requireScope('memory:write'), (req, res) => {
49
+ try {
50
+ res.json(removeFromQueue(req.params.id));
51
+ } catch (err) {
52
+ if (err.status === 404) return notFound(res, err.message);
53
+ serverError(res, err);
54
+ }
55
+ });
@@ -0,0 +1,55 @@
1
+ import express from 'express';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import os from 'node:os';
5
+ import { runInSandbox } from '../../core/sandbox.mjs';
6
+ import {
7
+ requireAuth,
8
+ requireScope,
9
+ sandboxRateLimiter,
10
+ requireSandboxEnabled,
11
+ } from '../auth.mjs';
12
+ import { logger } from '../../core/logger.mjs';
13
+
14
+ function badRequest(res, msg) {
15
+ return res.status(400).json({ error: msg });
16
+ }
17
+
18
+ function serverError(res, err) {
19
+ logger.error('sandbox', 'Internal server error', { error: err.message, stack: err.stack });
20
+ return res.status(500).json({ error: 'Internal server error' });
21
+ }
22
+
23
+ export const sandboxRouter = express.Router();
24
+
25
+ // ─── Sandbox ──────────────────────────────────────────────────────────────────
26
+
27
+ /**
28
+ * POST /api/sandbox
29
+ * Body: { code }
30
+ */
31
+ // Sandbox is rate-limited *before* requireAuth on purpose — limiter keys on
32
+ // the authenticated principal when present, so it's IP-bucketed for
33
+ // pre-auth misuse and key-bucketed once a PAT is attached.
34
+ sandboxRouter.post('/api/sandbox', sandboxRateLimiter(), requireAuth, requireScope('sandbox:run'), requireSandboxEnabled, async (req, res) => {
35
+ try {
36
+ const { code } = req.body || {};
37
+ if (!code) return badRequest(res, 'code is required');
38
+
39
+ const tmpDir = path.join(os.tmpdir(), 'total-recall-sandbox');
40
+ fs.mkdirSync(tmpDir, { recursive: true });
41
+ const script = path.join(tmpDir, `rest-${Date.now()}.mjs`);
42
+ fs.writeFileSync(script, code);
43
+
44
+ const result = await runInSandbox(script, 15000);
45
+ try { fs.unlinkSync(script); } catch {}
46
+
47
+ res.json({
48
+ success: result.success,
49
+ exit_code: result.code,
50
+ output: result.output,
51
+ });
52
+ } catch (err) {
53
+ serverError(res, err);
54
+ }
55
+ });
@@ -0,0 +1,118 @@
1
+ import express from 'express';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { requireAuth, requireScope } from '../auth.mjs';
5
+ import { logger } from '../../core/logger.mjs';
6
+ import { SKILLS_DIR } from './_shared.mjs';
7
+
8
+ function serverError(res, err) {
9
+ logger.error('skills', 'Internal server error', { error: err.message, stack: err.stack });
10
+ return res.status(500).json({ error: 'Internal server error' });
11
+ }
12
+
13
+ export const skillsRouter = express.Router();
14
+
15
+ skillsRouter.get('/api/skills', requireAuth, requireScope('files:read', 'ssss:read'), (req, res) => {
16
+ try {
17
+ if (!fs.existsSync(SKILLS_DIR)) {
18
+ fs.mkdirSync(SKILLS_DIR, { recursive: true });
19
+ }
20
+ const skills = fs.readdirSync(SKILLS_DIR).map(dir => {
21
+ const dirPath = path.join(SKILLS_DIR, dir);
22
+ const stats = fs.statSync(dirPath);
23
+
24
+ let subSkills = [];
25
+ const subSkillsPath = path.join(dirPath, 'skills');
26
+ if (fs.existsSync(subSkillsPath) && fs.statSync(subSkillsPath).isDirectory()) {
27
+ try {
28
+ subSkills = fs.readdirSync(subSkillsPath).filter(sd =>
29
+ fs.statSync(path.join(subSkillsPath, sd)).isDirectory()
30
+ );
31
+ } catch (e) {}
32
+ }
33
+
34
+ return {
35
+ name: dir,
36
+ size: stats.size,
37
+ modified: stats.mtime,
38
+ isDirectory: stats.isDirectory(),
39
+ subSkills
40
+ };
41
+ });
42
+ res.json(skills);
43
+ } catch (err) { serverError(res, err); }
44
+ });
45
+
46
+ skillsRouter.get('/api/skills/search', requireAuth, requireScope('files:read', 'ssss:read'), async (req, res) => {
47
+ try {
48
+ const q = req.query.q || '';
49
+ if (!q) {
50
+ return res.status(400).json({ error: 'Missing search query parameter `q`.' });
51
+ }
52
+ const { searchAndSort } = await import('../../../.agent/skills/total-recall/skills/skill/scripts/find-skills.mjs');
53
+ const results = searchAndSort(q);
54
+ res.json(results);
55
+ } catch (err) { serverError(res, err); }
56
+ });
57
+
58
+ skillsRouter.post('/api/skills/install', requireAuth, requireScope('files:write', 'ssss:write'), async (req, res) => {
59
+ try {
60
+ const { pkg } = req.body;
61
+ if (!pkg) {
62
+ return res.status(400).json({ error: 'Missing required `pkg` body parameter.' });
63
+ }
64
+ const { installSkill } = await import('../../../.agent/skills/total-recall/skills/skill/scripts/install-skill.mjs');
65
+ const result = installSkill(pkg);
66
+ res.json(result);
67
+ } catch (err) { serverError(res, err); }
68
+ });
69
+
70
+ skillsRouter.get('/api/skills/:name', requireAuth, requireScope('files:read', 'ssss:read'), (req, res) => {
71
+ try {
72
+ const { name } = req.params;
73
+ if (name.includes('..') || name.includes('/') || name.includes('\\')) {
74
+ return res.status(400).json({ error: 'Invalid skill name' });
75
+ }
76
+ const skillPath = path.join(SKILLS_DIR, name, 'SKILL.md');
77
+ if (!fs.existsSync(skillPath)) {
78
+ return res.status(404).json({ error: `Skill "${name}" not found` });
79
+ }
80
+ const content = fs.readFileSync(skillPath, 'utf8');
81
+ res.json({ name, content });
82
+ } catch (err) { serverError(res, err); }
83
+ });
84
+
85
+ skillsRouter.put('/api/skills/:name', requireAuth, requireScope('files:write', 'ssss:write'), (req, res) => {
86
+ try {
87
+ const { name } = req.params;
88
+ const { content } = req.body;
89
+ if (name.includes('..') || name.includes('/') || name.includes('\\')) {
90
+ return res.status(400).json({ error: 'Invalid skill name' });
91
+ }
92
+ if (typeof content !== 'string') {
93
+ return res.status(400).json({ error: 'Missing or invalid `content` field.' });
94
+ }
95
+ const skillDir = path.join(SKILLS_DIR, name);
96
+ if (!fs.existsSync(skillDir)) {
97
+ fs.mkdirSync(skillDir, { recursive: true });
98
+ }
99
+ const skillPath = path.join(skillDir, 'SKILL.md');
100
+ fs.writeFileSync(skillPath, content, 'utf8');
101
+ res.json({ success: true, message: `Skill "${name}" updated successfully` });
102
+ } catch (err) { serverError(res, err); }
103
+ });
104
+
105
+ skillsRouter.delete('/api/skills/:name', requireAuth, requireScope('files:write', 'ssss:write'), (req, res) => {
106
+ try {
107
+ const { name } = req.params;
108
+ if (name.includes('..') || name.includes('/') || name.includes('\\')) {
109
+ return res.status(400).json({ error: 'Invalid skill name' });
110
+ }
111
+ const skillDir = path.join(SKILLS_DIR, name);
112
+ if (!fs.existsSync(skillDir)) {
113
+ return res.status(404).json({ error: `Skill "${name}" not found` });
114
+ }
115
+ fs.rmSync(skillDir, { recursive: true, force: true });
116
+ res.json({ success: true, message: `Skill "${name}" deleted successfully` });
117
+ } catch (err) { serverError(res, err); }
118
+ });