@hanzlaa/rcode 4.1.2 → 4.3.1
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/cli/install.js +176 -13
- package/cli/lib/config.cjs +4 -2
- package/cli/lib/fsutil.cjs +13 -2
- package/cli/lib/homedir.cjs +21 -0
- package/cli/lib/schemas.cjs +6 -1
- package/cli/nuke.js +13 -8
- package/cli/postinstall.js +14 -4
- package/cli/rcode-slash-router.cjs +118 -0
- package/cli/uninstall.js +59 -1
- package/cli/update.js +10 -5
- package/dist/rcode.js +234 -230
- package/package.json +1 -1
- package/rcode/references/auto-init-guard.md +2 -2
- package/rcode/references/output-format.md +5 -5
- package/rcode/skills/actions/2-plan/rcode-create-milestone/steps/step-10-complete.md +1 -1
- package/server/dashboard.js +33 -13
- package/server/lib/api.js +62 -4
- package/server/lib/html/client/agents-data.js +22 -18
- package/server/lib/html/client/app.js +3 -0
- package/server/lib/html/client/components/AgentCard.js +127 -0
- package/server/lib/html/client/components/App.js +104 -39
- package/server/lib/html/client/components/CommandPalette.js +133 -0
- package/server/lib/html/client/components/FileReader.js +116 -0
- package/server/lib/html/client/components/FilterChips.js +94 -0
- package/server/lib/html/client/components/NotifyCenter.js +117 -0
- package/server/lib/html/client/components/OrchPanel.js +80 -52
- package/server/lib/html/client/components/PhaseGraph.js +300 -0
- package/server/lib/html/client/components/RejectDialog.js +78 -0
- package/server/lib/html/client/components/RunnerPicker.js +190 -0
- package/server/lib/html/client/components/Sidebar.js +106 -61
- package/server/lib/html/client/components/StatusSummaryBar.js +76 -0
- package/server/lib/html/client/components/TaskPipeline.js +83 -0
- package/server/lib/html/client/components/Topbar.js +86 -39
- package/server/lib/html/client/components/dashboard/Blockers.js +57 -0
- package/server/lib/html/client/components/dashboard/CompletedTasks.js +47 -0
- package/server/lib/html/client/components/dashboard/CurrentPhase.js +107 -0
- package/server/lib/html/client/components/dashboard/InProgress.js +72 -0
- package/server/lib/html/client/components/dashboard/ProgressDonut.js +101 -0
- package/server/lib/html/client/components/dashboard/ProgressTimeline.js +101 -0
- package/server/lib/html/client/components/dashboard/ProjectHealth.js +80 -0
- package/server/lib/html/client/components/dashboard/RecentDecisions.js +57 -0
- package/server/lib/html/client/components/dashboard/Timeline.js +143 -0
- package/server/lib/html/client/components/shared.js +47 -11
- package/server/lib/html/client/filter-state.js +72 -0
- package/server/lib/html/client/icons-client.js +7 -0
- package/server/lib/html/client/notify.js +75 -0
- package/server/lib/html/client/orchestrator.js +168 -41
- package/server/lib/html/client/preact.js +13 -8
- package/server/lib/html/client/store.js +70 -6
- package/server/lib/html/client/util.js +78 -0
- package/server/lib/html/client/vendor/htm.js +1 -0
- package/server/lib/html/client/vendor/preact-hooks.js +2 -0
- package/server/lib/html/client/vendor/preact.js +2 -0
- package/server/lib/html/client/views/AgentsView.js +144 -51
- package/server/lib/html/client/views/FilesView.js +20 -103
- package/server/lib/html/client/views/KanbanView.js +40 -21
- package/server/lib/html/client/views/MemoryView.js +26 -9
- package/server/lib/html/client/views/MilestonesView.js +4 -4
- package/server/lib/html/client/views/OrchestrationView.js +154 -19
- package/server/lib/html/client/views/OverviewView.js +47 -239
- package/server/lib/html/client/views/PhasesView.js +50 -6
- package/server/lib/html/client/views/RoadmapView.js +6 -3
- package/server/lib/html/client/views/SprintsView.js +50 -6
- package/server/lib/html/client/views/TasksView.js +4 -3
- package/server/lib/html/client.js +21 -4
- package/server/lib/html/css.js +2761 -8
- package/server/lib/html/icons.js +7 -0
- package/server/lib/html/shell.js +10 -3
- package/server/lib/scanner.js +376 -39
- package/server/orchestrator.js +346 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hanzlaa/rcode",
|
|
3
|
-
"version": "4.1
|
|
3
|
+
"version": "4.3.1",
|
|
4
4
|
"description": "rcode — the AI team that never forgets. Persistent memory, specialist agents, and slash commands for AI IDEs. Works in Claude Code, Cursor, Gemini, VS Code, and Antigravity.",
|
|
5
5
|
"main": "cli/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -25,8 +25,8 @@ rcode isn't configured for this project yet. Let me set it up — takes 30 secon
|
|
|
25
25
|
**1. Bootstrap local tooling** — copy bin from the global install:
|
|
26
26
|
|
|
27
27
|
```bash
|
|
28
|
-
|
|
29
|
-
TOOLS_SRC="$
|
|
28
|
+
GLOBAL_RCODE="$HOME/.rcode"
|
|
29
|
+
TOOLS_SRC="$GLOBAL_RCODE/bin/rcode-tools.cjs"
|
|
30
30
|
|
|
31
31
|
if [ ! -f "$TOOLS_SRC" ]; then
|
|
32
32
|
echo "ERROR: Global rcode tools not found at $TOOLS_SRC"
|
|
@@ -42,7 +42,7 @@ Use for major workflow transitions.
|
|
|
42
42
|
|
|
43
43
|
```
|
|
44
44
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
45
|
-
|
|
45
|
+
RCODE ► {STAGE NAME}
|
|
46
46
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
47
47
|
```
|
|
48
48
|
|
|
@@ -69,7 +69,7 @@ Use this when a router command dispatches to another command:
|
|
|
69
69
|
|
|
70
70
|
```
|
|
71
71
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
72
|
-
|
|
72
|
+
RCODE ► ROUTING
|
|
73
73
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
74
74
|
|
|
75
75
|
Input: {user's question or intent}
|
|
@@ -328,7 +328,7 @@ Use standard markdown pipe tables with status symbols:
|
|
|
328
328
|
**Majlis banner** (multi-agent council):
|
|
329
329
|
```
|
|
330
330
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
331
|
-
|
|
331
|
+
RCODE ► MAJLIS CONVENING
|
|
332
332
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
333
333
|
```
|
|
334
334
|
|
|
@@ -354,7 +354,7 @@ the banner, not inside it.
|
|
|
354
354
|
|
|
355
355
|
```
|
|
356
356
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
357
|
-
|
|
357
|
+
RCODE ► PLANNING SPRINT 01.1
|
|
358
358
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
359
359
|
التخطيط للسباق 01.1 — يرجى الانتظار
|
|
360
360
|
```
|
|
@@ -389,7 +389,7 @@ translated prose goes outside the art, on its own line(s).
|
|
|
389
389
|
|
|
390
390
|
- Varying box/banner widths within same output
|
|
391
391
|
- Mixing banner styles (`===`, `---`, `***`)
|
|
392
|
-
- Skipping `
|
|
392
|
+
- Skipping `RCODE ►` prefix in stage banners
|
|
393
393
|
- Random emoji (`🚀`, `✨`, `💫`) outside the approved set
|
|
394
394
|
- Missing Next Up block after workflow completions
|
|
395
395
|
- Hardcoding references to other methodologies in rcode's UX
|
|
@@ -16,7 +16,7 @@ Append `step-10-complete` to `stepsCompleted`. Add `completedAt: {ISO date}`.
|
|
|
16
16
|
|
|
17
17
|
```
|
|
18
18
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
19
|
-
|
|
19
|
+
RCODE ► ROADMAP CREATED
|
|
20
20
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
21
21
|
|
|
22
22
|
Source PRD: {inputFile}
|
package/server/dashboard.js
CHANGED
|
@@ -31,7 +31,7 @@ const { spawn } = require('child_process');
|
|
|
31
31
|
const CLIENT_DIR = path.join(__dirname, 'lib', 'html', 'client');
|
|
32
32
|
|
|
33
33
|
const { scanState } = require('./lib/scanner');
|
|
34
|
-
const { handleApiState, handleApiFiles, handleApiFile, handleApiHierarchy, handleApiMemory } = require('./lib/api');
|
|
34
|
+
const { handleApiState, handleApiFiles, handleApiFile, handleApiHierarchy, handleApiMemory, handleApiAgents } = require('./lib/api');
|
|
35
35
|
const { renderHtml } = require('./lib/html/shell');
|
|
36
36
|
|
|
37
37
|
// ---------- Configuration ----------
|
|
@@ -60,7 +60,20 @@ function loadOrchToken() {
|
|
|
60
60
|
const ORCH_TOKEN = loadOrchToken();
|
|
61
61
|
|
|
62
62
|
// ---------- HTTP Server ----------
|
|
63
|
+
// Every request runs through a try/catch so an unanticipated throw inside a
|
|
64
|
+
// handler (e.g. a pathological .planning tree in the scanner) returns a 500
|
|
65
|
+
// instead of crashing the whole server process.
|
|
63
66
|
const server = http.createServer((req, res) => {
|
|
67
|
+
try {
|
|
68
|
+
handleRequest(req, res);
|
|
69
|
+
} catch (err) {
|
|
70
|
+
console.error('[dashboard] request handler failed:', err && err.stack || err);
|
|
71
|
+
if (!res.headersSent) res.writeHead(500, { 'Content-Type': 'text/plain' });
|
|
72
|
+
res.end('Internal server error');
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
function handleRequest(req, res) {
|
|
64
77
|
const url = req.url || '/';
|
|
65
78
|
|
|
66
79
|
if (url === '/health') {
|
|
@@ -79,6 +92,11 @@ const server = http.createServer((req, res) => {
|
|
|
79
92
|
return;
|
|
80
93
|
}
|
|
81
94
|
|
|
95
|
+
if (url === '/api/agents') {
|
|
96
|
+
handleApiAgents(req, res, PROJECT_ROOT);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
82
100
|
if (url.startsWith('/api/file')) {
|
|
83
101
|
handleApiFile(req, res, PROJECT_ROOT);
|
|
84
102
|
return;
|
|
@@ -104,10 +122,12 @@ const server = http.createServer((req, res) => {
|
|
|
104
122
|
|
|
105
123
|
if (url.startsWith('/js/')) {
|
|
106
124
|
const name = url.slice(4).split('?')[0];
|
|
107
|
-
// Allow
|
|
108
|
-
// while still rejecting traversal
|
|
109
|
-
//
|
|
110
|
-
|
|
125
|
+
// Allow nested subdirectories (e.g. components/App.js, views/Foo.js,
|
|
126
|
+
// components/dashboard/ProgressDonut.js) while still rejecting traversal.
|
|
127
|
+
// The regex limits each segment to word chars, dots, and hyphens; the
|
|
128
|
+
// resolved-path check below is the real traversal guard (a `..` segment
|
|
129
|
+
// would pass this pattern but fail the CLIENT_DIR containment check).
|
|
130
|
+
if (!/^(?:[\w.-]+\/)*[\w.-]+\.js$/.test(name)) { res.writeHead(404); res.end('Not found'); return; }
|
|
111
131
|
// Defense-in-depth: resolved path must stay inside CLIENT_DIR even after
|
|
112
132
|
// any OS-level resolution (handles encoded traversal the regex might miss).
|
|
113
133
|
const resolved = path.resolve(CLIENT_DIR, name);
|
|
@@ -135,17 +155,17 @@ const server = http.createServer((req, res) => {
|
|
|
135
155
|
|
|
136
156
|
res.writeHead(404);
|
|
137
157
|
res.end('Not found');
|
|
138
|
-
}
|
|
158
|
+
}
|
|
139
159
|
|
|
140
160
|
server.listen(PORT, '127.0.0.1', () => {
|
|
141
161
|
console.log(`\n🕌 Majlis (مجلس) — rcode Dashboard`);
|
|
142
162
|
console.log(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`);
|
|
143
|
-
console.log(`
|
|
144
|
-
console.log(`
|
|
145
|
-
console.log(` Scanning:
|
|
146
|
-
console.log(` Refresh:
|
|
147
|
-
console.log(`
|
|
148
|
-
console.log(` Stop:
|
|
163
|
+
console.log(` 👉 OPEN THIS: http://localhost:${PORT}`);
|
|
164
|
+
console.log(` Mode: view-only`);
|
|
165
|
+
console.log(` Scanning: ${RCODE_DIR}`);
|
|
166
|
+
console.log(` Refresh: 30s soft poll`);
|
|
167
|
+
console.log(` Note: port ${PORT + 1} is the internal orchestrator API — not for the browser`);
|
|
168
|
+
console.log(` Stop: kill $(ss -ltnp 'sport = :${PORT}' | awk 'NR>1{match($6,/pid=([0-9]+)/,m); print m[1]}')`);
|
|
149
169
|
console.log(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`);
|
|
150
170
|
});
|
|
151
171
|
|
|
@@ -205,7 +225,7 @@ function spawnOrchestrator() {
|
|
|
205
225
|
try {
|
|
206
226
|
_orchProc = spawn(process.execPath, [ORCH_BIN], {
|
|
207
227
|
cwd: path.join(__dirname, '..'),
|
|
208
|
-
env: { ...process.env, ORCH_TOKEN, RCODE_DIR, PROJECT_ROOT },
|
|
228
|
+
env: { ...process.env, ORCH_TOKEN, RCODE_DIR, PROJECT_ROOT, DASH_PORT: String(PORT) },
|
|
209
229
|
stdio: 'pipe',
|
|
210
230
|
});
|
|
211
231
|
_orchProc.stdout.on('data', chunk => {
|
package/server/lib/api.js
CHANGED
|
@@ -8,7 +8,8 @@ const { scanState, scanMemoryBank } = require('./scanner');
|
|
|
8
8
|
function handleApiState(req, res, rcodeDir) {
|
|
9
9
|
const state = scanState(rcodeDir);
|
|
10
10
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
11
|
-
|
|
11
|
+
// Compact JSON — pretty-printing roughly doubled the polled payload.
|
|
12
|
+
res.end(JSON.stringify(state));
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
function handleApiFiles(req, res, projectRoot) {
|
|
@@ -192,13 +193,70 @@ function handleApiHierarchy(req, res, rcodeDir) {
|
|
|
192
193
|
})),
|
|
193
194
|
};
|
|
194
195
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
195
|
-
res.end(JSON.stringify(hierarchy
|
|
196
|
+
res.end(JSON.stringify(hierarchy));
|
|
196
197
|
}
|
|
197
198
|
|
|
198
199
|
function handleApiMemory(req, res, rcodeDir) {
|
|
199
200
|
const memory = scanMemoryBank(rcodeDir);
|
|
200
201
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
201
|
-
res.end(JSON.stringify(memory
|
|
202
|
+
res.end(JSON.stringify(memory));
|
|
202
203
|
}
|
|
203
204
|
|
|
204
|
-
|
|
205
|
+
// Parse the keys we surface on agent cards out of an agent definition's
|
|
206
|
+
// YAML frontmatter. Deliberately not a YAML parser: top-level `key: value`
|
|
207
|
+
// scalar lines are read directly; for `description` (usually a `|` block
|
|
208
|
+
// scalar) the first two indented lines are captured as a card-sized summary.
|
|
209
|
+
function parseAgentFrontmatter(raw) {
|
|
210
|
+
const meta = { name: null, model: null, tools: [], color: null, description: null };
|
|
211
|
+
if (!raw.startsWith('---')) return meta;
|
|
212
|
+
const end = raw.indexOf('\n---', 3);
|
|
213
|
+
if (end === -1) return meta;
|
|
214
|
+
let inDescription = false;
|
|
215
|
+
const descLines = [];
|
|
216
|
+
for (const line of raw.slice(3, end).split('\n')) {
|
|
217
|
+
const m = line.match(/^([A-Za-z][\w-]*):\s*(.*)$/);
|
|
218
|
+
if (m) {
|
|
219
|
+
inDescription = false;
|
|
220
|
+
const key = m[1].toLowerCase();
|
|
221
|
+
const value = m[2].trim();
|
|
222
|
+
if (key === 'description') {
|
|
223
|
+
if (value && value !== '|' && value !== '>') descLines.push(value);
|
|
224
|
+
else inDescription = true;
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
if (!value || value === '|' || value === '>') continue;
|
|
228
|
+
if (key === 'name') meta.name = value;
|
|
229
|
+
if (key === 'model') meta.model = value;
|
|
230
|
+
if (key === 'color') meta.color = value;
|
|
231
|
+
if (key === 'tools') meta.tools = value.split(',').map(t => t.trim()).filter(Boolean);
|
|
232
|
+
} else if (inDescription && descLines.length < 2 && /^\s+\S/.test(line)) {
|
|
233
|
+
descLines.push(line.trim());
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
meta.description = descLines.join(' ') || null;
|
|
237
|
+
return meta;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Read-only roster metadata for the Agents view. Scans the fixed
|
|
241
|
+
// rcode/agents/ directory (no user-supplied paths — nothing to contain) and
|
|
242
|
+
// returns one small frontmatter summary per agent .md file. Full prompt
|
|
243
|
+
// bodies are NOT included; the client fetches those lazily per agent via the
|
|
244
|
+
// existing /api/file handler when a card is opened.
|
|
245
|
+
function handleApiAgents(req, res, projectRoot) {
|
|
246
|
+
const agentsDir = path.join(projectRoot, 'rcode', 'agents');
|
|
247
|
+
let entries = [];
|
|
248
|
+
try { entries = fs.readdirSync(agentsDir, { withFileTypes: true }); }
|
|
249
|
+
catch { /* no agents dir — return an empty roster */ }
|
|
250
|
+
const agents = [];
|
|
251
|
+
for (const e of entries) {
|
|
252
|
+
if (!e.isFile() || !e.name.endsWith('.md')) continue;
|
|
253
|
+
let raw;
|
|
254
|
+
try { raw = fs.readFileSync(path.join(agentsDir, e.name), 'utf8'); }
|
|
255
|
+
catch { continue; }
|
|
256
|
+
agents.push({ file: e.name, ...parseAgentFrontmatter(raw) });
|
|
257
|
+
}
|
|
258
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
259
|
+
res.end(JSON.stringify(agents));
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
module.exports = { handleApiState, handleApiFiles, handleApiFile, handleApiHierarchy, handleApiMemory, handleApiAgents };
|
|
@@ -3,25 +3,29 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Previously lived in shell.js:17-36 as a server-rendered array.
|
|
5
5
|
* Now exported as a pure ESM constant so AgentsView can render it.
|
|
6
|
+
*
|
|
7
|
+
* `file` is the agent's definition under rcode/agents/ — fetched lazily by
|
|
8
|
+
* AgentsView when a card is opened. null = no prompt file on disk (system
|
|
9
|
+
* entries like Raees/Majlis/Diwan are skills, not agent definitions).
|
|
6
10
|
*/
|
|
7
11
|
|
|
8
12
|
export const AGENTS = [
|
|
9
|
-
{ name: 'Sadiq Damani', arabic: 'صادق', role: 'Director of Strategy', real: true, type: 'leadership' },
|
|
10
|
-
{ name: 'Waleed Al Harthi', arabic: 'وليد', role: 'CTO', real: true, type: 'leadership' },
|
|
11
|
-
{ name: 'Ahmed Al Hassani', arabic: 'أحمد الحسني', role: 'Technology & Development Director', real: true, type: 'leadership' },
|
|
12
|
-
{ name: 'Nasser', arabic: 'ناصر', role: 'Engineering Manager', real: true, type: 'leadership' },
|
|
13
|
-
{ name: 'Hussain', arabic: 'حسين', role: 'PM + Scrum Master', type: 'product' },
|
|
14
|
-
{ name: 'Layla', arabic: 'ليلى', role: 'Lead UX Designer', type: 'design' },
|
|
15
|
-
{ name: 'Zahra', arabic: 'زهرة', role: 'Branding & Creative Director', type: 'design' },
|
|
16
|
-
{ name: 'Omar', arabic: 'عمر', role: 'Full-Stack Engineer', type: 'engineering' },
|
|
17
|
-
{ name: 'Haitham Al Khamiyasi', arabic: 'هيثم', role: 'Senior Frontend', real: true, type: 'engineering' },
|
|
18
|
-
{ name: 'Yousef', arabic: 'يوسف', role: 'Senior Backend', type: 'engineering' },
|
|
19
|
-
{ name: 'Zayd', arabic: 'زيد', role: 'ML Engineer', type: 'engineering' },
|
|
20
|
-
{ name: 'Fatima', arabic: 'فاطمة', role: 'QA Lead', type: 'quality' },
|
|
21
|
-
{ name: 'Khalid', arabic: 'خالد', role: 'DevOps', type: 'engineering' },
|
|
22
|
-
{ name: 'Noor', arabic: 'نور', role: 'Scribe', type: 'support' },
|
|
23
|
-
{ name: 'Mariam', arabic: 'مريم', role: 'Marketing Lead', type: 'product' },
|
|
24
|
-
{ name: 'Raees', arabic: 'رئيس', role: 'Orchestration Director', type: 'system' },
|
|
25
|
-
{ name: 'Majlis', arabic: 'مجلس', role: 'Consulting Council', type: 'system' },
|
|
26
|
-
{ name: 'Diwan', arabic: 'ديوان', role: 'Dashboard Registry', type: 'system' },
|
|
13
|
+
{ name: 'Sadiq Damani', arabic: 'صادق', role: 'Director of Strategy', real: true, type: 'leadership', file: 'rcode-sadiq.md' },
|
|
14
|
+
{ name: 'Waleed Al Harthi', arabic: 'وليد', role: 'CTO', real: true, type: 'leadership', file: 'rcode-waleed.md' },
|
|
15
|
+
{ name: 'Ahmed Al Hassani', arabic: 'أحمد الحسني', role: 'Technology & Development Director', real: true, type: 'leadership', file: 'rcode-ahmed.md' },
|
|
16
|
+
{ name: 'Nasser', arabic: 'ناصر', role: 'Engineering Manager', real: true, type: 'leadership', file: 'rcode-nasser.md' },
|
|
17
|
+
{ name: 'Hussain', arabic: 'حسين', role: 'PM + Scrum Master', type: 'product', file: 'rcode-hussain-pm.md' },
|
|
18
|
+
{ name: 'Layla', arabic: 'ليلى', role: 'Lead UX Designer', type: 'design', file: 'rcode-layla.md' },
|
|
19
|
+
{ name: 'Zahra', arabic: 'زهرة', role: 'Branding & Creative Director', type: 'design', file: 'rcode-zahra.md' },
|
|
20
|
+
{ name: 'Omar', arabic: 'عمر', role: 'Full-Stack Engineer', type: 'engineering', file: 'rcode-omar.md' },
|
|
21
|
+
{ name: 'Haitham Al Khamiyasi', arabic: 'هيثم', role: 'Senior Frontend', real: true, type: 'engineering', file: 'rcode-haitham.md' },
|
|
22
|
+
{ name: 'Yousef', arabic: 'يوسف', role: 'Senior Backend', type: 'engineering', file: 'rcode-yousef.md' },
|
|
23
|
+
{ name: 'Zayd', arabic: 'زيد', role: 'ML Engineer', type: 'engineering', file: 'rcode-zayd.md' },
|
|
24
|
+
{ name: 'Fatima', arabic: 'فاطمة', role: 'QA Lead', type: 'quality', file: 'rcode-fatima.md' },
|
|
25
|
+
{ name: 'Khalid', arabic: 'خالد', role: 'DevOps', type: 'engineering', file: 'rcode-khalid.md' },
|
|
26
|
+
{ name: 'Noor', arabic: 'نور', role: 'Scribe', type: 'support', file: 'rcode-noor.md' },
|
|
27
|
+
{ name: 'Mariam', arabic: 'مريم', role: 'Marketing Lead', type: 'product', file: 'rcode-mariam.md' },
|
|
28
|
+
{ name: 'Raees', arabic: 'رئيس', role: 'Orchestration Director', type: 'system', file: null },
|
|
29
|
+
{ name: 'Majlis', arabic: 'مجلس', role: 'Consulting Council', type: 'system', file: null },
|
|
30
|
+
{ name: 'Diwan', arabic: 'ديوان', role: 'Dashboard Registry', type: 'system', file: null },
|
|
27
31
|
];
|
|
@@ -11,5 +11,8 @@ import { App } from './components/App.js';
|
|
|
11
11
|
|
|
12
12
|
const root = document.getElementById('app-root');
|
|
13
13
|
if (root) {
|
|
14
|
+
// Drop the SSR loading shell — Preact diffs against existing children,
|
|
15
|
+
// so the spinner must be gone before the first render.
|
|
16
|
+
root.textContent = '';
|
|
14
17
|
render(html`<${App}/>`, root);
|
|
15
18
|
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AgentCard — card, avatar, chips, and detail drawer for the Agents view.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from AgentsView so the view module stays focused on grouping,
|
|
5
|
+
* search, and fetch state. Per-role accent colors are driven by a single
|
|
6
|
+
* `agent-accent--<type>` class on the card/drawer root: it sets the
|
|
7
|
+
* --agent-accent custom property that the avatar, role badge, and hover
|
|
8
|
+
* border all read (see the AGENTS VIEW block at the end of css.js).
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { html } from '../preact.js';
|
|
12
|
+
import { setState } from '../store.js';
|
|
13
|
+
import { pressable, showToast } from './shared.js';
|
|
14
|
+
import { renderMd } from '../util.js';
|
|
15
|
+
|
|
16
|
+
const MAX_CARD_TOOL_CHIPS = 4;
|
|
17
|
+
|
|
18
|
+
/** "Sadiq Damani" -> "SD", "Hussain" -> "H". */
|
|
19
|
+
function initialsOf(name) {
|
|
20
|
+
return name.split(/\s+/).filter(Boolean).slice(0, 2).map(w => w[0]).join('').toUpperCase();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Per-role accent class — types map 1:1 to the CSS accent variants. */
|
|
24
|
+
export function accentClass(agent) {
|
|
25
|
+
return 'agent-accent--' + (agent.type || 'system');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// ---- Avatar circle with initials ----
|
|
29
|
+
function Avatar({ agent, large }) {
|
|
30
|
+
return html`<span class=${'agent-avatar' + (large ? ' agent-avatar--lg' : '')} aria-hidden="true">${initialsOf(agent.name)}</span>`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// ---- Metadata chips (model + tools), shared by card and drawer ----
|
|
34
|
+
export function MetaChips({ meta, maxTools }) {
|
|
35
|
+
if (!meta) return null;
|
|
36
|
+
const tools = meta.tools || [];
|
|
37
|
+
const shown = maxTools ? tools.slice(0, maxTools) : tools;
|
|
38
|
+
const extra = tools.length - shown.length;
|
|
39
|
+
if (!meta.model && !shown.length) return null;
|
|
40
|
+
return html`
|
|
41
|
+
<div class="agent-chips">
|
|
42
|
+
${meta.model ? html`<span class="agent-chip agent-chip--model">${meta.model}</span>` : null}
|
|
43
|
+
${shown.map(t => html`<span class="agent-chip" key=${t}>${t}</span>`)}
|
|
44
|
+
${extra > 0 ? html`<span class="agent-chip agent-chip--more">+${extra}</span>` : null}
|
|
45
|
+
</div>
|
|
46
|
+
`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ---- Single agent card ----
|
|
50
|
+
export function AgentCard({ agent, meta, onOpen }) {
|
|
51
|
+
return html`
|
|
52
|
+
<div class=${'agent-card ' + accentClass(agent)} ...${pressable(() => onOpen(agent))}>
|
|
53
|
+
<div class="agent-card-top">
|
|
54
|
+
<${Avatar} agent=${agent} />
|
|
55
|
+
<div class="agent-card-id">
|
|
56
|
+
<div class="agent-card-name">
|
|
57
|
+
${agent.name}
|
|
58
|
+
${agent.real ? html`<span class="real-badge">real</span>` : null}
|
|
59
|
+
</div>
|
|
60
|
+
<span class="role-badge">${agent.role}</span>
|
|
61
|
+
</div>
|
|
62
|
+
<span class="agent-card-arabic">${agent.arabic}</span>
|
|
63
|
+
</div>
|
|
64
|
+
${meta && meta.description ? html`<p class="agent-card-desc">${meta.description}</p>` : null}
|
|
65
|
+
<${MetaChips} meta=${meta} maxTools=${MAX_CARD_TOOL_CHIPS} />
|
|
66
|
+
</div>
|
|
67
|
+
`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ---- Detail drawer ----
|
|
71
|
+
export function AgentDrawer({ agent, meta, prompt, onClose }) {
|
|
72
|
+
const filePath = agent.file ? 'rcode/agents/' + agent.file : null;
|
|
73
|
+
|
|
74
|
+
function copyPath() {
|
|
75
|
+
navigator.clipboard.writeText(filePath).then(() => {
|
|
76
|
+
showToast('Path copied!');
|
|
77
|
+
}).catch(() => {});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function openInFiles() {
|
|
81
|
+
setState({ requestedFile: filePath });
|
|
82
|
+
window.location.hash = 'files';
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
let body;
|
|
86
|
+
if (!agent.file) {
|
|
87
|
+
body = html`<div class="agent-drawer-empty">No prompt file on disk — this is a system entry without an agent definition.</div>`;
|
|
88
|
+
} else if (prompt.loading) {
|
|
89
|
+
body = html`
|
|
90
|
+
<div class="skeleton"></div>
|
|
91
|
+
<div class="agent-drawer-skeleton skeleton"></div>
|
|
92
|
+
`;
|
|
93
|
+
} else if (prompt.error) {
|
|
94
|
+
body = html`<div class="agent-drawer-error">${prompt.error}</div>`;
|
|
95
|
+
} else if (prompt.text) {
|
|
96
|
+
body = html`<div class="md-render" dangerouslySetInnerHTML=${{ __html: renderMd(prompt.text) }} />`;
|
|
97
|
+
} else {
|
|
98
|
+
body = null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return html`
|
|
102
|
+
<div class="agent-drawer-backdrop" onClick=${onClose}></div>
|
|
103
|
+
<aside class=${'agent-drawer ' + accentClass(agent)} role="dialog" aria-modal="true" aria-label="${agent.name} — full prompt">
|
|
104
|
+
<div class="agent-drawer-head">
|
|
105
|
+
<${Avatar} agent=${agent} large />
|
|
106
|
+
<div class="agent-drawer-titles">
|
|
107
|
+
<div class="agent-drawer-name">
|
|
108
|
+
${agent.name}
|
|
109
|
+
<span class="agent-drawer-arabic">${agent.arabic}</span>
|
|
110
|
+
${agent.real ? html`<span class="real-badge">real</span>` : null}
|
|
111
|
+
</div>
|
|
112
|
+
<span class="role-badge">${agent.role}</span>
|
|
113
|
+
<${MetaChips} meta=${meta} />
|
|
114
|
+
</div>
|
|
115
|
+
<button class="agent-drawer-close" onClick=${onClose} aria-label="Close">×</button>
|
|
116
|
+
</div>
|
|
117
|
+
${filePath ? html`
|
|
118
|
+
<div class="agent-drawer-meta">
|
|
119
|
+
<span class="agent-drawer-meta-path">${filePath}</span>
|
|
120
|
+
<button class="agent-drawer-btn" onClick=${copyPath}>Copy path</button>
|
|
121
|
+
<button class="agent-drawer-btn agent-drawer-btn--link" onClick=${openInFiles}>View in Files →</button>
|
|
122
|
+
</div>
|
|
123
|
+
` : null}
|
|
124
|
+
<div class="agent-drawer-body">${body}</div>
|
|
125
|
+
</aside>
|
|
126
|
+
`;
|
|
127
|
+
}
|