@wipal/agent-team 1.1.3 → 1.2.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/package.json +1 -1
- package/src/server/api/projects.js +71 -0
- package/src/server/index.js +2 -0
- package/src/ui/css/styles.css +49 -1
- package/src/ui/index.html +86 -8
package/package.json
CHANGED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Projects API Routes
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Router } from 'express';
|
|
6
|
+
import { listValidProjects, getProject, updateProjectAccess } from '../../utils/global-registry.js';
|
|
7
|
+
|
|
8
|
+
const router = Router();
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* List all registered projects
|
|
12
|
+
*/
|
|
13
|
+
router.get('/', async (req, res) => {
|
|
14
|
+
try {
|
|
15
|
+
const projects = await listValidProjects();
|
|
16
|
+
res.json({ projects });
|
|
17
|
+
} catch (error) {
|
|
18
|
+
res.status(500).json({ error: error.message });
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Get current project
|
|
24
|
+
*/
|
|
25
|
+
router.get('/current', async (req, res) => {
|
|
26
|
+
try {
|
|
27
|
+
const projectRoot = req.app.get('projectRoot');
|
|
28
|
+
const project = await getProject(projectRoot);
|
|
29
|
+
|
|
30
|
+
res.json({
|
|
31
|
+
path: projectRoot,
|
|
32
|
+
...project
|
|
33
|
+
});
|
|
34
|
+
} catch (error) {
|
|
35
|
+
res.status(500).json({ error: error.message });
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Switch to a different project
|
|
41
|
+
*/
|
|
42
|
+
router.post('/switch', async (req, res) => {
|
|
43
|
+
try {
|
|
44
|
+
const { path: projectPath } = req.body;
|
|
45
|
+
|
|
46
|
+
if (!projectPath) {
|
|
47
|
+
return res.status(400).json({ error: 'Project path is required' });
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const project = await getProject(projectPath);
|
|
51
|
+
if (!project) {
|
|
52
|
+
return res.status(404).json({ error: 'Project not found in registry' });
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Update app's project root
|
|
56
|
+
req.app.set('projectRoot', projectPath);
|
|
57
|
+
await updateProjectAccess(projectPath);
|
|
58
|
+
|
|
59
|
+
res.json({
|
|
60
|
+
success: true,
|
|
61
|
+
project: {
|
|
62
|
+
path: projectPath,
|
|
63
|
+
...project
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
} catch (error) {
|
|
67
|
+
res.status(500).json({ error: error.message });
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
export default router;
|
package/src/server/index.js
CHANGED
|
@@ -10,6 +10,7 @@ import open from 'open';
|
|
|
10
10
|
import agentsRouter from './api/agents.js';
|
|
11
11
|
import rolesRouter from './api/roles.js';
|
|
12
12
|
import skillsRouter from './api/skills.js';
|
|
13
|
+
import projectsRouter from './api/projects.js';
|
|
13
14
|
|
|
14
15
|
const __filename = fileURLToPath(import.meta.url);
|
|
15
16
|
const __dirname = path.dirname(__filename);
|
|
@@ -37,6 +38,7 @@ export function createApp(projectRoot = process.cwd()) {
|
|
|
37
38
|
app.use('/api/agents', agentsRouter);
|
|
38
39
|
app.use('/api/roles', rolesRouter);
|
|
39
40
|
app.use('/api/skills', skillsRouter);
|
|
41
|
+
app.use('/api/projects', projectsRouter);
|
|
40
42
|
|
|
41
43
|
// Main page redirect
|
|
42
44
|
app.get('/', (req, res) => {
|
package/src/ui/css/styles.css
CHANGED
|
@@ -36,15 +36,63 @@ body {
|
|
|
36
36
|
|
|
37
37
|
/* Header */
|
|
38
38
|
.header {
|
|
39
|
-
text-align: center;
|
|
40
39
|
margin-bottom: 2rem;
|
|
41
40
|
}
|
|
42
41
|
|
|
42
|
+
.header-row {
|
|
43
|
+
display: flex;
|
|
44
|
+
justify-content: space-between;
|
|
45
|
+
align-items: flex-start;
|
|
46
|
+
flex-wrap: wrap;
|
|
47
|
+
gap: 1rem;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.header-row > div:first-child {
|
|
51
|
+
text-align: left;
|
|
52
|
+
}
|
|
53
|
+
|
|
43
54
|
.header h1 {
|
|
44
55
|
font-size: 2rem;
|
|
45
56
|
margin-bottom: 0.5rem;
|
|
46
57
|
}
|
|
47
58
|
|
|
59
|
+
/* Project Selector */
|
|
60
|
+
.project-selector {
|
|
61
|
+
display: flex;
|
|
62
|
+
align-items: center;
|
|
63
|
+
gap: 0.5rem;
|
|
64
|
+
padding: 0.75rem 1rem;
|
|
65
|
+
background: var(--bg-card);
|
|
66
|
+
border-radius: 0.5rem;
|
|
67
|
+
box-shadow: var(--shadow);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.project-selector label {
|
|
71
|
+
font-weight: 500;
|
|
72
|
+
color: var(--text-muted);
|
|
73
|
+
font-size: 0.875rem;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.project-selector select {
|
|
77
|
+
padding: 0.5rem 0.75rem;
|
|
78
|
+
border: 1px solid var(--border);
|
|
79
|
+
border-radius: 0.375rem;
|
|
80
|
+
font-size: 0.875rem;
|
|
81
|
+
min-width: 200px;
|
|
82
|
+
cursor: pointer;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.project-selector select:focus {
|
|
86
|
+
outline: none;
|
|
87
|
+
border-color: var(--primary);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.loading-indicator {
|
|
91
|
+
font-size: 0.75rem;
|
|
92
|
+
color: var(--primary);
|
|
93
|
+
font-style: italic;
|
|
94
|
+
}
|
|
95
|
+
|
|
48
96
|
.subtitle {
|
|
49
97
|
color: var(--text-muted);
|
|
50
98
|
}
|
package/src/ui/index.html
CHANGED
|
@@ -10,10 +10,26 @@
|
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
|
12
12
|
<div class="container">
|
|
13
|
-
<!-- Header -->
|
|
13
|
+
<!-- Header with Project Selector -->
|
|
14
14
|
<header class="header">
|
|
15
|
-
<
|
|
16
|
-
|
|
15
|
+
<div class="header-row">
|
|
16
|
+
<div>
|
|
17
|
+
<h1>🤖 Agent Team Dashboard</h1>
|
|
18
|
+
<p class="subtitle">Manage AI agents for your project</p>
|
|
19
|
+
</div>
|
|
20
|
+
<!-- Project Selector -->
|
|
21
|
+
<div class="project-selector" x-data="projectSelector()">
|
|
22
|
+
<label>Project:</label>
|
|
23
|
+
<select @change="switchProject($event)" x-model="currentProject" :disabled="loading">
|
|
24
|
+
<template x-for="project in projects" :key="project.path">
|
|
25
|
+
<option :value="project.path" :selected="project.path === currentProject">
|
|
26
|
+
<span x-text="project.name"></span>
|
|
27
|
+
</option>
|
|
28
|
+
</template>
|
|
29
|
+
</select>
|
|
30
|
+
<span x-show="loading" class="loading-indicator">Switching...</span>
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
17
33
|
</header>
|
|
18
34
|
|
|
19
35
|
<!-- Navigation -->
|
|
@@ -25,7 +41,7 @@
|
|
|
25
41
|
</nav>
|
|
26
42
|
|
|
27
43
|
<!-- Stats Cards -->
|
|
28
|
-
<div class="stats-grid" hx-get="/api/agents" hx-trigger="load" hx-swap="innerHTML">
|
|
44
|
+
<div class="stats-grid" hx-get="/api/agents" hx-trigger="load, projectChanged from:body" hx-swap="innerHTML">
|
|
29
45
|
<!-- Loaded via HTMX -->
|
|
30
46
|
<div class="loading">Loading...</div>
|
|
31
47
|
</div>
|
|
@@ -49,7 +65,7 @@
|
|
|
49
65
|
<!-- Recent Agents -->
|
|
50
66
|
<section class="section">
|
|
51
67
|
<h2>Your Agents</h2>
|
|
52
|
-
<div id="agents-list" hx-get="/api/agents" hx-trigger="load">
|
|
68
|
+
<div id="agents-list" hx-get="/api/agents" hx-trigger="load, projectChanged from:body">
|
|
53
69
|
<div class="loading">Loading agents...</div>
|
|
54
70
|
</div>
|
|
55
71
|
</section>
|
|
@@ -64,13 +80,75 @@
|
|
|
64
80
|
</div>
|
|
65
81
|
|
|
66
82
|
<script>
|
|
83
|
+
// Project Selector Alpine Component
|
|
84
|
+
function projectSelector() {
|
|
85
|
+
return {
|
|
86
|
+
projects: [],
|
|
87
|
+
currentProject: '',
|
|
88
|
+
loading: false,
|
|
89
|
+
|
|
90
|
+
async init() {
|
|
91
|
+
await this.loadProjects();
|
|
92
|
+
await this.loadCurrentProject();
|
|
93
|
+
},
|
|
94
|
+
|
|
95
|
+
async loadProjects() {
|
|
96
|
+
try {
|
|
97
|
+
const response = await fetch('/api/projects');
|
|
98
|
+
const data = await response.json();
|
|
99
|
+
this.projects = data.projects.filter(p => p.valid);
|
|
100
|
+
} catch (error) {
|
|
101
|
+
console.error('Failed to load projects:', error);
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
async loadCurrentProject() {
|
|
106
|
+
try {
|
|
107
|
+
const response = await fetch('/api/projects/current');
|
|
108
|
+
const project = await response.json();
|
|
109
|
+
this.currentProject = project.path;
|
|
110
|
+
} catch (error) {
|
|
111
|
+
console.error('Failed to load current project:', error);
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
async switchProject(event) {
|
|
116
|
+
const newPath = event.target.value;
|
|
117
|
+
if (newPath === this.currentProject) return;
|
|
118
|
+
|
|
119
|
+
this.loading = true;
|
|
120
|
+
try {
|
|
121
|
+
const response = await fetch('/api/projects/switch', {
|
|
122
|
+
method: 'POST',
|
|
123
|
+
headers: { 'Content-Type': 'application/json' },
|
|
124
|
+
body: JSON.stringify({ path: newPath })
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
if (response.ok) {
|
|
128
|
+
this.currentProject = newPath;
|
|
129
|
+
// Trigger HTMX refresh
|
|
130
|
+
document.body.dispatchEvent(new CustomEvent('projectChanged'));
|
|
131
|
+
} else {
|
|
132
|
+
const error = await response.json();
|
|
133
|
+
alert('Failed to switch project: ' + error.error);
|
|
134
|
+
}
|
|
135
|
+
} catch (error) {
|
|
136
|
+
alert('Failed to switch project: ' + error.message);
|
|
137
|
+
} finally {
|
|
138
|
+
this.loading = false;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
67
144
|
// Transform stats response
|
|
68
145
|
document.body.addEventListener('htmx:beforeSwap', function(evt) {
|
|
69
146
|
if (evt.detail.pathInfo.requestPath === '/api/agents') {
|
|
70
147
|
// First load - render as stats
|
|
71
148
|
if (evt.detail.target.matches('.stats-grid')) {
|
|
72
149
|
try {
|
|
73
|
-
const
|
|
150
|
+
const data = JSON.parse(evt.detail.xhr.response);
|
|
151
|
+
const agents = data.agents || [];
|
|
74
152
|
const statsHtml = renderStats(agents);
|
|
75
153
|
evt.detail.swap = 'innerHTML';
|
|
76
154
|
evt.detail.xhr.response = statsHtml;
|
|
@@ -93,11 +171,11 @@
|
|
|
93
171
|
<div class="stat-label">Roles Used</div>
|
|
94
172
|
</div>
|
|
95
173
|
<div class="stat-card">
|
|
96
|
-
<div class="stat-value">
|
|
174
|
+
<div class="stat-value">50</div>
|
|
97
175
|
<div class="stat-label">Skills Available</div>
|
|
98
176
|
</div>
|
|
99
177
|
<div class="stat-card">
|
|
100
|
-
<div class="stat-value">
|
|
178
|
+
<div class="stat-value">9</div>
|
|
101
179
|
<div class="stat-label">Roles Available</div>
|
|
102
180
|
</div>
|
|
103
181
|
`;
|