@yemi33/minions 0.1.1635 → 0.1.1636

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/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.1636 (2026-04-30)
4
+
5
+ ### Other
6
+ - Harden dashboard skill file reads
7
+
3
8
  ## 0.1.1635 (2026-04-30)
4
9
 
5
10
  ### Other
package/dashboard.js CHANGED
@@ -63,6 +63,42 @@ function reloadConfig() {
63
63
  PROJECTS = _getProjects(CONFIG);
64
64
  }
65
65
 
66
+ function _normalizeSkillDirForCompare(dir) {
67
+ const resolved = path.resolve(String(dir || '').replace(/\//g, path.sep));
68
+ return process.platform === 'win32' ? resolved.toLowerCase() : resolved;
69
+ }
70
+
71
+ function _skillEntrySource(entry) {
72
+ if (!entry) return '';
73
+ if (entry.scope === 'claude-code') return 'claude-code';
74
+ if (entry.scope === 'plugin') return 'plugin';
75
+ if (entry.scope === 'project') return 'project:' + entry.projectName;
76
+ return entry.scope || '';
77
+ }
78
+
79
+ function _isValidSkillFileName(file) {
80
+ return !!file && !file.includes('..') && !file.includes('\0') && !file.includes('/') && !file.includes('\\');
81
+ }
82
+
83
+ function _resolveSkillReadPath({ file, dir, source, config, skillFiles } = {}) {
84
+ if (!_isValidSkillFileName(file)) return null;
85
+ const requestedDir = dir ? _normalizeSkillDirForCompare(dir) : null;
86
+ const requestedSource = source || '';
87
+ const entries = Array.isArray(skillFiles) ? skillFiles : queries.collectSkillFiles(config || CONFIG);
88
+ for (const entry of entries) {
89
+ if (!entry || entry.file !== file || !entry.dir) continue;
90
+ if (requestedDir && _normalizeSkillDirForCompare(entry.dir) !== requestedDir) continue;
91
+ if (requestedSource && _skillEntrySource(entry) !== requestedSource) continue;
92
+
93
+ const baseDir = path.resolve(entry.dir);
94
+ const fullPath = path.resolve(baseDir, entry.file);
95
+ const rel = path.relative(baseDir, fullPath);
96
+ if (rel && (rel.startsWith('..') || path.isAbsolute(rel))) continue;
97
+ return fullPath;
98
+ }
99
+ return null;
100
+ }
101
+
66
102
  const PLANS_DIR = path.join(MINIONS_DIR, 'plans');
67
103
  const TEAMS_INBOX_PATH = path.join(ENGINE_DIR, 'teams-inbox.json');
68
104
 
@@ -4220,22 +4256,18 @@ What would you like to discuss or change? When you're happy, say "approve" and I
4220
4256
  const params = new URL(req.url, 'http://localhost').searchParams;
4221
4257
  const file = params.get('file');
4222
4258
  const dir = params.get('dir');
4223
- if (!file || file.includes('..') || file.includes('\0') || file.includes('/') || file.includes('\\')) { res.statusCode = 400; res.end('Invalid file'); return; }
4259
+ const source = params.get('source') || '';
4260
+ if (!_isValidSkillFileName(file)) { res.statusCode = 400; res.end('Invalid file'); return; }
4224
4261
 
4225
4262
  let content = '';
4226
- if (dir) {
4227
- // Direct path from collectSkillFiles validate resolved path stays within expected dir
4228
- const resolvedDir = path.resolve(dir.replace(/\//g, path.sep));
4229
- const fullPath = path.join(resolvedDir, file);
4230
- if (fullPath.startsWith(resolvedDir)) content = safeRead(fullPath) || '';
4231
- }
4232
- if (!content) {
4263
+ const skillPath = _resolveSkillReadPath({ file, dir, source, config: CONFIG });
4264
+ if (skillPath) content = safeRead(skillPath) || '';
4265
+ if (!content && !dir) {
4233
4266
  // Fallback: search Claude Code skills, then project skills
4234
4267
  const home = os.homedir();
4235
4268
  const claudePath = path.join(home, '.claude', 'skills', file.replace('.md', '').replace('SKILL', ''), 'SKILL.md');
4236
4269
  content = safeRead(claudePath) || '';
4237
4270
  if (!content) {
4238
- const source = params.get('source') || '';
4239
4271
  if (source.startsWith('project:')) {
4240
4272
  const proj = PROJECTS.find(p => p.name === source.replace('project:', ''));
4241
4273
  if (proj) content = safeRead(path.join(proj.localPath, '.claude', 'skills', file)) || '';
@@ -6371,6 +6403,7 @@ module.exports = {
6371
6403
  _parseDocChatResultText,
6372
6404
  _messageRequestsOrchestration,
6373
6405
  _formatDocChatContext,
6406
+ _resolveSkillReadPath,
6374
6407
  DOC_CHAT_DOCUMENT_DELIMITER,
6375
6408
  };
6376
6409
 
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "runtime": "copilot",
3
3
  "models": null,
4
- "cachedAt": "2026-04-30T10:13:15.188Z"
4
+ "cachedAt": "2026-04-30T14:01:57.688Z"
5
5
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.1635",
3
+ "version": "0.1.1636",
4
4
  "description": "Multi-agent AI dev team that runs from ~/.minions/ — five autonomous agents share a single engine, dashboard, and knowledge base",
5
5
  "bin": {
6
6
  "minions": "bin/minions.js"