agent-comms 1.4.2 → 1.5.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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +2 -2
- package/dist/bridges/claude-code/channel.js +1 -1
- package/dist/bridges/claude-code/channel.js.map +1 -1
- package/dist/bridges/codex/tool.js +1 -1
- package/dist/bridges/codex/tool.js.map +1 -1
- package/dist/bridges/mcp/index.js +1 -1
- package/dist/bridges/mcp/index.js.map +1 -1
- package/dist/bridges/pi/index.js +1 -1
- package/dist/bridges/pi/index.js.map +1 -1
- package/dist/bridges/user/cli.d.ts +12 -0
- package/dist/bridges/user/cli.d.ts.map +1 -0
- package/dist/bridges/user/cli.js +77 -0
- package/dist/bridges/user/cli.js.map +1 -0
- package/dist/bridges/user/controller.d.ts +39 -0
- package/dist/bridges/user/controller.d.ts.map +1 -0
- package/dist/bridges/user/controller.js +185 -0
- package/dist/bridges/user/controller.js.map +1 -0
- package/dist/bridges/user/tui.d.ts +21 -0
- package/dist/bridges/user/tui.d.ts.map +1 -0
- package/dist/bridges/user/tui.js +237 -0
- package/dist/bridges/user/tui.js.map +1 -0
- package/dist/bridges/user/web/index.html.d.ts +8 -0
- package/dist/bridges/user/web/index.html.d.ts.map +1 -0
- package/dist/bridges/user/web/index.html.js +386 -0
- package/dist/bridges/user/web/index.html.js.map +1 -0
- package/dist/bridges/user/web/server.d.ts +19 -0
- package/dist/bridges/user/web/server.d.ts.map +1 -0
- package/dist/bridges/user/web/server.js +277 -0
- package/dist/bridges/user/web/server.js.map +1 -0
- package/dist/cli.d.ts +8 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +65 -4
- package/dist/cli.js.map +1 -1
- package/dist/core/store.d.ts +2 -2
- package/dist/core/store.d.ts.map +1 -1
- package/dist/core/store.js +4 -4
- package/dist/core/store.js.map +1 -1
- package/package.json +6 -4
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TUI — terminal chat interface using readline + ANSI escape codes.
|
|
3
|
+
*
|
|
4
|
+
* Commands:
|
|
5
|
+
* /join <room> Join or switch to a room
|
|
6
|
+
* /leave [room] Leave the current (or specified) room
|
|
7
|
+
* /rooms List all rooms
|
|
8
|
+
* /agents List all agents
|
|
9
|
+
* /read [room] Read messages in current (or specified) room
|
|
10
|
+
* /dm <agent> <msg> Send a direct message
|
|
11
|
+
* /create <name> Create a public room
|
|
12
|
+
* /invite <room> <agent> Invite an agent to a room
|
|
13
|
+
* /kick <room> <agent> Kick an agent from a room
|
|
14
|
+
* /destroy <room> Destroy a room
|
|
15
|
+
* /help Show commands
|
|
16
|
+
* /quit Exit
|
|
17
|
+
*
|
|
18
|
+
* Anything without / is sent to the current room.
|
|
19
|
+
*/
|
|
20
|
+
import * as readline from "node:readline";
|
|
21
|
+
import { ChatController } from "./controller.js";
|
|
22
|
+
// ANSI helpers
|
|
23
|
+
const BOLD = "\x1b[1m";
|
|
24
|
+
const DIM = "\x1b[2m";
|
|
25
|
+
const CYAN = "\x1b[36m";
|
|
26
|
+
const GREEN = "\x1b[32m";
|
|
27
|
+
const YELLOW = "\x1b[33m";
|
|
28
|
+
const RED = "\x1b[31m";
|
|
29
|
+
const MAGENTA = "\x1b[35m";
|
|
30
|
+
const RESET = "\x1b[0m";
|
|
31
|
+
const CLEAR_LINE = "\r\x1b[2K";
|
|
32
|
+
function prompt(controller) {
|
|
33
|
+
const room = controller.activeRoom;
|
|
34
|
+
return room ? `${GREEN}${room}${RESET} > ` : `${DIM}>${RESET} `;
|
|
35
|
+
}
|
|
36
|
+
export async function runTui(userName) {
|
|
37
|
+
const controller = new ChatController(userName);
|
|
38
|
+
await controller.init();
|
|
39
|
+
console.log(`${CYAN}Connected as ${BOLD}${userName} (user)${RESET} [${controller.agentId}]`);
|
|
40
|
+
console.log(`${DIM}Type /help for commands. Anything else goes to the current room.${RESET}\n`);
|
|
41
|
+
const rl = readline.createInterface({
|
|
42
|
+
input: process.stdin,
|
|
43
|
+
output: process.stdout,
|
|
44
|
+
});
|
|
45
|
+
// Delivery event handler — print above the prompt
|
|
46
|
+
function onMessage(event) {
|
|
47
|
+
const line = formatForTerminal(event);
|
|
48
|
+
process.stdout.write(`${CLEAR_LINE}${line}\n${prompt(controller)}`);
|
|
49
|
+
}
|
|
50
|
+
controller.on("message", onMessage);
|
|
51
|
+
function onError(err) {
|
|
52
|
+
process.stdout.write(`${CLEAR_LINE}${RED}Error: ${err.message}${RESET}\n${prompt(controller)}`);
|
|
53
|
+
}
|
|
54
|
+
controller.on("error", onError);
|
|
55
|
+
// Input loop
|
|
56
|
+
async function handleInput(input) {
|
|
57
|
+
const trimmed = input.trim();
|
|
58
|
+
if (trimmed.length === 0)
|
|
59
|
+
return;
|
|
60
|
+
if (trimmed.startsWith("/")) {
|
|
61
|
+
await handleCommand(trimmed, controller);
|
|
62
|
+
}
|
|
63
|
+
else if (controller.activeRoom) {
|
|
64
|
+
const result = await controller.sendToCurrentRoom(trimmed);
|
|
65
|
+
if (result.isError) {
|
|
66
|
+
process.stdout.write(`${RED}${result.content}${RESET}\n`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
process.stdout.write(`${YELLOW}No active room. /join a room first or use /dm.${RESET}\n`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function ask() {
|
|
74
|
+
rl.question(prompt(controller), (input) => {
|
|
75
|
+
void handleInput(input).then(() => {
|
|
76
|
+
ask();
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
ask();
|
|
81
|
+
// Graceful shutdown
|
|
82
|
+
rl.on("close", () => {
|
|
83
|
+
controller.off("message", onMessage);
|
|
84
|
+
controller.off("error", onError);
|
|
85
|
+
void controller.shutdown().then(() => process.exit(0));
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
// Command handler
|
|
90
|
+
// ---------------------------------------------------------------------------
|
|
91
|
+
async function handleCommand(input, c) {
|
|
92
|
+
const parts = input.slice(1).split(/\s+/);
|
|
93
|
+
const cmd = parts[0]?.toLowerCase();
|
|
94
|
+
const arg1 = parts[1];
|
|
95
|
+
const arg2 = parts.slice(2).join(" ");
|
|
96
|
+
switch (cmd) {
|
|
97
|
+
case "join": {
|
|
98
|
+
if (!arg1) {
|
|
99
|
+
process.stdout.write(`${YELLOW}Usage: /join <room>${RESET}\n`);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
const result = await c.switchRoom(arg1);
|
|
103
|
+
printResult(result);
|
|
104
|
+
if (!result.isError) {
|
|
105
|
+
const read = await c.readRoom();
|
|
106
|
+
if (!read.isError && read.content !== "No messages.") {
|
|
107
|
+
process.stdout.write(`${DIM}${read.content}${RESET}\n`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
case "leave": {
|
|
113
|
+
const result = await c.leaveRoom(arg1);
|
|
114
|
+
printResult(result);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
case "rooms": {
|
|
118
|
+
const result = await c.listRooms();
|
|
119
|
+
printResult(result);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
case "agents": {
|
|
123
|
+
const result = await c.listAgents();
|
|
124
|
+
printResult(result);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
case "read": {
|
|
128
|
+
const result = await c.readRoom(arg1);
|
|
129
|
+
printResult(result);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
case "dm": {
|
|
133
|
+
if (!arg1 || !arg2) {
|
|
134
|
+
process.stdout.write(`${YELLOW}Usage: /dm <agent> <message>${RESET}\n`);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
const result = await c.dm(arg1, arg2);
|
|
138
|
+
printResult(result);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
case "create": {
|
|
142
|
+
if (!arg1) {
|
|
143
|
+
process.stdout.write(`${YELLOW}Usage: /create <name>${RESET}\n`);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
const result = await c.createRoom(arg1, "public", arg2);
|
|
147
|
+
printResult(result);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
case "invite": {
|
|
151
|
+
const agentId = parts[2];
|
|
152
|
+
if (!arg1 || !agentId) {
|
|
153
|
+
process.stdout.write(`${YELLOW}Usage: /invite <room> <agent>${RESET}\n`);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
const result = await c.invite(arg1, agentId);
|
|
157
|
+
printResult(result);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
case "kick": {
|
|
161
|
+
const agentId = parts[2];
|
|
162
|
+
if (!arg1 || !agentId) {
|
|
163
|
+
process.stdout.write(`${YELLOW}Usage: /kick <room> <agent>${RESET}\n`);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
const result = await c.kick(arg1, agentId);
|
|
167
|
+
printResult(result);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
case "destroy": {
|
|
171
|
+
if (!arg1) {
|
|
172
|
+
process.stdout.write(`${YELLOW}Usage: /destroy <room>${RESET}\n`);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
const result = await c.destroyRoom(arg1);
|
|
176
|
+
printResult(result);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
case "help":
|
|
180
|
+
process.stdout.write(`${CYAN}Commands:${RESET}
|
|
181
|
+
${GREEN}/join${RESET} <room> Join or switch to a room
|
|
182
|
+
${GREEN}/leave${RESET} [room] Leave current (or specified) room
|
|
183
|
+
${GREEN}/rooms${RESET} List all rooms
|
|
184
|
+
${GREEN}/agents${RESET} List all agents
|
|
185
|
+
${GREEN}/read${RESET} [room] Read messages in room
|
|
186
|
+
${GREEN}/dm${RESET} <agent> <msg> Send a direct message
|
|
187
|
+
${GREEN}/create${RESET} <name> Create a public room
|
|
188
|
+
${GREEN}/invite${RESET} <room> <id> Invite agent to room
|
|
189
|
+
${GREEN}/kick${RESET} <room> <id> Kick agent from room
|
|
190
|
+
${GREEN}/destroy${RESET} <room> Destroy a room
|
|
191
|
+
${GREEN}/help${RESET} Show this help
|
|
192
|
+
${GREEN}/quit${RESET} Exit
|
|
193
|
+
\n${DIM}Anything without / is sent to the current room.${RESET}\n`);
|
|
194
|
+
return;
|
|
195
|
+
case "quit":
|
|
196
|
+
process.stdout.write(`${DIM}Goodbye!${RESET}\n`);
|
|
197
|
+
process.exit(0);
|
|
198
|
+
default:
|
|
199
|
+
process.stdout.write(`${YELLOW}Unknown command: /${String(cmd)}. Type /help for commands.${RESET}\n`);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
// ---------------------------------------------------------------------------
|
|
203
|
+
// Formatting
|
|
204
|
+
// ---------------------------------------------------------------------------
|
|
205
|
+
function printResult(result) {
|
|
206
|
+
if (result.isError) {
|
|
207
|
+
process.stdout.write(`${RED}${result.content}${RESET}\n`);
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
process.stdout.write(`${result.content}\n`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
function formatForTerminal(event) {
|
|
214
|
+
switch (event.type) {
|
|
215
|
+
case "room_message":
|
|
216
|
+
return `${BOLD}[${event.message.room}] ${event.message.from}:${RESET} ${event.message.content}`;
|
|
217
|
+
case "dm":
|
|
218
|
+
return `${MAGENTA}DM from ${event.message.from}:${RESET} ${event.message.content}`;
|
|
219
|
+
case "member_joined":
|
|
220
|
+
return `${GREEN}→ ${event.agent} joined ${event.room}${RESET}`;
|
|
221
|
+
case "member_left":
|
|
222
|
+
return `${YELLOW}← ${event.agent} left ${event.room}${RESET}`;
|
|
223
|
+
case "member_status":
|
|
224
|
+
return `${CYAN}● ${event.agent} is now ${event.status} in ${event.room}${RESET}`;
|
|
225
|
+
case "delivery_status":
|
|
226
|
+
return `${DIM}✓ Message ${event.messageId} ${event.status} by ${event.agent}${RESET}`;
|
|
227
|
+
case "room_invite":
|
|
228
|
+
return `${CYAN}_invite to ${event.room} from ${event.from}${RESET}`;
|
|
229
|
+
case "room_members": {
|
|
230
|
+
const names = event.members
|
|
231
|
+
.map((m) => `${m.name} (${m.status})`)
|
|
232
|
+
.join(", ");
|
|
233
|
+
return `${DIM}Members of ${event.room}: ${names}${RESET}`;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
//# sourceMappingURL=tui.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tui.js","sourceRoot":"","sources":["../../../src/bridges/user/tui.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Frontend HTML — single-page chat UI served as a string constant.
|
|
3
|
+
*
|
|
4
|
+
* Vanilla JS + CSS, no build step, no framework. Communicates via
|
|
5
|
+
* REST API + WebSocket.
|
|
6
|
+
*/
|
|
7
|
+
export declare const FRONTEND_HTML = "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Agent Comms</title>\n<style>\n :root {\n --bg: #1a1a2e;\n --surface: #16213e;\n --border: #0f3460;\n --text: #e4e4e4;\n --dim: #888;\n --accent: #00b4d8;\n --green: #06d6a0;\n --red: #ef476f;\n --yellow: #ffd166;\n --purple: #b5838d;\n }\n * { margin: 0; padding: 0; box-sizing: border-box; }\n body {\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif;\n background: var(--bg);\n color: var(--text);\n height: 100vh;\n display: flex;\n }\n #sidebar {\n width: 260px;\n background: var(--surface);\n border-right: 1px solid var(--border);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n #sidebar h2 {\n padding: 12px 16px;\n font-size: 14px;\n color: var(--accent);\n border-bottom: 1px solid var(--border);\n }\n .sidebar-section {\n flex: 1;\n overflow-y: auto;\n padding: 8px;\n }\n .sidebar-section h3 {\n font-size: 11px;\n text-transform: uppercase;\n color: var(--dim);\n padding: 8px 8px 4px;\n }\n .room-item, .agent-item {\n padding: 6px 10px;\n border-radius: 6px;\n cursor: pointer;\n font-size: 13px;\n display: flex;\n align-items: center;\n gap: 6px;\n }\n .room-item:hover, .agent-item:hover { background: rgba(255,255,255,0.05); }\n .room-item.active { background: var(--border); }\n .status-dot {\n width: 8px; height: 8px;\n border-radius: 50%;\n display: inline-block;\n }\n .status-dot.active { background: var(--green); }\n .status-dot.idle { background: var(--yellow); }\n .status-dot.busy { background: var(--red); }\n .status-dot.offline { background: var(--dim); }\n #main {\n flex: 1;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n #header {\n padding: 12px 20px;\n border-bottom: 1px solid var(--border);\n font-size: 15px;\n font-weight: 600;\n background: var(--surface);\n }\n #messages {\n flex: 1;\n overflow-y: auto;\n padding: 16px 20px;\n display: flex;\n flex-direction: column;\n gap: 4px;\n }\n .msg {\n font-size: 13px;\n line-height: 1.5;\n }\n .msg .sender { font-weight: 600; color: var(--accent); }\n .msg .time { color: var(--dim); font-size: 11px; margin-left: 8px; }\n .msg.system { color: var(--dim); font-style: italic; }\n .msg.status { color: var(--yellow); font-size: 12px; }\n .msg.dm { color: var(--purple); }\n .msg .dm-badge {\n background: var(--purple);\n color: #fff;\n font-size: 10px;\n padding: 1px 5px;\n border-radius: 3px;\n font-weight: 600;\n }\n #input-bar {\n display: flex;\n padding: 12px 20px;\n gap: 8px;\n background: var(--surface);\n border-top: 1px solid var(--border);\n }\n #input {\n flex: 1;\n background: var(--bg);\n border: 1px solid var(--border);\n border-radius: 6px;\n padding: 8px 12px;\n color: var(--text);\n font-size: 13px;\n outline: none;\n }\n #input:focus { border-color: var(--accent); }\n #send-btn {\n background: var(--accent);\n border: none;\n border-radius: 6px;\n padding: 8px 16px;\n color: #fff;\n font-weight: 600;\n cursor: pointer;\n }\n #send-btn:hover { opacity: 0.9; }\n #empty-state {\n flex: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--dim);\n font-size: 14px;\n }\n</style>\n</head>\n<body>\n\n<div id=\"sidebar\">\n <h2>Agent Comms</h2>\n <div class=\"sidebar-section\">\n <h3>Rooms</h3>\n <div id=\"room-list\"></div>\n <h3>Agents</h3>\n <div id=\"agent-list\"></div>\n </div>\n</div>\n\n<div id=\"main\">\n <div id=\"header\">Select a room</div>\n <div id=\"messages\"></div>\n <div id=\"input-bar\">\n <input id=\"input\" type=\"text\" placeholder=\"Type a message or /command...\" autocomplete=\"off\" />\n <button id=\"send-btn\">Send</button>\n </div>\n</div>\n\n<script>\nconst $ = (s) => document.querySelector(s);\nconst messagesEl = $('#messages');\nconst inputEl = $('#input');\nconst headerEl = $('#header');\nconst roomListEl = $('#room-list');\nconst agentListEl = $('#agent-list');\n\nlet currentRoom = null;\nlet ws = null;\n\n// Connect WebSocket\nfunction connect() {\n const proto = location.protocol === 'https:' ? 'wss:' : 'ws:';\n ws = new WebSocket(proto + '//' + location.host);\n\n ws.onopen = () => { addSystem('Connected to mesh'); };\n ws.onclose = () => { addSystem('Disconnected \u2014 reconnecting...'); setTimeout(connect, 3000); };\n ws.onerror = () => {};\n\n ws.onmessage = (e) => {\n const frame = JSON.parse(e.data);\n if (frame.type === 'delivery') handleDelivery(frame.event);\n if (frame.type === 'result') addSystem(frame.result.content);\n if (frame.type === 'error') addSystem('Error: ' + frame.message);\n if (frame.type === 'state') {\n renderAgents(frame.agents);\n renderRooms(frame.rooms);\n }\n };\n}\n\nfunction sendAction(params) {\n if (ws && ws.readyState === WebSocket.OPEN) {\n ws.send(JSON.stringify(params));\n }\n}\n\n// Delivery event handling\nfunction handleDelivery(event) {\n switch (event.type) {\n case 'room_message':\n if (!currentRoom || event.message.room !== currentRoom) return;\n addMessage(event.message.from, event.message.content, event.message.timestamp);\n break;\n case 'dm':\n addDm(event.message.from, event.message.content, event.message.timestamp);\n break;\n case 'member_joined':\n addSystem(event.agent + ' joined ' + event.room);\n refreshState();\n break;\n case 'member_left':\n addSystem(event.agent + ' left ' + event.room);\n refreshState();\n break;\n case 'member_status':\n addStatus(event.agent + ' is now ' + event.status + ' in ' + event.room);\n refreshState();\n break;\n case 'delivery_status':\n addStatus('Message ' + event.messageId + ' ' + event.status + ' by ' + event.agent);\n break;\n case 'room_members':\n if (currentRoom === event.room) {\n addSystem('Members: ' + event.members.map(m => m.name + ' (' + m.status + ')').join(', '));\n }\n break;\n case 'room_invite':\n addSystem('Invited to ' + event.room + ' by ' + event.from);\n break;\n }\n}\n\n// UI rendering\nfunction addMessage(sender, content, timestamp) {\n const time = (timestamp || new Date().toISOString()).slice(11, 19);\n const div = document.createElement('div');\n div.className = 'msg';\n div.innerHTML = '<span class=\"sender\">' + esc(sender) + '</span><span class=\"time\">' + time + '</span>: ' + esc(content);\n messagesEl.appendChild(div);\n messagesEl.scrollTop = messagesEl.scrollHeight;\n}\n\nfunction addDm(sender, content, timestamp) {\n const time = (timestamp || new Date().toISOString()).slice(11, 19);\n const div = document.createElement('div');\n div.className = 'msg dm';\n div.innerHTML = '<span class=\"dm-badge\">DM</span> <span class=\"sender\">' + esc(sender) + '</span><span class=\"time\">' + time + '</span>: ' + esc(content);\n messagesEl.appendChild(div);\n messagesEl.scrollTop = messagesEl.scrollHeight;\n}\n\nfunction addSystem(text) {\n const div = document.createElement('div');\n div.className = 'msg system';\n div.textContent = text;\n messagesEl.appendChild(div);\n messagesEl.scrollTop = messagesEl.scrollHeight;\n}\n\nfunction addStatus(text) {\n const div = document.createElement('div');\n div.className = 'msg status';\n div.textContent = text;\n messagesEl.appendChild(div);\n messagesEl.scrollTop = messagesEl.scrollHeight;\n}\n\nfunction clearMessages() {\n messagesEl.innerHTML = '';\n}\n\nfunction esc(s) {\n const d = document.createElement('div');\n d.textContent = s;\n return d.innerHTML;\n}\n\n// State refresh\nasync function refreshState() {\n const [agentsRes, roomsRes] = await Promise.all([\n fetch('/api/agents'), fetch('/api/rooms')\n ]);\n renderAgents(await agentsRes.json());\n renderRooms(await roomsRes.json());\n}\n\nfunction renderAgents(agents) {\n agentListEl.innerHTML = '';\n for (const a of agents) {\n const div = document.createElement('div');\n div.className = 'agent-item';\n const dot = a.status || 'offline';\n div.innerHTML = '<span class=\"status-dot ' + dot + '\"></span> ' + esc(a.name);\n agentListEl.appendChild(div);\n }\n}\n\nfunction renderRooms(rooms) {\n roomListEl.innerHTML = '';\n for (const r of rooms) {\n const div = document.createElement('div');\n div.className = 'room-item' + (currentRoom === r.id ? ' active' : '');\n const joined = r.members.length;\n div.innerHTML = r.type.charAt(0).toUpperCase() + ' ' + esc(r.name) + ' <span style=\"color:var(--dim)\">(' + joined + ')</span>';\n div.onclick = () => joinRoom(r.id);\n roomListEl.appendChild(div);\n }\n}\n\nasync function joinRoom(roomId) {\n currentRoom = roomId;\n headerEl.textContent = roomId;\n clearMessages();\n\n // Highlight in sidebar\n document.querySelectorAll('.room-item').forEach(el => el.classList.remove('active'));\n sendAction({ action: 'join_room', room: roomId });\n\n // Load history\n const res = await fetch('/api/rooms/' + encodeURIComponent(roomId) + '/messages');\n const messages = await res.json();\n for (const m of messages) {\n addMessage(m.from, m.content, m.timestamp);\n }\n addSystem('Joined ' + roomId);\n inputEl.focus();\n}\n\n// Input handling\nfunction handleInput() {\n const text = inputEl.value.trim();\n if (!text) return;\n inputEl.value = '';\n\n if (text.startsWith('/')) {\n const parts = text.slice(1).split(/\\s+/);\n const cmd = parts[0].toLowerCase();\n switch (cmd) {\n case 'join': sendAction({ action: 'join_room', room: parts[1] }); break;\n case 'leave': sendAction({ action: 'leave_room', room: parts[1] || currentRoom }); break;\n case 'rooms': refreshState(); break;\n case 'agents': refreshState(); break;\n case 'dm': sendAction({ action: 'dm', target: parts[1], content: parts.slice(2).join(' ') }); break;\n case 'create': sendAction({ action: 'create_room', name: parts[1], type: 'public' }); break;\n case 'destroy': sendAction({ action: 'destroy_room', room: parts[1] }); break;\n case 'help':\n addSystem('Commands: /join, /leave, /rooms, /agents, /dm, /create, /destroy, /help');\n break;\n default:\n addSystem('Unknown command: /' + cmd);\n }\n } else if (currentRoom) {\n sendAction({ action: 'send', target: currentRoom, content: text });\n } else {\n addSystem('Join a room first (click one in the sidebar)');\n }\n}\n\n$('#send-btn').onclick = handleInput;\ninputEl.addEventListener('keydown', (e) => { if (e.key === 'Enter') handleInput(); });\n\n// Boot\nconnect();\nrefreshState();\ninputEl.focus();\n</script>\n</body>\n</html>";
|
|
8
|
+
//# sourceMappingURL=index.html.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.html.d.ts","sourceRoot":"","sources":["../../../../src/bridges/user/web/index.html.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,eAAO,MAAM,aAAa,2xVA0XlB,CAAC"}
|
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Frontend HTML — single-page chat UI served as a string constant.
|
|
3
|
+
*
|
|
4
|
+
* Vanilla JS + CSS, no build step, no framework. Communicates via
|
|
5
|
+
* REST API + WebSocket.
|
|
6
|
+
*/
|
|
7
|
+
export const FRONTEND_HTML = `<!DOCTYPE html>
|
|
8
|
+
<html lang="en">
|
|
9
|
+
<head>
|
|
10
|
+
<meta charset="utf-8">
|
|
11
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
12
|
+
<title>Agent Comms</title>
|
|
13
|
+
<style>
|
|
14
|
+
:root {
|
|
15
|
+
--bg: #1a1a2e;
|
|
16
|
+
--surface: #16213e;
|
|
17
|
+
--border: #0f3460;
|
|
18
|
+
--text: #e4e4e4;
|
|
19
|
+
--dim: #888;
|
|
20
|
+
--accent: #00b4d8;
|
|
21
|
+
--green: #06d6a0;
|
|
22
|
+
--red: #ef476f;
|
|
23
|
+
--yellow: #ffd166;
|
|
24
|
+
--purple: #b5838d;
|
|
25
|
+
}
|
|
26
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
27
|
+
body {
|
|
28
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
29
|
+
background: var(--bg);
|
|
30
|
+
color: var(--text);
|
|
31
|
+
height: 100vh;
|
|
32
|
+
display: flex;
|
|
33
|
+
}
|
|
34
|
+
#sidebar {
|
|
35
|
+
width: 260px;
|
|
36
|
+
background: var(--surface);
|
|
37
|
+
border-right: 1px solid var(--border);
|
|
38
|
+
display: flex;
|
|
39
|
+
flex-direction: column;
|
|
40
|
+
overflow: hidden;
|
|
41
|
+
}
|
|
42
|
+
#sidebar h2 {
|
|
43
|
+
padding: 12px 16px;
|
|
44
|
+
font-size: 14px;
|
|
45
|
+
color: var(--accent);
|
|
46
|
+
border-bottom: 1px solid var(--border);
|
|
47
|
+
}
|
|
48
|
+
.sidebar-section {
|
|
49
|
+
flex: 1;
|
|
50
|
+
overflow-y: auto;
|
|
51
|
+
padding: 8px;
|
|
52
|
+
}
|
|
53
|
+
.sidebar-section h3 {
|
|
54
|
+
font-size: 11px;
|
|
55
|
+
text-transform: uppercase;
|
|
56
|
+
color: var(--dim);
|
|
57
|
+
padding: 8px 8px 4px;
|
|
58
|
+
}
|
|
59
|
+
.room-item, .agent-item {
|
|
60
|
+
padding: 6px 10px;
|
|
61
|
+
border-radius: 6px;
|
|
62
|
+
cursor: pointer;
|
|
63
|
+
font-size: 13px;
|
|
64
|
+
display: flex;
|
|
65
|
+
align-items: center;
|
|
66
|
+
gap: 6px;
|
|
67
|
+
}
|
|
68
|
+
.room-item:hover, .agent-item:hover { background: rgba(255,255,255,0.05); }
|
|
69
|
+
.room-item.active { background: var(--border); }
|
|
70
|
+
.status-dot {
|
|
71
|
+
width: 8px; height: 8px;
|
|
72
|
+
border-radius: 50%;
|
|
73
|
+
display: inline-block;
|
|
74
|
+
}
|
|
75
|
+
.status-dot.active { background: var(--green); }
|
|
76
|
+
.status-dot.idle { background: var(--yellow); }
|
|
77
|
+
.status-dot.busy { background: var(--red); }
|
|
78
|
+
.status-dot.offline { background: var(--dim); }
|
|
79
|
+
#main {
|
|
80
|
+
flex: 1;
|
|
81
|
+
display: flex;
|
|
82
|
+
flex-direction: column;
|
|
83
|
+
overflow: hidden;
|
|
84
|
+
}
|
|
85
|
+
#header {
|
|
86
|
+
padding: 12px 20px;
|
|
87
|
+
border-bottom: 1px solid var(--border);
|
|
88
|
+
font-size: 15px;
|
|
89
|
+
font-weight: 600;
|
|
90
|
+
background: var(--surface);
|
|
91
|
+
}
|
|
92
|
+
#messages {
|
|
93
|
+
flex: 1;
|
|
94
|
+
overflow-y: auto;
|
|
95
|
+
padding: 16px 20px;
|
|
96
|
+
display: flex;
|
|
97
|
+
flex-direction: column;
|
|
98
|
+
gap: 4px;
|
|
99
|
+
}
|
|
100
|
+
.msg {
|
|
101
|
+
font-size: 13px;
|
|
102
|
+
line-height: 1.5;
|
|
103
|
+
}
|
|
104
|
+
.msg .sender { font-weight: 600; color: var(--accent); }
|
|
105
|
+
.msg .time { color: var(--dim); font-size: 11px; margin-left: 8px; }
|
|
106
|
+
.msg.system { color: var(--dim); font-style: italic; }
|
|
107
|
+
.msg.status { color: var(--yellow); font-size: 12px; }
|
|
108
|
+
.msg.dm { color: var(--purple); }
|
|
109
|
+
.msg .dm-badge {
|
|
110
|
+
background: var(--purple);
|
|
111
|
+
color: #fff;
|
|
112
|
+
font-size: 10px;
|
|
113
|
+
padding: 1px 5px;
|
|
114
|
+
border-radius: 3px;
|
|
115
|
+
font-weight: 600;
|
|
116
|
+
}
|
|
117
|
+
#input-bar {
|
|
118
|
+
display: flex;
|
|
119
|
+
padding: 12px 20px;
|
|
120
|
+
gap: 8px;
|
|
121
|
+
background: var(--surface);
|
|
122
|
+
border-top: 1px solid var(--border);
|
|
123
|
+
}
|
|
124
|
+
#input {
|
|
125
|
+
flex: 1;
|
|
126
|
+
background: var(--bg);
|
|
127
|
+
border: 1px solid var(--border);
|
|
128
|
+
border-radius: 6px;
|
|
129
|
+
padding: 8px 12px;
|
|
130
|
+
color: var(--text);
|
|
131
|
+
font-size: 13px;
|
|
132
|
+
outline: none;
|
|
133
|
+
}
|
|
134
|
+
#input:focus { border-color: var(--accent); }
|
|
135
|
+
#send-btn {
|
|
136
|
+
background: var(--accent);
|
|
137
|
+
border: none;
|
|
138
|
+
border-radius: 6px;
|
|
139
|
+
padding: 8px 16px;
|
|
140
|
+
color: #fff;
|
|
141
|
+
font-weight: 600;
|
|
142
|
+
cursor: pointer;
|
|
143
|
+
}
|
|
144
|
+
#send-btn:hover { opacity: 0.9; }
|
|
145
|
+
#empty-state {
|
|
146
|
+
flex: 1;
|
|
147
|
+
display: flex;
|
|
148
|
+
align-items: center;
|
|
149
|
+
justify-content: center;
|
|
150
|
+
color: var(--dim);
|
|
151
|
+
font-size: 14px;
|
|
152
|
+
}
|
|
153
|
+
</style>
|
|
154
|
+
</head>
|
|
155
|
+
<body>
|
|
156
|
+
|
|
157
|
+
<div id="sidebar">
|
|
158
|
+
<h2>Agent Comms</h2>
|
|
159
|
+
<div class="sidebar-section">
|
|
160
|
+
<h3>Rooms</h3>
|
|
161
|
+
<div id="room-list"></div>
|
|
162
|
+
<h3>Agents</h3>
|
|
163
|
+
<div id="agent-list"></div>
|
|
164
|
+
</div>
|
|
165
|
+
</div>
|
|
166
|
+
|
|
167
|
+
<div id="main">
|
|
168
|
+
<div id="header">Select a room</div>
|
|
169
|
+
<div id="messages"></div>
|
|
170
|
+
<div id="input-bar">
|
|
171
|
+
<input id="input" type="text" placeholder="Type a message or /command..." autocomplete="off" />
|
|
172
|
+
<button id="send-btn">Send</button>
|
|
173
|
+
</div>
|
|
174
|
+
</div>
|
|
175
|
+
|
|
176
|
+
<script>
|
|
177
|
+
const $ = (s) => document.querySelector(s);
|
|
178
|
+
const messagesEl = $('#messages');
|
|
179
|
+
const inputEl = $('#input');
|
|
180
|
+
const headerEl = $('#header');
|
|
181
|
+
const roomListEl = $('#room-list');
|
|
182
|
+
const agentListEl = $('#agent-list');
|
|
183
|
+
|
|
184
|
+
let currentRoom = null;
|
|
185
|
+
let ws = null;
|
|
186
|
+
|
|
187
|
+
// Connect WebSocket
|
|
188
|
+
function connect() {
|
|
189
|
+
const proto = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
190
|
+
ws = new WebSocket(proto + '//' + location.host);
|
|
191
|
+
|
|
192
|
+
ws.onopen = () => { addSystem('Connected to mesh'); };
|
|
193
|
+
ws.onclose = () => { addSystem('Disconnected — reconnecting...'); setTimeout(connect, 3000); };
|
|
194
|
+
ws.onerror = () => {};
|
|
195
|
+
|
|
196
|
+
ws.onmessage = (e) => {
|
|
197
|
+
const frame = JSON.parse(e.data);
|
|
198
|
+
if (frame.type === 'delivery') handleDelivery(frame.event);
|
|
199
|
+
if (frame.type === 'result') addSystem(frame.result.content);
|
|
200
|
+
if (frame.type === 'error') addSystem('Error: ' + frame.message);
|
|
201
|
+
if (frame.type === 'state') {
|
|
202
|
+
renderAgents(frame.agents);
|
|
203
|
+
renderRooms(frame.rooms);
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function sendAction(params) {
|
|
209
|
+
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
210
|
+
ws.send(JSON.stringify(params));
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Delivery event handling
|
|
215
|
+
function handleDelivery(event) {
|
|
216
|
+
switch (event.type) {
|
|
217
|
+
case 'room_message':
|
|
218
|
+
if (!currentRoom || event.message.room !== currentRoom) return;
|
|
219
|
+
addMessage(event.message.from, event.message.content, event.message.timestamp);
|
|
220
|
+
break;
|
|
221
|
+
case 'dm':
|
|
222
|
+
addDm(event.message.from, event.message.content, event.message.timestamp);
|
|
223
|
+
break;
|
|
224
|
+
case 'member_joined':
|
|
225
|
+
addSystem(event.agent + ' joined ' + event.room);
|
|
226
|
+
refreshState();
|
|
227
|
+
break;
|
|
228
|
+
case 'member_left':
|
|
229
|
+
addSystem(event.agent + ' left ' + event.room);
|
|
230
|
+
refreshState();
|
|
231
|
+
break;
|
|
232
|
+
case 'member_status':
|
|
233
|
+
addStatus(event.agent + ' is now ' + event.status + ' in ' + event.room);
|
|
234
|
+
refreshState();
|
|
235
|
+
break;
|
|
236
|
+
case 'delivery_status':
|
|
237
|
+
addStatus('Message ' + event.messageId + ' ' + event.status + ' by ' + event.agent);
|
|
238
|
+
break;
|
|
239
|
+
case 'room_members':
|
|
240
|
+
if (currentRoom === event.room) {
|
|
241
|
+
addSystem('Members: ' + event.members.map(m => m.name + ' (' + m.status + ')').join(', '));
|
|
242
|
+
}
|
|
243
|
+
break;
|
|
244
|
+
case 'room_invite':
|
|
245
|
+
addSystem('Invited to ' + event.room + ' by ' + event.from);
|
|
246
|
+
break;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// UI rendering
|
|
251
|
+
function addMessage(sender, content, timestamp) {
|
|
252
|
+
const time = (timestamp || new Date().toISOString()).slice(11, 19);
|
|
253
|
+
const div = document.createElement('div');
|
|
254
|
+
div.className = 'msg';
|
|
255
|
+
div.innerHTML = '<span class="sender">' + esc(sender) + '</span><span class="time">' + time + '</span>: ' + esc(content);
|
|
256
|
+
messagesEl.appendChild(div);
|
|
257
|
+
messagesEl.scrollTop = messagesEl.scrollHeight;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function addDm(sender, content, timestamp) {
|
|
261
|
+
const time = (timestamp || new Date().toISOString()).slice(11, 19);
|
|
262
|
+
const div = document.createElement('div');
|
|
263
|
+
div.className = 'msg dm';
|
|
264
|
+
div.innerHTML = '<span class="dm-badge">DM</span> <span class="sender">' + esc(sender) + '</span><span class="time">' + time + '</span>: ' + esc(content);
|
|
265
|
+
messagesEl.appendChild(div);
|
|
266
|
+
messagesEl.scrollTop = messagesEl.scrollHeight;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function addSystem(text) {
|
|
270
|
+
const div = document.createElement('div');
|
|
271
|
+
div.className = 'msg system';
|
|
272
|
+
div.textContent = text;
|
|
273
|
+
messagesEl.appendChild(div);
|
|
274
|
+
messagesEl.scrollTop = messagesEl.scrollHeight;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function addStatus(text) {
|
|
278
|
+
const div = document.createElement('div');
|
|
279
|
+
div.className = 'msg status';
|
|
280
|
+
div.textContent = text;
|
|
281
|
+
messagesEl.appendChild(div);
|
|
282
|
+
messagesEl.scrollTop = messagesEl.scrollHeight;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function clearMessages() {
|
|
286
|
+
messagesEl.innerHTML = '';
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function esc(s) {
|
|
290
|
+
const d = document.createElement('div');
|
|
291
|
+
d.textContent = s;
|
|
292
|
+
return d.innerHTML;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// State refresh
|
|
296
|
+
async function refreshState() {
|
|
297
|
+
const [agentsRes, roomsRes] = await Promise.all([
|
|
298
|
+
fetch('/api/agents'), fetch('/api/rooms')
|
|
299
|
+
]);
|
|
300
|
+
renderAgents(await agentsRes.json());
|
|
301
|
+
renderRooms(await roomsRes.json());
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function renderAgents(agents) {
|
|
305
|
+
agentListEl.innerHTML = '';
|
|
306
|
+
for (const a of agents) {
|
|
307
|
+
const div = document.createElement('div');
|
|
308
|
+
div.className = 'agent-item';
|
|
309
|
+
const dot = a.status || 'offline';
|
|
310
|
+
div.innerHTML = '<span class="status-dot ' + dot + '"></span> ' + esc(a.name);
|
|
311
|
+
agentListEl.appendChild(div);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function renderRooms(rooms) {
|
|
316
|
+
roomListEl.innerHTML = '';
|
|
317
|
+
for (const r of rooms) {
|
|
318
|
+
const div = document.createElement('div');
|
|
319
|
+
div.className = 'room-item' + (currentRoom === r.id ? ' active' : '');
|
|
320
|
+
const joined = r.members.length;
|
|
321
|
+
div.innerHTML = r.type.charAt(0).toUpperCase() + ' ' + esc(r.name) + ' <span style="color:var(--dim)">(' + joined + ')</span>';
|
|
322
|
+
div.onclick = () => joinRoom(r.id);
|
|
323
|
+
roomListEl.appendChild(div);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
async function joinRoom(roomId) {
|
|
328
|
+
currentRoom = roomId;
|
|
329
|
+
headerEl.textContent = roomId;
|
|
330
|
+
clearMessages();
|
|
331
|
+
|
|
332
|
+
// Highlight in sidebar
|
|
333
|
+
document.querySelectorAll('.room-item').forEach(el => el.classList.remove('active'));
|
|
334
|
+
sendAction({ action: 'join_room', room: roomId });
|
|
335
|
+
|
|
336
|
+
// Load history
|
|
337
|
+
const res = await fetch('/api/rooms/' + encodeURIComponent(roomId) + '/messages');
|
|
338
|
+
const messages = await res.json();
|
|
339
|
+
for (const m of messages) {
|
|
340
|
+
addMessage(m.from, m.content, m.timestamp);
|
|
341
|
+
}
|
|
342
|
+
addSystem('Joined ' + roomId);
|
|
343
|
+
inputEl.focus();
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Input handling
|
|
347
|
+
function handleInput() {
|
|
348
|
+
const text = inputEl.value.trim();
|
|
349
|
+
if (!text) return;
|
|
350
|
+
inputEl.value = '';
|
|
351
|
+
|
|
352
|
+
if (text.startsWith('/')) {
|
|
353
|
+
const parts = text.slice(1).split(/\\s+/);
|
|
354
|
+
const cmd = parts[0].toLowerCase();
|
|
355
|
+
switch (cmd) {
|
|
356
|
+
case 'join': sendAction({ action: 'join_room', room: parts[1] }); break;
|
|
357
|
+
case 'leave': sendAction({ action: 'leave_room', room: parts[1] || currentRoom }); break;
|
|
358
|
+
case 'rooms': refreshState(); break;
|
|
359
|
+
case 'agents': refreshState(); break;
|
|
360
|
+
case 'dm': sendAction({ action: 'dm', target: parts[1], content: parts.slice(2).join(' ') }); break;
|
|
361
|
+
case 'create': sendAction({ action: 'create_room', name: parts[1], type: 'public' }); break;
|
|
362
|
+
case 'destroy': sendAction({ action: 'destroy_room', room: parts[1] }); break;
|
|
363
|
+
case 'help':
|
|
364
|
+
addSystem('Commands: /join, /leave, /rooms, /agents, /dm, /create, /destroy, /help');
|
|
365
|
+
break;
|
|
366
|
+
default:
|
|
367
|
+
addSystem('Unknown command: /' + cmd);
|
|
368
|
+
}
|
|
369
|
+
} else if (currentRoom) {
|
|
370
|
+
sendAction({ action: 'send', target: currentRoom, content: text });
|
|
371
|
+
} else {
|
|
372
|
+
addSystem('Join a room first (click one in the sidebar)');
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
$('#send-btn').onclick = handleInput;
|
|
377
|
+
inputEl.addEventListener('keydown', (e) => { if (e.key === 'Enter') handleInput(); });
|
|
378
|
+
|
|
379
|
+
// Boot
|
|
380
|
+
connect();
|
|
381
|
+
refreshState();
|
|
382
|
+
inputEl.focus();
|
|
383
|
+
</script>
|
|
384
|
+
</body>
|
|
385
|
+
</html>`;
|
|
386
|
+
//# sourceMappingURL=index.html.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.html.js","sourceRoot":"","sources":["../../../../src/bridges/user/web/index.html.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,CAAC,MAAM,aAAa,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QA0XrB,CAAC"}
|