@adityaaria/spark 6.0.19 → 6.0.21
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/spark-install.sh +88 -2
- package/package.json +1 -1
- package/skills/project-scanner/SKILL.md +2 -2
- package/skills/using-spark/SKILL.md +8 -1
- package/src/cli/index.js +6 -0
- package/src/cli/output.js +1 -1
- package/src/dashboard/public/index.html +664 -222
- package/src/dashboard/server.js +132 -0
package/src/dashboard/server.js
CHANGED
|
@@ -14,6 +14,15 @@ const LOCK_FILE = path.join(process.cwd(), '.spark-lock.json');
|
|
|
14
14
|
const server = http.createServer((req, res) => {
|
|
15
15
|
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
16
16
|
|
|
17
|
+
// Handle JSON body for POST
|
|
18
|
+
const getBody = (request) => new Promise((resolve) => {
|
|
19
|
+
let body = '';
|
|
20
|
+
request.on('data', chunk => body += chunk.toString());
|
|
21
|
+
request.on('end', () => {
|
|
22
|
+
try { resolve(JSON.parse(body)); } catch(e) { resolve({}); }
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
|
|
17
26
|
// API Route: Get all markdown docs
|
|
18
27
|
if (req.url === '/api/docs' && req.method === 'GET') {
|
|
19
28
|
res.setHeader('Content-Type', 'application/json');
|
|
@@ -33,6 +42,129 @@ const server = http.createServer((req, res) => {
|
|
|
33
42
|
}
|
|
34
43
|
}
|
|
35
44
|
|
|
45
|
+
// API Route: Get File Tree (for Heatmap)
|
|
46
|
+
if (req.url === '/api/tree' && req.method === 'GET') {
|
|
47
|
+
res.setHeader('Content-Type', 'application/json');
|
|
48
|
+
|
|
49
|
+
const getDirectoryTree = (dirPath, relativePath = '') => {
|
|
50
|
+
const items = fs.readdirSync(dirPath);
|
|
51
|
+
let result = [];
|
|
52
|
+
|
|
53
|
+
for (const item of items) {
|
|
54
|
+
if (['node_modules', '.git', '.docs', '.agents', '.spark'].includes(item)) continue;
|
|
55
|
+
|
|
56
|
+
const fullPath = path.join(dirPath, item);
|
|
57
|
+
const itemRelativePath = path.join(relativePath, item);
|
|
58
|
+
const stat = fs.statSync(fullPath);
|
|
59
|
+
|
|
60
|
+
let isDanger = false;
|
|
61
|
+
let dangerReason = '';
|
|
62
|
+
|
|
63
|
+
const lowerItem = item.toLowerCase();
|
|
64
|
+
if (lowerItem.includes('controller')) {
|
|
65
|
+
isDanger = true;
|
|
66
|
+
dangerReason = 'Anti-Pattern: "God Object" tendency. Move business logic to specific Domain Services instead of Controllers.';
|
|
67
|
+
} else if (lowerItem.includes('utils')) {
|
|
68
|
+
isDanger = true;
|
|
69
|
+
dangerReason = 'Legacy Trap: "Utils" folders become untracked dumping grounds for dead code. Use specific domain modules.';
|
|
70
|
+
} else if (lowerItem.includes('helper')) {
|
|
71
|
+
isDanger = true;
|
|
72
|
+
dangerReason = 'Legacy Trap: "Helpers" lack clear boundaries and violate single responsibility principles.';
|
|
73
|
+
} else if (lowerItem.includes('api') && stat.isDirectory()) {
|
|
74
|
+
isDanger = true;
|
|
75
|
+
dangerReason = 'Anti-Pattern: Generic "API" folders often lead to poor separation of concerns. Group by feature/domain instead.';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (stat.isDirectory()) {
|
|
79
|
+
const children = getDirectoryTree(fullPath, itemRelativePath);
|
|
80
|
+
result.push({
|
|
81
|
+
name: item,
|
|
82
|
+
path: itemRelativePath,
|
|
83
|
+
type: 'dir',
|
|
84
|
+
isDanger,
|
|
85
|
+
dangerReason,
|
|
86
|
+
children
|
|
87
|
+
});
|
|
88
|
+
} else {
|
|
89
|
+
result.push({
|
|
90
|
+
name: item,
|
|
91
|
+
path: itemRelativePath,
|
|
92
|
+
type: 'file',
|
|
93
|
+
isDanger,
|
|
94
|
+
dangerReason
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return result;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
return res.end(JSON.stringify({ tree: getDirectoryTree(process.cwd()) }));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// API Route: Get SPARK Readme (for Guide)
|
|
105
|
+
if (req.url === '/api/readme' && req.method === 'GET') {
|
|
106
|
+
res.setHeader('Content-Type', 'application/json');
|
|
107
|
+
const readmePath = path.join(__dirname, '../../README.md');
|
|
108
|
+
if (fs.existsSync(readmePath)) {
|
|
109
|
+
return res.end(JSON.stringify({ content: fs.readFileSync(readmePath, 'utf-8') }));
|
|
110
|
+
}
|
|
111
|
+
return res.end(JSON.stringify({ content: '# Documentation not found' }));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// API Route: Save Custom Skill (for Skill Studio)
|
|
115
|
+
if (req.url === '/api/skills' && req.method === 'GET') {
|
|
116
|
+
res.setHeader('Content-Type', 'application/json');
|
|
117
|
+
let skills = [];
|
|
118
|
+
|
|
119
|
+
// 1. Core skills
|
|
120
|
+
const coreDir = path.join(__dirname, '../../skills');
|
|
121
|
+
if (fs.existsSync(coreDir)) {
|
|
122
|
+
try {
|
|
123
|
+
const items = fs.readdirSync(coreDir);
|
|
124
|
+
for (const item of items) {
|
|
125
|
+
const stat = fs.statSync(path.join(coreDir, item));
|
|
126
|
+
if (stat.isDirectory()) skills.push({ name: item, type: 'core' });
|
|
127
|
+
}
|
|
128
|
+
} catch (e) {}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// 2. Custom skills
|
|
132
|
+
const customDir = path.join(process.cwd(), '.agents', 'skills');
|
|
133
|
+
if (fs.existsSync(customDir)) {
|
|
134
|
+
try {
|
|
135
|
+
const items = fs.readdirSync(customDir);
|
|
136
|
+
for (const item of items) {
|
|
137
|
+
const stat = fs.statSync(path.join(customDir, item));
|
|
138
|
+
if (stat.isDirectory()) skills.push({ name: item, type: 'custom' });
|
|
139
|
+
}
|
|
140
|
+
} catch (e) {}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return res.end(JSON.stringify({ skills }));
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (req.url === '/api/skills' && req.method === 'POST') {
|
|
147
|
+
res.setHeader('Content-Type', 'application/json');
|
|
148
|
+
getBody(req).then(data => {
|
|
149
|
+
if (!data.name || !data.content) {
|
|
150
|
+
return res.end(JSON.stringify({ error: 'Missing name or content' }));
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Save to a safe, user-level directory that SPARK updates won't touch
|
|
154
|
+
// The Anthropic/Gemini standard allows putting skills in .agents/skills/
|
|
155
|
+
const customSkillsDir = path.join(process.cwd(), '.agents', 'skills', data.name);
|
|
156
|
+
|
|
157
|
+
try {
|
|
158
|
+
fs.mkdirSync(customSkillsDir, { recursive: true });
|
|
159
|
+
fs.writeFileSync(path.join(customSkillsDir, 'SKILL.md'), data.content, 'utf-8');
|
|
160
|
+
return res.end(JSON.stringify({ success: true, path: customSkillsDir }));
|
|
161
|
+
} catch (e) {
|
|
162
|
+
return res.end(JSON.stringify({ error: e.message }));
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
|
|
36
168
|
// API Route: Get Spark Lock status
|
|
37
169
|
if (req.url === '/api/status' && req.method === 'GET') {
|
|
38
170
|
res.setHeader('Content-Type', 'application/json');
|